pandas.DataFrameの作り方あれこれ
この記事では、pandasのDataFrameの作り方について説明します。
Python3.7以上pandas v1.3.5を想定していますが、pandasのバージョンについてはその限りではありません。
有用な使い方は説明できていないので、いい使い方があれば教えていただけると嬉しいです。
結論
長くなってしまったので、最初に結論を書きます。
リストのリストや辞書のリストだけでなく、dataclassのリストやnamedtupleのリストからでもDataFrameを簡単に作れます。
リストや辞書だけでなく、dataclassやnamedtupleも用途に応じて使っていきましょう。
import pandas as pd
from dataclasses import dataclass
from collections import namedtuple
# リストのリストの場合
# l = [[1, 2], [3, 4]]
# pd.DataFrame(l, columns=["x", "y"])
# 辞書のリストの場合
# l = [{"x": 1, "y": 2}, {"x": 3, "y": 4}]
# pd.DataFrame(l)
# dataclassの場合
@dataclass
class Point:
x: int
y: int
# namedtupleの場合
# Point = namedtuple("Point", ["x", "y"])
p1 = Point(1, 2)
p2 = Point(3, 4)
l = [p1, p2]
pd.DataFrame(l)
x | y | |
---|---|---|
0 | 1 | 2 |
1 | 3 | 4 |
pandasのDataFrameを使うのはデータを対話的に分析したいときでしょうから、クラスまで持ち出す必要があるかと言うと微妙です。
複数人で実験をするときや、一人でいろいろな実験を繰り返しするときのコードの可読性やメンテナンス性向上が主なメリットでしょうか。
pandas.DataFrameとは
皆さんpandas使ってますか、DataFrame使ってますか。
ご存じの通り、DataFrameは、2次元の表形式データを表すpandasの主要なデータ構造の一つです。
CSVファイルや、RDBMS(Relational Database Management System)の表や、スプレッドシートのデータに対応するデータ構造です。
この記事では、DataFrameの作り方、特にDataFrameコンストラクタへのデータの与え方に焦点を当てて紹介したいと思います。
pandasやDataFrameについては素敵な記事がたくさん公開されていますし、公式ドキュメントも充実しています。そちらをご覧ください。
- データ分析で頻出のPandas基本操作 - Qiita
- 7-1. pandasライブラリ — Pythonプログラミング入門 documentation
- pandas - Python Data Analysis Library
- Getting started — pandas 1.3.5 documentation
- 10 minutes to pandas — pandas 1.3.5 documentation
1. DataFrameの作り方
DataFrameにはいくつの作り方があります。
方法を大別すると、pandas.DataFrameコンストラクタにデータを渡す方法、pandasに定義されている関数にデータを渡す方法の2種類があります。
また、入力データ形式は、リストのリストや辞書のリスト、NumPy配列やCSV、SQLなど、いろいろなバリエーションがあります。
具体例や詳細な使い方については既に公開されている記事や公式ドキュメントを参考にしてください。
- pandas.DataFrameの構造とその作成方法 | note.nkmk.me
- Pandas DataFrameを徹底解説!(作成、行・列の追加と削除、indexなど) - AI-interのPython3入門
- pandas.DataFrame — pandas 1.3.5 documentation
- Input/output — pandas 1.3.5 documentation
DataFrameを作る方法をいくつか並べるとこんな感じでしょうか。結構たくさんありますね。
- pandas.DataFrame
- pandas.read_csv
- pandas.read_excel
- pandas.read_json
- pandas.read_html
- pandas.read_xml
- pandas.read_parquet
- pandas.read_sql
- etc.(省略した部分も含めた一覧は後述)
上記に含まれないものとしては、AWS Data Wranglerのような、pandasの外側のライブラリでDataFrameを生成・処理できるものも存在します。
2. DataFrameコンストラクタにデータを渡す方法
pandasではDataFrameコンストラクタにデータを与えることで、DataFrameを作成することができます。
公式ドキュメントを見ると、
data: ndarray (structured or homogeneous), Iterable, dict, or DataFrame
とあります。
つまり、ndarray(NumPy配列)、Iterable、dict(辞書)、DataFrameからDataFrameを作れるということです。
2.1. ndarrayとDataFrameの場合
ndarrayやDataFrameはそれ自身2次元のデータであるため、DataFrameが生成できるのは直感的です。
例は公式ドキュメントにも載っています。
NumPy配列からDataFrameを作る例を示します。
import pandas as pd
import numpy as np
ndarray = np.array([[1, 2], [3, 4]])
pd.DataFrame(ndarray, columns=["x", "y"])
x | y | |
---|---|---|
0 | 1 | 2 |
1 | 3 | 4 |
DataFrameからDataFrameを作る例を示します。
import pandas as pd
df = pd.DataFrame([[1, 2], [3, 4]], columns=["x", "y"])
pd.DataFrame(df)
x | y | |
---|---|---|
0 | 1 | 2 |
1 | 3 | 4 |
2.2. dictの場合
一方、dictについては説明や例にもあるように、列名をキーとし列に含まれる値をバリューとするdictを想定しています。
dictからDataFrameを作る例を示します。
import pandas as pd
d = {"x": [1, 3], "y": [2, 4]}
pd.DataFrame(d)
x | y | |
---|---|---|
0 | 1 | 2 |
1 | 3 | 4 |
2.3. Iterableの場合
では、Iterableではどんな値のIterableを想定しているのでしょうか。
ただの確認不足かもしれませんが、このあたりドキュメントにははっきり書いてありません。
いくつか動いてほしいものを試してみましょう。
2.3.1. リストのリストを使う方法
最初に思いつくのは、リストのリストやタプルのリストです。
2次元配列をそのまま表現するなら最もシンプルです。
実は、DataFrameからDataFrameを作るところですでに出現しています。
import pandas as pd
l = [[1, 2], [3, 4]]
pd.DataFrame(l, columns=["x", "y"])
x | y | |
---|---|---|
0 | 1 | 2 |
1 | 3 | 4 |
この方法では、列名は自分で与える必要があるのが他の方法との差です。
与え忘れると、適当な列名になります。
import pandas as pd
l = [[1, 2], [3, 4]]
pd.DataFrame(l)
0 | 1 | |
---|---|---|
0 | 1 | 2 |
1 | 3 | 4 |
2.3.2. 辞書のリストを使う方法
次に思いつくのは、辞書のリストです。
import pandas as pd
l = [{"x": 1, "y": 2}, {"x": 3, "y": 4}]
pd.DataFrame(l)
x | y | |
---|---|---|
0 | 1 | 2 |
1 | 3 | 4 |
列名を与えなくても、辞書のキー名を列名だと思ってくれます。
2.3.3. dataclassを使う方法
dataclassはPython3.7で導入された機能で、データを保持するのが主目的のクラスを宣言するときに便利な機能です。
辞書と比べると、型アノテーションが利用出来たり、コードの可読性が上がったり、値をイミュータブルにするなど制限を付けることができるといったメリットがあります。
自作クラスと比べると、いろいろなメソッドを自分で書かなくてよくなるメリットがあります。例えば、__repr__を実装しなくても、printした際の出力がきれいです。もちろん、__repr__の実装を変更することもできます。
前置きが長くなりましたが、dataclassのリストはどうでしょうか?やってみましょう。
import pandas as pd
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
p1 = Point(1, 2)
p2 = Point(3, 4)
l = [p1, p2]
pd.DataFrame(l)
x | y | |
---|---|---|
0 | 1 | 2 |
1 | 3 | 4 |
列名を与えなくても、フィールド名を列名だと思ってくれます。
dataclassについては、既に素敵な解説記事がたくさんあります。
2.3.4. namedtupleを使う方法
dataclassで行けるなら、namedtupleも行けるのではないか、そう考えるのは自然なことでしょう。
import pandas as pd
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p1 = Point(1, 2)
p2 = Point(3, 4)
l = [p1, p2]
pd.DataFrame(l)
x | y | |
---|---|---|
0 | 1 | 2 |
1 | 3 | 4 |
dataclassを全く同じ方法でDataFrameを生成することができました。
列名もばっちりです。
namedtupleについては、既に素敵な解説記事がたくさんあります。
2.4. ちょっとだけ深堀り
dataclassやnamedtupleを使わずに、単にクラスを定義したらどうなるでしょうか。
2.4.1. 自作クラスを使う方法(単純な実装)
とりあえず、メソッドなどを定義せずシンプルなクラスを作ってみます。
import pandas as pd
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p1 = Point(1, 2)
p2 = Point(3, 4)
l = [p1, p2]
pd.DataFrame(l)
0 | |
---|---|
0 | <\_\_main\_\_.Point object at 0x7f8f80bf5240> |
1 | <\_\_main\_\_.Point object at 0x7f8f80bf5198> |
メソッドを何も定義していないので当然といえば当然ですが、いまいちですね。
2.4.2. コードを見てみる
どうすれば自作クラスの場合に「いい感じ」のDataFrameを取得できるのか調べてみましょう。
コンストラクタ
まず、DataFrameコンストラクタのコードはframe.py#L573-L776です。
第一引数のdataがどんな型やクラスなのか判定して、処理を分岐しています。
DataFrameなのか辞書なのかNumPy配列なのかなどを判定した後に、frame.py#L682-L726でここまで判定したもの以外のIterableに対する処理が記述されています。
dataclassの場合
すこし横道にそれて、dataclassに対する処理は、frame.py#L686-L687です。
特別扱いされていますね。
上記の延長線上で、list(map(asdict, data))が呼ばれて、これ以降は辞書のリストと同じ扱いです。
namedtupleの場合
さらに横道にそれて、namedtupleに対する処理は、frame.py#L694で呼び出されているnested_data_to_arraysのconstruction.py#L480-L481です。
列名さえ取れてしまえば、あとはタプルのリストと同じ扱いです。
classの場合
元の道に戻ります。
ただのクラスの場合、コードのどこを通るでしょうか。
frame.py#L682のis_list_like(data)は、クラスのリストでもTrueになるはずです。
frame.py#L683-L684は、リストでないものをリスト化しているだけなのでスルーします。
frame.py#L685は、空でないリストを渡しているのでTrueです。
frame.py#L686-L687は、データクラスではないのでifの中には入りません。
frame.py#L688のtread_as_nestedは、4つないし5つの条件をチェックしています。
重要なのはis_list_like(data[0])です。
これは、クラスのリストの0番目(=結局クラス)がis_list_likeかをチェックしています。
is_list_likeの公式ドキュメントやコードのコメントを見ると、is_list_likeは文字列以外のIterableであればTrueを返します。
つまり、値をそれ以上展開すべきかどうかを判定しているということですね。
今回はクラスの中身、Pointクラスのx座標とy座標を展開してほしいとすると、tread_as_nestedがTureとなりその配下のブロックを通るようになってほしいということになります。
よって、まずはIterableにしてみましょう。
2.4.3. 自作クラスを使う方法(リスト劣化版)
Iterableにするために、__iter__と__next__を実装します。
import pandas as pd
class Point():
def __init__(self, x, y):
self.x = x
self.y = y
self.__n = 0
def __iter__(self):
self.__n = 0
return self
def __next__(self):
if self.__n == 0:
self.__n += 1
return self.x
elif self.__n == 1:
self.__n += 1
return self.y
else:
self.__n = 0
raise StopIteration
p1 = Point(1, 2)
p2 = Point(3, 4)
l = [p1, p2]
pd.DataFrame(l)
0 | 1 | |
---|---|---|
0 | 1 | 2 |
1 | 3 | 4 |
リストを使った場合と同じところまでは来ました。
というよりは、リストの劣化版が出来ただけですが…。
2.4.4. 自作クラスを使う方法(さらなる改良)
construction.py#L796-L805の中で列名を解決してくれそうでpandasの型ではないものは、abc.Mappingくらいでしょうか。
公式ドキュメントによるとabc.Mappingは辞書のようなオブジェクトのための抽象クラスで、実装するのはなかなか大変です。
興味のある方は実装してみていただければと思います。
ここまでの調査結果を見ても、dataclassとして宣言してから拡張していくのがおすすめです。
3. 関数にデータを渡す方法
pandas.DataFrameやpandasには、データを読み込むのに便利な関数が定義されています。
これらの関数は、第一引数はおおむねデータの所在であるファイルパスかファイルオブジェクトです。
ファイルパスにはURLも指定出来たり、ファイルオブジェクトにExcelFileオブジェクトを指定してファイルを開いたままシートごとに処理をしたりすることもできます。
# | 名前 | 説明 |
---|---|---|
1 | pandas.DataFrame.from_dict | 辞書からDataFrameを作る関数。辞書のバリューが行データなのか列データなのかを指定できるが、指定しない場合はDataFrameコンストラクタを呼び出す。 frame.py#L1510-L1593。 |
2 | pandas.DataFrame.from_records | 行データからDataFrameを作る関数。from_dict以外のコンストラクタframe.py#L1943-L2124。 |
3 | pandas.read_pickle | Python独自のシリアライズ型式であるpickle形式のファイルでDataFrameを保存する。 |
4 | pandas.read_table | tsvファイルからデータを取り込むときに使う。区切り文字は変えることができる。read_csvとおおむね一緒。 |
5 | pandas.read_csv | csvファイルからデータを取り込むときに使う。区切り文字は変えることができる。read_tsvとおおむね一緒。 |
6 | pandas.read_fwf | 固定長レコードのデータを取り込むときに使う。 |
7 | pandas.read_clipboard | クリップボードからcsvファイルを読み込むときに使う。read_csvファイルのオプションをキーワード引数で指定できる。 |
8 | pandas.read_excel | xls形式やxlsx形式のファイルからデータを取り込むときに使う。シートを指定したり取り込むに使うエンジンを指定することもできる。 |
9 | pandas.read_json | jsonファイルからデータを取り込むときに使う。orient引数で読み込み方を調整できる。 |
10 | pandas.read_html | html形式の表からデータを取り込むときに使う。html内のテーブルをすべて対象とするため戻り値はDataFrameのリストである。htmlをパースするエンジンを指定できる。 |
11 | pandas.read_xml | xml形式のファイルからデータを取り込むときに使う。XPathを使ったり、xmlをパースするエンジンを指定できる。 |
12 | pandas.read_hdf | Hierarchical Data Format形式のファイルからデータを取り込むときに使う。 |
13 | pandas.read_feather | Apache ArrowのFeather形式のファイルからデータを取り込むときに使う。Feather形式は列指向のデータ格納形式である。 |
14 | pandas.read_parquet | Apache Parquetのparquet形式のファイルからデータを取り込むときに使う。parquet形式をパースするエンジンを指定できる。parquet形式は列指向のデータ格納形式である。 |
15 | pandas.read_orc | ORC形式のファイルからデータを取り込むときに使う。ORC形式は列指向のデータ格納形式である。 |
16 | pandas.read_sas | XPORTやSAS7BDAT形式のファイルからデータを取り込むときに使う。 |
17 | pandas.read_spss | SPSSのファイルからデータを取り込むときに使う。 |
18 | pandas.read_sql_table | RDBMSからデータを取り込むときに使う。テーブル名とコネクションを指定する。コネクションにはコネクションオブジェクトかURLを指定できるが、SQLALchemyがサポートしているRDBMSでないと使えないので注意が必要。 |
19 | pandas.read_sql_query | RDBMSからデータを取り込むときに使う。SQLとコネクションを指定する。コネクションにはコネクションオブジェクトかURLを指定できる。SQLite3のコネクションしかサポートしていないとあるが、DBAPIv2に則っていれば動作はする。 |
20 | pandas.read_sql | RDBMSからデータを取り込むときに使う。read_sql_tableおよびread_sql_queryのラッパーである。テーブル名が与えられた場合はread_sql_table、SQLが与えられた場合はread_sql_queryを呼び出す。 |
21 | pandas.read_gbq | Google BigQueryからデータを取り込むときに使う。SQLの実行結果をDataFrameとして取り込むことができる。 |
22 | pandas.read_stata | Stata形式のファイルからデータを取り込むときに使う。 |
まとめ
冒頭の結論をご覧ください。