乙Py先生のプログラミング教室

乙Py先生のプログラミング教室
初学者のためのプログラミング学習サイト

今回は色々な図形を組み合わせて
デジタルアートを作ってみました


解説動画はこちら


 
はじめに

今回はPythonで作図できる図形を組み合わせて
デジタルアートを作るものです

今回紹介する図形は以下です。

リサージュ図形
ローズ曲線
スピログラフ
ヒルベルト曲線
ドラゴンカーブ
ペンローズタイル(没)
トロコイド曲線
曼荼羅

早速図形を描いてみましょう。


ライブラリのインストール 

今回はGoogle Colabを用いて作図します。
日本語は表示できないので
日本語表示のライブラリをインストールしておいてください。
pip install japanize_matplotlib







リサージュ図形

二つの正弦波を組み合わせてできる曲線です
周波数や位相の違いで様々なパターンが生成されます
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib

# パラメータ設定
A = 1
B = 1
a = 5
b = 4
delta = np.pi / 2

# tの範囲
t = np.linspace(0, 2 * np.pi, 1000)

# x, y の計算
x = A * np.sin(a * t + delta)
y = B * np.sin(b * t)

# 描画
plt.figure(figsize=(6, 6))
plt.plot(x, y)
plt.axis('off')
plt.show()
download


import numpy as np
import matplotlib.pyplot as plt

# 図形の数と色の設定
num_shapes = 10
colors = plt.cm.viridis(np.linspace(0, 1, num_shapes))

# 描画設定
plt.figure(figsize=(8, 8))

# 各リサージュ図形の描画
for i in range(num_shapes):
    A = 1
    B = 1
    a = np.random.randint(1, 10)
    b = np.random.randint(1, 10)
    delta = np.random.uniform(0, 2 * np.pi)
    
    t = np.linspace(0, 2 * np.pi, 1000)
    x = A * np.sin(a * t + delta)
    y = B * np.sin(b * t)
    
    plt.plot(x, y, color=colors[i], alpha=0.7)

# グラフの設定
plt.title("複雑なリサージュアート")
plt.axis('equal')
plt.axis('off')
plt.show()
download-1


ローズ曲線

極座標を使って描く花のような形の曲線です
import numpy as np
import matplotlib.pyplot as plt

# パラメータ設定
k = 5  # kは整数または分数
a = 1  # 振幅

# tの範囲
t = np.linspace(0, 2 * np.pi, 1000)

# x, y の計算
x = a * np.cos(k * t) * np.cos(t)
y = a * np.cos(k * t) * np.sin(t)

# 描画
plt.figure(figsize=(6, 6))
plt.plot(x, y, color='magenta')
plt.title("ローズ曲線")
plt.axis('equal')
plt.axis('off')
plt.show()
download-2


import numpy as np
import matplotlib.pyplot as plt

# パラメータのリスト
k_values = np.arange(1, 6)
a_values = np.linspace(0.5, 2.5, 5)

# プロット設定
fig, axes = plt.subplots(5, 5, figsize=(6,6))
fig.suptitle("ローズ曲線のグリッド", fontsize=16)

# 各プロットの描画
for i, a in enumerate(a_values):
    for j, k in enumerate(k_values):
        t = np.linspace(0, 2 * np.pi, 1000)
        x = a * np.cos(k * t) * np.cos(t)
        y = a * np.cos(k * t) * np.sin(t)
        
        ax = axes[i, j]
        ax.plot(x, y, color='magenta')
        ax.set_title(f'k={k}, a={a:.1f}')
        ax.axis('equal')
        ax.set_xlim(-2.5, 2.5)
        ax.set_ylim(-2.5, 2.5)
        ax.axis('off')  # 軸を非表示

plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.show()
download-3



スピログラフ

歯車を使って描くような複雑な曲線です
パラメータを変えることで多様な形ができます

小学生の時とかに作図キットを持っている人もいたかも
あれをPythonで再現します
import numpy as np
import matplotlib.pyplot as plt

