ジャンルから探す

Python English

PandasとMatplotlibを活用したデータ可視化

PandasとMatplotlibでグラフを描くとき、最初に必ずつまずくのが日本語ラベルの文字化け(□の羅列)です。結論から書くと、対処はpip install matplotlib-fontjaしてインポートするだけ。これで軸ラベルもタイトルも日本語で表示されます。

よくある「plt.rcParams['font.family'] = 'Arial'と書けば日本語が出る」という説明は誤りです。Arialに日本語のグリフは入っていませんので、この設定では文字化けが直りません。

この記事では、環境設定でつまずかないところから始めて、Pandas での前処理、折れ線・棒・ヒストグラム・散布図・箱ひげ図の描き方、そしてpandas 2系 / Matplotlib 3.10 以降で書き方が変わった箇所までを、動くコードで解説します。

スポンサーリンク

まず日本語表示を通す

最短の方法はmatplotlib-fontjaを入れることです。 インポートするだけで、日本語フォントの検出と設定を自動でやってくれます。

pip install pandas matplotlib matplotlib-fontja
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib_fontja  # インポートするだけで日本語が通る

plt.plot([1, 2, 3], [10, 20, 15])
plt.title("売上の推移")
plt.xlabel("月")
plt.ylabel("金額(万円)")
plt.show()

以前はjapanize-matplotlibが定番でしたが、Python 3.12 でdistutilsが削除された影響でインポートに失敗する時期がありました(現在は修正済みです)。これから始めるならmatplotlib-fontjaを選んでおく方が無難です。

ライブラリを追加せずに設定する場合

環境にライブラリを足せない場合は、OSに入っているフォントを直接指定します。

import matplotlib.pyplot as plt

# OSごとに入っている日本語フォントを候補として並べる
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = [
    'Hiragino Sans',       # macOS
    'Yu Gothic',           # Windows
    'Meiryo',              # Windows
    'Noto Sans CJK JP',    # Linux
    'IPAexGothic',
]
plt.rcParams['axes.unicode_minus'] = False  # マイナス記号の文字化けを防ぐ

axes.unicode_minus = Falseも併せて指定してください。日本語フォントを設定すると、負の値の軸ラベルだけが文字化けすることがあります。原因は、多くの日本語フォントがMatplotlibの使う特殊なマイナス記号(U+2212)を持っていないためです。

Pandas でデータを読み込んで整える

可視化の前に、読み込みの段階で型を確定させておくと後の処理が楽になります。

import pandas as pd

df = pd.read_csv(
    "sample_data.csv",
    parse_dates=["date"],   # 読み込み時に日付型にする
    encoding="utf-8-sig",   # Excelが書き出したCSVのBOM対策
)

print(df.head())
print(df.dtypes)

encoding="utf-8-sig"は地味に効きます。Excelから書き出したCSVには先頭にBOMが付いており、utf-8で読むと1列目の列名がdateのようになって参照できなくなります。

欠損値の確認と処理

# 列ごとの欠損数を確認する
print(df.isna().sum())

# ① 欠損のある行を落とす
df = df.dropna(subset=["value"])

# ② 平均で埋める
df["value"] = df["value"].fillna(df["value"].mean())

inplace=Trueは使わないでください。 pandas 2系ではinplaceの使用が非推奨の方向にあり、加えてコピーが発生するかどうかが分かりにくく、意図しない挙動の原因になります。上のように代入で書き戻すのが確実です。

スポンサーリンク

折れ線グラフ:時系列の推移を見る

df = df.sort_values("date")   # 時系列は必ず並べ替えてから描く

fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(df["date"], df["value"], marker="o", linestyle="-")

ax.set_title("時系列データの可視化")
ax.set_xlabel("日付")
ax.set_ylabel("値")
ax.grid(alpha=.3)

fig.autofmt_xdate()   # 日付ラベルを斜めにして重なりを防ぐ
plt.show()

2つのポイントがあります。

  • 描く前にsort_values()する:日付順に並んでいないと、線が前後に行き来してギザギザになります
  • fig, ax = plt.subplots()の形で書く:plt.plot()を直接呼ぶ書き方は、グラフが複数になった途端にどこに描かれるか分からなくなります

棒グラフ:カテゴリごとに比較する

summary = (
    df.groupby("category", as_index=False)["value"]
      .sum()
      .sort_values("value", ascending=False)   # 大きい順に並べる
)

fig, ax = plt.subplots(figsize=(8, 4))
ax.bar(summary["category"], summary["value"])

ax.set_title("カテゴリ別の合計値")
ax.set_xlabel("カテゴリ")
ax.set_ylabel("合計値")

# 各バーの上に値を表示する
ax.bar_label(ax.containers[0], fmt="%.0f")

plt.show()

棒グラフは必ず値の大きい順に並べ替えてください。 カテゴリ名のアルファベット順や登録順のまま出すと、比較という目的が果たせません。ax.bar_label()で数値を添えると、資料としてそのまま使えます。

スポンサーリンク

ヒストグラム:分布の形を見る

