やりたいこと
僕の趣味のひとつがヨーヨーです。たくさんのヨーヨーを収集し、振って遊んでいます。ヨーヨーにはいろいろな大きさ (直径・幅) があり、それによって振り心地が変わります。
ふと「僕が持っているヨーヨーの直径・幅ってどんな傾向があるんだろう?」と思いました。そこで Matplotlib を使って散布図を描画することで可視化してみることにしました。
ヨーヨーについて気になる方はこちら。
バージョン情報
$ sw_vers
ProductName: macOS
ProductVersion: 15.0.1
BuildVersion: 24A348
$ python --version
Python 3.13.0
$ pip list | grep -e matplotlib -e numpy -e scipy
matplotlib 3.9.2
numpy 2.1.3
scipy 1.14.1
方法
準備
持っているヨーヨーのスペック (名前と直径 (mm)、幅 (mm) など) を一覧化した CSV を用意します。僕は Notion で管理しているため、そのエクスポート機能を使用して CSV を生成しました。
用意した CSV を ~/Downloads/yoyos.csv
に配置します。
必要なライブラリのインストール
$ pip install matplotlib numpy scipy
散布図の生成
以下の Python スクリプトを用意して、実行します。
import csv
import os
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
from scipy.stats import gaussian_kde
matplotlib.use('Agg')
XMIN = 40.0
XMAX = 80.0
YMIIN = 30.0
YMAX = 60.0
diameters = []
widths = []
csv_filepath = os.path.join(os.environ['HOME'], 'Downloads/yoyos.csv')
with open(csv_filepath, 'r', encoding='utf-8') as f:
reader = csv.reader(f)
for i, row in enumerate(reader):
if i == 0:
continue
diameters.append(round(float(row[3]), 1))
widths.append(round(float(row[4]), 1))
x = np.array(diameters)
y = np.array(widths)
fig, ax = plt.subplots()
ax.set_xlabel('Diameter (mm)', fontsize=10)
ax.set_xlim(XMIN, XMAX)
ax.xaxis.set_major_locator(ticker.MultipleLocator(5.0))
ax.xaxis.set_minor_locator(ticker.MultipleLocator(1.0))
ax.set_ylabel('Width (mm)', fontsize=10)
ax.set_ylim(YMIIN, YMAX)
ax.yaxis.set_major_locator(ticker.MultipleLocator(5.0))
ax.yaxis.set_minor_locator(ticker.MultipleLocator(1.0))
ax.set_axisbelow(True)
ax.grid(which='both')
xy = np.vstack([x, y])
z = gaussian_kde(xy)(xy)
ax.scatter(x, y, alpha=0.8, c=z, cmap='jet')
fig.savefig(os.path.join(os.environ['HOME'], 'Downloads/fig.png'))
そうすると ~/Downloads/fig.png
に以下の散布図が PNG ファイルとして出力されます。
僕はどうやら直径 56mm、幅 44mm くらいのヨーヨーをたくさん持っているようです。
解説
Matplotlib のバックエンドを指定する
matplotlib.use('Agg')
Matplotlib は図を描画する際にデフォルトで Tkinter を使用します。しかし、僕の環境では
ModuleNotFoundError: No module named '_tkinter'
というエラーが発生してしまいます。Tkinter のインストールや Python の再インストールをしても解決しなかったため、とりあえず AGG (Anti-Grain Geometry) を使うことにしました。
僕の環境には気付いたらインストールされていましたが、無さそうな場合は Homebrew でインストールしてみてください。
$ brew install agg
CSV から値を読み込む
diameters = []
widths = []
csv_filepath = os.path.join(os.environ['HOME'], 'Downloads/yoyos.csv')
with open(csv_filepath, 'r', encoding='utf-8') as f:
reader = csv.reader(f)
for i, row in enumerate(reader):
if i == 0:
continue
diameters.append(round(float(row[3]), 1))
widths.append(round(float(row[4]), 1))
x = np.array(diameters)
y = np.array(widths)
用意した Downloads/yoyos.csv
には 4 列目に直径の値、5 列目に幅の値があります。そこで CSV を読み込んで、それらの値を配列に格納します。そして直径を x 軸、幅を y 軸として使用します。なお、CSV の 1 行目はヘッダ行なので無視します。
散布図の設定を行う
XMIN = 40.0
XMAX = 80.0
YMIIN = 30.0
YMAX = 60.0
# 省略
fig, ax = plt.subplots()
ax.set_xlabel('Diameter (mm)', fontsize=10)
ax.set_xlim(XMIN, XMAX)
ax.xaxis.set_major_locator(ticker.MultipleLocator(5.0))
ax.xaxis.set_minor_locator(ticker.MultipleLocator(1.0))
ax.set_ylabel('Width (mm)', fontsize=10)
ax.set_ylim(YMIIN, YMAX)
ax.yaxis.set_major_locator(ticker.MultipleLocator(5.0))
ax.yaxis.set_minor_locator(ticker.MultipleLocator(1.0))
ax.set_axisbelow(True)
ax.grid(which='both')
ここでは散布図の軸のラベルと表示範囲、そして目盛と罫線を設定しています。大きな目盛り (major) は 5mm 単位、小さな目盛り (minor) は 1mm 単位で設定しています。大きな目盛りと小さな目盛り両方で罫線を表示するには matplotlib.pyplot.grid 関数で which='both'
を指定する必要があります。
散布図をプロットする
xy = np.vstack([x, y])
z = gaussian_kde(xy)(xy)
plt.scatter(x, y, alpha=0.8, c=z, cmap='jet')
という記事を参考に scipy.stats.gaussian_kde クラスを使って散布図の密度を表現できるようにします。
散布図を出力する
plt.savefig(os.path.join(os.environ['HOME'], 'Downloads/fig.png'))
Downloads/fig.png
に散布図を PNG ファイルとして出力することで完了です。