静かなる名辞

pythonとプログラミングのこと


【python】seabornで棒グラフを信頼区間付きで描く

はじめに

 信頼区間付きの棒グラフはよく見かけます。複数グループの差が優位かどうかという議論に向いているからです。

 他のツールだと割と簡単に描けるグラフだったりするのですが、Pythonでやろうとするとmatplotlibは勝手に信頼区間を計算してくれません。信頼区間を計算してからエラーバーを出して……といったノウハウが紹介されていますが、正直面倒くさいです。

qiita.com

 そこでseabornを使います。こちらは何もしなくても信頼区間を出してくれるタイプのライブラリです。ただし、使い勝手には癖があります。

リファレンス通りに使ってみる

 凝ったことをする意味はあまりないので、まずはリファレンス通りに使います。

seaborn.barplot — seaborn 0.9.0 documentation

 リファレンスの記述を多少補ったのが以下のコードです。tipsデータセットでやっているようです。

import seaborn as sns
import matplotlib.pyplot as plt

tips = sns.load_dataset("tips")
print(tips.head())
""" =>
   total_bill   tip     sex smoker  day    time  size
0       16.99  1.01  Female     No  Sun  Dinner     2
1       10.34  1.66    Male     No  Sun  Dinner     3
2       21.01  3.50    Male     No  Sun  Dinner     3
3       23.68  3.31    Male     No  Sun  Dinner     2
4       24.59  3.61  Female     No  Sun  Dinner     4
"""

sns.barplot(x="day", y="total_bill", data=tips)
plt.savefig("result.png")

result.png
result.png

 折れ線グラフのときと使い方はほとんど変わりません。というか、見え方が違うだけのようにも見えます(折れ線の方だとx軸の位置は指定できますが)。

【python】seabornで折れ線グラフを信頼区間付きで描く - 静かなる名辞

 なお、エラーバーの形式はいじれます。普通は傘がほしいと思うので、capsizeを指定します。また、デフォルトでは少しエラーバーが濃すぎる気がするので、errwidthで調整するとよさげです。

 適当に調整したのが下のものです。

sns.barplot(x="day", y="total_bill", data=tips, 
            capsize=0.1, errwidth=1.2)

result.png
result.png

もともとDataFrameになっていない任意のデータで行う

 例によって例のごとく、seabornの開発方針はpandasべったりなので、どんな形式・内容のデータであっても一回DataFrameに突っ込んでからやった方が良さそうです。ただ、受け付けてくれる形式が少し特殊なので、変換方法を覚えておいた方が得です。

 普通のデータはこんな感じで定義したいことが多いと思います。棒ごとに配列があるイメージですね。

df = pd.DataFrame({"A": [11, 12, 11, 10, 10, 13, 11, 10, 15, 11, 10],
                   "B": [20, 21, 20, 21, 20, 21, 20, 21, 20, 21, 20]})

 meltを使って縦持ちに変換すると、seabornと親和性の高いフォーマットになります。結論から言うと、こう書くと良いでしょう。

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

df = pd.DataFrame({"A": [11, 12, 11, 10, 10, 13, 11, 10, 15, 11, 10],
                   "B": [20, 21, 20, 21, 20, 21, 20, 21, 20, 21, 20]})

sns.barplot(x="class", y="data", 
            data=df.melt(var_name="class", value_name="data"), 
            capsize=0.1, errwidth=1.2)
plt.savefig("result.png")

 これでちゃんとABごとに集計してくれます。ちなみにmeltした結果はこうなります。

   class  data
0      A    11
1      A    12
2      A    11
3      A    10
4      A    10
5      A    13
6      A    11
7      A    10
8      A    15
9      A    11
10     A    10
11     B    20
12     B    21
13     B    20
14     B    21
15     B    20
16     B    21
17     B    20
18     B    21
19     B    20
20     B    21
21     B    20

pandas.DataFrame.melt — pandas 0.23.4 documentation

まとめ

 データを縦持ちに変換するところでひと手間余計にかかりますが、matplotlibでゼロからやるよりは楽だし、間違いも少なさそうなので良いと思います。