note.nkmk.me

pandas.DatraFrameとSeriesを相互に変換

Date: 2020-01-26 / tags: Python, pandas

二次元データを表すpandas.DataFrameと一次元データを表すpandas.Seriesを相互に変換する方法を説明する。

便宜上「変換」という言葉を使っているが、正しくは、pandas.DataFrameの列や行をpandas.Seriesとして取得したり、単独あるいは複数のpandas.Seriesをもとにpandas.DataFrameを生成したりする処理となる。

ここでは以下の内容について説明する。

  • pandas.DataFrameからpandas.Seriesを取得
    • pandas.DataFrameの列をpandas.Seriesとして取得
    • pandas.DataFrameの行をpandas.Seriesとして取得
      • データ型に注意
  • pandas.Seriespandas.DataFrameに変換
  • 複数のpandas.Seriesからpandas.DataFrameを生成
    • インデックスが共通の場合
    • インデックスが異なる場合
    • 要素数が異なる場合
  • ビューとコピー

最後に説明するように、元のオブジェクトと取得・生成したオブジェクトがメモリを共有し、いずれかの要素を変更すると他方の要素も変更されることがある。それぞれのオブジェクトを別々に扱いたい場合は注意。

pandas.DataFrameおよびpandas.SeriesをNumPy配列numpy.ndarrayやPython組み込みのリスト型と相互に変換する方法については以下の記事を参照。

以下のサンプルコードのpandasのバージョンは0.25.3。バージョンが異なると振る舞いが異なるかもしれない。

スポンサーリンク

pandas.DataFrameからpandas.Seriesを取得

インデックス参照[]loc[](列名・行名で指定), iloc[](列番号・行番号で指定)を使ってpandas.DataFrameの一行・一列を選択すると、pandas.Seriesとして取得できる。インデックス参照やloc[], iloc[]についての詳細は以下の記事を参照。

例として、以下のpandas.DataFrameを使う。

import pandas as pd

df = pd.DataFrame({'col0': [0, 1, 2], 'col1': [0.0, 0.1, 0.2], 'col2': ['a', 'b', 'c']},
                  index=['row0', 'row1', 'row2'])
print(df)
#       col0  col1 col2
# row0     0   0.0    a
# row1     1   0.1    b
# row2     2   0.2    c

print(df.dtypes)
# col0      int64
# col1    float64
# col2     object
# dtype: object

pandas.DataFrameの列をpandas.Seriesとして取得

df[列名]で列名を指定すると、その列をpandas.Seriesとして取得できる。

s = df['col1']
print(s)
# row0    0.0
# row1    0.1
# row2    0.2
# Name: col1, dtype: float64

print(type(s))
# <class 'pandas.core.series.Series'>

print(s.dtype)
# float64

print(s.index)
# Index(['row0', 'row1', 'row2'], dtype='object')

print(s.name)
# col1

loc[]で列名、iloc[]で列番号をスカラー値で指定しても同様。pandas.Seriesとして取得できる。

print(df.loc[:, 'col1'])
# row0    0.0
# row1    0.1
# row2    0.2
# Name: col1, dtype: float64

print(df.iloc[:, 2])
# row0    a
# row1    b
# row2    c
# Name: col2, dtype: object

上の例のように行を:で全体を指定するのではなく、リストやスライスで任意の要素のみを選択することも可能。

print(df.iloc[[0, 2], 2])
# row0    a
# row2    c
# Name: col2, dtype: object

print(df.iloc[:2, 2])
# row0    a
# row1    b
# Name: col2, dtype: object

列の選択にリストやスライスを使う場合は一列分を選択してもpandas.Seriesではなくpandas.DataFrameとなるので注意。

df_only = df[['col1']]
print(df_only)
#       col1
# row0   0.0
# row1   0.1
# row2   0.2

print(type(df_only))
# <class 'pandas.core.frame.DataFrame'>

df_only2 = df.iloc[:, 1:2]
print(df_only2)
#       col1
# row0   0.0
# row1   0.1
# row2   0.2

print(type(df_only2))
# <class 'pandas.core.frame.DataFrame'>

pandas.DataFrameの行をpandas.Seriesとして取得

行を選択する場合も同様。

loc[]で行名、iloc[]で行番号をスカラー値で指定するとpandas.Seriesとして取得できる。

s_r = df.loc['row1', :]
print(s_r)
# col0      1
# col1    0.1
# col2      b
# Name: row1, dtype: object

