pandas.DataFrameの構造とその作成方法
pandas.DataFrame
は二次元の表形式のデータ(テーブルデータ)を表す、pandasの基本的な型。
ここでは、はじめにpandas.DataFrame
の構造と基本操作について説明し、そのあとでコンストラクタpandas.DataFrame()
による作成方法およびファイルからの読み込み方法について説明する。
一次元データであるpandas.Series
からpandas.DataFrame
を生成する方法については以下の記事を参照。
本記事のサンプルコードのpandasのバージョンは以下の通り。バージョンによって仕様が異なる可能性があるので注意。NumPyもインポートしている。
import pandas as pd
import numpy as np
print(pd.__version__)
# 2.0.3
pandas.DataFrameの構造
3つの構成要素: values, columns, index
DataFrame
は、データ本体であるvalues
、列名(列ラベル)columns
、行名(行ラベル)index
の3つの要素から構成されている。
最もシンプルなDataFrame
は以下のようなもの。DataFrame
の作成については後で説明するので、ここでは特に気にしなくてよい。
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
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
columns
とindex
はここでは特に設定していないためRangeIndex
型で0始まりの連番となる。tolist()
メソッドでリスト化できる。
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'>
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
型。これもtolist()
メソッドでリスト化できる。
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'>
print(df.columns.tolist())
# ['col_0', 'col_1', 'col_2', 'col_3']
print(type(df.columns.tolist()))
# <class 'list'>
Index
型のcolumns
とindex
は基本的には行・列の名前(ラベル)が格納された配列だと考えてよく、インデックス[]
で要素を取得できる。ただし、通常のリストやNumPy配列ndarray
と異なり、別の値を代入して変更することはできない。
print(df.columns[0])
# col_0
# df.columns[0] = 'Col_0'
# TypeError: Index does not support mutable operations
行名や列名を変更するには、DataFrame
のrename()
メソッドやset_axis()
メソッドなどを使う。
より発展的な内容として、階層的なインデックスを持つことも可能。以下の記事を参照。
行・列・要素の選択・抽出および変更
以下のDataFrame
を例とする。
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
行・列・要素の選択・抽出
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[行名, :]
として行を選択できる。この場合、末尾の, :
は省略可。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.at['row_0', 'col_1'])
# 1
print(type(df.at['row_0', 'col_1']))
# <class 'numpy.int64'>
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[]
, iloc[]
, at[]
, iat[]
、および、行・列の選択についての詳細は以下の記事を参照。
行名・列名や行番号・列番号ではなく、条件による抽出も可能。
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[]
やiloc[]
, at[]
, iat[]
で選択した範囲に新たな値を代入できる。
df.at['row_1', 'col_2'] = 600
print(df)
# col_0 col_1 col_2 col_3
# row_0 0 1 2 3
# row_1 4 5 600 7
# row_2 8 9 10 11
df.iloc[:, 1] = [10, 50, 90]
print(df)
# col_0 col_1 col_2 col_3
# row_0 0 10 2 3
# row_1 4 50 600 7
# row_2 8 90 10 11
列ごとに様々な型を持つDataFrame
pandas.DataFrame
は各列に異なる型のデータを格納できる。各列のデータ型はdtypes
属性で確認できる。
df_multi = pd.DataFrame({'col_0': [0, 1, 2],
'col_1': [0.0, 0.1, 0.2],
'col_2': ['A', 'B', 'C']})
print(df_multi)
# col_0 col_1 col_2
# 0 0 0.0 A
# 1 1 0.1 B
# 2 2 0.2 C
print(df_multi.dtypes)
# col_0 int64
# col_1 float64
# col_2 object
# dtype: object
データ型は変更することも可能。以下の記事を参照。
数値だけでなく文字列や日時を含むデータを簡便に扱えるのはpandas.DataFrame
の大きな利点である。
- 関連記事: pandasで特定の文字列を含む行を抽出(完全一致、部分一致)
- 関連記事: pandasの文字列メソッドで置換や空白削除などの処理を行う
- 関連記事: pandasで日付・時間の列を処理(文字列変換、年月日抽出など)
コンストラクタpandas.DataFrame()の基本的な使い方
コンストラクタpandas.DataFrame()
では、第一引数data
にデータ本体となるリストや配列、辞書などを指定する。ここでは例として二次元のNumPy配列ndarray
を使う。他の例については後述。
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
上の例のように、デフォルトでは列名columns
と行名index
は連番の数値となる。引数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
デフォルトでは、各列のデータ型は自動的に選択される。引数dtype
に任意のデータ型を指定することも可能。
print(pd.DataFrame(np.arange(9).reshape(3, 3),
dtype=float))
# 0 1 2
# 0 0.0 1.0 2.0
# 1 3.0 4.0 5.0
# 2 6.0 7.0 8.0
引数dtype
に指定できるのは単一のデータ型のみで、すべての列がその型となる。各列に異なるデータ型を指定するには生成後にastype()
を使う。
二次元配列・リストからDataFrameを作成
コンストラクタpandas.DataFrame()
の第一引数data
に二次元のNumPy配列ndarray
を指定すると、その配列をvalues
とするpandas.DataFrame
が生成される。
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),
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
二次元のリスト(リストのリスト)でもよい。
print(pd.DataFrame([[0, 1, 2], [3, 4, 5], [6, 7, 8]],
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
格納されたリストの要素数がバラバラの場合は、足りない部分が欠損値NaN
となる。
print(pd.DataFrame([[0, 1, 2], [3, 4, 5], [6, 7, 8, 9, 10]],
columns=['col_0', 'col_1', 'col_2', 'col_3', 'col_4'],
index=['row_0', 'row_1', 'row_2']))
# col_0 col_1 col_2 col_3 col_4
# row_0 0 1 2 NaN NaN
# row_1 3 4 5 NaN NaN
# row_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)},
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: All arrays must be of the same length
各行に配列・リストを割り当てる
複数のリストや配列を列ではなく行に割り当てるには、.T
で転置する方法がある。上述のように各列に配列やリストを割り当ててpandas.DataFrame
を生成してから転置する。
print(pd.DataFrame({'row_0': [0, 1, 2],
'row_1': np.arange(3, 6),
'row_2': (6, 7, 8)},
index=['col_0', 'col_1', 'col_2']).T)
# col_0 col_1 col_2
# row_0 0 1 2
# row_1 3 4 5
# row_2 6 7 8
pandas.DataFrame.from_dict()
で引数orient='index'
とする方法もある。第一引数に辞書を指定する。
print(pd.DataFrame.from_dict({'row_0': [0, 1, 2],
'row_1': np.array([3, 4, 5]),
'row_2': [6, 7, 8]},
orient='index',
columns=['col_0', 'col_1', 'col_2']))
# col_0 col_1 col_2
# row_0 0 1 2
# row_1 3 4 5
# row_2 6 7 8
辞書のリスト・辞書からDataFrameを作成
コンストラクタpandas.DataFrame()
の第一引数data
には辞書のリストも指定できる。
それぞれの辞書のキーが列名となる。
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}],
index=['row_0', 'row_1', 'row_2']))
# col_0 col_1 col_2 col_3
# row_0 0 1.0 2 NaN
# row_1 3 NaN 5 100.0
# row_2 6 7.0 8 NaN
辞書を値とする辞書でもよい。外側の辞書のキーが列名、内側の辞書のキーが行名となる。
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.json_normalize()
を使うとネストした辞書などのより複雑な構造にも対応できる。以下の記事を参照。
またJSON形式の文字列やファイルを直接読み込むこともできる。
CSVファイルやExcelファイルから読み込み
実務上はコンストラクタでpandas.DataFrame
を生成することはほとんどなく、ファイルから読み込むことが多い。
上述のJSONファイルのほか、CSVファイルやExcelファイル(xls
, xlsx
)をpandas.DataFrame
として読み込むことができる。