seabornが受け付けるデータ構造#

データ可視化ライブラリであるseabornは、データを提供する必要があります。この章では、そのタスクを実行する様々な方法を説明します。seabornはいくつかの異なるデータセット形式をサポートしており、ほとんどの関数はpandasまたはnumpyライブラリのオブジェクト、そしてリストや辞書のようなPythonの組み込み型で表されたデータを受け付けます。これらの異なるオプションに関連付けられた使用方法を理解することで、ほぼすべてのデータセットに対して役立つ可視化を迅速に作成できます。

注記

現在(v0.13.0)の時点では、ここで説明されているすべてのオプションが、seabornのほとんどの関数でサポートされていますが、すべてではありません。具体的には、いくつかの古い関数(例:lmplot()regplot())は、受け付けるものがより限定されています。

ロングフォームデータとワイドフォームデータ#

seabornのほとんどのプロット関数は、データのベクトルを対象としています。xyをプロットする場合、各変数はベクトルである必要があります。seabornは、ある表形式で編成された複数のベクトルを持つデータセットを受け入れます。「ロングフォーム」と「ワイドフォーム」のデータテーブルには根本的な違いがあり、seabornはそれぞれを異なる方法で処理します。

ロングフォームデータ#

ロングフォームデータテーブルには、次の特徴があります。

  • 各変数は列です。

  • 各観測値は行です。

簡単な例として、「flights」データセットを考えてみましょう。このデータセットは、1949年から1960年までの各月の航空旅客数を記録しています。このデータセットには、3つの変数(、旅客数)があります。

flights = sns.load_dataset("flights")
flights.head()
旅客数
0 1949 1月 112
1 1949 2月 118
2 1949 3月 132
3 1949 4月 129
4 1949 5月 121

ロングフォームデータでは、テーブルの列に、変数の1つに明示的に割り当てることで、プロットにおける役割が与えられます。たとえば、年間の旅客数を月ごとにプロットする場合は、次のようになります。

sns.relplot(data=flights, x="year", y="passengers", hue="month", kind="line")
../_images/data_structure_6_0.png

ロングフォームデータの利点は、プロットの明示的な仕様に適していることです。変数と観測値を明確に定義できる限り、任意の複雑さのデータセットに対応できます。しかし、この形式は、しばしば頭に浮かぶデータのモデルとは異なるため、慣れるのに時間がかかります。

ワイドフォームデータ#

単純なデータセットの場合、スプレッドシートで表示されるような方法でデータについて考える方が直感的であることがよくあります。そこでは、列と行が異なる変数のレベルを含んでいます。たとえば、「flights」データセットをワイドフォーム構成に変換するには、「ピボット」して、各列に年ごとの各月の時系列を含めることができます。

flights_wide = flights.pivot(index="year", columns="month", values="passengers")
flights_wide.head()
1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
1949 112 118 132 129 121 135 148 148 136 119 104 118
1950 115 126 141 135 125 149 170 170 158 133 114 140
1951 145 150 178 163 172 178 199 199 184 162 146 166
1952 171 180 193 181 183 218 230 242 209 191 172 194
1953 196 196 236 235 229 243 264 272 237 211 180 201

ここでは、同じ3つの変数がありますが、異なる方法で編成されています。このデータセットの変数は、名前付きフィールドではなく、テーブルの次元にリンクされています。各観測値は、テーブル内のセルの値と、行および列インデックスに対するそのセルの座標の両方によって定義されます。

ロングフォームデータでは、データセット内の変数に名前でアクセスできます。ワイドフォームデータではそうではありません。それにもかかわらず、テーブルの次元とデータセット内の変数の間に明確な関連性があるため、seabornはこれらの変数をプロットにおける役割に割り当てることができます。

注記

seabornは、xyも割り当てられていない場合、dataの引数をワイドフォームとして扱います。

sns.relplot(data=flights_wide, kind="line")
../_images/data_structure_11_0.png

このプロットは、前のプロットと非常によく似ています。seabornは、データフレームのインデックスをxに、データフレームの値をyに割り当て、各月ごとに別々の線を描画しています。ただし、2つのプロットには注目すべき違いがあります。データセットがロングフォームからワイドフォームに変換する「ピボット」操作を行ったとき、値の意味に関する情報は失われました。その結果、y軸のラベルがありません。(線には点線もあります。これはrelplot()が列変数をhuestyleの両方のセマンティックにマッピングして、プロットをよりアクセスしやすくしているためです。ロングフォームの場合にはそうしませんでした。しかし、style="month"を設定することで、そうすることもできました)。

これまでのところ、ワイドフォームデータを使用しながらタイピングを大幅に削減し、ほぼ同じプロットを作成しました。これは簡単そうです!しかし、ロングフォームデータの大きな利点は、一度正しい形式でデータを取得すると、その構造について考える必要がなくなることです。含まれる変数についてのみ考えてプロットを設計できます。たとえば、年ごとの毎月の時系列を表す線を描画するには、変数を再割り当てするだけです。

