[pandas超入門]DataFrameをさまざまなオブジェクトに変換しようPythonデータ処理入門

DataFrameオブジェクトは便利に使えますが、別形式のオブジェクトに変換できると便利なこともあります。今回はその方法を紹介していきます。

» 2024年08月02日 05時00分 公開
[かわさきしんじDeep Insider編集部]
「Pythonデータ処理入門」のインデックス

連載目次

本シリーズと本連載について

 本シリーズ「Pythonデータ処理入門」は、Pythonの基礎をマスターした人を対象に以下のような、Pythonを使ってデータを処理しようというときに便利に使えるツールやライブラリ、フレームワークの使い方の基礎を説明するものです。

  • NumPy(「NumPy超入門」の目次はこちら
  • pandas(本連載)
  • Matplotlib

 なお、本連載では以下のバージョンを使用しています。

  • Python 3.12
  • pandas 2.2.1

 前回はDataFrameオブジェクトのソートや、行や列の入れ替えなどの話をしました。今回はDataFrameオブジェクトをNumPyの多次元配列やPythonリストや辞書、JSON形式の文字列に変換する方法について見ていきます。

DataFrameオブジェクトをNumPyの多次元配列に変換

 DataFrameオブジェクトをNumPyの多次元配列(ndarray)に変換するto_numpyメソッドについては以前にも取り上げましたが、その書式を以下にまとめておきましょう。

pandas.DataFrame.to_numpyメソッド

pandas.DataFrame.to_numpy(dtype=None, copy=False, na_value=NoDefault.no_default)

to_numpyメソッドの構文

 DataFrameオブジェクトをNumPyの多次元配列に変換したものを返送する。パラメーターは以下の通り。

  • dtype:変換後の要素のデータ型を指定する。省略時は変換される要素を共通して表現できるものになる(整数と浮動小数点数を変換するなら浮動小数点数型のいずれかになるなど)
  • copy:Trueを指定すると、元データのビューではなく、コピーしたものが返送されることが保証される。False(デフォルト値)を指定した場合でも、コピーが行われる場合がある点には注意すること
  • na_value:欠損値を表現するのに使用する値を指定する。省略時はDataFrameオブジェクトの各列のデータ型とdtypeパラメーターの値による

dtypeパラメーターの指定

 ここでは以下の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オブジェクト

 この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の多次元配列の要素は全て同じデータ型になる

 NumPyの多次元配列の要素は全て同じデータ型となるので、特に指定しなければ、元のDataFrameオブジェクトの全要素の値を表現できるようなデータ型が使われます。この場合は整数値と浮動小数点数値を表現できるようにfloat64型が多次元配列のdtypeになっています。

 なお、DataFrameオブジェクトのvalue属性でもNumPyの多次元配列を得られますが、values属性のドキュメントではvalue属性ではなく、to_numpyメソッドで多次元配列を得ることが推奨されている点には注意してください。

a = df.values
print(a)  # 同上

DataFrame.value属性

 以下は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]]

dtypeパラメーターを指定する例

 2つ目の例では「dtype='int32'」としているので、浮動小数点数値が整数値に変換されている点に注意してください。

copyパラメーターの指定

 次に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オブジェクト

 先ほどと同様な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

「copy=True」とすれば必ずコピーされる

 この場合はデータは必ずコピーされる(つまり、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」を指定したみた

 「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

「copy=False」としてもコピーされることはある

 この例では、リストを基に(多次元配列を途中で作らずに)DataFrameオブジェクトを作成し、「copy=False」を指定してそのDataFrameオブジェクトから多次元配列を作成している点が上の例と異なっています。このときには、コピーが発生し、0行0列の要素の値を変更してもそれがDataFrameオブジェクトには影響していません(ただし、必ずそうなると筆者が言い切ることもできません)。

 copyパラメーターにTrueを指定すると必ずコピーされるのであって、Falseを指定してもコピーされることがあり、その場合にはコピーされないことを前提としたコードが想定通りには動かないことを覚えておくようにしましょう。

na_valueパラメーターを指定する

 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

サンプルのDataFrameオブジェクト

 特に指定せずに多次元配列を作成すると以下のようになります。

a = df.to_numpy()
print(a)
# 出力結果:
#[[nan  2.  3.]
# [ 4. nan  6.]
# [ 7.  8.  9.]]

na_valueパラメーターを指定しない場合

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

na_valueパラメーターを指定する例

 すると、このパラメーターに指定した値が欠損値の値として使われるようになります。

DataFrameオブジェクトをPythonのリストに変換

 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メソッド

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

iterrowsメソッドでDataFrameオブジェクトの行を反復

 上で述べたように、タプルの第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]]

iterrowsメソッドを使ってリストのリストを作成

 あるいは、DataFrameオブジェクトの行インデックスを返すindex属性を使って次のようなコードも書けます。

l = [df.loc[idx].to_list() for idx in df.index]
print(l)  # 同上

