seabornでカテゴリ別データを描画するときに入力データを指定する方法のまとめ

seabornはpythonのグラフ作成ライブラリで、Matplotlibのラッパーである。
少ない行数で書くことができて、見た目をいい感じに描画してくれるので、俺はなかなかseabornが好きだ。

そのseabornには、カテゴリ別のデータをグラフにする関数がある。 これらの描画関数に入力データを与える方法がいくつか存在するので、それをまとめてみた。
(しかし、俺がよく分かっていない箇所も結構あるので、誤りを含む可能性があることは注意してほしい。)

カテゴリカル データの描画関数一覧

いったん本題から外れる。 カテゴリカル データを描画するための関数は、以下の8種類だ(Plotting with categorical data — seaborn 0.10.0 documentationから引用)。

  • stripplot
  • swarmplot
  • boxplot(箱ひげ図)
  • violinplot
  • boxenplot
  • pointplot
  • barplot
  • countplot

その他に、上記の関数をまとめて扱う、catplotという関数もある。catplotを使っても内部的にはstripplotなどの上記の関数が呼び出される。
catplotはfigure-levelの関数で、stripplotやboxplotなどはaxes-levelの関数だ、と公式チュートリアルには書いてある。……だが書いている俺が、figure-levelの関数とaxes-levelの関数って何のことなのか分かっていない。

で、上記の各関数でどんなグラフが描けるのか、については、以下の記事が詳しく説明している。

seabornによる統計データ可視化(ポケモン種族値を例に)(1) - 午睡二時四十分

しかし、今回の記事の主題は、グラフの形式の話ではない。描画関数に与えるデータ、入力データをどう指定すればいいかという問題である。

公式サイトの説明を見てみよう

「catplotはfigure-levelの関数で、stripplotやboxplotなどはaxes-levelの関数だ」と書いたが、この2つのグループによって入力データを指定する方法は少し違うらしい。

この記事ではaxes-levelの関数、具体的な関数の方だけ説明する。各関数のドキュメントには以下のように書いてある。

Input data can be passed in a variety of formats, including:

  • Vectors of data represented as lists, numpy arrays, or pandas Series objects passed directly to the x, y, and/or hue parameters.
  • A “long-form” DataFrame, in which case the x, y, and hue variables will determine how the data are plotted.
  • A “wide-form” DataFrame, such that each numeric column will be plotted.
  • An array or list of vectors.

In most cases, it is possible to use numpy or Python objects, but pandas objects are preferable because the associated names will be used to annotate the axes. Additionally, you can use Categorical types for the grouping variables to control the order of plot elements.
seaborn.swarmplot — seaborn 0.10.0 documentation ほか

箇条書きで4つの方法が書いてある。以下では、箇条書きの項目を入れ替えて説明する。
最初にサンプルのデータをロードしておこう。

import numpy as np
import seaborn as sns
import pandas as pd

tips = sns.load_dataset("tips")

Google Colaborate上で実行した。

pandas、seabornのバージョンは以下の通り。

print(sns.__version__)
print(pd.__version__)
---
0.9.0
0.24.2

方法1 引数dataにDataFrameを入力。x, y, hueで列を指定する

公式サイトだと2番目に書いてある方法だ。

  • A “long-form” DataFrame, in which case the x, y, and hue variables will determine how the data are plotted.

公式の例ではこの方法を一番多く使っているようなので、これを最初に持ってきた。
dataにpandasのDataFrameを渡して、xとyに(描きたいグラフによってはhueやsizeに)列名を指定する方法である。

ax = sns.swarmplot(x="day", y="total_bill", data=tips)

f:id:soratokimitonoaidani:20190818145910p:plain

x軸とy軸のラベルに、DataFrameの列名が書かれている。これはもとのDataFrameを入力したためである。


引数のxとyを入れ替えると、縦横を入れ替えたグラフになる。

ax = sns.swarmplot(x="total_bill", y="day", data=tips)

f:id:soratokimitonoaidani:20190818145912p:plain

x/y軸のどっちが数値データでどっちがカテゴリーのデータなのか、入力内容から自動的に判断しているということだろう。賢い。


では意地悪をして、試しに両方カテゴリーの列を指定してみよう。

ax = sns.swarmplot(x="day", y="smoker", data=tips)

---
ValueError                                Traceback (most recent call last)
(中略)
ValueError: Neither the `x` nor `y` variable appears to be numeric.

xとyがどっちも数値データじゃないんですけど、ってエラーを出してくる。そりゃ、このエラーになるだろうな。

方法2 直接リスト/Series/ndarrayを入力

公式サイトだと2番目に書いてある方法だ。

  • Vectors of data represented as lists, numpy arrays, or pandas Series objects passed directly to the x, y, and/or hue parameters.

(和訳:list、numpyのarray、もしくはpandasのSeriesオブジェクトで表されたデータのベクトルを、x, y, hueの引数に直接渡す)
箇条書きの1つの項目の中に複数のことを書いてある。すなわち、リスト/Series/ndarrayの3つを入力できるらしい。

