PandasにはDataFrameと呼ばれる基本構造があります。Pandasを使う上では基礎知識となりますので、この記事を通して学んでおくことをおすすめします。
DataFrameを理解すればPandasを使うのがぐっと楽になるはずです。
DataFrameとは
DataFrame
はざっくり言うと二次元配列に相当します。一次元配列はSeries
でしたね。Series
については以下の記事を参考にしてください。
pandasの最も基本的な構造Seriesについて(基礎的な知識から活用まで) /features/pandas-series.html
Seriesをラベリングしてディクショナリのように寄せ集めるとDataFrameになります。DataFrameは公式ドキュメントで以下のように説明されています。
Two-dimensional size-mutable, potentially heterogeneous tabular data structure with labeled axes (rows and columns). Arithmetic operations align on both row and column labels. Can be thought of as a dict-like container for Series objects. The primary pandas data structure.
ここでも、最後の文で主要なデータ構造であるとの表記が見られます。これを理解しないとPandasでは何もできないに等しいと行っても過言ではありません。英文の内容を見ると、DataFrameオブジェクトというのは
- 2次元の大きさが変更できる表形式データである
- 列ごとに様々な形式のデータが格納される。
- 各次元(行と列)方向にはラベリングされた軸(axes)が存在する。
- 各次元(行と列)方向に数学的な操作を行うことができる。
- Seriesオブジェクトを辞書のような形式で格納できるオブジェクトと考えることができる。
となっています。インデックスで操作可能な、Seriesの寄せ集めという認識ですね。
DataFrameオブジェクトのAPIドキュメントを以下に掲載しておきます。
class DataFrame(data=None, index=None, columns=None, dtype=None, copy=False)
params:
パラメータ名 | 型 | 概要 |
---|---|---|
data |
(省略可能)初期値None NumPyの配列,辞書 もしくはDataFrame |
データとなる部分を指定します。辞書の中にSeries,配列,値もしくはリストに相当するオブジェクトを含むことができます。version 0.23.0以降、Python3.6以降では指定されたデータの順番が保持されます。 |
index |
Indexオブジェクト もしくは配列に相当するもの |
(省略可能)初期値None 行ごとのラベルを指定します。Noneの場合、RangeIndexによって0から順にラベリングします。 |
columns |
Indexオブジェクト もしくは配列に相当するもの |
(省略可能)初期値None 列ごとのラベルを指定します。Noneの場合、RangeIndexによって0から順にラベリングします。 |
dtype |
データ型 | (省略可能)初期値None 使用するデータ型を指定します。1つのデータ型しか指定できません。 |
copy |
bool値 | (省略可能)初期値False オブジェクトを生成する際、コピーを生成するかどうかを指定します。 |
実際に使ってみる
それでは実際に使っていきます。
DataFrameオブジェクトの生成
DataFrameオブジェクトのdata
部分を指定する方法は数多く存在します。
- 2次元配列
- 配列、リスト、またはタプルによって構成される辞書
- NumPyの配列
- Seriesを含む辞書
- 複数の辞書からなる辞書
- 辞書のリストもしくはSeriesのリスト
- リストやタプルからなるリスト
- 他のDataFrame
ざっと羅列されただけではイメージがわきにくいと思うので1つ1つやってみます。また、PandasのDataFrameはJupyter上ではHTMLでレイアウトされた見やすい形に整形されます。
Jupyter Notebookはデータ分析においては非常に有用なツールです。以下の記事で詳しく解説していますのでぜひ参考にしてみてください。
データ分析の必需品「Jupyter Notebook」の魅力とは /machine_learning/2016/12/13/jupyter_notebook.html
2次元配列
2次元配列からDataFrameを生成してみます。
In [1]: import pandas as pd
In [2]: twod_array = [[0,1,2],[3,4,5]]
In [3]: pd.DataFrame(twod_array)
Out[3]:
0 1 2
0 0 1 2
1 3 4 5
配列、リスト、またはタプルによって構成される辞書
複雑な表現をしていますが、いろんなものを辞書表記によって含めることができるという意味です。
In [4]: dic_arr = {'a':[1,2,3],'b':[4,5,6]}
In [5]: pd.DataFrame(dic_arr)
Out[5]:
a b
0 1 4
1 2 5
2 3 6
In [6]: list_arr = {'a': ['Tokyo','Kanagawa','Chiba'],'b':['Saitama','Tochigi','
...: Ibaraki']}
In [7]: pd.DataFrame(list_arr)
Out[7]:
a b
0 Tokyo Saitama
1 Kanagawa Tochigi
2 Chiba Ibaraki
In [8]: tup_arr = {'a':(1,2,3),'b':(4,5,6)}
In [9]: pd.DataFrame(tup_arr)
Out[9]:
a b
0 1 4
1 2 5
2 3 6
これらの複合形式でも可能です。
In [10]: mix_dic = {'a':[1,2,3],'b':['a','b','c'],'c':(4,5,6)}
In [11]: pd.DataFrame(mix_dic)
Out[11]:
a b c
0 1 a 4
1 2 b 5
2 3 c 6
NumPyの配列
NumPyの配列を使って入れ込むことももちろん可能です。
In [4]: pd.DataFrame(numpy_array)
Out[4]:
0 1 2
0 1 2 3
1 4 5 6
Seriesを含む辞書
1次元データを扱うSeriesを辞書形式でつなげてDataFrameを作ることもできます。このとき辞書のキーがカラム名になります。
下のデータはいくつかの都道府県の人口(単位:1000人)と世帯数(単位:1000世帯)になります。(統計局ホームページ)より
In [6]: state = pd.Series(["Tokyo", "Osaka", "Aichi", "Hiroshima", "Fukuoka"])
In [7]: population = pd.Series([13515, 8839, 7483, 2844, 5102])
In [8]: num_household = pd.Series([6691, 3918, 3060, 1209, 2197])
In [9]: statistic = pd.DataFrame({
...: "state" : state,
...: "population": population,
...: "num_household":num_household
...: })
In [10]: statistic
Out[10]:
num_household population state
0 6691 13515 Tokyo
1 3918 8839 Osaka
2 3060 7483 Aichi
3 1209 2844 Hiroshima
4 2197 5102 Fukuoka
複数の辞書からなる辞書
今のと同じ内容のものを辞書同士を辞書でつなげることで実現できます。それぞれのキーを都市名にしてみます。
In [13]: statistic_dict = {
...: "population" : {"Tokyo": 13515, "Osaka": 8839, "Aichi": 7483, "Hiroshima": 28
...: 44, "Fukuoka": 5102 },
...: "num_household": {"Osaka":3918, "Aichi":3060,"Tokyo":6691, "Hiroshima":1209,
...: "Fukuoka": 2197}
...: }
In [14]: statistic_2 = pd.DataFrame(statistic_dict)
In [15]: statistic_2
Out[15]:
num_household population
Aichi 3060 7483
Fukuoka 2197 5102
Hiroshima 1209 2844
Osaka 3918 8839
Tokyo 6691 13515
この場合、1つ1つの辞書に含まれているキーの値がインデックスの値(今回だと都市名)になります。ディクショナリ形式なので、作成するときに指定した辞書のキーの順番が一致している必要はありません。(辞書には順番の概念がありません)
辞書のリストもしくはSeriesのリスト
辞書やSeriesをリストに格納してDataFrameを作成することも可能です。このとき辞書のキーはカラムになります。
In [19]: pd.DataFrame([
...: {"Tokyo": 6691, "Osaka": 3918, "Hiroshima": 1209, "Aichi": 3060, "Fukuoka"
...: : 2197} ,
...: {"Tokyo":13515, "Osaka":8839, "Hiroshima": 2844, "Aichi":7843, "Fukuoka":5
...: 102}
...: ], index=["num_household", "population"]
...: )
Out[19]:
Aichi Fukuoka Hiroshima Osaka Tokyo
num_household 3060 2197 1209 3918 6691
population 7843 5102 2844 8839 13515
In [20]: pd.DataFrame([
...: pd.Series([6691, 3918, 1209, 3060, 2197], index=["Tokyo", "Osaka", "Hirosh
...: ima", "Aichi", "Fukuoka"]),
...: pd.Series([13515, 8839, 2844, 7843, 5102], index=["Tokyo","Osaka","Hiroshi
...: ma", "Aichi", "Fukuoka"])
...: ] , index=["num_household", "population"])
Out[20]:
Tokyo Osaka Hiroshima Aichi Fukuoka
num_household 6691 3918 1209 3060 2197
population 13515 8839 2844 7843 5102
DataFrameの引数index
を指定することでインデックスを指定することができます。
リストやタプルからなるリスト
リストやタプルが連なっているリストからも作成可能です。先ほどと同じデータを使って作成してみましょう。columns
引数を使ってカラム名を指定しています。
In [24]: pd.DataFrame([
...: (6691, 3918, 1209, 3060, 2197),
...: [13515, 8839, 2844, 7834, 5102]
...: ],index=["num_household", "population"], columns=["Tokyo", "Osaka", "Hiroshima
...: ", "Aichi", "Fukuoka"])
Out[24]:
Tokyo Osaka Hiroshima Aichi Fukuoka
num_household 6691 3918 1209 3060 2197
population 13515 8839 2844 7834 5102
他のDataFrame
他のDataFrameを入れることでも作成可能です。しかも二重の入れ子構造になるのではなく、全く同じものが出てきます。
In [31]: statistic
Out[31]:
num_household population state
0 6691 13515 Tokyo
1 3918 8839 Osaka
2 3060 7483 Aichi
3 1209 2844 Hiroshima
4 2197 5102 Fukuoka
In [32]: pd.DataFrame(statistic)
Out[32]:
num_household population state
0 6691 13515 Tokyo
1 3918 8839 Osaka
2 3060 7483 Aichi
3 1209 2844 Hiroshima
4 2197 5102 Fukuoka
Masked array
masked arrayというのはある配列があり、その中でmaskをかけることで特定の値を無効(NaN値)にする配列のことです。NumPyにもmasked arrayを使うモジュールが存在しておりimport numpy.ma as ma
でインポートされることが多いです。maskはTrue
かFalse
のbool値によって成り立っており、True
=1
、False
=0
で代用することも可能です。True
だと値がマスクされ、その値は無効になります。
In [33]: import numpy.ma as ma
In [37]: x = np.arange(10).reshape(2,5)
In [38]: mask = np.random.choice([0,1],size=10).reshape(2,5)
In [39]: mask
Out[39]:
array([[1, 1, 0, 0, 0],
[0, 0, 1, 1, 1]])
In [40]: mx = ma.masked_array(x, mask=mask)
In [41]: pd.DataFrame(mx)
Out[41]:
0 1 2 3 4
0 NaN NaN 2.0 3.0 4.0
1 5.0 6.0 NaN NaN NaN
In [42]: mx
Out[42]:
masked_array(
data=[[--, --, 2, 3, 4],
[5, 6, --, --, --]],
mask=[[ True, True, False, False, False],
[False, False, True, True, True]],
fill_value=999999)
masked arrayをDataFrameに入れるとマスクされた値はNaN
値として処理されます。
dtypeでデータ型の指定
dtype引数をしていすることでデータ型を指定することができます。
In [74]: df = pd.DataFrame( {
...: 'a': [1, 1, 0],
...: 'b': [0, 1, 0]}, dtype=bool)
...:
In [75]: df
Out[75]:
a b
0 True False
1 True True
2 False False
In [76]: df = pd.DataFrame( {
...: 'a': [1, 1, 0],
...: 'b': [0, 1, 0]}, dtype=float)
...:
In [77]: df
Out[77]:
a b
0 1.0 0.0
1 1.0 1.0
2 0.0 0.0
引数copyの指定
引数copy
をTrueにしておくと、代入されたDataFrameの値の変化が反映されなくなります。
In [78]: df
Out[78]:
a b
0 1.0 0.0
1 1.0 1.0
2 0.0 0.0
In [80]: df_2 = pd.DataFrame(df, copy=False)
In [81]: df_3 = pd.DataFrame(df, copy=True)
In [82]: df["c"] = df["a"] == df["b"]
In [83]: df
Out[83]:
a b c
0 1.0 0.0 False
1 1.0 1.0 True
2 0.0 0.0 True
In [84]: df_2
Out[84]:
a b c
0 1.0 0.0 False
1 1.0 1.0 True
2 0.0 0.0 True
In [85]: df_3
Out[85]:
a b
0 1.0 0.0
1 1.0 1.0
2 0.0 0.0
欠損値の補完
DataFrameでは複数のデータ・セットが格納された時、そのインデックスとカラムに対応する値が存在しない場合が発生する可能性があります。そんなとき、DataFrameはNaN
でその値を補完します。
In [43]: num_per_house = pd.Series( # Fukuokaの値がないデータを作る(ちなみにこれは1世帯あたりの平均人数)
...: {"Tokyo": 1.99, "Osaka": 2.22, "Aichi": 2.41, "Hiroshima":2.29})
...:
In [45]: statistic_2
Out[45]:
num_household population
Aichi 3060 7483
Fukuoka 2197 5102
Hiroshima 1209 2844
Osaka 3918 8839
Tokyo 6691 13515
In [46]: statistic_2["num_per_house"] = num_per_house
In [47]: statistic_2
Out[47]:
num_household population num_per_house
Aichi 3060 7483 2.41
Fukuoka 2197 5102 NaN
Hiroshima 1209 2844 2.29
Osaka 3918 8839 2.22
Tokyo 6691 13515 1.99
データの追加方法
簡単にどうやってデータを追加するか見ていきましょう。先程の欠損値の例でやったように、新しいカラムを追加するときはDataFrame["新しいカラム名"] = データ
とすればデータを格納することができます。
格納するデータはSeries
でも辞書でも配列でも問題ありません。
In [48]: series = pd.Series([0,0,0])
In [51]: df = pd.DataFrame([[1,2,4],[3,8,9]], index=["a","b"], columns=["new", "old", "
...: now"])
In [52]: df
Out[52]:
new old now
a 1 2 4
b 3 8 9
In [53]: df["predict"] = series
In [54]: df
Out[54]:
new old now predict
a 1 2 4 NaN
b 3 8 9 NaN
値の削除
まとめてカラムを消したいときなどはPythonに組み込みで含まれているdel
コマンドを使います。
In [55]: del df["predict"]
In [56]: df
Out[56]:
new old now
a 1 2 4
b 3 8 9
値の参照
loc
やiloc
でできます。他にもカラム名を指定することでも可能です。loc
は直接カラム名やインデックス名を指定し、iloc
では場所を数字で表します。
In [61]: rand_int
Out[61]:
A B C D E
a 0 3 6 2 4
b 5 0 2 2 3
c 6 6 3 8 8
d 8 1 7 5 5
In [62]: rand_int.loc["a", "B"]
Out[62]: 3
In [63]: rand_int.iloc[0, 1]
Out[63]: 3
In [64]: rand_int["A"]
Out[64]:
a 0
b 5
c 6
d 8
Name: A, dtype: int64
In [65]: rand_int.A
Out[65]:
a 0
b 5
c 6
d 8
Name: A, dtype: int64
In [68]: rand_int.loc[:, "B"]
Out[68]:
a 3
b 0
c 6
d 1
Name: B, dtype: int64
In [69]: rand_int[2:4]
Out[69]:
A B C D E
c 6 6 3 8 8
d 8 1 7 5 5
DataFrameの属性(Attributes)
Seriesと同様、DataFrameには非常にたくさんの属性が存在します。Seiresで紹介したものと同じものがほとんどですが若干異なる部分があるので紹介します。
まずはSeriesにはないものから紹介します。
属性(Attribute) | 説明 |
---|---|
columns |
カラム名一覧を表示します |
style |
HTML形式でDataFrameを表示する際に用いるStylerを表示します。 |
この2つになります。
In [95]: df
Out[95]:
a b c
0 1.0 0.0 False
1 1.0 1.0 True
2 0.0 0.0 True
In [96]: df.columns
Out[96]: Index(['a', 'b', 'c'], dtype='object')
In [97]: df.style
Out[97]: <pandas.io.formats.style.Styler at 0x10904d6d8>
残りはSeriesのものと同一なのでSeriesのときと挙動や使い方が違うものだけ取り上げます。属性自体の数はSeriesのものよりかなり減っています。
属性(Attribute) | 説明 |
---|---|
T |
軸を入れ替えたものを返します。Seriesオブジェクトそのものを返します。 |
at |
at[インデックス]の形で使用。値を抜き出します。1つの値だけを抜き出します。 |
axes |
インデックスとなっているものを返します。 |
blocks |
(非推奨) 内部プロパティを表示します。as_blocks()で同様の表示ができる。 |
dtypes |
カラムごとのデータ型を表示します。 |
ftypes |
data部分がparse かdense のときそれを表示します。 |
iat |
インデックスの値に関係なく、iat[i]でi番目の値にアクセスできます。 |
iloc |
インデックスの値ではなく番号指定で目的の値にアクセスできます。複数の値の指定も可能。 |
index |
インデックスに使われているオブジェクトを返します。 |
ix |
(非推奨) アイテムの位置を指定することによってその値を表示します。まぎらわしさがあったのでilocとlocを使うことが推奨されています。 |
loc |
インデックスの値を指定することで該当する値を指定します。loc[インデックス]の形で使います。 |
ndim |
次元数を返します。Seriesの場合は1です。 |
shape |
data部分の形状(shape)を返します。Seriesの場合は1次元配列と同様”(アイテムの個数,)”の形で返されます。 |
size |
data部分のアイテム数を返します。 |
values |
データ部分のみを返します。 |
empty |
data部分に何も指定されていなければTrueが帰ってきます。 |
T
は転置になります。
In [109]: df
Out[109]:
a b c
0 1.0 0.0 False
1 1.0 1.0 True
2 0.0 0.0 True
In [110]: df.T
Out[110]:
0 1 2
a 1 1 0
b 0 1 0
c False True True
at
で値を1つだけ抽出できます。インデックスとカラム名の組で指定します。
In [111]: df.at[(0, "b")]
Out[111]: 0.0
axes
でカラム名とインデックスになっているオブジェクトを返します。
In [112]: df.axes
Out[112]: [RangeIndex(start=0, stop=3, step=1), Index(['a', 'b', 'c'], dtype='object')]
dtype
がなくなり、dtypes
だけになります。カラムごとのデータ型を示します。
In [117]: df.dtypes
Out[117]:
a float64
b float64
c bool
dtype: object
ftype
がなくなり、ftypes
でカラムごとに密(dense)か疎(sparse)かをかえします。
In [120]: df.ftypes
Out[120]:
a float64:dense
b float64:dense
c bool:dense
dtype: object
iat
で値を1つだけ抜き出します。場所を数字で指定します。
In [124]: df.iat[0,1]
Out[124]: 0.0
iloc
だとスライス表記が使えます。
In [125]: df.iloc[0,:2]
Out[125]:
a 1
b 0
Name: 0, dtype: object
loc
でカラム名を指定して値を抜き出します。
In [131]: df.loc[:, "a"]
Out[131]:
0 1.0
1 1.0
2 0.0
Name: a, dtype: float64
In [132]: df.loc[1, "a"]
Out[132]: 1.0
values
でNumPyで使われる2次元配列が返されます。
In [138]: df.values
Out[138]:
array([[1.0, 0.0, False],
[1.0, 1.0, True],
[0.0, 0.0, True]], dtype=object)
まとめ
今回はPandasのDataFrameについての紹介と簡単な使い方、そして属性(Attributes)について紹介しました。DataFrameがそもそも何なのかこの記事を通して理解していただけたなら幸いです。
PandasではDataFrameにデータを格納しこれに対し様々な操作を行うことでデータ整形を行います。
自分が普段どんなオブジェクトを使ってどんな操作を施しているのかを理解できるようになるとコードを書くスピードも格段に上がると思いますので、ぜひ自分なりに色々調べてみてください。
参考
- The numpy.ma module -NumPy v1.13 Manual
- Python for Data Analysis 2nd edition –Wes McKinney(書籍)
- pandas.DataFrame -Pandas-0.23.1 documentation