print(type(s_r))
# <class 'pandas.core.series.Series'>

print(s_r.dtype)
# object

print(s_r.index)
# Index(['col0', 'col1', 'col2'], dtype='object')

print(s_r.name)
# row1

全体を指定する場合、列の指定の:は省略可能。

print(df.loc['row1'])
# col0      1
# col1    0.1
# col2      b
# Name: row1, dtype: object

リストやスライスで任意の要素のみを選択することもできる。

print(df.iloc[2, [0, 2]])
# col0    2
# col2    c
# Name: row2, dtype: object

行の場合と同様、リストやスライスで一行分を選択するとpandas.Seriesではなくpandas.DataFrameとなる。

df_only_r = df.iloc[[1]]
print(df_only_r)
#       col0  col1 col2
# row1     1   0.1    b

print(type(df_only_r))
# <class 'pandas.core.frame.DataFrame'>

df[スライス]で行を選択することもできるが、これで一行分を選択した場合もpandas.DataFrame

df_only_r2 = df[1:2]
print(df_only_r2)
#       col0  col1 col2
# row1     1   0.1    b

print(type(df_only_r2))
# <class 'pandas.core.frame.DataFrame'>

データ型に注意

pandas.DataFrameは列ごとにデータ型dtypeを保持するのに対し、pandas.Seriesは全体で一つのデータ型となる。

上の例ではデータ型がobjectとなっていた。

s_r = df.loc['row1', :]
print(s_r)
# col0      1
# col1    0.1
# col2      b
# Name: row1, dtype: object

objectでは要素自体は様々な型をとれるので各要素は元の型のまま。

print(s_r[0])
# 1

print(type(s_r[0]))
# <class 'numpy.int64'>

print(s_r[1])
# 0.1

print(type(s_r[1]))
# <class 'numpy.float64'>

print(s_r[2])
# b

print(type(s_r[2]))
# <class 'str'>

上の例のように元のpandas.DataFrameに文字列の列が含まれている場合はobjectとなるが、整数intや浮動小数点数floatなど数値の列のみの場合は注意が必要。

以下のように、暗黙のキャスト(型変換)が行われる。

df_n = df[['col0', 'col1']]
print(df_n)
#       col0  col1
# row0     0   0.0
# row1     1   0.1
# row2     2   0.2

print(df_n.dtypes)
# col0      int64
# col1    float64
# dtype: object

s_n_r = df_n.iloc[1]
print(s_n_r)
# col0    1.0
# col1    0.1
# Name: row1, dtype: float64

print(s_n_r[0])
# 1.0

print(type(s_n_r[0]))
# <class 'numpy.float64'>

print(s_n_r[1])
# 0.1

print(type(s_n_r[1]))
# <class 'numpy.float64'>

値が変わることはないが、例えば、キャストされた要素をintだと思ってインデックス[]に指定するとfloatに変わっていてエラーになったりすることがあるので要注意。

pandas.Seriesをpandas.DataFrameに変換

pandas.DataFrameのコンストラクタにpandas.Seriesを渡すと、pandas.Seriesを列とするpandas.DataFrameが生成される。

s = pd.Series([0, 1, 2], index=['a', 'b', 'c'])
print(s)
# a    0
# b    1
# c    2
# dtype: int64

df = pd.DataFrame(s)
print(df)
#    0
# a  0
# b  1
# c  2

print(type(df))
# <class 'pandas.core.frame.DataFrame'>

pandas.Seriesを要素とするリストを指定すると、元のpandas.Seriesが行となる。

df_ = pd.DataFrame([s])
print(df_)
#    a  b  c
# 0  0  1  2

print(type(df_))
# <class 'pandas.core.frame.DataFrame'>

pandas.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

複数のpandas.Seriesからpandas.DataFrameを生成

複数のpandas.Seriesを連結してpandas.DataFrameを生成することも可能。以下の例では2つのpandas.Seriesの場合を示すが、3つ以上でも同じ。要素を増やせばよい。

インデックスが共通の場合

上の例と同様にコンストラクタpandas.DataFrame()を使う。

s1 = pd.Series([0, 1, 2], index=['a', 'b', 'c'])
print(s1)
# a    0
# b    1
# c    2
# dtype: int64

s2 = pd.Series([0.0, 0.1, 0.2], index=['a', 'b', 'c'])
print(s2)
# a    0.0
# b    0.1
# c    0.2
# dtype: float64

