pandas.DataFrameとSeriesを相互に変換
pandasのDataFrame
とSeries
を相互に変換する方法を説明する。
便宜上「変換」という言葉を使っているが、実際は、Series
からDataFrame
を生成したり、DataFrame
の列や行をSeries
として取得したりする処理となる。
最後に説明するように、元のオブジェクトと生成・取得したオブジェクトがメモリを共有し、一方の要素を変更すると他方の要素も変更される場合があるので注意。
DataFrame
およびSeries
をNumPy配列ndarray
やPython組み込みのリストlist
と相互に変換する方法については以下の記事を参照。
本記事のサンプルコードのpandasのバージョンは以下の通り。バージョンによって仕様が異なる可能性があるので注意。
import pandas as pd
print(pd.__version__)
# 2.1.4
SeriesをDataFrameに変換
Series
をDataFrame
に変換するには、Series
のto_frame()
メソッドかコンストラクタpd.DataFrame()
を使う。
to_frame()
to_frame()
メソッドは呼び出し元のSeries
を列とするDataFrame
を返す。第一引数に列名を指定できる。
s = pd.Series([0, 1, 2], index=['A', 'B', 'C'])
print(s)
# A 0
# B 1
# C 2
# dtype: int64
print(s.to_frame())
# 0
# A 0
# B 1
# C 2
print(s.to_frame('X'))
# X
# A 0
# B 1
# C 2
Series
にname
属性が設定されているとname
属性が列名となる。to_frame()
の第一引数を指定すると第一引数が優先される。
s_name = pd.Series([0, 1, 2], index=['A', 'B', 'C'], name='X')
print(s_name)
# A 0
# B 1
# C 2
# Name: X, dtype: int64
print(s_name.to_frame())
# X
# A 0
# B 1
# C 2
print(s_name.to_frame('Y'))
# Y
# A 0
# B 1
# C 2
pd.DataFrame()
コンストラクタpd.DataFrame()
にSeries
を渡すとSeries
を列とするDataFrame
、Series
を要素とするリストを渡すとSeries
を行とするDataFrame
が生成される。
s = pd.Series([0, 1, 2], index=['A', 'B', 'C'])
print(s)
# A 0
# B 1
# C 2
# dtype: int64
print(pd.DataFrame(s))
# 0
# A 0
# B 1
# C 2
print(pd.DataFrame([s]))
# A B C
# 0 0 1 2
Series
のname
属性が設定されているとname
属性が列名・行名となる。
s_name = pd.Series([0, 1, 2], index=['A', 'B', 'C'], name='X')
print(s_name)
# A 0
# B 1
# C 2
# Name: X, dtype: int64
print(pd.DataFrame(s_name))
# X
# A 0
# B 1
# C 2
print(pd.DataFrame([s_name]))
# A B C
# X 0 1 2
複数のSeriesからDataFrameを生成
複数のSeries
からDataFrame
を生成することも可能。以下の例では2つSeries
の場合を示すが、3つ以上のSeries
でも同様に処理できる。
コンストラクタpd.DataFrame()
かpd.concat()
関数を使う
インデックスが共通の場合
コンストラクタpd.DataFrame()
を使う例は以下の通り。異なるデータ型dtype
のSeries
を行とする場合は暗黙の型変換が行われるので注意。
s1 = pd.Series([0, 1, 2], index=['A', 'B', 'C'])
s2 = pd.Series([0.0, 0.1, 0.2], index=['A', 'B', 'C'])
print(pd.DataFrame({'col1': s1, 'col2': s2}))
# col1 col2
# A 0 0.0
# B 1 0.1
# C 2 0.2
print(pd.DataFrame([s1, s2]))
# A B C
# 0 0.0 1.0 2.0
# 1 0.0 0.1 0.2
pd.concat()
関数を利用する方法もある。
print(pd.concat([s1, s2], axis=1))
# 0 1
# A 0 0.0
# B 1 0.1
# C 2 0.2
元のSeries
にname
属性が設定されていると以下のようになる。コンストラクタに辞書で指定する場合は明示的に列名を指定する必要がある。
s1_name = pd.Series([0, 1, 2], index=['A', 'B', 'C'], name='X')
s2_name = pd.Series([0.0, 0.1, 0.2], index=['A', 'B', 'C'], name='Y')
print(pd.DataFrame({s1_name.name: s1_name, s2_name.name: s2_name}))
# X Y
# A 0 0.0
# B 1 0.1
# C 2 0.2
print(pd.DataFrame([s1_name, s2_name]))
# A B C
# X 0.0 1.0 2.0
# Y 0.0 0.1 0.2
print(pd.concat([s1_name, s2_name], axis=1))
# X Y
# A 0 0.0
# B 1 0.1
# C 2 0.2
インデックスが異なる場合
DataFrame
はSeries
のインデックスを基準に生成される。Series
が異なるインデックスを持つ場合は欠損値NaN
が生じる。
s1 = pd.Series([0, 1, 2], index=['A', 'B', 'C'])
s3 = pd.Series([0.1, 0.2, 0.3], index=['B', 'C', 'D'])
print(pd.DataFrame({'col1': s1, 'col3': s3}))
# col1 col3
# A 0.0 NaN
# B 1.0 0.1
# C 2.0 0.2
# D NaN 0.3
print(pd.DataFrame([s1, s3]))
# A B C D
# 0 0.0 1.0 2.0 NaN
# 1 NaN 0.1 0.2 0.3
print(pd.concat([s1, s3], axis=1))
# 0 1
# A 0.0 NaN
# B 1.0 0.1
# C 2.0 0.2
# D NaN 0.3
pandasにおける欠損値の処理については以下の記事を参照。
pd.concat()
でjoin='inner'
とすると共通するインデックスのみが残る。
print(pd.concat([s1, s3], axis=1, join='inner'))
# 0 1
# B 1 0.1
# C 2 0.2
インデックスを変更するにはset_axis()
などを使う。インデックスを揃えることが可能。
print(s3.set_axis(s1.index))
# A 0.1
# B 0.2
# C 0.3
# dtype: float64
print(pd.DataFrame({'col1': s1, 'col3': s3.set_axis(s1.index)}))
# col1 col3
# A 0 0.1
# B 1 0.2
# C 2 0.3
インデックスを無視したい場合、Series
のvalues
属性でNumPy配列ndarray
として指定する方法がある。pd.concat()
ではエラーになるので注意。
print(s1.values)
# [0 1 2]
print(type(s1.values))
# <class 'numpy.ndarray'>
print(pd.DataFrame({'col1': s1.values, 'col3': s3.values}))
# col1 col3
# 0 0 0.1
# 1 1 0.2
# 2 2 0.3
print(pd.DataFrame([s1.values, s3.values]))
# 0 1 2
# 0 0.0 1.0 2.0
# 1 0.1 0.2 0.3
# print(pd.concat([s1.values, s3.values], axis=1))
# TypeError: cannot concatenate object of type '<class 'numpy.ndarray'>'; only Series and DataFrame objs are valid
pd.DataFrame()
の引数index
, columns
で任意の行名・列名を指定できる。
print(pd.DataFrame([s1.values, s3.values], index=['X', 'Y'], columns=['A', 'B', 'C']))
# A B C
# X 0.0 1.0 2.0
# Y 0.1 0.2 0.3
要素数が異なる場合
要素数が異なるSeries
の場合も、インデックスindex
を基準にDataFrame
が生成される。足りない分はNaN
となる。
s1 = pd.Series([0, 1, 2], index=['A', 'B', 'C'])
s4 = pd.Series([0.1, 0.3], index=['B', 'D'])
print(pd.DataFrame({'col1': s1, 'col4': s4}))
# col1 col4
# A 0.0 NaN
# B 1.0 0.1
# C 2.0 NaN
# D NaN 0.3
print(pd.DataFrame([s1, s4]))
# A B C D
# 0 0.0 1.0 2.0 NaN
# 1 NaN 0.1 NaN 0.3
print(pd.concat([s1, s4], axis=1))
# 0 1
# A 0.0 NaN
# B 1.0 0.1
# C 2.0 NaN
# D NaN 0.3
print(pd.concat([s1, s4], axis=1, join='inner'))
# 0 1
# B 1 0.1
上述のように、インデックスを変更するにはset_axis()
などを使う。任意のインデックスを設定できる。
print(pd.DataFrame({'col1': s1, 'col4': s4.set_axis(['A', 'B'])}))
# col1 col4
# A 0 0.1
# B 1 0.3
# C 2 NaN
values
属性(ndarray
)に対する振る舞いはコンストラクタへの指定方法によって異なる。
# print(pd.DataFrame({'col1': s1.values, 'col4': s4.values}))
# ValueError: All arrays must be of the same length
print(pd.DataFrame([s1.values, s4.values]))
# 0 1 2
# 0 0.0 1.0 2.0
# 1 0.1 0.3 NaN
DataFrameをSeriesに変換
[]
によるインデックス指定やloc[]
, iloc[]
でDataFrame
の行・列をSeries
として取得できる。[]
やloc[]
, iloc[]
についての詳細は以下の記事を参照。
DataFrameの列をSeriesとして取得
[]
またはloc[]
で列名、iloc[]
で列番号をスカラー値で指定すると、その列をSeries
として取得できる。
df = pd.DataFrame({'col0': [0, 1, 2], 'col1': [3, 4, 5], 'col2': [6, 7, 8]},
index=['row0', 'row1', 'row2'])
print(df)
# col0 col1 col2
# row0 0 3 6
# row1 1 4 7
# row2 2 5 8
print(df['col0'])
# row0 0
# row1 1
# row2 2
# Name: col0, dtype: int64
print(df.loc[:, 'col0'])
# row0 0
# row1 1
# row2 2
# Name: col0, dtype: int64
print(df.iloc[:, 0])
# row0 0
# row1 1
# row2 2
# Name: col0, dtype: int64
loc[]
やiloc[]
では、リストやスライスで任意の行の要素のみを選択することも可能。
print(df.iloc[[0, 2], 0])
# row0 0
# row2 2
# Name: col0, dtype: int64
print(df.iloc[:2, 0])
# row0 0
# row1 1
# Name: col0, dtype: int64
リストやスライスで一列を選択するとSeries
ではなく一列のDataFrame
となるので注意。
print(df.loc[:, ['col0']])
# col0
# row0 0
# row1 1
# row2 2
print(df.iloc[:, :1])
# col0
# row0 0
# row1 1
# row2 2
DataFrameの行をSeriesとして取得
loc[]
で行名、iloc[]
で行番号をスカラー値で指定すると、その行をSeries
として取得できる。
df = pd.DataFrame({'col0': [0, 1, 2], 'col1': [3, 4, 5], 'col2': [6, 7, 8]},
index=['row0', 'row1', 'row2'])
print(df)
# col0 col1 col2
# row0 0 3 6
# row1 1 4 7
# row2 2 5 8
print(df.loc['row0', :])
# col0 0
# col1 3
# col2 6
# Name: row0, dtype: int64
print(df.iloc[0, :])
# col0 0
# col1 3
# col2 6
# Name: row0, dtype: int64
全体を指定する場合、列指定の:
は省略可能。
print(df.loc['row0'])
# col0 0
# col1 3
# col2 6
# Name: row0, dtype: int64
print(df.iloc[0])
# col0 0
# col1 3
# col2 6
# Name: row0, dtype: int64
リストやスライスで任意の列の要素のみを選択することもできる。
print(df.iloc[0, [0, 2]])
# col0 0
# col2 6
# Name: row0, dtype: int64
print(df.iloc[0, :2])
# col0 0
# col1 3
# Name: row0, dtype: int64
リストやスライスで一行を選択するとSeries
ではなく一行のDataFrame
となるので注意。
print(df.loc[['row0']])
# col0 col1 col2
# row0 0 3 6
print(df.iloc[:1])
# col0 col1 col2
# row0 0 3 6
データ型に注意
DataFrame
は列ごとにデータ型dtype
を保持するのに対し、Series
は全体で一つのデータ型となる。
DataFrame
の行をSeries
として取得する場合は要注意。
整数int
の列と浮動小数点数float
の列からなるDataFrame
の行をSeries
として取得すると、データ型はfloat
になる。int
の列の要素がfloat
に型変換される。
df_multi = pd.DataFrame({'col0': [0, 1, 2], 'col1': [0.0, 0.1, 0.2]},
index=['row0', 'row1', 'row2'])
print(df_multi)
# col0 col1
# row0 0 0.0
# row1 1 0.1
# row2 2 0.2
s_row = df_multi.loc['row2']
print(s_row)
# col0 2.0
# col1 0.2
# Name: row2, dtype: float64
DataFrame
に文字列などobject
の列が含まれている場合、行をSeries
として取得するとデータ型はobject
になる。
df_multi['col2'] = ['a', 'b', 'c']
print(df_multi)
# col0 col1 col2
# row0 0 0.0 a
# row1 1 0.1 b
# row2 2 0.2 c
print(df_multi.dtypes)
# col0 int64
# col1 float64
# col2 object
# dtype: object
s_row = df_multi.loc['row2']
print(s_row)
# col0 2
# col1 0.2
# col2 c
# Name: row2, dtype: object
object
では要素が様々な型を持つので各要素は元の型のまま。
print(type(s_row['col0']))
# <class 'numpy.int64'>
print(type(s_row['col1']))
# <class 'numpy.float64'>
print(type(s_row['col2']))
# <class 'str'>
ビューとコピー(メモリの共有)
DataFrame
とSeries
を相互に変換する場合、生成されたオブジェクトは元のオブジェクトのビューまたはコピーになる。ビューは元のオブジェクトとメモリを共有し、一方を変更すると他方も変更される。
SeriesをDataFrameに変換する場合
to_frame()
to_frame()
メソッドは可能な限りビューを返す。copy()
でコピーを生成可能。
s = pd.Series([0, 1], index=['A', 'B'])
df = s.to_frame()
s['A'] = 100
print(df)
# 0
# A 100
# B 1
s = pd.Series([0, 1], index=['A', 'B'])
df_copy = s.copy().to_frame()
s['A'] = 100
print(df_copy)
# 0
# A 0
# B 1
pd.DataFrame()
コンストラクタpd.DataFrame()
はデフォルトでは可能な限りビューを返す。引数copy
をTrue
とするとコピーを返す。
s = pd.Series([0, 1], index=['A', 'B'])
df = pd.DataFrame(s)
s['A'] = 100
print(df)
# 0
# A 100
# B 1
s = pd.Series([0, 1], index=['A', 'B'])
df_copy = pd.DataFrame(s, copy=True)
s['A'] = 100
print(df_copy)
# 0
# A 0
# B 1
pd.concat()
pd.concat()
はデフォルトではコピーを返す。引数copy
をFalse
とすると可能な限りビューを返す。
s1 = pd.Series([0, 1], index=['A', 'B'])
s2 = pd.Series([0.0, 0.1], index=['A', 'B'])
df = pd.concat([s1, s2], axis=1)
s1['A'] = 100
print(df)
# 0 1
# A 0 0.0
# B 1 0.1
s1 = pd.Series([0, 1], index=['A', 'B'])
s2 = pd.Series([0.0, 0.1], index=['A', 'B'])
df_copy_false = pd.concat([s1, s2], axis=1, copy=False)
s1['A'] = 100
print(df_copy_false)
# 0 1
# A 100 0.0
# B 1 0.1
なお、pd.DataFrame()
やpd.concat()
などの引数copy
が提供されている関数やメソッドでは、copy=True
とすると必ずコピーが生成されるが、copy=False
では可能な限りビューを生成するという処理になる。
copy=False
であっても、メモリレイアウトによってはビューが生成できずにコピーが生成される。必ずビューが生成されるとは限らないので注意。
DataFrameをSeriesに変換する場合
DataFrame
の行や列をSeries
として取得すると、基本的にはSeries
は元のDataFrame
のビューとなる。
df = pd.DataFrame({'col0': [0, 1, 2], 'col1': [3, 4, 5], 'col2': [6, 7, 8]},
index=['row0', 'row1', 'row2'])
print(df)
# col0 col1 col2
# row0 0 3 6
# row1 1 4 7
# row2 2 5 8
s = df['col0']
s['row0'] = 10
print(s)
# row0 10
# row1 1
# row2 2
# Name: col0, dtype: int64
print(df)
# col0 col1 col2
# row0 10 3 6
# row1 1 4 7
# row2 2 5 8
別々に扱いたい場合はcopy()
でコピーを生成する。
s_copy = df['col1'].copy()
s_copy['row0'] = 100
print(s_copy)
# row0 100
# row1 4
# row2 5
# Name: col1, dtype: int64
print(df)
# col0 col1 col2
# row0 10 3 6
# row1 1 4 7
# row2 2 5 8
リストを使って特定の要素のみを取り出す場合はビューではなくコピーが生成される。
s_list = df.loc[['row0', 'row2'], 'col2']
s_list['row0'] = 1000
print(s_list)
# row0 1000
# row2 8
# Name: col2, dtype: int64
print(df)
# col0 col1 col2
# row0 10 3 6
# row1 1 4 7
# row2 2 5 8
loc[]
やiloc[]
などでDataFrame
の一部を選択して新たなDataFrame
を生成する場合は、指定方法によってビューが生成されるかコピーが生成されるかが異なる。