DataFrameオブジェクトは便利に使えますが、別形式のオブジェクトに変換できると便利なこともあります。今回はその方法を紹介していきます。
本シリーズ「Pythonデータ処理入門」は、Pythonの基礎をマスターした人を対象に以下のような、Pythonを使ってデータを処理しようというときに便利に使えるツールやライブラリ、フレームワークの使い方の基礎を説明するものです。
なお、本連載では以下のバージョンを使用しています。
前回はDataFrameオブジェクトのソートや、行や列の入れ替えなどの話をしました。今回はDataFrameオブジェクトをNumPyの多次元配列やPythonリストや辞書、JSON形式の文字列に変換する方法について見ていきます。
DataFrameオブジェクトをNumPyの多次元配列(ndarray)に変換するto_numpyメソッドについては以前にも取り上げましたが、その書式を以下にまとめておきましょう。
pandas.DataFrame.to_numpy(dtype=None, copy=False, na_value=NoDefault.no_default)
DataFrameオブジェクトをNumPyの多次元配列に変換したものを返送する。パラメーターは以下の通り。
ここでは以下のDataFrameオブジェクトをNumPyの多次元配列に変換してみましょう。
mylist = [[1, 2., 3.], [4, 5., 6.], [7, 8., 9.]]
index = [f'r{i}' for i in range(3)]
columns = [f'c{i}' for i in range(3)]
df = pd.DataFrame(mylist, columns=columns, index=index)
print(df)
# 出力結果:
# c0 c1 c2
#r0 1 2.0 3.0
#r1 4 5.0 6.0
#r2 7 8.0 9.0
このDataFrameオブジェクトは'c0'列の値は整数で、他の列の値は浮動小数点数です。これを何の指定もせずにto_numpyメソッドで変換する例を以下に示します。
a = df.to_numpy()
print(a)
# 出力結果:
#[[1. 2. 3.]
# [4. 5. 6.]
# [7. 8. 9.]]
# NumPyの多次元配列は全要素が同じ型なので、全てが浮動小数点数値に変換される
print(a.dtype) # float64
NumPyの多次元配列の要素は全て同じデータ型となるので、特に指定しなければ、元のDataFrameオブジェクトの全要素の値を表現できるようなデータ型が使われます。この場合は整数値と浮動小数点数値を表現できるようにfloat64型が多次元配列のdtypeになっています。
なお、DataFrameオブジェクトのvalue属性でもNumPyの多次元配列を得られますが、values属性のドキュメントではvalue属性ではなく、to_numpyメソッドで多次元配列を得ることが推奨されている点には注意してください。
a = df.values
print(a) # 同上
以下はdtypeパラメーターに変換後の多次元配列のdtypeを指定する例です。
# dtypeパラメーターに'float64'を指定
a = df.to_numpy(dtype='float64')
print(a) # 同上
# dtypeパラメーターに'int32'を指定
a = df.to_numpy(dtype='int32')
print(a)
# 出力結果:浮動小数点数値が整数値に変換されている点に注目
#[[1 2 3]
# [4 5 6]
# [7 8 9]]
2つ目の例では「dtype='int32'」としているので、浮動小数点数値が整数値に変換されている点に注意してください。
次にcopyパラメーターを指定した場合の振る舞いを見てみましょう。
import numpy as np
mylist = [[1, 2., 3.], [4, 5., 6.], [7, 8., 9.]]
tmp = np.array(mylist)
index = [f'r{i}' for i in range(3)]
columns = [f'c{i}' for i in range(3)]
df = pd.DataFrame(tmp, columns=columns, index=index)
print(df)
# 出力結果:
# c0 c1 c2
#r0 1.0 2.0 3.0
#r1 4.0 5.0 6.0
#r2 7.0 8.0 9.0
先ほどと同様なDataFrameオブジェクトを作成していますが、NumPyをインポートし、途中で「tmp = np.array(mylist)」としている点が異なっています。一度、多次元配列を作成し、それを基にDataFrameオブジェクトを作ったので、'c0'列の要素が整数値ではなく浮動小数点数値になっています(ただし、ここではそれは本筋とは無関係です)。
copyパラメーターにTrueを指定して、このDataFrameオブジェクトをNumPyの多次元配列に変換し、多次元配列の0行0列の値を変更してみるとどうなるでしょう。
a = df.to_numpy(copy=True)
a[0, 0] = 100
print(a)
# 出力結果:
#[[100. 2.2 3.3]
# [ 4. 5.5 6.6]
# [ 7. 8.8 9.9]]
print(df)
# 出力結果:
# c0 c1 c2
#r0 1 2.2 3.3
#r1 4 5.5 6.6
#r2 7 8.8 9.9
この場合はデータは必ずコピーされる(つまり、to_numpyメソッドで作成した多次元配列は元のDataFrameオブジェクトあるいは最初に作成した多次元配列のビューではない)ので、一方への変更がもう一方に影響することはありません。そのため、上の出力結果のように多次元配列の要素を変更してもDataFrameオブジェクトの対応する要素の値は変化しません。
対して、「copy=False」とするとどうなるでしょう。
a = df.to_numpy(copy=False)
a[0, 0] = 100
print(a)
# 出力結果:
#[[100. 2. 3.]
# [ 4. 5. 6.]
# [ 7. 8. 9.]]
print(df)
# 出力結果:
# c0 c1 c2
#r0 100.0 2.0 3.0
#r1 4.0 5.0 6.0
#r2 7.0 8.0 9.0
# 返送された多次元配列はDataFrameオブジェクトのビューであるため、
# 多次元配列を変更すると、元のDataFrameオブジェクトも変更される
print(tmp)
# 出力結果:
#[[100. 2. 3.]
# [ 4. 5. 6.]
# [ 7. 8. 9.]]
「copy=False」を指定してto_numpyメソッドを呼び出し、その戻り値の0行0列にある要素の値を変更してみました。すると、今度は多次元配列に対する変更が元のDataFrameオブジェクトに影響しています。それどころか、DataFrameオブジェクトを作成で使用した多次元配列(tmp)の要素までもが影響を受けています。
このように「copy=False」を指定すると、複数のオブジェクトが一つのデータを共有することになることがあります。といっても、以下に示すようにそうはならない場合もあります。
mylist = [[1, 2., 3.], [4, 5., 6.], [7, 8., 9.]]
index = [f'r{i}' for i in range(3)]
columns = [f'c{i}' for i in range(3)]
df = pd.DataFrame(mylist, columns=columns, index=index)
a = df.to_numpy(copy=False)
a[0, 0] = 100
print(df)
# 出力結果:copy=Falseを指定したからといってコピーされないわけではない
# c0 c1 c2
#r0 1 2.0 3.0
#r1 4 5.0 6.0
#r2 7 8.0 9.0
この例では、リストを基に(多次元配列を途中で作らずに)DataFrameオブジェクトを作成し、「copy=False」を指定してそのDataFrameオブジェクトから多次元配列を作成している点が上の例と異なっています。このときには、コピーが発生し、0行0列の要素の値を変更してもそれがDataFrameオブジェクトには影響していません(ただし、必ずそうなると筆者が言い切ることもできません)。
copyパラメーターにTrueを指定すると必ずコピーされるのであって、Falseを指定してもコピーされることがあり、その場合にはコピーされないことを前提としたコードが想定通りには動かないことを覚えておくようにしましょう。
na_valueパラメーターは、DataFrameオブジェクトに欠損値が含まれている場合に、多次元配列でそれをどんな値で表現するかを指定するものです。ここでは以下のようなDataFrameオブジェクトを作成して、na_valueパラメーターを指定しない場合と指定した場合の振る舞いの違いを見てみます。
mylist = [[np.nan, 2., 3.], [4, np.nan, 6.], [7, 8., 9.]]
columns = [f'c{i}' for i in range(3)]
index = [f'r{i}' for i in range(3)]
df = pd.DataFrame(mylist, columns=columns, index=index)
print(df)
# 出力結果:
# c0 c1 c2
#r0 NaN 2.0 3.0
#r1 4.0 NaN 6.0
#r2 7.0 8.0 9.0
特に指定せずに多次元配列を作成すると以下のようになります。
a = df.to_numpy()
print(a)
# 出力結果:
#[[nan 2. 3.]
# [ 4. nan 6.]
# [ 7. 8. 9.]]
0行0列と1行1列のnp.nan値がnan(実際にはnumpy.float64型の値です)になっているのが分かります。次にna_valueパラメーターにnp.inf値を指定してみましょう。
a = df.to_numpy(na_value=np.inf)
print(a)
# 出力結果:
#[[inf 2. 3.]
# [ 4. inf 6.]
# [ 7. 8. 9.]]
すると、このパラメーターに指定した値が欠損値の値として使われるようになります。
Seriesオブジェクトにはtolistメソッドがあり、これを使うとSeriesオブジェクトをリストに変換できます。
以下は上で作成したDataFrameオブジェクトの列や行を取り出して、それをリストに変換する例です。
l = df['c0'].to_list() # 'c0'列をリストに変換
print(l) # [nan, 4.0, 7.0]
l = df.loc['r2'].to_list() # 'r2'行をリストに変換
print(l) # [7.0, 8.0, 9.0]
しかし、DataFrameオブジェクトにはto_listメソッドがありません。DataFrameオブジェクトをリストのリストに変換するには幾つかの方法が考えられます。1つはiterrowsメソッドを呼び出すことです。
pandas.DataFrame.iterrows()
DataFrameオブジェクトの各行を、そのインデックスと内容(Seriesオブジェクト)を要素とするタプルの形で反復する。namedtuple(名前付きタプル)として反復するitertuplesメソッドもある。
以下にこのメソッドの使用例を示します。
for row in df.iterrows():
print(f'{row[0]}:\n{row[1]}')
# 出力結果:
#r0:
#c0 NaN
#c1 2.0
#c2 3.0
#Name: r0, dtype: float64
#r1:
#c0 4.0
#c1 NaN
#c2 6.0
#Name: r1, dtype: float64
#r2:
#c0 7.0
#c1 8.0
#c2 9.0
#Name: r2, dtype: float64
上で述べたように、タプルの第0要素は行インデックス、第1要素がその値(Seriesオブジェクト)です。このことから、次のようなコードを書けば、リストのリストを作成できるでしょう。
l = [row[1].to_list() for row in df.iterrows()]
# l = []
# for row in df.iterrows():
# l.append(row[1].to_list())
print(l) # [[nan, 2.0, 3.0], [4.0, nan, 6.0], [7.0, 8.0, 9.0]]
あるいは、DataFrameオブジェクトの行インデックスを返すindex属性を使って次のようなコードも書けます。
l = [df.loc[idx].to_list() for idx in df.index]
print(l) # 同上
index属性を反復すると、行インデックスが得られるので、それをloc属性に指定して各行をSeriesオブジェクトとして取り出して、さらにto_listメソッドを呼び出すことで、リストのリストを作成しています。
しかし、先ほどのto_numpyメソッドを使って多次元配列を作成してしまえば、多次元配列が持つtolistメソッドでリストのリストを作成できます。
a = df.to_numpy()
l = a.tolist()
print(l) # [[nan, 2.0, 3.0], [4.0, nan, 6.0], [7.0, 8.0, 9.0]]
通常はこの方法を使うのがめんどくさくなくてよいでしょう。
DataFrameオブジェクトはリストのリストからも作成できますが、辞書からも作成できます。これについては第3回で取り上げました。簡単な例を以下に示しておきます。
d = {'col0': [0, 3, 6], 'col1': [1, 4, 7], 'col2': [2, 5, 8]}
df = pd.DataFrame(d)
print(df)
# 出力結果:
# col0 col1 col2
#0 0 1 2
#1 3 4 5
#2 6 7 8
これとは逆にDataFrameオブジェクトを辞書に変換することも可能です。これにはto_dictメソッドを使います。
pandas.DataFrame.to_dict(orient='dict')
DataFrameオブジェクトを辞書に変換する。orientパラメーターには以下を指定可能(省略時は'dict'が指定されたものと見なされる)。
値 | 説明 |
---|---|
'dict' | 列インデックスをキーとして、{行インデックス: 値}をそのキーの値とする辞書に変換 |
'list' | 列インデックスをキーとして、その列の値を要素とするリストをそのキーの値とする辞書に変換 |
'series' | 列インデックスをキーとして、その列の値を要素とするSeriesオブジェクトをそのキーの値とする辞書に変換 |
'split' | 行インデックスを'index'キーの値に、列インデックスを'columns'キーの値に、各行の値を格納するリストを要素とするリストを'data'キーの値とする辞書に変換 |
'records' | 各行の値をまとめた{列名: 値}形式の辞書を要素とするリストに変換 |
'index' | 各行のインデックスをキーに、その列名と対応する要素の値をキー/値の組みとする辞書に変換 |
orientパラメーターに指定可能な値(抜粋) |
上記の表ではどんなものが作成されるかが分かりにくいかもしれません。以下のサンプルコードを見ながら、何を指定するとどんな辞書(やリスト)が作成されるかを確認してください。大まかな分類としては、以下のようなことがいえるでしょう。
この他に'tight'も指定できますが、ここでは説明は省略します。
d = df.to_dict()
print(d)
# 出力結果(途中に改行を挿入):
#{'col0': {0: 0, 1: 3, 2: 6},
# 'col1': {0: 1, 1: 4, 2: 7},
# 'col2': {0: 2, 1: 5, 2: 8}}
d = df.to_dict(orient='list')
print(d) # {'col0': [0, 3, 6], 'col1': [1, 4, 7], 'col2': [2, 5, 8]}
d = df.to_dict('series')
print(d['col0'])
# 出力結果:
#0 0
#1 3
#2 6
#Name: col0, dtype: int64
d = df.to_dict('split')
print(d)
# 出力結果(途中に改行を挿入):
#{'index': [0, 1, 2], 'columns': ['col0', 'col1', 'col2'],
# 'data': [[0, 1, 2], [3, 4, 5], [6, 7, 8]]}
d = df.to_dict('records')
print(d)
# 出力結果(途中に改行を挿入):
#[{'col0': 0, 'col1': 1, 'col2': 2},
# {'col0': 3, 'col1': 4, 'col2': 5},
# {'col0': 6, 'col1': 7, 'col2': 8}]
d = df.to_dict('index')
print(d)
# 出力結果(途中に改行を挿入):
#{0: {'col0': 0, 'col1': 1, 'col2': 2},
# 1: {'col0': 3, 'col1': 4, 'col2': 5},
# 2: {'col0': 6, 'col1': 7, 'col2': 8}}
このようにして作成した辞書(の一部)をpandas.DataFrame.from_dictクラスメソッドに渡すことで、DataFrameオブジェクトに復元できます。以下に例を示します。
d = df.to_dict(orient='series')
df2 = pd.DataFrame.from_dict(d, orient='columns')
print(df2)
# 出力結果:
# col0 col1 col2
#0 0 1 2
#1 3 4 5
#2 6 7 8
d = df.to_dict(orient='index')
df2 = pd.DataFrame.from_dict(d, orient='index')
print(df2) # 同上
orientパラメーターに'dict'/'list'/'series'のいずれかを指定して作成した辞書をfrom_dictクラスメソッドに渡す場合はorientパラメーターに'columns'を指定します(あるいはorientパラメーターの指定を省略)。orientパラメーターに'index'を指定した作成した辞書をfrom_dictクラスメソッドに渡す場合にはorientパラメーターにやはり'index'を指定します。
最後にDataFrameオブジェクトをJSON形式の文字列に変換する、あるいはJSON形式のデータとしてファイルに書き出すto_jsonメソッドを紹介します。
pandas.DataFrame.to_json(path_or_buf=None, *, indent=None)
DataFrameオブジェクトをJSON形式の文字列に変換する。外部ファイルを指定した場合には変換されたものがファイルに書き出される。外部ファイルを指定しなかった場合は、変換後の文字列が返される。パラメーターは以下の通り(抜粋)。
ここでは以下のようなDataFrameオブジェクトを例として使いましょう。
d = {'c0': [0, 2], 'c1': [1, 3]}
df = pd.DataFrame(d, index=['r0', 'r1'])
print(df)
# 出力結果:
# c0 c1
#r0 0 1
#r1 2 3
ではこのDataFrameオブジェクトをJSON形式でファイルに書き出してみます。
from pathlib import Path
path = Path('data.json')
df.to_json(path)
print(path.read_text()) # {"c0":{"r0":0,"r1":2},"c1":{"r0":1,"r1":3}}
書き込んだテキストの内容を見ると1行にまとまっています。これはこれでコンパクトですが、分かりやすいかというとそうでもありません。そこでindentパラメーターにインデント幅を指定してみましょう。
df.to_json(path, indent=2)
print(path.read_text())
# 出力結果:
#{
# "c0":{
# "r0":0,
# "r1":2
# },
# "c1":{
# "r0":1,
# "r1":3
# }
#}
ここではindentパラメーターに2を指定したので、インデント幅2でテキストが成形されました。
ファイルのパスを指定しないときには、JSON形式の文字列に変換した結果が返されます。以下はその例です。
s = df.to_json(indent=2)
print(s) # 先ほどと同じ
変換されたJSON文字列はjsonモジュールのloads関数で辞書に復元でき、さらにそれをDataFrameコンストラクターに渡してDataFrameオブジェクトに復元できます。
import json
d = json.loads(s)
df2 = pd.DataFrame(d)
print(df2)
# 出力結果:
# c0 c1
#r0 0 1
#r1 2 3
今回はDataFrameオブジェクトのNumPyの多次元配列への変換、リスト(のリスト)への変換、辞書への変換、JSON形式の文字列への変換(またはファイルへの書き出し)について解説しました。DataFrameオブジェクトは便利に使えますが、最後には別のオブジェクトに変換する必要が出てくることもあります。そうしたときに、今回紹介した変換手段が役立つかもしれません。
Copyright© Digital Advantage Corp. All Rights Reserved.