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はTrueFalseのbool値によって成り立っており、True=1False=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

値の参照

locilocでできます。他にもカラム名を指定することでも可能です。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部分がparsedenseのときそれを表示します。
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にデータを格納しこれに対し様々な操作を行うことでデータ整形を行います。

自分が普段どんなオブジェクトを使ってどんな操作を施しているのかを理解できるようになるとコードを書くスピードも格段に上がると思いますので、ぜひ自分なりに色々調べてみてください。

参考