概要(棒+折れ線は「量と推移」を一枚で伝える定番の複合グラフ)
棒グラフは“量”の比較、折れ線グラフは“推移”や“傾向”の読み取りに向いています。2つを重ねることで「月別売上の量(棒)とユーザー数の推移(線)」のように、違う性質の情報を一度に伝えられます。重要なのは、軸の扱い(同軸か二軸か)、時系列の整列、色と凡例の明確化です。
基本の重ね方(同じ軸で棒+折れ線)
最小例(同じ単位なら同じ軸で重ねる)
import pandas as pd
import matplotlib.pyplot as plt
df = pd.DataFrame({
"month": ["Jan", "Feb", "Mar", "Apr"],
"sales": [100, 120, 90, 140], # 量(棒)
"users": [50, 55, 53, 60] # 推移(線)
})
fig, ax = plt.subplots(figsize=(6, 4))
ax.bar(df["month"], df["sales"], color="#4e79a7", alpha=0.6, label="Sales")
ax.plot(df["month"], df["users"], color="#f28e2c", marker="o", linewidth=2, label="Users")
ax.set_title("Monthly Sales and Users")
ax.set_xlabel("Month")
ax.set_ylabel("Value")
ax.legend()
ax.grid(True, axis="y", alpha=0.3)
plt.tight_layout()
plt.show()
Python同じスケールで比較可能なら、一本のAxesに重ねるのがシンプルで見やすいです。棒は透明度を少し下げると、線が埋もれません。
時系列をインデックスにして自然な軸へ
df = pd.DataFrame({
"date": pd.date_range("2025-01-01", periods=6, freq="D"),
"sales": [100, 120, 90, 140, 110, 130],
"users": [50, 55, 53, 60, 58, 62]
}).set_index("date")
ax = df[["sales"]].plot(kind="bar", color="#4e79a7", alpha=0.6, figsize=(7, 4), label="Sales")
df["users"].plot(ax=ax, color="#f28e2c", marker="o", linewidth=2, label="Users")
ax.set_title("Daily Sales and Users")
ax.set_ylabel("Value")
ax.legend()
ax.grid(True, axis="y", alpha=0.3)
plt.tight_layout()
plt.show()
PythonDateTimeIndexを使うと、目盛りや範囲指定が自然になり、時系列との相性が良くなります。
二軸グラフ(単位や桁が違うときの安全な重ね方)
左右で単位を分ける(売上と成長率など)
import pandas as pd
import matplotlib.pyplot as plt
df = pd.DataFrame({
"month": ["Jan", "Feb", "Mar", "Apr"],
"revenue": [100, 120, 90, 140], # 万円
"growth_pct": [0.10, 0.05, -0.08, 0.15] # 比率
})
fig, ax1 = plt.subplots(figsize=(6, 4))
ax2 = ax1.twinx()
ax1.bar(df["month"], df["revenue"], color="#4e79a7", alpha=0.6, label="Revenue")
ax2.plot(df["month"], df["growth_pct"], color="#e15759", marker="o", linewidth=2, label="Growth rate")
ax1.set_ylabel("Revenue (10k JPY)")
ax2.set_ylabel("Growth (%)")
ax1.set_xlabel("Month")
ax1.set_title("Revenue and Growth")
lines = ax1.lines + ax2.lines
labels = [l.get_label() for l in lines]
ax1.legend(lines, labels)
ax1.grid(True, axis="y", alpha=0.3)
plt.tight_layout()
plt.show()
Python単位が違う場合は二軸に分け、軸ラベルに単位を明記します。凡例は両軸の線をまとめて統合します。
整形のポイント(頻度の違い・スケール調整・見やすさの工夫)
異なる頻度を“揃えてから”重ねる
import pandas as pd
sales = pd.DataFrame({
"date": pd.date_range("2025-01-01", periods=6, freq="D"),
"sales": [100, 120, 90, 140, 110, 130]
}).set_index("date")
users = pd.DataFrame({
"date": pd.date_range("2025-01-01", periods=3, freq="2D"),
"users": [50, 60, 55]
}).set_index("date")
users_daily = users["users"].resample("D").ffill()
merged = pd.concat([sales["sales"], users_daily], axis=1)
Python重ねる前に、resampleや補間(ffill/bfill/interpolate)で“比較可能な時間軸”へ揃えると、グラフが破綻しません。
二軸が嫌なら正規化で同軸へ
import numpy as np
merged["users_norm"] = (merged["users"] - merged["users"].min()) / (merged["users"].max() - merged["users"].min())
Python視覚的に重ねたいが桁が違う場合は、0〜1の正規化や標準化で同じ軸に載せる手もあります。解釈に注意し、凡例や注記で“正規化済み”を明示します。
見やすさの工夫(透明度・線幅・グリッド)
棒はalpha=0.5〜0.7にして折れ線を太め(linewidth=2〜3)に。yグリッドを薄く入れると量の読み取りが早くなり、凡例は短い語で重複色を避けます。目盛り・ラベルは単位を含めて誤読を防ぎます。
実践例(売上×移動平均、在庫×販売、割合+量)
日次売上(棒)と7日移動平均(線)
import pandas as pd
import matplotlib.pyplot as plt
s = pd.Series([100, 120, 80, 200, 150, 90, 110, 130, 140],
index=pd.date_range("2025-01-01", periods=9, freq="D"))
ma7 = s.rolling("7D").mean()
fig, ax = plt.subplots(figsize=(7, 4))
s.plot(kind="bar", ax=ax, color="#4e79a7", alpha=0.6, label="Daily Sales")
ma7.plot(ax=ax, color="#f28e2c", linewidth=3, label="7D Moving Avg")
ax.set_title("Daily Sales with 7D Moving Average")
ax.set_ylabel("Sales")
ax.legend()
ax.grid(True, axis="y", alpha=0.3)
plt.tight_layout()
plt.show()
Python棒で日々のばらつきを、線で傾向を示せます。移動平均は期間窓(”7D”)が不等間隔にも自然です。
在庫推移(線)と販売数(棒)を同軸で
import pandas as pd
import matplotlib.pyplot as plt
df = pd.DataFrame({
"date": pd.date_range("2025-01-01", periods=8, freq="D"),
"stock": [300, 290, 280, 270, 260, 250, 240, 235],
"sales": [20, 25, 15, 30, 18, 22, 24, 26]
}).set_index("date")
fig, ax = plt.subplots(figsize=(7, 4))
df["sales"].plot(kind="bar", ax=ax, color="#4e79a7", alpha=0.6, label="Sales")
df["stock"].plot(ax=ax, color="#59a14f", linewidth=2, label="Stock")
ax.set_title("Stock and Sales")
ax.set_ylabel("Value")
ax.legend()
ax.grid(True, axis="y", alpha=0.3)
plt.tight_layout()
plt.show()
Python同じ軸上でも“量(棒)”と“推移(線)”を視覚的に分けられます。
量(棒)と割合(線)を二軸で
import pandas as pd
import matplotlib.pyplot as plt
df = pd.DataFrame({
"month": ["Jan","Feb","Mar","Apr","May"],
"orders": [100, 120, 90, 140, 160],
"return_rate": [0.05, 0.04, 0.07, 0.03, 0.06]
})
fig, ax1 = plt.subplots(figsize=(7, 4))
ax2 = ax1.twinx()
ax1.bar(df["month"], df["orders"], color="#4e79a7", alpha=0.6, label="Orders")
ax2.plot(df["month"], df["return_rate"], color="#e15759", marker="o", linewidth=2, label="Return rate")
ax1.set_ylabel("Orders (count)")
ax2.set_ylabel("Return rate (%)")
ax1.set_title("Orders and Return Rate")
lines = ax1.lines + ax2.lines
labels = [l.get_label() for l in lines]
ax1.legend(lines, labels)
ax1.grid(True, axis="y", alpha=0.3)
plt.tight_layout()
plt.show()
Python量と割合のようにスケールが違うものは、二軸で誤解なく伝えます。
つまずき対策(時系列の揃え・単位の明示・凡例の統合・保存)
時系列は揃える
重ねる前にto_datetimeで日時型へ、sort_valuesで昇順へ、頻度が違えばresample+補間で合わせます。タイムゾーンが混在する場合は一度UTCへ統一してからローカルへ。
単位は軸ラベルに明記
二軸では左右の単位が異なるため、軸ラベルに単位を必ず含めます。タイトルやキャプションでも補足して誤読を防ぎます。
凡例は統一
twinxの場合、左右のラインをまとめてlegendへ渡し、色の重複や曖昧なラベルを避けます。線種やマーカーで系列の識別を強めます。
仕上げと出力
tight_layoutで余白を整え、figsizeで比率を調整。レポート用はplt.savefig(“chart.png”, dpi=200)やPDF/SVGで高解像度保存します。
まとめ(「量は棒、推移は線」。単位と時間軸を整え、重ねるか二軸かを選ぶ)
棒+折れ線の複合は、量の比較と推移の読み取りを一枚で両立させる強力な手法です。単位や桁が同じなら同軸で重ね、違うなら二軸で分ける。重ねる前に時系列を揃え、色・透明度・凡例・軸ラベルを丁寧に整える。これだけで、初心者でも“誤解なく情報量が多い”グラフを安定して作れます。