# スピログラフのパラメータ
R = 100  # 大きい円の半径
r = 30   # 小さい円の半径
d = 160   # 描画する点の距離

# 最大公約数を使って完全なループを描画
gcd = np.gcd(R, r)
lcm = (R * r) // gcd
t = np.linspace(0, 2 * np.pi * (r // gcd), 1000 * (r // gcd))

# 描画設定
fig, ax = plt.subplots(figsize=(8, 8))
ax.set_xlim(-R - r - d, R + r + d)
ax.set_ylim(-R - r - d, R + r + d)
ax.set_aspect('equal')
ax.axis('off')

# スピログラフの描画
x = (R - r) * np.cos(t) + d * np.cos((R - r) / r * t)
y = (R - r) * np.sin(t) - d * np.sin((R - r) / r * t)
ax.plot(x, y, color='blue')
plt.show()
download-4


import numpy as np
import matplotlib.pyplot as plt

# スピログラフのパラメータ
r = 30  # 小さい円の半径

# 描画設定
fig, axs = plt.subplots(5, 5, figsize=(8,8))
fig.subplots_adjust(hspace=0.3, wspace=0.3)

# Rとdを変化させてプロット
R_values = np.linspace(50, 150, 5)
d_values = np.linspace(20, 80, 5)

for i, R in enumerate(R_values):
    for j, d in enumerate(d_values):
        ax = axs[i, j]
        gcd = np.gcd(int(R), r)
        t = np.linspace(0, 2 * np.pi * (r // gcd), 1000 * (r // gcd))
        
        x = (R - r) * np.cos(t) + d * np.cos((R - r) / r * t)
        y = (R - r) * np.sin(t) - d * np.sin((R - r) / r * t)
        
        ax.plot(x, y, color='blue')
        ax.set_xlim(-R - r - d, R + r + d)
        ax.set_ylim(-R - r - d, R + r + d)
        ax.set_aspect('equal')
        ax.axis('off')
        ax.set_title(f"R={int(R)}, d={int(d)}", fontsize=8)

plt.show()
download-5

import numpy as np
import matplotlib.pyplot as plt

# スピログラフのパラメータ
R = 150  # 大きい円の半径
r1 = 30  # 中間の円の半径
r2 = 15  # 小さい円の半径
d = 50   # 描画する点の距離

# 描画設定
fig, ax = plt.subplots(figsize=(8, 8))
ax.set_xlim(-R - r1 - r2 - d, R + r1 + r2 + d)
ax.set_ylim(-R - r1 - r2 - d, R + r1 + r2 + d)
ax.set_aspect('equal')
ax.axis('off')

# スピログラフの描画
gcd = np.gcd(np.gcd(R, r1), r2)
t = np.linspace(0, 2 * np.pi * (r2 // gcd), 1000 * (r2 // gcd))

x = (R - r1) * np.cos(t) + d * np.cos((R - r1) / r1 * t) + r2 * np.cos((R - r1) / r2 * t)
y = (R - r1) * np.sin(t) - d * np.sin((R - r1) / r1 * t) - r2 * np.sin((R - r1) / r2 * t)

ax.plot(x, y, color='blue')
plt.show()
download-6

import numpy as np
import matplotlib.pyplot as plt

# パラメータ設定
R = 100  # 大きい円の半径
r = 30   # 小さい円の半径
d = 60   # 描画する点の距離
a = 5    # リサージュのx軸周波数
b = 4    # リサージュのy軸周波数
delta = np.pi / 2  # 位相差

# 描画設定
fig, ax = plt.subplots(figsize=(8, 8))
ax.set_xlim(-R - r - d, R + r + d)
ax.set_ylim(-R - r - d, R + r + d)
ax.set_aspect('equal')
ax.axis('off')

# スピログラフの方程式
gcd = np.gcd(R, r)
t = np.linspace(0, 2 * np.pi * (r // gcd), 1000 * (r // gcd))

x_spiro = (R - r) * np.cos(t) + d * np.cos((R - r) / r * t)
y_spiro = (R - r) * np.sin(t) - d * np.sin((R - r) / r * t)

# リサージュの方程式
x_lissajous = np.sin(a * t + delta)
y_lissajous = np.sin(b * t)

# 合成
x = x_spiro + x_lissajous * 10
y = y_spiro + y_lissajous * 10

# 図形の描画
ax.plot(x, y, color='blue')
plt.show()
download-7




ヒルベルト曲線

空間を埋め尽くすフラクタルの一種で
曲線が空間を埋めるように描かれます
import matplotlib.pyplot as plt

def hilbert_curve(x0, y0, xi, xj, yi, yj, n):
    if n <= 0:
        x_mid = x0 + (xi + yi) // 2
        y_mid = y0 + (xj + yj) // 2
        points.append((x_mid, y_mid))
    else:
        hilbert_curve(x0, y0, yi // 2, yj // 2, xi // 2, xj // 2, n - 1)
        hilbert_curve(x0 + xi // 2, y0 + xj // 2, xi // 2, xj // 2, yi // 2, yj // 2, n - 1)
        hilbert_curve(x0 + xi // 2 + yi // 2, y0 + xj // 2 + yj // 2, xi // 2, xj // 2, yi // 2, yj // 2, n - 1)
        hilbert_curve(x0 + xi // 2 + yi, y0 + xj // 2 + yj, -yi // 2, -yj // 2, -xi // 2, -xj // 2, n - 1)

# 描画設定
order = 6  # ヒルベルト曲線の階数
points = []
hilbert_curve(0, 0, 2**order, 0, 0, 2**order, order)

# プロット
x_vals, y_vals = zip(*points)
plt.plot(x_vals, y_vals)
plt.title(f'Hilbert Curve of Order {order}')
plt.axis('equal')
plt.axis('off')
plt.show()
download-8





ドラゴンカーブ

繰り返し折り返すことで生成されるフラクタル曲線です
中華のどんぶりに描かれている模様っぽいものです
import matplotlib.pyplot as plt
from math import cos, sin, radians

def dragon_curve(order, step=1):
    def recursive_dragon(n, angle, sign):
        if n == 0:
            forward(step)
        else:
            recursive_dragon(n - 1, angle, 1)
            turn(sign * angle)
            recursive_dragon(n - 1, angle, -1)

    def forward(dist):
        nonlocal x, y
        x += dist * directions[0]
        y += dist * directions[1]
        points.append((x, y))

    def turn(angle):
        cos_theta = cos(angle)
        sin_theta = sin(angle)
        directions[0], directions[1] = (
            directions[0] * cos_theta - directions[1] * sin_theta,
            directions[0] * sin_theta + directions[1] * cos_theta
        )

    # 初期化
    x, y = 0, 0
    directions = [1, 0]  # 初期方向
    points = [(x, y)]

    # ドラゴンカーブの生成
    recursive_dragon(order, radians(90), 1)
    return points

# 描画
order = 10  # ドラゴンカーブの階数
points = dragon_curve(order)
x_vals, y_vals = zip(*points)

plt.plot(x_vals, y_vals)
plt.title(f'Dragon Curve of Order {order}')
plt.axis('equal')
plt.axis('off')
plt.show()
download-9





ペンローズタイル(没)

本来は非周期的に平面を埋め尽くすタイルのパターンですが
これはちょいとちがっちゃってます
import matplotlib.pyplot as plt
import numpy as np

def draw_kite(ax, x, y, angle, size):
    # カイトの4つの頂点を計算
    points = np.array([
        [0, 0],
        [np.cos(angle), np.sin(angle)],
        [np.cos(angle + np.pi / 5), np.sin(angle + np.pi / 5)],
        [np.cos(angle - np.pi / 5), np.sin(angle - np.pi / 5)]
    ]) * size
    points += [x, y]
    ax.fill(points[:, 0], points[:, 1], edgecolor='black', fill=False)

def draw_dart(ax, x, y, angle, size):
    # ダートの4つの頂点を計算
    points = np.array([
        [0, 0],
        [np.cos(angle), np.sin(angle)],
        [np.cos(angle + np.pi / 5) / 2, np.sin(angle + np.pi / 5) / 2],
        [np.cos(angle - np.pi / 5) / 2, np.sin(angle - np.pi / 5) / 2]
    ]) * size
    points += [x, y]
    ax.fill(points[:, 0], points[:, 1], edgecolor='black', fill=False)

def draw_penrose_tiling(order, size):
    fig, ax = plt.subplots()
    ax.set_aspect('equal')
    ax.axis('off')

    for i in range(order):
        angle = i * 2 * np.pi / order
        draw_kite(ax, 0, 0, angle, size)
        draw_dart(ax, 0, 0, angle, size)

    plt.show()

# 描画
order = 30
size = 1000
draw_penrose_tiling(order, size)
download-10


import matplotlib.pyplot as plt
import numpy as np

def draw_penrose(ax, x, y, angle, size, depth):
    if depth == 0:
        return
    
    golden_ratio = (1 + np.sqrt(5)) / 2
    new_size = size / golden_ratio

    # パチ形の描画
    points = np.array([
        [0, 0],
        [np.cos(angle) * size, np.sin(angle) * size],
        [np.cos(angle + 2 * np.pi / 5) * size, np.sin(angle + 2 * np.pi / 5) * size],
        [np.cos(angle - 2 * np.pi / 5) * new_size, np.sin(angle - 2 * np.pi / 5) * new_size]
    ])
    points += np.array([x, y])
    ax.fill(points[:, 0], points[:, 1], edgecolor='black', fill=False)

    # 再帰的に描画
    for i in range(5):
        new_angle = angle + i * 2 * np.pi / 5
        draw_penrose(ax, points[i % 4, 0], points[i % 4, 1], new_angle, new_size, depth - 1)

def plot_penrose_tiling(size, depth):
    fig, ax = plt.subplots()
    ax.set_aspect('equal')
    ax.axis('off')
    draw_penrose(ax, 0, 0, 0, size, depth)
    plt.show()

# 描画
size = 300
depth = 2
plot_penrose_tiling(size, depth)
download-11


トロコイド曲線

円が他の円の内側や外側を転がることで描かれる曲線です
ほぼほぼスピログラフですね
import matplotlib.pyplot as plt
import numpy as np

# トロコイド曲線のパラメータ
R = 5  # 大きな円の半径
r = 3  # 小さな円の半径
d = 5  # 描画する点の距離

# tの範囲
t = np.linspace(0, 10 * np.pi, 1000)

# トロコイド曲線の方程式
x = (R - r) * np.cos(t) + d * np.cos((R - r) / r * t)
y = (R - r) * np.sin(t) - d * np.sin((R - r) / r * t)

# 描画
plt.plot(x, y)
plt.title('Trochoid Curve')
plt.axis('equal')
plt.axis('off')
plt.show()
download-12

import matplotlib.pyplot as plt
import numpy as np

# 固定パラメータ
R = 6  # 大きな円の半径

# tの範囲
t = np.linspace(0, 10 * np.pi, 1000)

# プロットの準備
fig, axes = plt.subplots(5, 5, figsize=(8,8))
fig.suptitle('Trochoid Curves')

# rとdを変化させてプロット
r_values = np.linspace(1, 5, 5)
d_values = np.linspace(1, 5, 5)

for i, r in enumerate(r_values):
    for j, d in enumerate(d_values):
        x = (R - r) * np.cos(t) + d * np.cos((R - r) / r * t)
        y = (R - r) * np.sin(t) - d * np.sin((R - r) / r * t)
        
        ax = axes[i, j]
        ax.plot(x, y)
        ax.set_title(f'r={r:.1f}, d={d:.1f}')
        ax.set_aspect('equal')
        ax.axis('off')

plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
download-13
import matplotlib.pyplot as plt
import numpy as np

# パラメータ設定
R = 5  # スピログラフ用の大きな円の半径
r = 3  # スピログラフ用の小さな円の半径
d = 5  # トロコイド用の点の距離

A = 5  # リサージュ曲線の振幅
B = 4
a = 3  # リサージュ曲線のx軸方向の周波数
b = 2  # リサージュ曲線のy軸方向の周波数
delta = np.pi / 2  # リサージュ曲線の位相差

# tの範囲
t = np.linspace(0, 10 * np.pi, 1000)

# スピログラフ(トロコイド)曲線
x_spiro = (R - r) * np.cos(t) + d * np.cos((R - r) / r * t)
y_spiro = (R - r) * np.sin(t) - d * np.sin((R - r) / r * t)

# リサージュ曲線
x_lissajous = A * np.sin(a * t + delta)
y_lissajous = B * np.sin(b * t)

# 組み合わせた曲線
x_combined = x_spiro + x_lissajous
y_combined = y_spiro + y_lissajous

# 描画
plt.figure(figsize=(8, 8))
plt.plot(x_combined, y_combined, label='Combined Curve')
plt.title('Combined Curve of Spirograph, Lissajous, and Trochoid')
plt.axis('equal')
plt.axis('off')
plt.show()
download-14

import matplotlib.pyplot as plt
import numpy as np

# パラメータ設定
R = 5  # スピログラフ用の大きな円の半径
r = 3  # スピログラフ用の小さな円の半径
d = 5  # トロコイド用の点の距離

A = 5  # リサージュ曲線の振幅
B = 4
a = 3  # リサージュ曲線のx軸方向の周波数
b = 2  # リサージュ曲線のy軸方向の周波数
delta = np.pi / 2  # リサージュ曲線の位相差

n = 36 # 円周上の個数

# tの範囲
t = np.linspace(0, 10 * np.pi, 1000)

# スピログラフ(トロコイド)曲線
x_spiro = (R - r) * np.cos(t) + d * np.cos((R - r) / r * t)
y_spiro = (R - r) * np.sin(t) - d * np.sin((R - r) / r * t)

# リサージュ曲線
x_lissajous = A * np.sin(a * t + delta)
y_lissajous = B * np.sin(b * t)

# 組み合わせた曲線
x_combined = x_spiro + x_lissajous
y_combined = y_spiro + y_lissajous

# 円周上に配置するための角度
angles = np.linspace(0, 2 * np.pi, n, endpoint=False)

# 描画
plt.figure(figsize=(8, 8))

for angle in angles:
    # 回転行列を適用
    x_rotated = x_combined * np.cos(angle) - y_combined * np.sin(angle)
    y_rotated = x_combined * np.sin(angle) + y_combined * np.cos(angle)
    
    # 配置する中心を決定
    center_x = 15 * np.cos(angle)
    center_y = 15 * np.sin(angle)
    plt.plot(x_rotated + center_x, y_rotated + center_y, label=f'Angle {angle:.2f} rad')

plt.title(f'{n} Combined Curves on Circle')
plt.axis('equal')
plt.axis('off')
plt.show()
download-15




曼荼羅

同心円状や同心方形状に
円や方形の図形を配置した図形のことです
import matplotlib.pyplot as plt
import numpy as np

# 円の数とそれぞれの半径
num_circles = 12
radii = np.linspace(0.1, 5, num_circles)

# 角度の設定
angles = np.linspace(0, 2 * np.pi, 100)

# 描画
plt.figure(figsize=(8, 8))

for radius in radii:
    for angle in np.linspace(0, 2 * np.pi, num_circles, endpoint=False):
        # 各円の中心
        center_x = radius * np.cos(angle)
        center_y = radius * np.sin(angle)
        
        # 円を描画
        x = center_x + radius * np.cos(angles)
        y = center_y + radius * np.sin(angles)
        plt.plot(x, y, color='purple')

plt.title('Mandala Pattern')
plt.axis('equal')
plt.axis('off')
plt.show()
download-16


import matplotlib.pyplot as plt
import numpy as np

# 円の数とそれぞれの半径
num_layers = 36
num_circles_per_layer = 12
radii = np.linspace(0.5, 5, num_layers)

# 使用する色
colors = ['red', 'orange', 'yellow', 'green', 'blue', 'purple']

# 描画
plt.figure(figsize=(8, 8))

for i, radius in enumerate(radii):
    for angle in np.linspace(0, 2 * np.pi, num_circles_per_layer, endpoint=False):
        # 各円の中心
        center_x = radius * np.cos(angle)
        center_y = radius * np.sin(angle)
        
        # 円を描画
        angles_circle = np.linspace(0, 2 * np.pi, 100)
        x = center_x + radius * 0.3 * np.cos(angles_circle)
        y = center_y + radius * 0.3 * np.sin(angles_circle)
        plt.plot(x, y, color=colors[i % len(colors)])

plt.title('Complex Mandala Pattern')
plt.axis('equal')
plt.axis('off')
plt.show()

download-17


import matplotlib.pyplot as plt
import numpy as np

# 円の数とそれぞれの半径
num_layers = 24
num_circles_per_layer = 24
radii = np.linspace(0.5, 5, num_layers)

# 使用する色
colors = ['red', 'orange', 'yellow', 'green', 'blue', 'purple']

# 描画
plt.figure(figsize=(8, 8))

for i, radius in enumerate(radii):
    for angle in np.linspace(0, 2 * np.pi, num_circles_per_layer, endpoint=False):
        # 各円の中心
        center_x = radius * np.cos(angle)
        center_y = radius * np.sin(angle)
        
        # 円を描画
        angles_circle = np.linspace(0, 2 * np.pi, 100)
        size_variation = 0.2 + 0.1 * np.sin(angle * num_layers)  # サイズに変化をつける
        x = center_x + radius * size_variation * np.cos(angles_circle)
        y = center_y + radius * size_variation * np.sin(angles_circle)
        
        # ランダムな線幅
        # linewidth = np.random.uniform(0.5, 2.5)
        linewidth = 1
        plt.plot(x, y, color=colors[i % len(colors)], linewidth=linewidth)

plt.title('Complex Mandala Pattern with Variations')
plt.axis('equal')
plt.axis('off')
plt.show()
download-18


こんな感じで、図形を組み合わせることで
様々な紋様を描く事ができます。

複雑な図形を組み合わせれば
それなりにアートっぽく
なって行くんではないでしょうか

気になった方はコピペして
色々パラメータ変えて
試してみてください

それでは。


今回はインスタ風のQRコードを作成する
方法についてです。

解説動画はこちら



通常のQRコード

sample

普通のQRコードはこんな感じです。
四角い黒で表現され、お堅い感じです。

インスタ風は、丸みがあり少しポップな色味です。

これを再現するには通常のPythonライブラリでは
難しいようでした。



インスタ風QRコードを再現する

ということで、自作することにしました。

Google Colabで実行できるコードを作成しました。


ライブラリのインストール

実行にはqrcodeライブラリが必要なのでインストールしましょう。
pip install qrcode

QRコード作成コード
import qrcode
from PIL import Image, ImageDraw

def create_round_qr(data, file_path):
    # QRコードを生成
    qr = qrcode.QRCode(
        version=1,
        error_correction=qrcode.constants.ERROR_CORRECT_H,
        box_size=10,
        border=4,
    )
    qr.add_data(data)
    qr.make(fit=True)

    # QRコードをイメージに変換
    img = qr.make_image(fill='black', back_color='white').convert('RGB')
    pix = img.load()

    # 新しいイメージを作成
    width, height = img.size
    round_img = Image.new('RGB', (width, height), 'white')
    draw = ImageDraw.Draw(round_img)

    # 各ピクセルをチェックして形状を描画
    cell_size = 10  # box_sizeと同じにする
    pink = (255, 105, 180)
    white = (255, 255, 255)

    # 描画開始位置を計算(余白を除く)
    start_pos = cell_size * 4

    # 形状を変換
    for y in range(start_pos, height, cell_size):
        for x in range(start_pos, width, cell_size):
            if pix[x, y] == (0, 0, 0):
                draw.ellipse((x, y, x + cell_size, y + cell_size), fill=pink)

    # 隅の四角
    corner_size = cell_size * 7
    for dx, dy in [(0, 0), (width - corner_size - start_pos * 2, 0), (0, height - corner_size - start_pos * 2)]:
        x0, y0 = start_pos + dx, start_pos + dy
        x1, y1 = x0 + corner_size, y0 + corner_size
        draw.rounded_rectangle((x0, y0, x1, y1), radius=0, fill=white)
        draw.rounded_rectangle((x0, y0, x1, y1), radius=15, fill=pink)
        draw.rounded_rectangle((x0 + 1 * cell_size, y0 + 1 * cell_size, x1 - 1 * cell_size, y1 - 1 * cell_size), radius=7, fill=white)
        draw.rounded_rectangle((x0 + 2 * cell_size, y0 + 2 * cell_size, x1 - 2 * cell_size, y1 - 2 * cell_size), radius=5, fill=pink)

    # 保存
    round_img.save(file_path)

一旦QRコードを作成して画像化し
それを加工する内容になっています。

色は pink で指定していますが
変えたい場合はコードのRGB値を変えて貰えば変わります。



実際に使ってみましょう
from PIL import Image
from IPython.display import display

# QRコードを作成(URL , ファイルパス)
create_round_qr("http://www.otupy.net/", "insta.png")

# 画像を読み込む
img = Image.open("insta.png")

# 画像を表示
display(img)
download
すごくpopな形のQRコードが作成できました。

これを配布したら、オシャレですね

今回はインスタ風の
QRコードを作成するコードについてでした

色々試して遊んでみてください。

それでは。

今回は大谷選手の
ホームラン記録を可視化してみました。


解説動画はこちら



先日2024年09月20日
6打数3安打3ホームランなど
前人未到の記録を打ち立てた
ドジャース大谷選手

2004年シーズンの
今日までの成績を可視化してみました。


データの取得


可視化の元となるデータを取得します。

2024/09/21日現在
今季通算 603打数179安打 打率.297 52本塁打
122打点 125得点 52盗塁
という驚異的な成績です

この結果になるように加工していきます。

import requests
from bs4 import BeautifulSoup
import pandas as pd
import warnings
warnings.filterwarnings('ignore')

url = "https://times.abema.tv/articles/-/10018233"

res = requests.get(url)
soup = BeautifulSoup(res.content,"lxml")
table = soup.table

columns = ['日付', '対戦相手', '打順', '第1', '第2', '第3', '第4', '第5', '第6', '第7']
data = []
trs = table.find_all("tr")
for tr in trs[1:]:
    tds = tr.find_all(["td","th"])
    if "なし" in str(tds) or "中止" in str(tds):
        continue
    tmp = [td.text.replace("\xa0","").replace("\n","").replace("\r","").replace("\t","") for td in tds]
    tmp = tmp[0:3]+[t.replace("ニ","二").replace("2","二").replace("3","三") for t in tmp[3:]]
    if len(tmp)>=11:
        tmp = tmp[:10]
    data.append(tmp)

data2 = []
for d in data:
    t1,t2 = d[0:3],d[3:]
    for t in t2:
        if ""!=t:
            data2.append(t1+[t])

# データの確認
data2[0:2]
[['3月20日', 'パドレス', '2指', '遊ゴ'], ['3月20日', 'パドレス', '2指', '右安']]


これを集計しやすいようにデータフレームに加工していきます。
df = pd.DataFrame(data2,columns=["date","match","order","result"])

# 日本語の日付を変換する関数
def convert_date(date_str):
    month,day = date_str.replace("日","").split("月")
    return f"2024-{int(month):02}-{int(day):02}"

df['date'] = df['date'].apply(convert_date)
df['date'] = pd.to_datetime(df['date'])

# 本塁打
df['home_run'] = df['result'].apply(lambda x: 1 if '本' in x else 0)

# 安打
df['hit1'] = df['result'].apply(lambda x: 1 if '安' in x else 0)

# 2塁打
df['hit2'] = df['result'].apply(lambda x: 1 if x.endswith('二') else 0)

# 3塁打
df['hit3'] = df['result'].apply(lambda x: 1 if x.endswith('三') else 0)

# 指定された用語のリスト
terms = ['四球', '敬遠', '死球', '打妨', '中犠', '右犠', '左犠']

# 打数計算
df['flag'] = df['result'].apply(lambda x: 0 if x in terms else 1)

# 打席数合計
df['strokes'] = df['flag'].cumsum()

# ヒット数合計
df['hit_total'] = df[['hit1','hit2','hit3']].sum(axis=1).cumsum()

# ホームラン数合計
df['home_run_total'] = df['home_run'].cumsum()

# 打率
df['average'] = df[['hit_total','home_run_total']].sum(axis=1) / df['strokes']

# 603打数179安打 打率.297 52本塁打
df.tail()
index,date,match,order,result,home_run,hit1,hit2,hit3,flag,strokes,hit_total,home_run_total,average
688,2024-09-20 00:00:00,マーリンズ,1指,右本,1,0,0,0,1,599,125,51,0.2938230383973289
689,2024-09-21 00:00:00,ロッキーズ,1指,空振,0,0,0,0,1,600,125,51,0.29333333333333333
690,2024-09-21 00:00:00,ロッキーズ,1指,中安,0,1,0,0,1,601,126,51,0.2945091514143095
691,2024-09-21 00:00:00,ロッキーズ,1指,中本,1,0,0,0,1,602,126,52,0.2956810631229236
692,2024-09-21 00:00:00,ロッキーズ,1指,一安,0,1,0,0,1,603,127,52,0.296849087893864

これで同じ結果になりました。



Altairでプロットする

ここからこのデータを用いて
可視化を行います。

import pandas as pd
import altair as alt

# Altairで折れ線グラフを作成
chart = alt.Chart(df).mark_line().encode(
    x='date',
    y='average'
).properties(
    title='Data vs Average',
    width=800
)
chart.display()
visualization (1)



打率とホームラン数の推移を
2軸でプロットするとこうなります。
# averageの折れ線グラフ
average_line = alt.Chart(df).mark_line(color='blue').encode(
    x='date',
    y=alt.Y('average', axis=alt.Axis(title='Average'))
)

# home_run_totalの折れ線グラフ
home_run_line = alt.Chart(df).mark_line(color='red').encode(
    x='date',
    y=alt.Y('home_run_total', axis=alt.Axis(title='Home Run Total'))
)

# 二重軸のグラフを作成
chart = alt.layer(
    average_line,
    home_run_line
).resolve_scale(
    y='independent'
).properties(
    title='Data vs Average and Home Run Total',
    width=800
)
chart.display()
visualization


途中から急にホームラン数のピッチが上がっているようです。

ここからどれだけ伸びるのか
残り試合のホームラン数の予測もしてみます。


ホームラン数を時系列予測

残り試合は8試合です。
ARIMAという時系列予測モデルを用いて
予測をしてきます。

先ほどのデータフレームを加工して
予測データを作っていきます。

import pandas as pd
from statsmodels.tsa.arima.model import ARIMA
import matplotlib.pyplot as plt

# 日付をインデックスに設定
df2 = df.copy()
df2 = df.groupby('date').max()

# 欠損日を埋めて連続した日付にする
df2 = df2.asfreq('D')

# ARIMAモデルのフィッティング
model = ARIMA(df2['home_run_total'], order=(1, 1, 1))
model_fit = model.fit()

# 8日先までの予測
forecast = model_fit.forecast(steps=8)

# 結果のプロット
plt.figure(figsize=(12, 4))
plt.plot(df2.index, df2['home_run_total'], label='Actual')
plt.plot(pd.date_range(df2.index[-1] + pd.Timedelta(days=1), periods=8, freq='D'), forecast, label='Forecast', color='red')
plt.legend()
plt.show()
download

実測と予測を組み合わせて可視化しました。

残り8試合だとすると
およそ55本くらいまでは伸びそうです。

予測が外れて60本塁打くらいまで
行って欲しいですね!!!!

今回は大谷選手の打席結果を用いた
ホームラン成績の可視化でした

それでは。

このページのトップヘ