Python

Python seaborn チュートリアル APIの概要 データ構造(1)

原文のドキュメントはこちらから。

seabornにデータを提供するためのさまざまな方法を確認する。いくつかの異なるデータセットのフォーマットをサポートし、ほとんどの関数は、pandasやnumpyのオブジェクトや、リストや辞書のようなPythonの組み込み型のデータを受け付ける。これらの使用パターンを理解することで、有用なビジュアライゼーションを素早く作成することができる。

本稿執筆時点(v0.11.0)では、ここで取り上げたオプションは、seabornのモジュールの一部(すなわち、リレーショナルモジュールとディストリビューションモジュール)のみでサポートされている。他のモジュールも同じような柔軟性があるが、いくつかの例外がある(catplot()とlmplot()は名前付き変数を持つロングフォームデータに限定される)。今後数回のリリースサイクルで標準化される予定だが、それまでは、データセットで期待されたことが実行されない場合は、各関数のドキュメントを確認すること。

ロングフォームデータとワイドフォームデータの比較

プロット関数のほとんどは、データのベクトルに向けられている。xとyをプロットするとき、各変数はベクトルでなければならない。seabornは、表形式で構成された複数のベクトルを持つデータセットを受け付けることができる。データテーブルには、”long-form”と “wide-form”という基本的な区別があり、seabornはそれぞれ異なる扱いをする。

ロングフォームデータ

ロングフォームデータ、次のような特徴がある。

  • 各変数は列
  • 各観測値・記録は行

簡単な例として、航空会社の1949年から1960年までの各月の乗客数を記録した “FLIGHTS” データセットを考える。このデータセットには3つの変数(年、月、乗客数)がある。

import seaborn as sns
import matplotlib.pyplot as plt

flights = sns.load_dataset("flights")
print(flights.head())
   year month  passengers
0  1949   Jan         112
1  1949   Feb         118
2  1949   Mar         132
3  1949   Apr         129
4  1949   May         121

ロングフォームデータでは、表の列は、変数の1つに明示的に割り当て、プロットでの役割を与えられる。たとえば、1年あたりの乗客数を月ごとにプロットすると、次のようになる。

import seaborn as sns
import matplotlib.pyplot as plt

flights = sns.load_dataset("flights")
sns.relplot(data=flights, x="year", y="passengers", hue="month", kind="line")
plt.show()

ロングフォームデータのメリットは、それ自体がプロットの明示的な指定に適していることである。変数とオブザベーションが明確に定義できる限り、任意の複雑さのデータセットを受け付けることできる。しかし、この形式は、直感的ではないことが多いため慣れるのに時間がかかる。

ワイドフォームデータ

単純なデータセットの場合は、スプレッドシートのような見え方の方法でデータを考えた方が直感的に理解できることが多い。例えば、フライトデータセットを「ピボット」することで、各列が各月の時系列を何年にもわたって持つようにすることで、ワイドフォームデータの形式に変換できる。

import seaborn as sns
import matplotlib.pyplot as plt

flights = sns.load_dataset("flights")
flights_wide = flights.pivot(index="year", columns="month", values="passengers")
print(flights_wide.head())
month  Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
year
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 では、xもyも代入されていない場合、データへの引数をワイドフォームのデータとして扱う。

import seaborn as sns
import matplotlib.pyplot as plt

flights = sns.load_dataset("flights")
flights_wide = flights.pivot(index="year", columns="month", values="passengers")
sns.relplot(data=flights_wide, kind="line")
plt.show()

先ほどプロットと非常によく似ている。Seabornは、データフレームのインデックスをxに、データフレームの値をyに割り当て、月ごとに線を引いている。2つのプロットには注目すべき違いがある。データセットをロングフォームからワイドフォームに変換する「ピボット」操作を行ったとき、値が何を意味するのかという情報が失われた。その結果、y軸のラベルがない。(これはrelplot()が列変数を色相とスタイルの両方のセマンティックにマッピングし、プロットがよりアクセスしやすいようにしたためである。ロングフォームの場合は出来なかったが、style=”month “を設定するとできる。)

これまで、ワイドフォームのデータを使用している時は、タイピングの回数を大幅に減らし、ほぼ同じプロットを作成できた。ロングフォームデータの大きなメリットは、データを正しい形式にすると、構造を考える必要がなくなること。その中に含まれる変数だけを考えてプロットをデザインできる。例えば、各年の月次時系列を表す線を引くには、変数を代入し直すだけである。

import seaborn as sns
import matplotlib.pyplot as plt

flights = sns.load_dataset("flights")
flights_wide = flights.pivot(index="year", columns="month", values="passengers")
sns.relplot(data=flights, x="month", y="passengers", hue="year", kind="line")
plt.show()

ワイドフォームデータで同じリマッピングを行うには、表を転置する必要がある。

import seaborn as sns
import matplotlib.pyplot as plt

flights = sns.load_dataset("flights")
flights_wide = flights.pivot(index="year", columns="month", values="passengers")
sns.relplot(data=flights_wide.transpose(), kind="line")
plt.show()