print(pd.DataFrame({'col0': s1, 'col1': s2}))
#    col0  col1
# 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

上の結果から分かるように、異なるデータ型dtypepandas.Seriesを行とする場合、暗黙の型変換が行われるので注意。

pd.concat()を利用する方法もある。

print(pd.concat([s1, s2], axis=1))
#    0    1
# a  0  0.0
# b  1  0.1
# c  2  0.2

元のpandas.Seriesに属性nameが設定されている場合。

s1_name = pd.Series([0, 1, 2], index=['a', 'b', 'c'], name='X')
print(s1_name)
# a    0
# b    1
# c    2
# Name: X, dtype: int64

s2_name = pd.Series([0.0, 0.1, 0.2], index=['a', 'b', 'c'], name='Y')
print(s2_name)
# a    0.0
# b    0.1
# c    0.2
# Name: Y, dtype: float64

コンストラクタに辞書で指定する場合は明示的に列名を指定する必要がある。

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

コンストラクタにpandas.Seriesを要素とするリストを指定する場合とpd.concat()で連結する場合は、自動的にnameが列名や行名となる。

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

インデックスが異なる場合

これまでの例のように、複数のpandas.Seriesからpandas.DataFrameを生成する場合、インデックス(ラベル)indexを元にpandas.DataFrameが生成される。

異なるインデックスを持つpandas.Seriesだと、欠損値NaNが生じる。

s3 = pd.Series([0.1, 0.2, 0.3], index=['b', 'c', 'd'])
print(s3)
# b    0.1
# c    0.2
# d    0.3
# dtype: float64

print(pd.DataFrame({'col0': s1, 'col1': s3}))
#    col0  col1
# 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

インデックスを無視したい場合、pandas.Seriesvalues属性でnumpy.ndarrayを取得して使う方法がある。pd.concat()ではエラーになるので注意。

print(s1.values)
# [0 1 2]

print(type(s1.values))
# <class 'numpy.ndarray'>

print(pd.DataFrame({'col0': s1.values, 'col1': s3.values}))
#    col0  col1
# 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

pandas.Seriesnumpy.ndarrayが混在している場合の振る舞いはコンストラクタへの指定方法によって異なる。

print(pd.DataFrame({'col0': s1, 'col1': s3.values}))
#    col0  col1
# a     0   0.1
# b     1   0.2
# c     2   0.3

print(pd.DataFrame([s1, s3.values]))
#      a    b    c
# 0  0.0  1.0  2.0
# 1  NaN  NaN  NaN

コンストラクタの引数indexcolumnsに所望のインデックスを明示的に指定するほうが間違いは少ないかもしれない。

print(pd.DataFrame({'col0': s1.values, 'col1': s3.values}, index=s1.index))
#    col0  col1
# a     0   0.1
# b     1   0.2
# c     2   0.3

print(pd.DataFrame([s1.values, s3.values], columns=s1.index))
#      a    b    c
# 0  0.0  1.0  2.0
# 1  0.1  0.2  0.3

要素数が異なる場合

要素数が異なる場合もデフォルトではindexを元にpandas.DataFrameが生成される。足りない分はNaNとなる。

s4 = pd.Series([0.1, 0.2], index=['b', 'd'])
print(s4)
# b    0.1
# d    0.2
# dtype: float64

print(pd.DataFrame({'col0': s1, 'col1': s4}))
#    col0  col1
# a   0.0   NaN
# b   1.0   0.1
# c   2.0   NaN
# d   NaN   0.2

print(pd.DataFrame([s1, s4]))
#      a    b    c    d
# 0  0.0  1.0  2.0  NaN
# 1  NaN  0.1  NaN  0.2

print(pd.concat([s1, s4], axis=1, join='inner'))
#    0    1
# b  1  0.1

values属性(numpy.ndarray)に対する振る舞いはコンストラクタへの指定方法によって異なる。

# print(pd.DataFrame({'col0': s1.values, 'col1': s4.values}))
# ValueError: arrays must all be same length

print(pd.DataFrame([s1.values, s4.values]))
#      0    1    2
# 0  0.0  1.0  2.0
# 1  0.1  0.2  NaN

numpy.ndarrayをさらにPython組み込みのリストに変換して要素を加えて要素数を揃える、という方法もあるが、適当なindexを設定してから欠損値NaNを処理するほうが簡単。

