Rのggplotは、一度理屈を理解すれば、簡潔な記述で柔軟にグラフを描くことが可能になる素晴らしいパッケージです。
一方、pythonについては、よく使われるmatplotlibは覚えにくいし、seabornもmatplotlibのラッパーでいまいち、と思っていました。
しかし、seabornのリファレンスを読んで理解すると、結構すっきりとした記述でグラフを描けるということがわかったため、ggplotと比較しながらseabornによるグラフ描画方法をまとめてみました。
細かい見た目はおいておき、主要なグラフの簡潔な描画方法と、データを把握するのに必要な軸の設定方法についてまとめ、最後に少しだけ理屈を理解した上でグラフを描画する方法についてまとめています。
※遅ればせながら、seaborn0.11で追加されたdisplot()
を使った度数分布の描画について大幅に追記しました。初版の記事で、「distplot()
もFacetGrid()
とmap()
のショートカットが欲しい」とつぶやいていましたが、0.11で対応されました。結果的に積み上げ棒グラフも簡単に書けるようになり、主要なグラフはかなり簡単に書けるようになったと思います!(2022.7.25追記)
以下のバージョンで動作を確認しています。
R 4.11、tidyverse 1.3.1 (ggplot2 3.3.5、readr 2.0.2、forcats 0.5.1)
Python 3.9.7、seaborn 0.11.2、matplotlib 3.4.2、pandas 1.3.4、numpy 1.21.3
データ準備
まずは説明に使うデータセットを読み込みます。seabornのデータセットを使うので、Rではseabornのgithubレポジトリからデータを取得しています。
ggplot
library(tidyverse)
theme_set(theme_bw())
df_mpg = read_csv("https://raw.githubusercontent.com/mwaskom/seaborn-data/master/mpg.csv")
df_fmr <- read_csv("https://raw.githubusercontent.com/mwaskom/seaborn-data/master/fmri.csv") %>%
mutate(region = fct_relevel(region, c("parietal", "frontal")))
df_tips <- read_csv("https://raw.githubusercontent.com/mwaskom/seaborn-data/master/tips.csv") %>%
mutate(day = fct_relevel(day, c("Thur", "Fri", "Sat", "Sun")),
sex = fct_relevel(sex, c("Male", "Female")),
time = fct_relevel(time, c("Lunch", "Dinner")),
smoker = fct_relevel(smoker, c("Yes", "No")))
df_flight <- read_csv("https://raw.githubusercontent.com/mwaskom/seaborn-data/master/flights.csv")
- カテゴリカルデータのラベルの順序はforcatsを使って変えている。
seaborn
import pandas as pd
import seaborn as sns
df_mpg = sns.load_dataset("mpg")
df_fmr = sns.load_dataset("fmri")
df_tips = sns.load_dataset("tips")
df_flight = sns.load_dataset("flights")
散布図
X軸:量的変数、Y軸:量的変数
色:量的変数 or カテゴリ変数
形:カテゴリ変数
サイズ:量的変数 or カテゴリ変数
行/列:カテゴリ変数
ポイントの色と形にマッピング
ggplot
df_mpg %>%
ggplot(aes(x=weight, y=mpg, color=origin, shape=origin)) +
geom_point()
seaborn
sns.relplot(data=df_mpg, x="weight", y="mpg", hue="origin", style="origin")
- マッピングする属性="変数名"を引数とするシンプルな記述方法。
- 色はhue。色相という意味があるらしい。
形状はstyleで、当然色と形は異なる変数をマッピングしても構わない。 - styleは量的変数の指定をしても、自動でカテゴリ変数として扱われるとのこと(ggplotではshapeに量的変数を指定するとエラーとなる)
- 散布図1つならば
scatterplot()
でも良いが、後に出てくる軸にマッピング(ggplotでいうfacet)を行おうとするとrelplot()
が便利なので、すべてrelplot()
に統一。内部ではscatterplot()
を用いている。
ポイントのサイズにマッピング
ggplot
df_mpg %>%
ggplot(aes(x=weight, y=mpg, size=cylinders)) +
geom_point()
seaborn
sns.relplot(data=df_mpg, x="weight", y="mpg", size="cylinders")
軸にマッピング
ggplot
df_mpg %>%
ggplot(aes(x=weight, y=mpg)) +
geom_point() +
facet_grid(.~origin)
seaborn
sns.relplot(data=df_mpg, x="weight", y="mpg", col="origin")
- 横軸は引数col、縦軸はrowに変数名を渡す。引数col_wrapに整数を渡すとその列数で折り返してグラフを描画する。
散布図 with 回帰曲線
X軸:量的変数、Y軸:量的変数
色:カテゴリ変数
行/列:カテゴリ変数
ggplot
df_mpg %>%
ggplot(aes(x=weight, y=mpg, color=origin)) +
geom_point() +
geom_smooth(method=lm)
seaborn
sns.lmplot(data=df_mpg, x="weight", y="mpg", hue="origin", truncate=True)
- 回帰曲線を描く場合は
lmplot()
。1つの図ならばregplot()
でも良いが、軸にマッピングするならばlmplot()
が便利。 - 色(hue)、軸(col, row)にマッピング出来る。それ以外はできない。
- markers=["o", "^", "x"]のようにmarkers引数で、hueに合わせてプロットの形状を指定することができる。
- truncate=Trueとするとggplotのデフォルトのようにデータが存在する範囲で直線を描く。
- orderに整数nを指定すると、n次関数を描く。
- lowess=Trueでlowess平滑化による曲線を描く。
- log=Trueでy ~ log(x)の曲線を描く。
- scatter=Falseでプロットを描かない。
- ci=None で信頼区間を描かない。
折れ線グラフ
X軸:量的変数(系列データ 基本的に等間隔)、Y軸:量的変数
色:量的変数 or カテゴリ変数
線のスタイル:カテゴリ変数
サイズ:量的変数 or カテゴリ変数
行/列:カテゴリ変数
ggplot
df_fmr %>%
ggplot(aes(x=timepoint, y=signal, color=event, linetype=event)) +
geom_line(stat="summary") +
facet_grid(.~region)
- stat="summary"でyに指定した変数の平均を算出して縦軸にする(デフォルトはstat="identity"。この場合は統計量の計算は行われない。)
seaborn
sns.relplot(data=df_fmr, x="timepoint", y="signal",
hue="event", style="event", col="region", kind="line")
- 折れ線グラフを描く場合は、
relplot
にkind="line"を指定する。 - 折れ線グラフ一つの場合は、
lineplot()
でも可。 - yにマッピングした変数の平均が縦軸になる。
- 散布図と同様に、hue、size、col、rowにマッピングすることが可能。
- 線のスタイル(実線やダッシュ、style)にマッピングすることも可能。
- 引数 units に変数をマッピングすることで、その変数の単位で線を(同じ色やスタイルで)描画可能。units引数を使う場合、estimator引数はNoneにする必要がある。
- markers=Trueでポイントも描く。
- ci=Noneで信頼区間を描かない。
棒グラフ
X軸:カテゴリ変数、Y軸:量的変数
色:カテゴリ変数
行/列:カテゴリ変数
ggplot
df_tips %>%
ggplot(aes(x=day, y=total_bill, fill=sex)) +
geom_bar(position="dodge", stat="summary") +
facet_grid(.~smoker)
- stat="summary"でyに指定した変数の平均を算出して縦軸にする。特に統計量の計算をしないならば、stat="identity"またはgeom_colを使う。
seaborn
sns.catplot(data=df_tips, x="day", y="total_bill", hue="sex", col="smoker", kind="bar")
- 棒グラフ1つのならば
barplot()
でも良いが、軸にマッピングするならばcatplot(kind="bar")
が便利 - yにマッピングした変数の平均が縦軸になる。引数 estimatorに"median"など指定すれば、平均以外の統計量も可能。
- 横棒グラフにする場合は、xとyの変数を入れ替える。
- 色(hue)、軸(col, row)にマッピング出来る。
- hueにマッピングした場合、棒は横に並ぶ。ggplotでいうとposition="dodge"。position="fill"やposition="stack"にあたる積み上げグラフは後述の
displot()
を使う。 - 引数ci=False でエラーバーを描かない。
- 引数orderに横軸の値の順序をリストで指定可能。
箱ひげ図
X軸:カテゴリ変数、Y軸:量的変数
色:カテゴリ変数
行/列:カテゴリ変数
ggplot
df_tips %>%
ggplot(aes(x=day, y=total_bill, fill=sex)) +
geom_boxplot() +
facet_grid(.~smoker)
seaborn
sns.catplot(data=df_tips, x="day", y="total_bill", hue="sex", col="smoker", kind="box")
- 箱ひげ図は
catplot(kind="box")
。箱ひげ図一つならばboxplot()
でも可。
Violin plot
X軸:カテゴリ変数、Y軸:量的変数
色:カテゴリ変数
行/列:カテゴリ変数
あまり使いませんが、一応。
ggplot
df_tips %>%
ggplot(aes(x=day, y=total_bill, fill=sex)) +
geom_violin() +
facet_grid(.~smoker)
seaborn
sns.catplot(data=df_tips, x="day", y="total_bill", hue="sex", col="smoker", kind="violin")
- violin plotは
catplot(kind="violine")
。violin plot一つならば、violinplot()
でも可。
棒グラフ(1つのカテゴリカル変数の度数分布)
X軸:カテゴリ変数
色:カテゴリ変数
行/列:カテゴリ変数
ggplot
df_tips %>%
ggplot(aes(x=smoker, fill=sex)) +
geom_bar(position="dodge") +
facet_grid(.~time)
seaborn
sns.catplot(data=df_tips, x="smoker", hue="sex", col="time", kind="count")
-
catplot(kind="count")
。棒グラフ一つならばcountplot()
でも可。 - 横棒グラフにしたい場合は、引数xではなくyに変数を指定する。
seaborn 0.11から実装されたdisplot()
を用いても描画可能。
sns.displot(data=df_tips, x="smoker", hue="sex", col="time", multiple="dodge", shrink=0.8)
- shrink引数に1以下の数値を適当に与えて、カテゴリ間の間隔を空ける。
- それぞれの色の棒を横に並べる場合は引数multipleに"dodge"を指定する。
- 横棒グラフにしたい場合は、
catplot()
と同じように、xではなくyに変数を指定する。
※xがカテゴリ変数の場合のdisplotはexperimental featureとのことです。 - col、rowにマッピングしない場合は
histplot()
でも可。
displot()
の引数multipleを変えることで、0.10以前までは難しかった積み上げ棒グラフ、100%積み上げ棒グラフ(ggplotのgeom_bar()
にposition="fill"やposition="stack"を指定したようなグラフ)も描画可能となりました!
sns.displot(data=df_tips, x="smoker", hue="sex", col="time", multiple="stack", shrink=0.8)
sns.displot(data=df_tips, x="smoker", hue="sex", col="time", multiple="fill", shrink=0.8)
dsiplot()
で度数(カウント)ではなく、いずれかの変数の合計値を積み上げたい場合は、weights引数に変数名を指定する。(ggplotのgeom_bar()
のweight引数にマッピングするのと同じ挙動となる。)
sns.displot(data=df_tips, x="smoker", hue="sex", col="time", weights="total_bill", multiple="stack", shrink=0.8)
ヒストグラム(一つの量的変数の度数分布)
X軸:量的変数
色:カテゴリ変数
行/列:カテゴリ変数
ggplot
df_tips %>%
ggplot(aes(x=total_bill, fill=sex)) +
geom_histogram(alpha=0.5, position="identity") +
facet_grid(.~time)
- デフォルトではfillにマッピングした変数毎に積み上げて、ヒストグラムを描くので、それぞれ独立(重ねて)してヒストグラムを描く場合はposition="identity"を指定する。
- ビンの設定はデフォルトで、bins=30なので、変えたい場合はbins(binの数)、binwidth(binの幅)、breaks(ベクトルで直接bin境界を指定)いずれかで設定。
- 縦軸を密度にしたい場合は
geom_histgram()
の引数に、aes(y=..density..)を指定する。
seaborn
sns.displot(data=df_tips, x="total_bill", hue="sex", col="time", bins=30)
- 0.11で度数分布を
relplot()
,catplot()
と同じように(FacetGrid()
とmap()
を組み合わせた形で)描画できるdisplot
が追加された。ヒストグラムを描く関数であったdistplot()
と紛らわしいですが、distplot()
は非推奨となる。 - 軸にマッピングしない場合は、
histplot()
を使っても良い。 - ビンの設定を変更する場合は引数 binsに値(この場合binの数)またはリスト(この場合binの境界を直接指定)、もしくはbinwidthに値(binの幅)を指定する。
- 積み上げる場合は引数 multiple に"stack"を指定する。(デフォルトは"layer"で重ねる。)
- 縦軸を密度にしたい場合は引数statに"density"を指定する。("密度"の正規化の単位がggplotとは異なるよう。)
- 棒の境界線が不要な場合は、引数elementに"step"を指定する。
ヒストグラムに密度関数を重ねる
ggplot
df_tips %>%
ggplot(aes(x=total_bill)) +
geom_histogram(aes(y=..density.., fill=sex), alpha=0.5, position="identity") +
geom_density(aes(color=sex)) +
facet_grid(.~time)
- 内部で計算した値をyにマッピングしたり若干面倒。
seaborn
sns.displot(data=df_tips, x="total_bill", hue="sex", col="time", bins=30, stat="density", kde=True)
- 引数statを指定しない場合は、通常のヒストグラムに密度関数を重ねることも可能。
- ヒストグラムを描画しない場合は、引数kindに"kde"を指定し、kde、bins、statを指定しない。
ヒートマップ(2つのカテゴリカル変数の度数分布)
X軸:カテゴリ変数、y軸:カテゴリ変数
行/列:カテゴリ変数
主として度数を見たい2つのカテゴリ変数のカテゴリ数が多い場合は、ヒートマップのほうが見やすい。(以下の例はカテゴリ数は多くありませんが。)
ggplot
df_tips %>%
ggplot(aes(x=time, y=day, weight=total_bill)) +
geom_bin2d() +
facet_grid(.~sex)
- ラベルを重ねる場合は
geom_text(stat="bin2d", aes(label=..count..))
を追加する。 - あらかじめ度数や合計値の計算をしている場合は、その変数をfillにマッピングし、
geom_raster()
でヒートマップを描ける。
seaborn
sns.displot(data=df_tips, x="day", y="time", col="sex", weights="total_bill", cbar=True)
- いずれかの変数の合計値を積み上げたい場合は、weights引数に変数名を指定する。
- 軸にマッピングしない場合は、
histplot()
を使っても良い。 - ラベルを重ねることはできない。ラベルを重ねるときは
sns.heatmap()
を使うしかなさそう。
2次元ヒストグラム(2つの量的変数の度数分布)
X軸:量的変数、y軸:量的変数
行/列:カテゴリ変数
ggplot
df_tips %>%
ggplot(aes(x=total_bill, y=tip)) +
geom_bin2d(bins=10) +
facet_grid(.~sex)
- 1次元のヒストグラムと同じように、binsやbinwidthでビンを調整する。
seaborn
sns.displot(data=df_tips, x="total_bill", y="tip", col="sex", cbar=True, bins=10)
- 軸にマッピングしない場合は、
histplot()
を使っても良い。 - 1次元のヒストグラムと同じように、binsやbinwidthでビンを調整する。
- 2次元の密度関数を描く場合は引数kindを"kde"とする。(軸にマッピングしない場合は
kdeplot()
でも可)
軸の設定
細かい設定の話はしませんが、軸はデータを把握する上で重要なので、簡単に触れます。
ggplot
df_mpg %>%
ggplot(aes(x=weight, y=mpg, color=origin, shape=origin)) +
geom_point() +
scale_x_log10() +
scale_y_log10() +
coord_cartesian(xlim=c(2000, 4000), ylim=c(10, 40))
seaborn
sns.relplot(data=df_mpg, x="weight", y="mpg", hue="origin", style="origin", height=3).\
set(xlim=(2000, 4000), ylim=(10, 40), xscale="log", yscale="log", aspect=0.5)
-
relplot()
やscatterplot()
などに.set()
を続ける。 - 引数にはx/ylim()(軸範囲)、x/yscale(リニア/対数スケール)以外にも/ytick(軸メモリ)、xx/ylabel(軸ラベル)、title(グラフタイトル)など設定できる。
- aspectはy軸対x軸の数値。グラフの大きさは
relplot()
の引数に高さを単位インチで指定できる。
seaborn応用編
-
FacetGrid()
、relplot()
、catplot()
、displot()
、lmplot()
の返り値はFacetGridオブジェクト。FacetGridオブジェクトにmap()
を適用し、プロットを描く。 -
map()
の返り値は自分自身なので、map()
を続けて適用しプロットを重ねることができる。
g = sns.FacetGrid(data=df_tips, col="time")
g = g.map(sns.kdeplot, "total_bill", "tip")
g = g.map(sns.scatterplot, "total_bill", "tip")
-
scatterplot()
やbarplot()
、histplot()
などの返り値はmatplotlibのAxesオブジェクト。 - これらの関数は引数axにAxesオブジェクトを指定できるが、指定しない場合は、現在のAxesオブジェクトが使われる。
- なので
ax = 上記の関数
を続けていけば、プロットを重ねることができる。
ax = sns.lineplot(data=df_fmr, x="timepoint", y="signal", hue="event")
ax = sns.scatterplot(data=df_fmr, x="timepoint", y="signal", hue="event")
# 2行目は ax = sns.scatterplot(data=df_fmr, x="timepoint", y="signal", hue="event", ax=ax) と等価。
- FacetGridオブジェクトのaxes属性はmatplotlibのAxesオブジェクトの2次元配列となっている。
- よってFaceGrid.axes[0, 0]のように、所定のグリッド位置([0,0]は左上)のAxesオブジェクトを得ることができる。
- これを利用して、以下のように所定の位置だけ、プロットを重ねることが可能。
g = sns.FacetGrid(data=df_tips, col="time")
g = g.map(sns.kdeplot, "total_bill", "tip")
sns.scatterplot(data=df_tips, x="total_bill", y="tip", ax=g.axes[0, 0])
このあたりの話、詳しくは公式のfigure-levelとAxes-levelを参照ください。
また、Axesオブジェクトを使いこなそうとすると、matplotlibのオブジェクト指向インターフェイスの理解が必要だと思います。
matplotlibについては@skotaroさんが素晴らしい記事 をまとめてくれています。この記事でも参考にさせていただきました。またseabornについてのこちらの記事も参考にさせていただきました。
編集履歴
- seabornではできなかった、pythonで100%積み上げグラフを描く方法を追記。Rのサンプル読み込みで不足の行があり追記。(当該行がないとサンプルのグラフ通りにならないものがありました)(2020.6.21)
- 主にseaborn0.11で対応された
displot()
に関連する度数グラフの内容を大幅追加した。- カテゴリ変数/連続変数、1変数/2変数の組み合わせで度数分布に関する項目をそれぞれ記載する形式に変更。それに伴い、使用機会がすくない、密度関数の記載を削除した。
- 見出しタイトルをグラフの名前に変更し、見出しタイトルの直下にマッピング可能な属性(引数)と変数の種類(量的/カテゴリ)を明示する形式とした。
- そのほか、折れ線グラフでマッピング可能な引数を追記するなど、グラフの説明を微修正をした。(2022.7.25)