pandas.DataFrameの構造とその作成方法
pandas.DataFrame
は二次元の表形式のデータ(テーブルデータ)を表す、pandasの基本的な型。
ここではまずはじめにpandas.DataFrame
の構造と基本操作について説明する。
pandas.DataFrame
の構造- 3つの構成要素:
values
,columns
,index
- 列名
columns
, 行名index
の変更 - 行・列・要素の選択・抽出および変更
- 列ごとに様々な型を持つ
DataFrame
- 3つの構成要素:
そのあとでコンストラクタpandas.DataFrame()
による作成方法およびファイルからの読み込み方法について説明する。
- 二次元配列・リストから
DataFrame
を作成 - 複数の一次元配列・リストから
DataFrame
を作成 - 辞書のリスト・辞書から
DataFrame
を作成 - CSVファイルやExcelファイルから読み込み
pandas関連の記事は以下のリンクから。
pandas.DataFrameの構造
3つの構成要素: values, columns, index
DataFrame
はvalues
, columns
, index
の3つの要素から構成されている。
その名前の通り、values
は実際のデータの値、columns
は列名(列ラベル)、index
は行名(行ラベル)。
最もシンプルなDataFrame
は以下のようなもの。なおDataFrame
の作成については後述。ここでは特に気にしなくてよい。
import pandas as pd
import numpy as np
df_simple = pd.DataFrame(np.arange(12).reshape(3, 4))
print(df_simple)
# 0 1 2 3
# 0 0 1 2 3
# 1 4 5 6 7
# 2 8 9 10 11
values
, columns
, index
はそのままDataFrame
の属性としてアクセスできる。
values
はNumPy配列ndarray
。
print(df_simple.values)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(type(df_simple.values))
# <class 'numpy.ndarray'>
columns
とindex
はここでは特に設定していないためRangeIndex
型で単純な連番となっている。
print(df_simple.columns)
# RangeIndex(start=0, stop=4, step=1)
print(type(df_simple.columns))
# <class 'pandas.core.indexes.range.RangeIndex'>
print(df_simple.index)
# RangeIndex(start=0, stop=3, step=1)
print(type(df_simple.index))
# <class 'pandas.core.indexes.range.RangeIndex'>
RangeIndex
型はlist()
やtolist()
メソッドでリスト化できる。
print(list(df_simple.columns))
# [0, 1, 2, 3]
print(type(list(df_simple.columns)))
# <class 'list'>
print(df_simple.columns.tolist())
# [0, 1, 2, 3]
print(type(df_simple.columns.tolist()))
# <class 'list'>
columns
, index
を設定することで、各列・各行に任意の名前(ラベル)をつけることができる。
df = pd.DataFrame(np.arange(12).reshape(3, 4),
columns=['col_0', 'col_1', 'col_2', 'col_3'],
index=['row_0', 'row_1', 'row_2'])
print(df)
# col_0 col_1 col_2 col_3
# row_0 0 1 2 3
# row_1 4 5 6 7
# row_2 8 9 10 11
このときのcolumns
とindex
はIndex
型。
print(df.columns)
# Index(['col_0', 'col_1', 'col_2', 'col_3'], dtype='object')
print(type(df.columns))
# <class 'pandas.core.indexes.base.Index'>
print(df.index)
# Index(['row_0', 'row_1', 'row_2'], dtype='object')
print(type(df.index))
# <class 'pandas.core.indexes.base.Index'>
Index
型もlist()
やtolist()
メソッドでリスト化できる。
print(df.columns.tolist())
# ['col_0', 'col_1', 'col_2', 'col_3']
print(type(df.columns.tolist()))
# <class 'list'>
列名columns, 行名indexの変更
columns
とindex
のIndex
型は様々なメソッドを持っているが、基本的には行・列の名前(ラベル)が格納された配列だと考えてよい。
インデックス[]
で要素を取得できるが、通常のリストやNumPy配列ndarray
と異なり、新たな値を代入して要素を変更することができない。
print(df.columns[0])
# col_0
# df.columns[0] = 'Col_0'
# TypeError: Index does not support mutable operations
columns
, index
属性に新しいリストや配列を設定することは可能。このときは通常のリストやndarray
をそのまま使える。Index
型に変更したりする必要はない。
df.columns = ['Col_0', 'Col_1', 'Col_2', 'Col_3']
df.index = ['Row_0', 'Row_1', 'Row_2']
print(df)
# Col_0 Col_1 Col_2 Col_3
# Row_0 0 1 2 3
# Row_1 4 5 6 7
# Row_2 8 9 10 11
一方、values
属性は変更不可。新たな配列などを直接設定することはできない。values
の要素の変更については次に説明する。
# df.values = np.arange(12).reshape(3, 4) * 10
# AttributeError: can't set attribute
上述のように、columns
とindex
の要素をインデックスで指定して新たな値を代入することはできないが、rename()
メソッドを使うと個別に変更することができる。以下の記事を参照。
より発展的な内容として、階層的なインデックスを持つことも可能。以下の記事を参照。
行・列・要素の選択・抽出および変更
上述のようにDataFrame
のvalues
属性は変更できないが、行や列、要素を選択して新たな値を代入することはできる。
行・列・要素の選択・抽出
df['列名']
で列を選択できる。選択した列は一次元データを表すpandas.Series
となる。
print(df['Col_1'])
# Row_0 1
# Row_1 5
# Row_2 9
# Name: Col_1, dtype: int64
print(type(df['Col_1']))
# <class 'pandas.core.series.Series'>
属性のようにdf.列名
として列を選択することも可能。ただし、列名がDataFrame
のメソッドと同じ場合は使えないので注意が必要。
print(df.Col_1)
# Row_0 1
# Row_1 5
# Row_2 9
# Name: Col_1, dtype: int64
より一般的な行・列・要素の選択にはloc[]
を使う。loc[行名, 列名]
のように範囲を指定する。スライス:
も使えるため、例えば:
で全行を指定すると列が選択できる。
print(df.loc[:, 'Col_1'])
# Row_0 1
# Row_1 5
# Row_2 9
# Name: Col_1, dtype: int64
行の選択も可能。
print(df.loc['Row_1', :])
# Col_0 4
# Col_1 5
# Col_2 6
# Col_3 7
# Name: Row_1, dtype: int64
print(type(df.loc['Row_1', :]))
# <class 'pandas.core.series.Series'>
この場合、末尾の, :
は省略できる。ndarray
のスライスと同様の考え方。
print(df.loc['Row_1'])
# Col_0 4
# Col_1 5
# Col_2 6
# Col_3 7
# Name: Row_1, dtype: int64
リストでの指定もできる。複数行・複数列を指定した場合はpandas.DataFrame
となる。
print(df.loc[['Row_0', 'Row_2'], ['Col_1', 'Col_3']])
# Col_1 Col_3
# Row_0 1 3
# Row_2 9 11
print(type(df.loc[['Row_0', 'Row_2'], ['Col_1', 'Col_3']]))
# <class 'pandas.core.frame.DataFrame'>
単独の要素を選択したい場合はloc[]
でもよいが、at[]
のほうがより高速。
print(df.loc['Row_0', 'Col_1'])
# 1
print(type(df.loc['Row_0', 'Col_1']))
# <class 'numpy.int64'>
print(df.at['Row_0', 'Col_1'])
# 1
at[]
は単独の要素の選択に特化しており、loc[]
のようにスライスやリストでの範囲選択はできない。
# print(df.at[:, 'Col_1'])
# TypeError: 'slice(None, None, None)' is an invalid key
loc[]
, at[]
は行名・列名で位置を指定するが、行番号・列番号で指定するiloc[]
, iat[]
もある。
print(df.iloc[[0, 2], [1, 3]])
# Col_1 Col_3
# Row_0 1 3
# Row_2 9 11
print(df.iat[0, 1])
# 1
loc[]
, at[]
や行・列の選択についての詳細は以下の記事を参照。
行名・列名や行番号・列番号ではなく、条件による抽出も可能。
print(df.query('Col_0 > 2'))
# Col_0 Col_1 Col_2 Col_3
# Row_1 4 5 6 7
# Row_2 8 9 10 11
詳細は以下の記事を参照。
行・列・要素の変更
loc[]
などで選択した範囲には新たな値を代入できる。
df.loc[:, 'Col_1'] = [10, 50, 90]
print(df)
# Col_0 Col_1 Col_2 Col_3
# Row_0 0 10 2 3
# Row_1 4 50 6 7
# Row_2 8 90 10 11
全体を選択してまるごと変更することも可能。
df.loc[:] = np.arange(12).reshape(3, 4) * 100
print(df)
# Col_0 Col_1 Col_2 Col_3
# Row_0 0 100 200 300
# Row_1 400 500 600 700
# Row_2 800 900 1000 1100
選択範囲と同じ形状でないとエラーになる。
# df.loc[:, 'Col_1'] = [10, 50, 90, 130]
# ValueError: Length of values does not match length of index
# df.loc[:] = np.arange(16).reshape(4, 4) * 100
# ValueError: cannot set using a slice indexer with a different length than the value
列ごとに様々な型を持つDataFrame
pandas.DataFrame
は各列に異なる型のデータを格納できる。
df_multi = pd.read_csv('data/src/sample_pandas_normal.csv', index_col=0)
print(df_multi)
# age state point
# name
# Alice 24 NY 64
# Bob 42 CA 92
# Charlie 18 CA 70
# Dave 68 TX 70
# Ellen 24 CA 88
# Frank 30 NY 57
各列のデータ型はdtypes
属性で確認できる。
print(df_multi.dtypes)
# age int64
# state object
# point int64
# dtype: object
データ型は変更することも可能。以下の記事を参照。
数値だけでなく文字列や日時を含むデータを簡便に扱えるのはnumpy.ndarray
に対するpandas.DataFrame
の大きな利点である。
- 関連記事: pandasで特定の文字列を含む行を抽出(完全一致、部分一致)
- 関連記事: pandasの文字列メソッドで置換や空白削除などの処理を行う
- 関連記事: pandasで日付・時間の列を処理(文字列変換、年月日抽出など)
二次元配列・リストからDataFrameを作成
コンストラクタpandas.DataFrame()
の第一引数data
に二次元のNumPy配列ndarray
を指定すると、その配列がそのままvalues
となるpandas.DataFrame
が生成される。デフォルトではcolumns
, index
は連番の数値となる。
import pandas as pd
import numpy as np
print(np.arange(9).reshape(3, 3))
# [[0 1 2]
# [3 4 5]
# [6 7 8]]
print(pd.DataFrame(np.arange(9).reshape(3, 3)))
# 0 1 2
# 0 0 1 2
# 1 3 4 5
# 2 6 7 8
コンストラクタpandas.DataFrame()
の引数columns
, index
に任意の列名・行名を指定できる。
print(pd.DataFrame(np.arange(9).reshape(3, 3),
columns=['col_0', 'col_1', 'col_2'],
index=['row_0', 'row_1', 'row_2']))
# col_0 col_1 col_2
# row_0 0 1 2
# row_1 3 4 5
# row_2 6 7 8
ndarray
ではなく、二次元のリスト(リストのリスト)でもOK。
print(pd.DataFrame([[0, 1, 2], [3, 4, 5], [6, 7, 8]]))
# 0 1 2
# 0 0 1 2
# 1 3 4 5
# 2 6 7 8
格納されたリストの要素数がバラバラの場合は足りない部分が欠損値NaN
となる。
print(pd.DataFrame([[0, 1, 2], [3, 4, 5], [6, 7, 8, 9, 10]]))
# 0 1 2 3 4
# 0 0 1 2 NaN NaN
# 1 3 4 5 NaN NaN
# 2 6 7 8 9.0 10.0
欠損値NaN
の扱いについては以下の記事を参照。
複数の一次元配列・リストからDataFrameを作成
コンストラクタpandas.DataFrame()
の第一引数data
には、キーを列名、値を各列の要素となるリストや配列、タプルなどとした辞書dict
を指定できる。
print(pd.DataFrame({'col_0': [0, 1, 2],
'col_1': np.arange(3, 6),
'col_2': (6, 7, 8)}))
# col_0 col_1 col_2
# 0 0 3 6
# 1 1 4 7
# 2 2 5 8
引数index
で行名を指定することももちろん可能。
print(pd.DataFrame({'col_0': [0, 1, 2],
'col_1': np.arange(3, 6),
'col_2': (6, 7, 8)},
index=['row_0', 'row_1', 'row_2']))
# col_0 col_1 col_2
# row_0 0 3 6
# row_1 1 4 7
# row_2 2 5 8
辞書の値に指定するリストや配列などの要素数が一致していないとエラーになる。上述の二次元リスト(リストのリスト)のように足りない部分が欠損値NaN
になることはない。
# print(pd.DataFrame({'col_0': [0, 1, 2, 100],
# 'col_1': np.arange(3, 6),
# 'col_2': (6, 7, 8)}))
# ValueError: arrays must all be same length
複数のリストや配列を列ではなく行にしたい場合は、.T
で転置する方法がある。
print(pd.DataFrame({'row_0': [0, 1, 2],
'row_1': np.arange(3, 6),
'row_2': (7, 8, 9)}).T)
# 0 1 2
# row_0 0 1 2
# row_1 3 4 5
# row_2 7 8 9
そのほか、pd.DataFrame.from_dict()
で引数orient='index'
とする方法もある。この場合、辞書の値は同じ型でないとエラーになるので注意。
print(pd.DataFrame.from_dict(
{'row_0': [0, 1, 2],
'row_1': [3, 4, 5],
'row_2': [6, 7, 8]},
orient='index'))
# 0 1 2
# row_0 0 1 2
# row_1 3 4 5
# row_2 6 7 8
print(pd.DataFrame.from_dict(
{'row_0': np.array([0, 1, 2]),
'row_1': np.array([3, 4, 5]),
'row_2': np.array([6, 7, 8])},
orient='index'))
# 0 1 2
# row_0 0 1 2
# row_1 3 4 5
# row_2 6 7 8
# print(pd.DataFrame.from_dict(
# {'row_0': [0, 1, 2],
# 'row_1': np.array([3, 4, 5]),
# 'row_2': [6, 7, 8]},
# orient='index'))
# TypeError: Expected list, got numpy.ndarray
辞書のリスト・辞書からDataFrameを作成
コンストラクタpandas.DataFrame()
の第一引数data
に辞書のリストを指定してもよい。
この場合、辞書のキーが列名となる。引数index
で行名を指定することも可能。
print(pd.DataFrame([{'col_0': 0, 'col_1': 1, 'col_2': 2},
{'col_0': 3, 'col_1': 4, 'col_2': 5},
{'col_0': 6, 'col_1': 7, 'col_2': 8}]))
# col_0 col_1 col_2
# 0 0 1 2
# 1 3 4 5
# 2 6 7 8
print(pd.DataFrame([{'col_0': 0, 'col_1': 1, 'col_2': 2},
{'col_0': 3, 'col_1': 4, 'col_2': 5},
{'col_0': 6, 'col_1': 7, 'col_2': 8}],
index=['row_0', 'row_1', 'row_2']))
# col_0 col_1 col_2
# row_0 0 1 2
# row_1 3 4 5
# row_2 6 7 8
それぞれの辞書のキーが一致していない場合は存在しない値が欠損値NaN
で埋められる。
print(pd.DataFrame([{'col_0': 0, 'col_1': 1, 'col_2': 2},
{'col_0': 3, 'col_2': 5, 'col_3': 100},
{'col_0': 6, 'col_1': 7, 'col_2': 8}]))
# col_0 col_1 col_2 col_3
# 0 0 1.0 2 NaN
# 1 3 NaN 5 100.0
# 2 6 7.0 8 NaN
辞書を値とする辞書でもOK。外側の辞書のキーが列名、内側の辞書のキーが行名となる。
print(pd.DataFrame({'col_0': {'row_0': 0, 'row_1': 1, 'row_2': 2},
'col_1': {'row_0': 3, 'row_2': 4, 'row_3': 5},
'col_2': {'row_0': 6, 'row_1': 7, 'row_2': 8}}))
# col_0 col_1 col_2
# row_0 0.0 3.0 6.0
# row_1 1.0 NaN 7.0
# row_2 2.0 4.0 8.0
# row_3 NaN 5.0 NaN
辞書を元にDataFrame
を作成するユースケースとして実際にありえるのはWebAPIなどで取得したデータを処理する場合。そのような場合は、pandas.io.json.json_normalize()
という関数を使うとネストした辞書などのより複雑な構造にも対応できる。以下の記事を参照。
またJSON形式の文字列やファイルを直接読み込むこともできる。
CSVファイルやExcelファイルから読み込み
実務上はコンストラクタでpandas.DataFrame
を生成することはほとんどなく、ファイルから読み込むことが多い。
上述のJSONファイルのほか、CSVファイルやExcelファイル(xls
, xlsx
)をpandas.DataFrame
として読み込むことができる。
以下の記事を参照。