よく使うグラフをseabornで可視化する方法を調べてみた

私はデータ分析が主な仕事ではないですが、簡単な集計を行い、対話的にデータを可視化して傾向をつかむような機会は度々あります。

そのたびに私はR言語やExcelで可視化を行うか、Pythonで可視化を行う必要がある際は不満も持ちつつ```matplotlib```の無骨なグラフを使っていました。ただ、この間「[seabornがかなり便利だ](https://qiita.com/ninomiyt/items/e02d29791eee1e8f02b1#comment-e794026a600c911022e9)」というコメントを頂き、実際にいくつかのサンプルを見ても```matplotlib```をラップして簡単に```ggplot2```のような美しいグラフが描けるようなので今回調べてみることにしました。

https://seaborn.pydata.org/

今回は関係ありませんが、R言語を使うのは、可視化ライブラリの問題というより、データフレームの操作が```tidyverse```のほうがPythonの```pandas```より簡潔だからという理由です。

## 可視化を試してみるグラフ

ソフトウェアエンジニアの仕事では複雑な統計検定をすることはほぼ無く、「（例えばエラーの原因を調べるために）データを集計して全体の傾向をつかむ」ような可視化がほとんどです。そのため、使用するグラフは限られていて、まずはシンプルなグラフで試行錯誤していきます。

そんなときに役立つ方針が『[グラフをつくる前に読む本](https://www.amazon.co.jp/dp/4774192198)』という本にまとめられていて、こちらで薦められているグラフの描き方を調べていきます。こちらの書評ブログ（[「グラフをつくる前に読む本」が名著だった話](https://hidekatsu-izuno.hatenablog.com/entry/2018/05/13/233737)）で更にまとめられた表を引用します。

<table>
<tbody>
<tr>
<th rowspan="2">得意な表現方法</th>
<th rowspan="2" width="25%">個別</th>
<th colspan="2" width="50%">全体</th>
</tr>
<tr>
<th width="25%">実数</th>
<th width="25%">割合</th>
</tr>
<tr>
<td rowspan="2">データ間の比較</td>
<td rowspan="2">棒グラフ</td>
<td>レーダーチャート</td>
<td>円グラフ</td>
</tr>
<tr>
<td colspan="2">積み上げ棒グラフ</td>
</tr>
<tr>
<td>時間の経過による推移</td>
<td>折れ線グラフ</td>
<td colspan="2">面グラフ</td>
</tr>
<tr>
<td>データの偏り</td>
<td colspan="3">ヒートマップ</td>
</tr>
<tr>
<td>データ項目同士の関係</td>
<td colspan="3">散布図</td>
</tr>
</tbody>
</table>

これらのグラフと、データの大まかな分布を調べるときに使う「**ヒストグラム**」の描き方を調べていきます。上の表や本では紹介されていないのですが、「データの偏り」の一部を表現するのに役立つと思っています。

このうち、**円グラフ以外**の可視化方法を調べていきます。本の中でも円グラフは「積み上げ棒グラフのほうが使いやすい」と主張されていて、[他の分析者の方もそういう記事を書かれていますし](https://webtan.impress.co.jp/e/2008/11/28/4461)、自分の実体験上でもそのとおりなのでほとんど使用しません。また実は、seabornのissueでも「[Sorry, no, seaborn will never support pie charts.（seabornでは円グラフを実装しない）](https://github.com/mwaskom/seaborn/issues/766)」とバッサリ明言されているようです。

さて、これらのグラフが簡単に可視化できると私の用途では嬉しいのですが、```seaborn```ではどうなのでしょうか？


## 棒グラフ

データの集計をすると必ず使うグラフです。『グラフをつくる前に読む本』では以下のように紹介されています。

> 棒グラフでは、複数並んだ棒の高さを比べて**「棒が大きい（小さい）項目はどれだろう？」と考えます**。つまり**棒グラフが一番得意な表現方法はデータの「比較」**です。棒グラフを使えば、比べたいデータを最もわかりやすく図で表現できます。

[公式のサンプルコード](https://seaborn.pydata.org/examples/grouped_barplot.html)に欲しいものに近いものがありました。

[```sns.load_dataset("titanic")```](https://seaborn.pydata.org/generated/seaborn.load_dataset.html)は、Kaggleで有名なタイタニックの生存者のデータセットを```pandas```のデータフレームとして読み込んでくれています。R言語に比べて「ちょっと可視化を試したい」ときのサンプルデータに困ることが多かったので助かりますね。

```python
import seaborn as sns
sns.set(style="whitegrid")

# Load the example Titanic dataset
titanic = sns.load_dataset("titanic")

# Draw a nested barplot to show survival for class and sex
g = sns.catplot(x="class", y="survived", hue="sex", data=titanic,
                height=6, kind="bar", palette="muted")
g.despine(left=True)
g.set_ylabels("survival probability")
```

![index.png](https://qiita-image-store.s3.amazonaws.com/0/127762/cc6053ce-2d9b-901b-e6f8-89424690381e.png)

ちなみに```titanic```の中身はこのようになっています。```x```や```y```ではこれらのカラム名を指定しています。

<table>
  <thead>
    <tr>
      <th></th>
      <th>survived</th>
      <th>pclass</th>
      <th>sex</th>
      <th>age</th>
      <th>sibsp</th>
      <th>parch</th>
      <th>fare</th>
      <th>embarked</th>
      <th>class</th>
      <th>who</th>
      <th>adult_male</th>
      <th>deck</th>
      <th>embark_town</th>
      <th>alive</th>
      <th>alone</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0</td>
      <td>3</td>
      <td>male</td>
      <td>22.0</td>
      <td>1</td>
      <td>0</td>
      <td>7.2500</td>
      <td>S</td>
      <td>Third</td>
      <td>man</td>
      <td>True</td>
      <td>NaN</td>
      <td>Southampton</td>
      <td>no</td>
      <td>False</td>
    </tr>
    <tr>
      <th>1</th>
      <td>1</td>
      <td>1</td>
      <td>female</td>
      <td>38.0</td>
      <td>1</td>
      <td>0</td>
      <td>71.2833</td>
      <td>C</td>
      <td>First</td>
      <td>woman</td>
      <td>False</td>
      <td>C</td>
      <td>Cherbourg</td>
      <td>yes</td>
      <td>False</td>
    </tr>
    <tr>
      <th>2</th>
      <td>1</td>
      <td>3</td>
      <td>female</td>
      <td>26.0</td>
      <td>0</td>
      <td>0</td>
      <td>7.9250</td>
      <td>S</td>
      <td>Third</td>
      <td>woman</td>
      <td>False</td>
      <td>NaN</td>
      <td>Southampton</td>
      <td>yes</td>
      <td>True</td>
    </tr>
    <tr>
      <th>3</th>
      <td>1</td>
      <td>1</td>
      <td>female</td>
      <td>35.0</td>
      <td>1</td>
      <td>0</td>
      <td>53.1000</td>
      <td>S</td>
      <td>First</td>
      <td>woman</td>
      <td>False</td>
      <td>C</td>
      <td>Southampton</td>
      <td>yes</td>
      <td>False</td>
    </tr>
    <tr>
      <th>4</th>
      <td>0</td>
      <td>3</td>
      <td>male</td>
      <td>35.0</td>
      <td>0</td>
      <td>0</td>
      <td>8.0500</td>
      <td>S</td>
      <td>Third</td>
      <td>man</td>
      <td>True</td>
      <td>NaN</td>
      <td>Southampton</td>
      <td>no</td>
      <td>True</td>
    </tr>
  </tbody>
</table>


また、どうやら勝手にエラーバーをつけてくれるようです。[ドキュメント](https://seaborn.pydata.org/generated/seaborn.barplot.html)を読む限り、デフォルトでは95%信頼区間のようです。また、大学の研究室とかであまり把握しないまま発表してしまうと、教授から「自分が理解してない情報を載せてるのか！」って説教されると思うので気をつけてください。

また、[```catplot```](https://seaborn.pydata.org/generated/seaborn.catplot.html)関数で```kind="bar"```を指定すると、```barplot```が呼び出されるそうです。というわけで、必要最低限の引数で可視化するときは次のようになりそうです。

```python
sns.barplot(x="class", y="survived", hue="sex", data=titanic)
```

![index.png](https://qiita-image-store.s3.amazonaws.com/0/127762/168e3199-b420-a904-03b1-aa62eb1c7521.png)

```python
sns.barplot(x="class", y="survived", data=titanic)
```

![index.png](https://qiita-image-store.s3.amazonaws.com/0/127762/555c1b5d-213a-4891-7473-13cb1480d442.png)

なかなか簡単ですね。





## 折れ線グラフ

例によって『グラフをつくる前に読む本』を引用します。

> 折れ線グラフでは、ある時点とある時点を左から右に線で結んで「**先の傾きが大きい（小さい）時点はどれだろう？**」、「**傾きの傾向が変化するのはどの時点だろう？**」と考えます。つまり**折れ線グラフが一番得意な表現方法はデータの「推移」**です。折れ線グラフを使えば、データの変化が最もわかりやすく図で表現できます。

これは[公式ドキュメントに良いサンプル](https://seaborn.pydata.org/generated/seaborn.lineplot.html)がありますね。

```python
import seaborn as sns; sns.set()
fmri = sns.load_dataset("fmri")
sns.lineplot(x="timepoint", y="signal", data=fmri)
```

![index.png](https://qiita-image-store.s3.amazonaws.com/0/127762/3e6f1764-1ebc-7c8e-1535-7f082ccb9f93.png)

上下に書かれているのは、例によってデフォルトでは95%信頼区間だそうです。また、層別する場合は```hue```引数を指定します。

```python
sns.lineplot(x="timepoint", y="signal", hue="event", data=fmri)
```

![index.png](https://qiita-image-store.s3.amazonaws.com/0/127762/8513372c-c434-bf04-55d6-c0053cb75195.png)

ただ、（可視化のサンプルなので問題無いといえば無いのですが）[```load_dataset```のドキュメント](https://seaborn.pydata.org/generated/seaborn.load_dataset.html)を見ても、「seabornのドキュメント用のデータ」としか書かれていなくて困りますね。

## ヒストグラム

他のグラフとレイヤーが違いますが、これも日常的に使うグラフだと思います。これもデータの「偏り」や「分布」を俯瞰して見るのに適したグラフだと思います。

『グラフをつくる前に読む本』で紹介されていないのは（一応）棒グラフの使い方の一種だからだということと、おそらく著者がマーケティング領域の方なので「全体を俯瞰する」ことにより重きを置いているからなんじゃないかと推察しています。

一応ヒートマップと棒グラフ（≒ヒストグラム）を比較した文章は本の中にありました。

> データ項目を比較するだけなら棒グラフが適しています。棒グラフはデータ項目の比較には適していますが、**全体を俯瞰して「まんべんなくデータが散らばっている」、もしくは「データが特定の項目に偏っている」と把握するのには適していません**。

ただ、我々はログファイルなどの、より生データに近いデータを集計する場合が多く、そのような場合にはヒストグラムが活躍する場面が多いように思います。[暮らしに役立つQC七つ道具(4) ―― ヒストグラム：「全体」の「傾向」をつかむ](http://www.kumikomi.net/archives/2009/06/4.php)という記事ではこのように紹介されています。余談ですが、品質管理分野ではけっこうこの手のノウハウが体系化されてて勉強になりそうですね。

> 今回取り上げるQC七つ道具は，「ヒストグラム」です．ヒストグラムとは，測定した値を区間(階級)に分けて，データの個数をグラフ化したもので，度数分布図とも呼ばれます．
> ヒストグラムは，データの全体的な分布の傾向や偏りを確かめるのに使われ，分布の形や中心の位置，バラツキの大きさなどを見ることによって問題点を明らかにすることができます．
> 一般的に，製造工程のバラツキは，正規分布になるのがよいとされています．

私は、以前の上司からデータ分析についていろいろと教わったのですが、そのときに「WEBサイトの高速化の案件があった。そのときにレスポンスタイムが二つ山のある形をしていたため、『リクエストによっては時間のかかる処理（DBへ何度もリクエストしてしまっている等）があるんじゃないか？』という仮説を得ることができた」という話を聞いたことがあります。

これは平均値や中央値などだけを見ていても得られない知見ですし、ヒートマップでも（不可能ではないにせよ）難しいんじゃないかと思います。また、ヒストグラムは一次元の情報から有益な手がかり（次に調査すべきアイデア）を得られる可能性があるので、探索的にデータを分析する際に「とりあえずの一手」として使いやすいと感じています。

話が長くなりましたが、ヒストグラムは[```distplot```](https://seaborn.pydata.org/generated/seaborn.distplot.html)関数でプロットできます。ヒストグラムの場合は「数値の配列を、適切な粒度で集計して棒グラフにする」ような指定方法をします。

```python
sns.distplot(titanic['fare'])
```

![index.png](https://qiita-image-store.s3.amazonaws.com/0/127762/d314890b-ecb4-7538-ca6c-2438dcc7d7d0.png)

ヒストグラム以外にプロットしてしまうのは、gaussian kernel density estimateで推定した値のようです。これは初めて使ったのですが、[2次元でもプロットできる](https://seaborn.pydata.org/generated/seaborn.kdeplot.html#seaborn.kdeplot)ようで、使いどころによってはなかなか面白そうです。

もしヒストグラムのみ表示したい場合はこうです。kde引数で、y軸のメモリが変わってしまうのはちょっと気持ち悪いですね…。

```python
sns.distplot(titanic['fare'], kde=False)
```

![index.png](https://qiita-image-store.s3.amazonaws.com/0/127762/64f55026-1a2a-5399-59fa-0c9ccf5513c6.png)

ドキュメントによると[```norm_hist```](https://seaborn.pydata.org/generated/seaborn.distplot.html)という引数で指定できそうだったんですが、今の私の環境では変わりませんでした。

> norm_hist : bool, optional
> If True, the histogram height shows a density rather than a count. This is implied if a KDE or fitted density is plotted.

「他の変数で層別してヒストグラムを作る」ような作業も[FacetGrid](https://seaborn.pydata.org/generated/seaborn.FacetGrid.html)を使えばできました。

```python
# heightが未指定だと画像が小さくなってしまう
g = sns.FacetGrid(titanic, hue="sex", height=5)
g.map(sns.distplot, "fare", kde=False) 
g.add_legend()
```

![index.png](https://qiita-image-store.s3.amazonaws.com/0/127762/94dfb3bc-2f33-9f0e-4336-1ffa18ab215e.png)

他にもいくつか情報があるので、これ以上は実際使うときに調べてみることとします。

* [Lec56-59_Titanic号のデータを解析する](http://www.tsjshg.info/udemy/Lec56-59.html)
* [ヒストグラム | matplotlib / seaborn を利用してヒストグラムを描く方法](https://stats.biopapyrus.jp/python/hist.html)


## ヒートマップ

『グラフをつくる前に読む本』から引用です。

> **ヒートマップは数字を色に置き換えて、「どのデータ項目にデータが偏っているだろうか？」と考えます**。つまり**ヒートマップが一番得意な表現方法は量の「偏り」**です。

```python
flights = sns.load_dataset("flights")
flights = flights.pivot("month", "year", "passengers")
ax = sns.heatmap(flights)
```

こちらも[```heatmap```関数](https://seaborn.pydata.org/generated/seaborn.heatmap.html)でなんとかなります。なんでもありますね。

![index.png](https://qiita-image-store.s3.amazonaws.com/0/127762/55befc25-34c7-d85b-7424-3c0c4afcdee9.png)

ちなみに、```pivot```は```seaborn```の機能ではなく、単に```pandas```の```DataFrame```の[```pivot```メソッド](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.pivot.html)を呼んでいるだけです。これ知らなかったんですが便利ですね。

[pandasのピボットテーブルでカテゴリ毎の統計量などを算出](https://note.nkmk.me/python-pandas-pivot-table/)

## 散布図

これも『グラフをつくる前に読む本』から引用から。

> 複数の項目を表現した点を俯瞰して見て**「縦軸と横軸の相関（2つのデータ項目が密接に関わり合っている状態）はあるだろうか？」と考えます**。つまり**散布図が一番得意な表現方法は、2つのデータ項目の「関係」**です。

これは[```jointplot```](https://seaborn.pydata.org/generated/seaborn.jointplot.html)で、ヒストグラムと併せて描けます。

```python
tips = sns.load_dataset("tips")
g = sns.jointplot(x="total_bill", y="tip", data=tips)
```

![index.png](https://qiita-image-store.s3.amazonaws.com/0/127762/7dd9c640-1a94-45e7-b240-f8a97386fe5c.png)

また、[```scatterplot```](https://seaborn.pydata.org/generated/seaborn.scatterplot.html)を使うとヒストグラムが無いバージョンもできるし、[```pairplot```](https://seaborn.pydata.org/generated/seaborn.pairplot.html)を使えば変数の組み合わせで一気に描けるようですね。

『グラフをつくる前に読む本』では更に、「相関と因果関係を混同しないようにしよう」とか「単なる偶然である可能性も考慮しよう」という話も書いていて、実際に注意すべきなのですが、この記事の範疇を超えてしまうのでこれ以上は突っ込みません。ぜひ読んでみてください。


## 積み上げ棒グラフ

> 積み上げ棒グラフは、棒グラフと円グラフの良い所取りをしたグラフです。円グラフは**内訳を「円の角度」で表現しましたが、積み上げ棒グラフは「棒の高さ」で表現します**。（中略）**積み上げ棒グラフは、円グラフが得意とする「内訳」と棒グラフが得意とする「比較」のどちらを強調したいか決めれば、何が言いたいのかより伝わります**。

おそらく世間一般で円グラフを使うような場面では、私は積み上げ棒グラフを使います。私が欲しい機能は2つで、[The Python Graph Gallery](https://python-graph-gallery.com/)から引用すると以下の2つです。

まず、通常の積み上げ棒グラフ([#12 Stacked barplot with matplotlib](https://python-graph-gallery.com/12-stacked-barplot-with-matplotlib/))です。これは総量を比較するために使います。
![12_stacked_barplot.png](https://qiita-image-store.s3.amazonaws.com/0/127762/8dd8ad03-3b0d-c896-8eba-7bc658af15eb.png)
次にパーセント表示の積み上げ棒グラフ([#13 Percent stacked barplot](https://python-graph-gallery.com/13-percent-stacked-barplot/))です。これは割合で比較するときに使います。
![12_stacked_percent_barplot.png](https://qiita-image-store.s3.amazonaws.com/0/127762/e6e7d19e-fb4e-f957-7de7-ea9fe08dfc02.png)




ただ、残念ながらseabornには今のところ積み上げ棒グラフの機能は無いようです。[Horizontal bar plots](https://seaborn.pydata.org/examples/horizontal_barplot.html)の例のように「トータルと別々にプロットする」くらいしか方法はありません。パーセント表示の積み上げ棒グラフの場合は少々手間がかかってしまいそうです。


また、どうやらpandasの[```plot```メソッドには```stacked=True```にすると積み上げ棒グラフが簡単に描けるオプションがある](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.plot.html)ようです。これと使い分けるのでもいいかもしれませんね。

## 面グラフ

あまり私自身聞き慣れませんでしたが、面グラフは「時系列データを用いた時間変化による内訳の推移」を表現するグラフです。私は、LIFULL HOME'Sの地域ごとの物件数の推移を表現するために使ったことがあります。

> 面グラフでは、面の推移を比べて「**全体に対して占める割合が大きい（小さい）データ量はどのように変化するのだろう？」と考えます**。つまり**面グラフが一番得意な表現方法はデータ全体「内訳」の「推移」**です。面グラフは、データ（数字）の総量と内訳の変化が最もわかりやすく図で表現できます。

残念ながら、これも```seaborn```では実装されていないようです。この辺りを参考に```matplotlib```でコツコツ実装していくしかなさそうです。

* [#251 Stacked area chart with seaborn style](https://python-graph-gallery.com/251-stacked-area-chart-with-seaborn-style/)
* [#254 Pandas Stacked area chart](https://python-graph-gallery.com/254-pandas-stacked-area-chart/)

![251_seaborn_style_on_stacked_area_chart.png](https://qiita-image-store.s3.amazonaws.com/0/127762/75e122b1-d91c-ce34-e5af-7ed534c52ffb.png)

これは割と理想的ですね。

## レーダーチャート

> 円の中心から数えて、数が大きければ円の外側に、数が小さければ円の内側に折れ線が向かいます。それを見て「**レーダーチャートは円の中にまんべんなく面を描けているだろうか？」と考えます**。線で結ばれてできた面の滑らかさを頭の中で思い描きながら、図1のように特定の項目に偏っていないかを確認します。つまり**レーダーチャートが一番得意な表現方法は、複数あるデータ項目の「比較」**です。**全体と部分を同時に比較している**のです。

私はレーダーチャートを使ったことはほとんど無い気がします。ただ、「物件スペックの比較」のような用途ではハマる使い方できるんじゃないかと思っています。

これも```seaborn```では実装されておらず、自前でがんばって実装していくしかなさそうです。私が欲しいグラフはこれですね。

* [#391 Radar chart with several individuals](https://python-graph-gallery.com/391-radar-chart-with-several-individuals/)

![391_Several_indiv_Radarchart.png](https://qiita-image-store.s3.amazonaws.com/0/127762/ec24c851-4848-28ad-5efc-0c70553d39fa.png)


ちなみに『海外では「スパイダーチャート」「スターチャート」、中国では「雷達図」と呼ばれていることが多い』ということも書いていたのですが、ライブラリ等のドキュメントではRader Chartと書かれていることが多いです。分野によって違うのかな？

## まとめ

今回試してみて、私の用途で感じた利点と欠点です。今のところ、基本的な集計は```seaborn```で、対応しない部分は```matplotlib```で実装するのが良さそうだと感じています。

**利点**

* 基本的なグラフは描くことができる
* 自動で信頼区間等の追加情報も描いてくれるので、場合によっては「めちゃめちゃバラついてるのに集計してしまって気づけない」ような場合を防げるかもしれない。人によっては余計なお世話と感じてしまうかも
* pandasと組み合わせた操作がやりやすい

**欠点**

* 世間一般的にマイナーなグラフが未実装（個人的には積み上げ棒グラフが無いのがきつい）
* 用途から外れた場合、結局```matplotlib```で実装する必要がある。その際にグラフのデザインの統一感を気にすると面倒かもしれない


また、今回多くを参考にさせて頂いた『[グラフをつくる前に読む本](https://www.amazon.co.jp/dp/4774192198)』は更に詳しいノウハウや、それぞれが生まれてきた歴史に触れられています。グラフ・マニアの方はぜひ読んでみてください。