(seabornは現在、ワイドフォームデータの列変数を、そのデータタイプに関係なく分類的ものとみなし、ロングフォーム変数は数値であるため、定量的なカラーパレットと凡例が割り当てられている。これは将来的に変更される可能性がある。)

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

import seaborn as sns
import matplotlib.pyplot as plt

flights = sns.load_dataset("flights")
flights_wide = flights.pivot(index="year", columns="month", values="passengers")
sns.catplot(data=flights_wide, kind="box")
plt.show()

pandasを使ってワイドフォームデータを表現する場合、変数は数個(3個以下)に制限される。これはseabornがマルチインデックス情報を利用していないためである。xarrayプロジェクトは、ラベル付きのN次元配列オブジェクトを提供している。現在のところ、seabornはxarrayのオブジェクトを直接サポートしていないが、to_pandasメソッドを使ってロングフォームのpandas.DataFrameに変換し、他のロングフォームデータと同様にseabornでプロットすることができる。

まとめると、ロングフォームデータセットとワイドフォームデータセットは、次のようになる。

メッシーデータ

多くのデータセットは、ロングフォーム、ワイドフォームでも明確な解釈ができない。もし、これらが「整頓されている」とするならば、より曖昧なデータセットは扱いが困難になる可能性がある。乱雑なデータセットでは、変数がキーによって一意に定義されているわけでも、テーブルの次元によって一意に定義されているわけでもない。これは、反復測定データでよく起こるが、各行がデータ収集の単位に対応するようにテーブルを構成するのは自然なことである。

ある心理学の実験結果のシンプルなデータセットを考える。実験では、20人の被験者に対してアナグラムの記憶と、集中しているかを確認した。

import seaborn as sns
import matplotlib.pyplot as plt

anagrams = sns.load_dataset("anagrams")
print(anagrams)
    subidr    attnr  num1  num2  num3
0        1  divided     2   4.0     7
1        2  divided     3   4.0     5
2        3  divided     3   5.0     6
3        4  divided     5   7.0     5
4        5  divided     4   5.0     8
5        6  divided     5   5.0     6
6        7  divided     5   4.5     6
7        8  divided     5   7.0     8
8        9  divided     2   3.0     7
9       10  divided     6   5.0     6
10      11  focused     6   5.0     6
11      12  focused     8   9.0     8
12      13  focused     6   5.0     9
13      14  focused     8   8.0     7
14      15  focused     8   8.0     7
15      16  focused     6   8.0     7
16      17  focused     7   7.0     6
17      18  focused     7   8.0     6
18      19  focused     5   6.0     6
19      20  focused     6   6.0     5

注意変数は、被験者間の変数であるが被験者の変数もある。アナグラムへの可能な解の数(1から3まで変化)。従属する測定値は、記憶性能のスコアである。これらの2つの変数(数とスコア)は、いくつかの列に渡って共同で符号化されている。そのため、全体のデータセットは明らかにロングフォームでもワイドフォームでもない。

注意力と解答数の関数として平均スコアをプロットするためにはどのようにするか確認する。まず、データを2つの構造のうちの1つに強制する必要がある。各変数が列で、各行が観測値・記録であるような、整然としたロングフォームの表に変換しする。このタスクを達成するために、pandas.DataFrame.melt()メソッドを使うことができる。

import seaborn as sns
import matplotlib.pyplot as plt

anagrams = sns.load_dataset("anagrams")
anagrams_long = anagrams.melt(id_vars=["subidr", "attnr"], var_name="solutions", value_name="score")
print(anagrams_long.head())
   subidr    attnr solutions  score
0       1  divided      num1    2.0
1       2  divided      num1    3.0
2       3  divided      num1    3.0
3       4  divided      num1    5.0
4       5  divided      num1    4.0

これで、自由にプロットが作成できる。

import seaborn as sns
import matplotlib.pyplot as plt

anagrams = sns.load_dataset("anagrams")
anagrams_long = anagrams.melt(id_vars=["subidr", "attnr"], var_name="solutions", value_name="score")
sns.catplot(data=anagrams_long, x="solutions", y="score", hue="attnr", kind="point")
plt.show()

追加文献と要点

表形式データ構造のより長い議論は、Hadley Whickhamによる論文「Tidy Data」を読むとよい。seabornはこの論文で定義されている概念とは少し異なる概念を使用していることに注意が必要。この論文では、Tidynessをロングフォームと関連付けているが、データセット内の変数とテーブルの次元の間に明確なマッピングがある「Tidy wide-form」データと、そのようなマッピングが存在しない「messy data」とで区別している。

ロングフォームデータにはメリットがある。データセットの変数を明示的にプロットの役割に割り当てることで、図を作成できる。(3つ以上の変数でも作成できる。)本格的な分析を始める際は、可能な限りロングフォームでデータを表現することを薦める。seabornのドキュメントのほとんどの例は、ロングフォームのデータを使用する。※データセットがワイドフォームの方が自然な場合には、seabornが有用である。