sns.relplot(data=flights, x="month", y="passengers", hue="year", kind="line")
../_images/data_structure_13_0.png

ワイドフォームデータセットで同じ再マッピングを実現するには、テーブルを転置する必要があります。

sns.relplot(data=flights_wide.transpose(), kind="line")
../_images/data_structure_15_0.png

(この例は、seabornが現在、ワイドフォームデータセットの列変数をそのデータ型に関係なくカテゴリ変数と見なしているという別の問題も示しています。一方、ロングフォーム変数は数値であるため、数量的なカラーパレットと凡例が割り当てられています。これは将来的に変更される可能性があります)。

明示的な変数割り当てがないということは、各プロットタイプが、ワイドフォームデータの次元とプロットにおける役割の間に固定マッピングを定義する必要があることも意味します。この自然なマッピングはプロットタイプによって異なる可能性があるため、ワイドフォームデータを使用すると結果は予測しにくくなります。たとえば、カテゴリカルプロットは、テーブルの次元をxに割り当て、次に行全体で集計します(インデックスは無視します)。

sns.catplot(data=flights_wide, kind="box")
../_images/data_structure_17_0.png

pandasを使用してワイドフォームデータを表す場合、変数はごくわずか(3つ以下)に制限されます。これは、seabornがマルチインデックス情報を使用していないためです。pandasはこの情報を用いて、表形式で追加の変数を表現します。xarrayプロジェクトは、ラベル付きN次元配列オブジェクトを提供しており、これは高次元へのワイドフォームデータの一般化と見なすことができます。現在、seabornはxarrayからのオブジェクトを直接サポートしていませんが、to_pandasメソッドを使用してロングフォームのpandas.DataFrameに変換し、他のロングフォームデータセットと同様にseabornでプロットできます。

要約すると、ロングフォームとワイドフォームのデータセットは、次のようなものだと考えることができます。

../_images/data_structure_19_0.png

整然としないデータ#

多くのデータセットは、ロングフォームまたはワイドフォームのどちらのルールでも明確に解釈できません。明確にロングフォームまたはワイドフォームであるデータセットが“整然とした(tidy)”ものであるとすれば、これらのより曖昧なデータセットは「整然としない」と言えるかもしれません。整然としないデータセットでは、変数はキーによっても、テーブルの次元によっても一意に定義されていません。これは、反復測定データでよく発生します。このデータでは、各行がデータ収集の単位に対応するようにテーブルを編成することが一般的です。注意が分散していたり集中していたりするアナグラムを学習した記憶タスクを20人の被験者が行った心理学実験からのこの単純なデータセットを考えてみましょう。

anagrams = sns.load_dataset("anagrams")
anagrams
被験者ID 注意 数1 数2 数3
0 1 分散 2 4.0 7
1 2 分散 3 4.0 5
2 3 分散 3 5.0 6
3 4 分散 5 7.0 5
4 5 分散 4 5.0 8
5 6 分散 5 5.0 6
6 7 分散 5 4.5 6
7 8 分散 5 7.0 8
8 9 分散 2 3.0 7
9 10 分散 6 5.0 6
10 11 集中 6 5.0 6
11 12 集中 8 9.0 8
12 13 集中 6 5.0 9
13 14 集中 8 8.0 7
14 15 集中 8 8.0 7
15 16 集中 6 8.0 7
16 17 集中 7 7.0 6
17 18 集中 7 8.0 6
18 19 集中 5 6.0 6
19 20 集中 6 6.0 5

注意変数は被験者間変数ですが、被験者内変数もあります。アナグラムの可能な解の数で、1から3まで変化します。従属変数は記憶パフォーマンスのスコアです。これらの2つの変数(数とスコア)は、複数の列にまたがって共同でエンコードされます。その結果、データセット全体は、ロングフォームでもワイドフォームでもありません。

seabornに平均スコアを注意と解の数関数としてプロットするように指示するには、どうすればよいでしょうか?まず、データを2つの構造のいずれかに強制的に変換する必要があります。各変数が列で、各行が観測値である整然としたロングフォームテーブルに変換してみましょう。pandas.DataFrame.melt()メソッドを使用して、このタスクを実行できます。

anagrams_long = anagrams.melt(id_vars=["subidr", "attnr"], var_name="solutions", value_name="score")
anagrams_long.head()
被験者ID 注意 解の数 スコア
0 1 分散 数1 2.0
1 2 分散 数1 3.0
2 3 分散 数1 3.0
3 4 分散 数1 5.0
4 5 分散 数1 4.0

これで、目的のプロットを作成できます。

sns.catplot(data=anagrams_long, x="solutions", y="score", hue="attnr", kind="point")
../_images/data_structure_25_0.png