index属性の値をloc属性に指定してSeriesオブジェクトを得たら、それをリストに変換することで、リストのリストを作成

 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]]

to_numpyメソッドで多次元配列を作成して、そのtolistメソッドを呼び出す

 通常はこの方法を使うのがめんどくさくなくてよいでしょう。

DataFrameオブジェクトをPythonの辞書に変換

 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オブジェクトを作成する

 これとは逆にDataFrameオブジェクトを辞書に変換することも可能です。これにはto_dictメソッドを使います。

pandas.DataFrame.to_dictメソッド

pandas.DataFrame.to_dict(orient='dict')

pandas.DataFrame.to_dictメソッドの構文(抜粋)

 DataFrameオブジェクトを辞書に変換する。orientパラメーターには以下を指定可能(省略時は'dict'が指定されたものと見なされる)。

説明
'dict' 列インデックスをキーとして、{行インデックス: 値}をそのキーの値とする辞書に変換
'list' 列インデックスをキーとして、その列の値を要素とするリストをそのキーの値とする辞書に変換
'series' 列インデックスをキーとして、その列の値を要素とするSeriesオブジェクトをそのキーの値とする辞書に変換
'split' 行インデックスを'index'キーの値に、列インデックスを'columns'キーの値に、各行の値を格納するリストを要素とするリストを'data'キーの値とする辞書に変換
'records' 各行の値をまとめた{列名: 値}形式の辞書を要素とするリストに変換
'index' 各行のインデックスをキーに、その列名と対応する要素の値をキー/値の組みとする辞書に変換
orientパラメーターに指定可能な値(抜粋)


 上記の表ではどんなものが作成されるかが分かりにくいかもしれません。以下のサンプルコードを見ながら、何を指定するとどんな辞書(やリスト)が作成されるかを確認してください。大まかな分類としては、以下のようなことがいえるでしょう。

  • 列指向:'dict'/'list'/'series'を指定すると、列インデックスをキーとした辞書が作成される(その値は辞書かリストかSeriesオブジェクトのいずれかになる)
  • 行指向:'index'を指定すると、行インデックスをキーに、各列のラベルと値をキーに対応する値とする辞書が作成される
  • その他:'split'を指定すると行インデックスを'index'キーの値、列インデックスを'columns'キーの値、データが'data'キーの値とする辞書が作成される。'records'を指定すると、行ごとに{列インデックス: 値}を要素とする辞書が作成され、それらを要素とするリストが作成される

 この他に'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}}

orientパラメーターを指定する例

 このようにして作成した辞書(の一部)を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)  # 同上

from_dictクラスメソッドによる辞書からDataFrameオブジェクトの作成

 orientパラメーターに'dict'/'list'/'series'のいずれかを指定して作成した辞書をfrom_dictクラスメソッドに渡す場合はorientパラメーターに'columns'を指定します(あるいはorientパラメーターの指定を省略)。orientパラメーターに'index'を指定した作成した辞書をfrom_dictクラスメソッドに渡す場合にはorientパラメーターにやはり'index'を指定します。

DataFrameオブジェクトをJSON形式のデータに変換

 最後にDataFrameオブジェクトをJSON形式の文字列に変換する、あるいはJSON形式のデータとしてファイルに書き出すto_jsonメソッドを紹介します。

pandas.DataFrame.to_jsonメソッド

pandas.DataFrame.to_json(path_or_buf=None, *, indent=None)

pandas.DataFrame.to_jsonメソッドの構文(抜粋)

 DataFrameオブジェクトをJSON形式の文字列に変換する。外部ファイルを指定した場合には変換されたものがファイルに書き出される。外部ファイルを指定しなかった場合は、変換後の文字列が返される。パラメーターは以下の通り(抜粋)。

  • path_or_buf:書き出すファイルの指定。省略時はファイルに書き出さずに、変換後の文字列が戻り値になる
  • indent:各レコードをインデントするときの空白文字の長さ

 ここでは以下のような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オブジェクト

 ではこの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}}

JSON形式のデータとしてファイルに書き込み

 書き込んだテキストの内容を見ると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形式に変換されたDataFrameオブジェクト

 変換された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

JSON形式の文字列からDataFrameオブジェクトを作成


 今回はDataFrameオブジェクトのNumPyの多次元配列への変換、リスト(のリスト)への変換、辞書への変換、JSON形式の文字列への変換(またはファイルへの書き出し)について解説しました。DataFrameオブジェクトは便利に使えますが、最後には別のオブジェクトに変換する必要が出てくることもあります。そうしたときに、今回紹介した変換手段が役立つかもしれません。

「Pythonデータ処理入門」のインデックス

Pythonデータ処理入門

Copyright© Digital Advantage Corp. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

AI for エンジニアリング
「サプライチェーン攻撃」対策
1P情シスのための脆弱性管理/対策の現実解
OSSのサプライチェーン管理、取るべきアクションとは
Microsoft & Windows最前線2024
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。