Pythonデータ分析 #6 可視化: matplotlibの基本とチャートの選び方

groupby で集計した表をいくら眺めても見えなかったものが、折れ線グラフを 1 枚描いた瞬間にすぐ浮かび上がることはよくあります。売上がいつから落ち始めたのか、どの 1 件が平均を丸ごと引き上げたのか、といった質問は数字よりも図の方がはるかに速いです。今回は matplotlib の最小構造を押さえ、目的に合ったチャートを選ぶ基準と、日本語環境の定番問題であるフォントの文字化けまで整理します。

  • #1 スタート
  • #2 読み込みと探索
  • #3 選択とフィルタ
  • #4 変形と欠損値
  • #5 グループと集計・結合
  • #6 可視化 ← 今回
  • #7 polars入門とまとめ

表より図が速い瞬間 #

要約統計は、トレンドと外れ値に弱いです。describe() が教えてくれる平均と標準偏差が同じでも、時間とともに着実に上がるデータと、上下に振れるデータはまったく別のデータです。トレンドは線を描いてはじめて見え、外れ値は点を打ってはじめて見えます。だから探索段階において、図は選択肢ではなく基本の道具です。

まず、今回の記事全体で使うサンプルデータを作ります。#5 で扱った注文データと同じ形です。

サンプルデータ
import pandas as pd
import numpy as np

rng = np.random.default_rng(42)
dates = pd.date_range("2026-01-01", periods=180, freq="D")
df = pd.DataFrame({
    "注文日": rng.choice(dates, size=600),
    "カテゴリ": rng.choice(["衣類", "食品", "家電", "書籍"], size=600),
    "金額": rng.integers(5_000, 120_000, size=600),
    "数量": rng.integers(1, 6, size=600),
})

matplotlibの最小モデル: FigureとAxes #

matplotlib は学ぶことが多そうに見えますが、実務で必要な構造は 2 つだけです。

  • Figure: 画用紙全体です。サイズと保存の単位はここに付きます。
  • Axes: 画用紙の上の座標平面 1 つです。実際の描画はすべて Axes が担当します。

この 2 つを一度に作る plt.subplots() パターンを 1 つだけきちんと身につければ十分です。

基本パターン
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 4))
ax.plot([1, 2, 3, 4], [10, 30, 25, 40])
ax.set_title("基本パターン")
plt.show()

検索していると、plt.plot(...) のように plt に直接描くコードもよく見かけます。短い実験なら問題ありませんが、グラフが 2 つ以上になると「いまどこに描いているのか」があいまいになります。最初から fig, ax = plt.subplots() で始めて ax に描く習慣を付ければ、混乱がありません。

DataFrame.plotで素早く #

pandas の DataFrame.plot は、内部で matplotlib を呼び出すショートカットのインターフェースです。探索段階ではこちらの方がはるかに速いです。

DataFrame.plot
monthly = df.groupby(df["注文日"].dt.to_period("M"))["金額"].sum()
monthly.plot(figsize=(8, 4), marker="o")
plt.show()

plot() が返す値は matplotlib の Axes です。そのため pandas で素早く描いた後、タイトルやラベルといった細部の調整は matplotlib のメソッドで続けられます。2 つの道具が別々ではなく、同じ図を触っているわけです。

pandasで描いてmatplotlibで仕上げる
ax = monthly.plot(figsize=(8, 4), marker="o")
ax.set_title("月別売上")
ax.set_ylabel("金額(円)")

チャートの選び方: 4種類で十分です #

チャートの種類は数十ありますが、分析段階で実際に使うのは 4 種類です。「何を見せたいのか」から出発すれば、迷うことはほとんどありません。

見せたいものチャート呼び出し
時間に沿ったトレンド折れ線グラフmonthly.plot()
項目間の比較棒グラフby_cat.plot.bar()
値の分布ヒストグラムdf["金額"].plot.hist(bins=30)
2変数の関係散布図df.plot.scatter(x="金額", y="数量")

4 種類を実際に描くと次のようになります。

4種類の基本チャート
by_cat = df.groupby("カテゴリ")["金額"].sum().sort_values(ascending=False)

monthly.plot(marker="o")                  # トレンド: 折れる時点が見えます
by_cat.plot.bar()                         # 比較: 項目間の大きさの違い
df["金額"].plot.hist(bins=30)             # 分布: 偏りと裾
df.plot.scatter(x="金額", y="数量")        # 関係: 相関と外れ値

逆に言えば、円グラフや 3D チャートが必要になる瞬間は、分析段階ではほとんどありません。比較なら棒グラフの方が常に正確に読み取れます。

飾りは控えめに: タイトル、軸ラベル、凡例まで #

探索用の図でカラーパレットやスタイルを選ぶのに時間を使うのは無駄です。人に見せる図でも、次の 3 つで十分です。

