PandasとSeabornで学ぶデータ分析と可視化
Seaborn を使う最大の利点は、1行で「集計+作図+凡例+エラーバー」までやってくれることです。Matplotlib で10行かかる処理が、sns.barplot(data=df, x="都市", y="年齢")だけで終わります。
ただし Seaborn は「整然データ(tidy data)」を前提にしているため、データの形を先に整えないと期待通りに描けません。また、日本語ラベルは Matplotlib と同じく設定なしでは文字化けします。
この記事では、Pandas で形を整えるところから、Seaborn の主要なグラフ、そして公式が推奨するsns.objectsインターフェースまでを、動くコードで解説します。
環境の準備と日本語表示
pip install pandas seaborn matplotlib matplotlib-fontja
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib_fontja # 日本語の文字化けを防ぐ
sns.set_theme(style="whitegrid") # 見た目のテーマを設定
plt.rcParams['axes.unicode_minus'] = False
注意点が1つあります。sns.set_theme()はフォント設定を上書きします。 そのため、matplotlib_fontjaのインポートより前にset_theme()を呼ぶと文字化けが戻ることがあります。上のようにインポート → set_theme()の順で書いてください。
それでも化ける場合は、テーマ側にフォントを渡します。
sns.set_theme(style="whitegrid", font="Hiragino Sans") # macOS
# sns.set_theme(style="whitegrid", font="Yu Gothic") # Windows
Seaborn が求めるデータの形
ここが最初の関門です。 Seaborn は「1行=1観測、1列=1変数」というロング形式を前提にしています。Excel でよく作る「横に月が並んだ表」(ワイド形式)のままでは、x=やhue=に渡す列がありません。
# ワイド形式(Excelでよくある形)
wide = pd.DataFrame({
"商品": ["A", "B"],
"1月": [100, 80],
"2月": [120, 90],
"3月": [140, 70],
})
# → ロング形式に変換する
long = wide.melt(id_vars="商品", var_name="月", value_name="売上")
print(long)
# 商品 月 売上
# 0 A 1月 100
# 1 B 1月 80
# 2 A 2月 120
# ...
melt()でロング形式にすれば、そのまま渡せます。
sns.lineplot(data=long, x="月", y="売上", hue="商品", marker="o")
plt.show()
hue=に列名を渡すだけで系列が分かれ、凡例まで自動で付くのが Seaborn の強みです。逆に言えば、この形にできていないと利点がほとんど活きません。「うまく描けない」ときの原因は、9割がデータの形です。
Pandas での前処理
df = pd.read_csv("data.csv", encoding="utf-8-sig", parse_dates=["date"])
# 欠損の確認
print(df.isna().sum())
# 欠損を落とす(列を指定する)
df = df.dropna(subset=["value"])
# 平均で埋める場合
df["value"] = df["value"].fillna(df["value"].mean())
# 条件で絞る
adults = df[df["age"] >= 20]
# グループ集計
summary = df.groupby("category", as_index=False)["value"].agg(["mean", "sum", "count"])
encoding="utf-8-sig"は Excel が書き出した CSV の BOM 対策です。utf-8で読むと1列目の列名が壊れて参照できなくなります。
inplace=Trueは使わず、上のように代入で書き戻してください。pandas 2系では非推奨の方向にあり、コピーの有無も分かりにくくなります。
基本のグラフ4種
棒グラフ(barplot):平均値の比較
tips = sns.load_dataset("tips")
sns.barplot(data=tips, x="day", y="total_bill", hue="sex")
plt.title("曜日別・性別ごとの平均会計額")
plt.show()
barplotが描くのは合計ではなく「平均値」で、黒い縦線は95%信頼区間です。合計を出したいときはestimator="sum"を指定するか、事前にgroupby().sum()してから渡します。ここは誤解しやすいポイントです。
箱ひげ図(boxplot):ばらつきを見る
sns.boxplot(data=tips, x="day", y="total_bill")
plt.show()
データ点が少ないとき(数十件程度)は、箱ひげ図よりstripplotやswarmplotで実際の点を出した方が誠実です。3件のデータで箱ひげ図を描いても、四分位数に意味がありません。
sns.boxplot(data=tips, x="day", y="total_bill", showfliers=False)
sns.stripplot(data=tips, x="day", y="total_bill", color=".25", size=3, alpha=.5)
plt.show()
ヒストグラム(histplot):分布を見る
sns.histplot(data=tips, x="total_bill", bins="auto", kde=True)
plt.show()
kde=Trueで分布の推定曲線が重なります。binsを指定しない場合は自動で決まるので、まずそのまま描いて形を見るのがおすすめです。
散布図(scatterplot):2変数の関係を見る
sns.scatterplot(data=tips, x="total_bill", y="tip", hue="time", size="size", alpha=.7)
plt.show()
hue(色)、size(大きさ)、style(形)に別々の列を割り当てられるので、4変数までを1枚に表現できます。ただし詰め込みすぎると読めなくなるので、実用上は3つまでにとどめるのが無難です。
相関ヒートマップ:pandas 2系での注意点
文字列の列が混ざったままdf.corr()を呼ぶとエラーになります。 pandas 1系では自動で数値列だけを使ってくれましたが、2系では明示が必要です。
# ❌ 文字列列があると ValueError
# corr = tips.corr()
# ✅ 数値列だけを対象にする
corr = tips.corr(numeric_only=True)
fig, ax = plt.subplots(figsize=(6, 5))
sns.heatmap(corr, annot=True, fmt=".2f", cmap="coolwarm",
vmin=-1, vmax=1, square=True, ax=ax)
ax.set_title("相関のヒートマップ")
plt.show()
vmin=-1, vmax=1の指定が重要です。 指定しないとデータの範囲に合わせて色が自動調整され、相関0.3が真っ赤に見えるといった誤読を招きます。相関行列のスケールは必ず固定してください。
pairplot:全変数の関係を一覧する
iris = sns.load_dataset("iris")
sns.pairplot(iris, hue="species", diag_kind="hist", corner=True)
plt.show()
corner=Trueを付けると対角線の上半分が省略され、同じ情報の重複が消えて見やすくなります。
注意点として、pairplotは列数の2乗に比例して重くなります。列が20を超えるようなデータでそのまま呼ぶと、数分固まります。vars=["列A", "列B", "列C"]で対象を絞ってください。
回帰プロット:数値どうしにしか使えない
regplotは散布図に回帰直線を重ねますが、x も y も数値列である必要があります。カテゴリ列(都市名、商品名など)を渡すとエラーになります。
# ❌ y に文字列のカテゴリは渡せない
# sns.regplot(data=df, x="年齢", y="都市")
# ✅ 数値どうしで
sns.regplot(data=tips, x="total_bill", y="tip", scatter_kws={"alpha": .4})
plt.show()
カテゴリごとに回帰直線を引き分けたい場合はlmplotを使います。
sns.lmplot(data=tips, x="total_bill", y="tip", hue="smoker", height=5)
plt.show()
figure-level と axes-level の違い
Seaborn の関数は2種類あり、これを混同するとレイアウトが崩れます。
| 種類 | 関数の例 | 特徴 |
|---|---|---|
| axes-level | scatterplot、barplot、boxplot、histplot |
ax=を受け取る。既存の図に描き込める |
| figure-level | relplot、catplot、displot、lmplot、pairplot |
自分で図全体を作る。ax=は使えない |
# axes-level:複数を並べられる
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
sns.histplot(data=tips, x="total_bill", ax=axes[0])
sns.boxplot(data=tips, x="day", y="tip", ax=axes
)
plt.tight_layout()
plt.show()
# figure-level:col= で自動的に分割してくれる
g = sns.relplot(data=tips, x="total_bill", y="tip",
col="time", hue="smoker", height=4)
g.figure.suptitle("時間帯別の会計額とチップ", y=1.02)
plt.show()
figure-level 関数の戻り値はAxesではなくFacetGridです。 タイトルを付けるときはg.figure.suptitle()、保存するときはg.savefig()を使います。plt.title()を呼んでも、最後のサブプロットにしか付きません。
これから書くなら sns.objects インターフェース
Seaborn 0.12 以降には、グラフを部品の組み合わせで宣言的に書く新しいインターフェースが入りました。R の ggplot2 に近い書き方です。
import seaborn.objects as so
(
so.Plot(tips, x="total_bill", y="tip", color="time")
.add(so.Dot(alpha=.5))
.add(so.Line(), so.PolyFit()) # 回帰直線を重ねる
.facet(col="smoker") # 列方向に分割
.label(x="会計額", y="チップ", title="会計額とチップの関係")
.show()
)
従来の関数群も引き続き使えますが、「何を描くか」と「どう見せるか」が分離されるので、複雑なグラフほど読みやすくなります。新規に書くならこちらを検討する価値があります。
保存するとき
# axes-level の場合
fig.savefig("chart.png", dpi=150, bbox_inches="tight")
# figure-level の場合
g.savefig("chart.png", dpi=150, bbox_inches="tight")
bbox_inches="tight":軸ラベルや凡例が切れるのを防ぐplt.show()より前に呼ぶ:表示後は図が破棄され、空の画像になることがある
まとめ
- 日本語は
matplotlib-fontjaをインポート。sns.set_theme()はその後に呼ぶ - Seaborn はロング形式が前提。ワイド形式は
melt()で変換する barplotが描くのは合計ではなく平均。合計ならestimator="sum"df.corr()にはnumeric_only=True、ヒートマップにはvmin=-1, vmax=1regplotは数値列どうしにしか使えない。カテゴリ別ならlmplotpairplotは列数の2乗で重くなる。vars=で絞る- figure-level 関数は
ax=を取れない。戻り値はFacetGrid - 新規に書くなら
seaborn.objectsも検討する
グラフを細かく制御したい場合は Matplotlib を直接触る方が早いこともあります。そちらはPandasとMatplotlibを活用したデータ可視化にまとめました。