​なお、以降ではいったんx_y_という変数に入力データを代入し、その次に描画関数の引数にx_y_を指定する。(これは変数のtypeをハッキリさせるためである。)
また、入力データの内容が同じなので、結果として描画される図が同じ場合、適宜割愛する。

PandasのSeries

まずSeriesを使ってみましょう。seaborn公式の説明と順序が違うけど、DataFrameを元に作るならSeries型が一番自然でしょう。

x_ = tips["day"]
y_ = tips["total_bill"]
print(type(x_))
print(type(y_))

ax = sns.swarmplot(x=x_, y=y_)

---
<class 'pandas.core.series.Series'>
<class 'pandas.core.series.Series'>

図は「方法1」で描いたものと同じなので省略。


pythonのlist

次に、pythonのlistに変換してから入力してみる。

x_ = list(tips["day"])
y_ = list(tips["total_bill"])
print(type(x_))
print(type(y_))

ax = sns.swarmplot(x=x_, y=y_)

f:id:soratokimitonoaidani:20190818145859p:plain

同じように見えるが、pandasのSeriesのときの違いが2点ある。

  • x軸とy軸のラベルが無い
  • 横軸の並び順が違う

軸ラベルが無い理由は良いだろう。pandasのSeriesにはnameという属性があるが、pythonのlistには無いから、swarmplot関数にはラベル名が入力されていないのだ。

で、横軸の並び順が違うんだけど?どうして???
多分、公式ドキュメントで

Additionally, you can use Categorical types for the grouping variables to control the order of plot elements.

と書いてあるあたりが関係しているのだと思う。

tips["day"].dtype
---
CategoricalDtype(categories=['Thur', 'Fri', 'Sat', 'Sun'], ordered=False)

tips["day"]の中には曜日の並び順の情報も入っているようだ。 ところがpython標準のリストに変換した段階で、並び順の情報は消えてしまうので、順序はバラバラになっているようだ。(完全には理解できてない。)


numpyのarray

最後。numpy.ndarrayに変換して入力。

x_ = np.array(tips["day"])
y_ = np.array(tips["total_bill"])
print(type(x_))
print(type(y_))

ax = sns.swarmplot(x=x_, y=y_)
---
<class 'numpy.ndarray'>
<class 'numpy.ndarray'>

図はpythonのlistの場合と全く同じなので、割愛する。


異なる型の組み合わせ

また、xとyは同じ型である必要はない。別の型を組み合わせた例。

x_ = tips["day"]
y_ = np.array(tips["total_bill"])
print(type(x_))
print(type(y_))

ax = sns.swarmplot(x=x_, y=y_)
---
<class 'pandas.core.series.Series'>
<class 'numpy.ndarray'>

f:id:soratokimitonoaidani:20190818145902p:plain

方法3

公式サイトだと3番目に書いてある方法だ。

A “wide-form” DataFrame, such that each numeric column will be plotted.

方法1はlong-form dataだけど、方法3はwide-form dataらしい。……って、何のこっちゃ?
Plotting with categorical data — seaborn 0.10.0 documentation にIrisデータを使った例が出ている。
それに倣ってtipsデータでやってみると……

sns.swarmplot(data=tips)

f:id:soratokimitonoaidani:20190818145906p:plain

dataにDataFrameを指定して、引数x,yを指定しないやり方。
出力されるグラフはこれまでと違っていて、横軸に各列が並び、縦軸でその列の数値の分布が描かれる(カテゴリデータは無視される)。

方法4 vectorのarrayまたはlist(不明、失敗)

公式サイトだと4番目に書いてある方法だ。

An array or list of vectors.

しかし……よく分からない。 vectorのarrayまたはlist、と書いてある。
vectorという型はpythonには無い。 2番目で「Vectors of data represented as lists, numpy arrays, or pandas Series objects」と言っているのを見ると、vectorというのは「list/numpy ndarray/pandas Series」の総称のことかなぁと推測される。

「listまたはnumpy ndarrayまたはpandas Series」の「arrayまたはlist」だから…例えば、listのlistを入力にすればいいということだろう。作ってみよう。

x_ = list(tips["day"])
y_ = list(tips["total_bill"])
data_ = [x_, y_]

print(type(x_))
print(type(y_))
print(type(data_))
sns.swarmplot(data=data_)
---

<class 'list'>
<class 'list'>
<class 'list'>
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
(中略)
ValueError: could not convert string to float: 'Sun'

だめだ。エラーが出てしまった。
公式のドキュメントが何を言ってるのか分からないですね。

まとめ

  • seabornでカテゴリカル データを描画する関数を使うとき、入力データをどう指定すればいいかを調べた
  • DataFrameの他にも、Series、pythonのlist、numpyのndarrayを入力に使うことができる
  • 特にDataFrame/Seriesを使うと軸ラベルや並び順の点で便利なようだ

残りの疑問

  • 方法2でpandasのSeriesの時だけ横軸の並び順が違ったのはなぜ?
  • long-form dataとwide-form dataって何?
  • figure-levelの関数と、axes-levelの関数って何?
  • 方法4はどういうやり方が正解なの?

ここは今後の課題にします。もしも見た人が知ってるなら、教えて下さい……

それでは。