必要な飾りのすべて
fig, ax = plt.subplots(figsize=(8, 4))
monthly.plot(ax=ax, marker="o", label="売上")
ax.set_title("月別売上トレンド")
ax.set_xlabel("月")
ax.set_ylabel("金額(円)")
ax.legend()

避けるべきものもはっきりしています。3D チャート は奥行き方向の歪みのせいで値を正確に読み取れません。二重軸(twinx)は 2 つの軸のスケールの取り方しだいで、同じデータがまったく違う印象を与えるため、見る人を誤導しやすいです。2 つの指標を比較したいなら、グラフを上下に 2 つ描く方が安全です。

日本語フォントの文字化け: 日本語環境の定番問題 #

上のコードをそのまま実行すると、タイトルやラベルの日本語がすべて豆腐(□)で表示される環境が多くあります。matplotlib のデフォルトフォントに日本語のグリフがないためです。OS ごとの標準日本語フォントを指定すれば解決します。

日本語フォント設定
import platform
import matplotlib.pyplot as plt

if platform.system() == "Windows":
    plt.rcParams["font.family"] = "Meiryo"            # メイリオ
elif platform.system() == "Darwin":
    plt.rcParams["font.family"] = "Hiragino Sans"     # macOS
else:
    plt.rcParams["font.family"] = "Noto Sans CJK JP"  # Linux(要インストール)

plt.rcParams["axes.unicode_minus"] = False

最後の行が見落としやすい落とし穴です。日本語フォントに切り替えると、今度は負の軸ラベルのマイナス記号が豆腐に化けます。matplotlib がデフォルトで使う Unicode のマイナス(U+2212)が、日本語フォントに含まれていないことが多いためです。axes.unicode_minus を無効にすると通常のハイフンに置き換えられ、正常に表示されます。日本語フォントの設定とこのオプションは、常にワンセットで覚えておけばよいです。ノートブックなら、このセルを一番上に置いて始めるのが楽です。

なお、japanize-matplotlib(後継の matplotlib-fontja もあります)というパッケージをインストールして import するだけで、日本語フォントを一括設定する近道もあります。OS を判定するコードを書きたくない場合に便利です。

保存とノートブックでの表示 #

Jupyter ノートブックでは、セルを実行すると図がすぐ下に表示されます。スクリプトとして実行するときは plt.show() を呼ばないとウィンドウが開きません。ファイルに保存するときは savefig を使います。

保存
fig.savefig("report.png", dpi=150, bbox_inches="tight")
  • dpi: 解像度です。画面での確認用なら 100、文書への挿入用なら 150〜300 で十分です。
  • bbox_inches="tight": ラベルが図の外に切れてしまう問題を防いでくれます。

注意点が 1 つあります。plt.show() を呼んだ後に savefig を呼ぶと、空の図が保存される環境があります。保存が目的なら、savefig を先に呼ぶ順序を守れば大丈夫です。

総合: groupbyの結果を1枚のレポート図に #

#5 で作った 2 つの集計(月別トレンド、カテゴリ別比較)を、1 枚の図に並べて収めてみます。plt.subplots(1, 2) で Axes を 2 つ作り、各集計結果を ax= 引数で差し込む構造です。

総合サンプル
monthly = df.groupby(df["注文日"].dt.to_period("M"))["金額"].sum()
by_cat = df.groupby("カテゴリ")["金額"].sum().sort_values(ascending=False)

fig, axes = plt.subplots(1, 2, figsize=(12, 4))

monthly.plot(ax=axes[0], marker="o")
axes[0].set_title("月別売上トレンド")
axes[0].set_ylabel("金額(円)")

by_cat.plot.bar(ax=axes[1], rot=0)
axes[1].set_title("カテゴリ別売上")

fig.tight_layout()
fig.savefig("monthly_report.png", dpi=150, bbox_inches="tight")

fig.tight_layout() は、2 つのグラフのラベルが互いに重ならないよう間隔を自動調整します。集計は pandas が、配置と保存は matplotlib が受け持つこの分業が、可視化コードの基本骨格です。グラフが 4 つに増えても、plt.subplots(2, 2)axes[行, 列] のインデックスで同じパターンがそのまま拡張できます。

まとめ #

今回扱った内容です。

  • matplotlib の構造は Figure(画用紙)と Axes(座標平面)の 2 つで十分で、plt.subplots() パターン 1 つから始めます
  • DataFrame.plot は matplotlib のショートカットのインターフェースで、返ってきた Axes を続けて仕上げられます
  • チャートは目的で選びます。トレンドは折れ線、比較は棒、分布はヒストグラム、関係は散布図です
  • 飾りはタイトル、軸ラベル、凡例までにとどめ、3D と二重軸は避けます
  • 日本語フォントの指定と axes.unicode_minus = False は常にワンセットです
  • 保存は savefigdpibbox_inches="tight" をあわせて指定します

次回(#7 polars入門とまとめ)では、pandas よりはるかに速い次世代ライブラリ polars を体験し、全 7 回の流れを 1 枚に整理してシリーズを締めくくります。

X