fig, ax = plt.subplots(figsize=(8, 4))
ax.hist(df["value"].dropna(), bins=20, edgecolor="black")

ax.set_title("値の分布")
ax.set_xlabel("値")
ax.set_ylabel("度数")
plt.show()

binsの数で見え方が大きく変わります。 少なすぎると山が潰れ、多すぎると凹凸だらけになります。まずbins="auto"で自動決定させ、そのうえで調整するのが効率的です。

散布図:2変数の関係を見る

fig, ax = plt.subplots(figsize=(6, 6))
ax.scatter(df["feature1"], df["feature2"], alpha=0.4, s=20)

ax.set_title("散布図による相関の確認")
ax.set_xlabel("特徴1")
ax.set_ylabel("特徴2")
plt.show()

alphaを下げるのが定石です。点が数千を超えると重なりが潰れて、密度の違いが見えなくなります。それでも足りない場合はax.hexbin()で密度表示に切り替えてください。

箱ひげ図:カテゴリ別のばらつきを見る

ここは書き方が変わった箇所です。 Matplotlib 3.10(2024年12月)で、向きを指定するvert引数が非推奨になり、orientationに置き換わりました。

groups = [g["value"].dropna().values for _, g in df.groupby("category")]
labels = [name for name, _ in df.groupby("category")]

fig, ax = plt.subplots(figsize=(8, 4))

# ❌ 旧: ax.boxplot(groups, vert=False)
# ✅ 新: orientation を使う
ax.boxplot(groups, tick_labels=labels, orientation="horizontal")

ax.set_title("カテゴリ別のばらつき")
ax.set_xlabel("値")
plt.show()

ラベル指定の引数名もlabelsからtick_labelsに変わっています。古いコードをそのまま動かすと警告が出るので、この2点は書き換えておいてください。

df.boxplot(column="value", by="category")という書き方もできますが、自動で「Boxplot grouped by category」という余計なタイトルが付き、plt.title()で消せません。資料に使うなら上のようにax.boxplot()を直接呼ぶ方が扱いやすいです。

ヒートマップ:相関行列を見る

ここも pandas 2系で挙動が変わりました。 文字列の列が混ざったままdf.corr()を呼ぶと、エラーになります。

import seaborn as sns

# ❌ 文字列列があると ValueError になる
# corr = df.corr()

# ✅ 数値列だけを対象にする
corr = df.corr(numeric_only=True)

fig, ax = plt.subplots(figsize=(8, 6))
sns.heatmap(corr, annot=True, fmt=".2f", cmap="coolwarm",
            vmin=-1, vmax=1, ax=ax)

ax.set_title("相関のヒートマップ")
plt.show()

vmin=-1, vmax=1の指定を忘れないでください。指定しないと、実際のデータの範囲に合わせて色のスケールが自動調整され、相関0.3が真っ赤に見えるようなことが起こります。相関行列は必ず-1〜1で固定します。

練習用データセットの読み込み

手元にデータが無いときは、seabornに同梱のデータセットが手軽です。scikit-learnを追加でインストールする必要がありません。

import seaborn as sns

df = sns.load_dataset("iris")     # アヤメの計測データ
# df = sns.load_dataset("titanic") # タイタニック号の乗客データ
# df = sns.load_dataset("tips")    # レストランのチップデータ

print(df.head())
print(df.describe())

読み込みには通信が発生します(初回にGitHubから取得します)。オフライン環境ではscikit-learnのload_iris()を使ってください。

df.hist(figsize=(10, 6), bins=20)
plt.tight_layout()   # ラベルの重なりを解消する
plt.show()

複数のグラフを並べたときはplt.tight_layout()を最後に呼びます。これが無いと、軸ラベルが隣のグラフに食い込んで読めなくなります。

画像として保存する

fig.savefig("chart.png", dpi=150, bbox_inches="tight")
  • bbox_inches="tight":軸ラベルが切れるのを防ぎます。これを付けないと、回転させた日付ラベルの下側が欠けます
  • dpi=150:既定の100だと資料に貼ったときに粗く見えます
  • plt.show()より前に呼ぶ:表示後は図が破棄され、空の画像が保存されることがあります

まとめ

  • 日本語の文字化けはmatplotlib-fontjaをインポートするだけで解決する。Arialを指定しても直らない
  • マイナス記号の化けはaxes.unicode_minus = Falseで防ぐ
  • ExcelのCSVはencoding="utf-8-sig"で読む
  • inplace=Trueを使わず、代入で書き戻す
  • 時系列はsort_values()してから、棒グラフは値の大きい順に並べてから描く
  • 箱ひげ図のvertはMatplotlib 3.10で非推奨。orientationとtick_labelsを使う
  • df.corr()はnumeric_only=Trueを付ける。ヒートマップはvmin=-1, vmax=1で固定する
  • 保存はbbox_inches="tight"を付けてplt.show()より前に

統計的なグラフをもっと簡単に描きたい場合は、Seabornの方が短く書けます。PandasとSeabornで学ぶデータ分析と可視化で解説しているので、あわせてどうぞ。