参考資料と要点#

表形式データ構造に関するより詳細な議論については、Hadley Wickhamによる“Tidy Data”論文を参照してください。seabornは、論文で定義されている概念とはやや異なる概念を使用していることに注意してください。論文では整然性とロングフォーマット構造を関連付けていますが、ここでは、データセット内の変数とテーブルの次元との間に明確なマッピングが存在する「整然としたワイドフォーマット」データと、そのようなマッピングが存在しない「非整然データ」を区別しています。

ロングフォーマット構造には明確な利点があります。これにより、データセット内の変数をプロットの役割に明示的に割り当てることで図を作成することができ、3つ以上の変数を使用することもできます。可能であれば、本格的な分析に着手する際には、データはロングフォーマット構造で表現するようにしてください。seabornドキュメントのほとんどの例では、ロングフォーマットデータを使用します。しかし、データセットをワイドフォーマットのままにする方が自然な場合は、seabornは引き続き有用であることを覚えておいてください。

ロングフォーマットデータの可視化オプション#

ロングフォーマットデータには正確な定義がありますが、seabornはメモリ内のデータ構造全体での実際の編成方法に関してかなり柔軟です。ドキュメントの残りの部分の例では、通常、pandas.DataFrameオブジェクトを使用し、プロット内の変数にそれらの列の名前を割り当てることで、それらの変数を参照します。しかし、Python辞書やそのインターフェースを実装するクラスにベクトルを格納することも可能です。

flights_dict = flights.to_dict()
sns.relplot(data=flights_dict, x="year", y="passengers", hue="month", kind="line")
../_images/data_structure_28_0.png

groupbyのsplit-apply-combine操作など、多くのpandas操作では、情報が入力データフレームの列から出力のインデックスに移動したデータフレームが生成されます。名前が保持されている限り、データは通常どおり参照できます。

flights_avg = flights.groupby("year").mean(numeric_only=True)
sns.relplot(data=flights_avg, x="year", y="passengers", kind="line")
../_images/data_structure_30_0.png

さらに、データのベクトルをxy、およびその他のプロット変数に引数として直接渡すことができます。これらのベクトルがpandasオブジェクトである場合、name属性を使用してプロットにラベル付けされます。

year = flights_avg.index
passengers = flights_avg["passengers"]
sns.relplot(x=year, y=passengers, kind="line")
../_images/data_structure_32_0.png

NumPy配列やPythonシーケンスインターフェースを実装するその他のオブジェクトも機能しますが、名前がない場合は、さらに調整しないとプロットの情報が不足します。

sns.relplot(x=year.to_numpy(), y=passengers.to_list(), kind="line")
../_images/data_structure_34_0.png

ワイドフォーマットデータの可視化オプション#

ワイドフォーマットデータを渡すためのオプションはさらに柔軟です。ロングフォーマットデータと同様に、名前(および場合によってはインデックス)情報を使用できるため、pandasオブジェクトが推奨されます。しかし、本質的に、単一ベクトルまたはベクトルのコレクションと見なすことができる形式であれば、dataに渡すことができ、通常は有効なプロットを作成できます。

上記の例では、長方形のpandas.DataFrameを使用しました。これは、その列のコレクションと見なすことができます。pandasオブジェクトの辞書またはリストも機能しますが、軸ラベルは失われます。

flights_wide_list = [col for _, col in flights_wide.items()]
sns.relplot(data=flights_wide_list, kind="line")
../_images/data_structure_36_0.png

コレクション内のベクトルは、同じ長さである必要はありません。indexがある場合は、それを使用してベクトルを調整します。

two_series = [flights_wide.loc[:1955, "Jan"], flights_wide.loc[1952:, "Aug"]]
sns.relplot(data=two_series, kind="line")
../_images/data_structure_38_0.png

一方、NumPy配列や単純なPythonシーケンスには序数インデックスが使用されます。

two_arrays = [s.to_numpy() for s in two_series]
sns.relplot(data=two_arrays, kind="line")
../_images/data_structure_40_0.png

しかし、そのようなベクトルの辞書は少なくともキーを使用します。

two_arrays_dict = {s.name: s.to_numpy() for s in two_series}
sns.relplot(data=two_arrays_dict, kind="line")
../_images/data_structure_42_0.png

長方形のNumPy配列は、インデックス情報のないデータフレームと同様に扱われ、列ベクトルのコレクションと見なされます。これは、NumPyのインデックス付け操作の動作とは異なることに注意してください。NumPyでは、単一のインデックス付け子は行にアクセスします。しかし、これはpandasが配列をデータフレームに変換する方法、またはmatplotlibがそれをプロットする方法と一貫しています。

flights_array = flights_wide.to_numpy()
sns.relplot(data=flights_array, kind="line")
../_images/data_structure_44_0.png