s4.index = ['a', 'b']
print(s4)
# a    0.1
# b    0.2
# dtype: float64

print(pd.DataFrame({'col0': s1, 'col1': s4}))
#    col0  col1
# a     0   0.1
# b     1   0.2
# c     2   NaN

print(pd.DataFrame({'col0': s1, 'col1': s4}).fillna(100))
#    col0   col1
# a     0    0.1
# b     1    0.2
# c     2  100.0

ビューとコピー

pandas.DataFrameの行や列をpandas.Seriesとして取得する場合、pandas.Seriesは元のpandas.DataFrameのビューとなる。pandas.DataFramepandas.Seriesはメモリを共有し、いずれかの要素を変更すると他方の要素も変更される。

print(df)
#       col0  col1 col2
# row0     0   0.0    a
# row1     1   0.1    b
# row2     2   0.2    c

s = df['col0']
print(s)
# row0    0
# row1    1
# row2    2
# Name: col0, dtype: int64

df.iat[0, 0] = 100
print(df)
#       col0  col1 col2
# row0   100   0.0    a
# row1     1   0.1    b
# row2     2   0.2    c

print(s)
# row0    100
# row1      1
# row2      2
# Name: col0, dtype: int64

別々に扱いたい場合はcopy()でコピーを生成する。

s_copy = df['col1'].copy()
print(s_copy)
# row0    0.0
# row1    0.1
# row2    0.2
# Name: col1, dtype: float64

df.iat[0, 1] = 100
print(df)
#       col0   col1 col2
# row0   100  100.0    a
# row1     1    0.1    b
# row2     2    0.2    c

print(s_copy)
# row0    0.0
# row1    0.1
# row2    0.2
# Name: col1, dtype: float64

なお、リストを使って特定の要素のみを取り出すような場合はビューではなくコピーが生成されることもある。

s_l = df.loc[['row0', 'row2'], 'col2']
print(s_l)
# row0    a
# row2    c
# Name: col2, dtype: object

df.iat[0, 2] = 'XXX'
print(df)
#       col0   col1 col2
# row0   100  100.0  XXX
# row1     1    0.1    b
# row2     2    0.2    c

print(s_l)
# row0    a
# row2    c
# Name: col2, dtype: object

ビューが生成されるかコピーが生成されるかを判定する手段はないので要注意。詳細は以下の記事を参照。

pandas.Seriesからpandas.DataFrameを生成する場合も同様。

コンストラクタpd.DataFrame()はデフォルトでは可能な限りビューを返す。

print(s)
# a    0
# b    1
# c    2
# dtype: int64

df = pd.DataFrame(s)
print(df)
#    0
# a  0
# b  1
# c  2

s[0] = 100
print(s)
# a    100
# b      1
# c      2
# dtype: int64

print(df)
#      0
# a  100
# b    1
# c    2

引数copy=Trueとするとコピーが生成される。デフォルトはcopy=False

df_copy = pd.DataFrame(s, copy=True)
print(df_copy)
#      0
# a  100
# b    1
# c    2

s[1] = 100
print(s)
# a    100
# b    100
# c      2
# dtype: int64

print(df_copy)
#      0
# a  100
# b    1
# c    2

pd.concat()ではデフォルトでコピーが返される。

df_c = pd.concat([s1, s2], axis=1)
print(df_c)
#    0    1
# a  0  0.0
# b  1  0.1
# c  2  0.2

s1[0] = 100
print(s1)
# a    100
# b      1
# c      2
# dtype: int64

print(df_c)
#    0    1
# a  0  0.0
# b  1  0.1
# c  2  0.2

pd.concat()にも引数copyがあるが、copy=Falseとしても必ずビューが生成されるとは限らない。

df_c_false = pd.concat([s1, s2], axis=1, copy=False)
print(df_c_false)
#      0    1
# a  100  0.0
# b    1  0.1
# c    2  0.2

s1[1] = 100
print(s1)
# a    100
# b    100
# c      2
# dtype: int64

print(df_c_false)
#      0    1
# a  100  0.0
# b    1  0.1
# c    2  0.2

コンストラクタpd.DataFrame()を含め、引数copyが提供されている関数やメソッドにおいて、copy=Trueとすると必ずコピーが生成されるが、copy=Falseでは可能な限りビューを生成するという処理になる。

copy=Falseであっても、メモリレイアウト上、不可能な場合はビューではなくコピーとなる。必ずビューが生成されるとは限らないので注意。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事