pandas.DataFrameの作り方あれこれ


# pandas.DataFrameの作り方あれこれ

この記事では、pandasのDataFrameの作り方について説明します。
Python3.7以上pandas v1.3.5を想定していますが、pandasのバージョンについてはその限りではありません。

有用な使い方は説明できていないので、いい使い方があれば教えていただけると嬉しいです。

## 結論

長くなってしまったので、最初に結論を書きます。

**リストのリスト**や**辞書のリスト**だけでなく、**dataclassのリスト**や**namedtupleのリスト**からでもDataFrameを簡単に作れます。
リストや辞書だけでなく、dataclassやnamedtupleも用途に応じて使っていきましょう。


```python
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](https://qiita.com/ysdyt/items/9ccca82fc5b504e7913a)
* [7\-1\. pandasライブラリ — Pythonプログラミング入門 documentation](https://utokyo-ipp.github.io/7/7-1.html)
* [pandas \- Python Data Analysis Library](https://pandas.pydata.org/getting_started.html)
* [Getting started — pandas 1\.3\.5 documentation](https://pandas.pydata.org/docs/getting_started/index.html#getting-started)
* [10 minutes to pandas — pandas 1\.3\.5 documentation](https://pandas.pydata.org/docs/user_guide/10min.html)

## 1. DataFrameの作り方

DataFrameにはいくつの作り方があります。
方法を大別すると、**pandas.DataFrameコンストラクタ**にデータを渡す方法、**pandasに定義されている関数にデータを渡す方法**の2種類があります。
また、入力データ形式は、**リストのリスト**や**辞書のリスト**、**NumPy配列**や**CSV**、**SQL**など、いろいろなバリエーションがあります。

具体例や詳細な使い方については既に公開されている記事や公式ドキュメントを参考にしてください。

* [pandas\.DataFrameの構造とその作成方法 \| note\.nkmk\.me](https://note.nkmk.me/python-pandas-dataframe-values-columns-index/)
* [Pandas DataFrameを徹底解説！\(作成、行・列の追加と削除、indexなど\) \- AI\-interのPython3入門](https://ai-inter1.com/pandas-dataframe_basic/)
* [pandas\.DataFrame — pandas 1\.3\.5 documentation](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.DataFrame.html)
* [Input/output — pandas 1\.3\.5 documentation](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/io.html)

DataFrameを作る方法をいくつか並べるとこんな感じでしょうか。結構たくさんありますね。

* [pandas\.DataFrame](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.DataFrame.html)
* [pandas\.read\_csv](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_csv.html#pandas.read_csv)
* [pandas\.read\_excel](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_excel.html#pandas.read_excel)
* [pandas\.read\_json](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_json.html#pandas.read_json)
* [pandas\.read\_html](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_html.html#pandas.read_html)
* [pandas\.read\_xml](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_xml.html#pandas.read_xml)
* [pandas\.read\_parquet](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_parquet.html#pandas.read_parquet)
* [pandas\.read\_sql](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_sql.html#pandas.read_sql)
* etc.(省略した部分も含めた一覧は後述)

上記に含まれないものとしては、[AWS Data Wrangler](https://github.com/awslabs/aws-data-wrangler)のような、pandasの外側のライブラリでDataFrameを生成・処理できるものも存在します。

## 2. DataFrameコンストラクタにデータを渡す方法

pandasではDataFrameコンストラクタにデータを与えることで、DataFrameを作成することができます。
[公式ドキュメント](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.DataFrame.html)を見ると、

> data: ndarray (structured or homogeneous), Iterable, dict, or DataFrame

とあります。
つまり、**ndarray(NumPy配列)**、**Iterable**、**dict(辞書)**、**DataFrame**からDataFrameを作れるということです。

### 2.1. ndarrayとDataFrameの場合

ndarrayやDataFrameはそれ自身2次元のデータであるため、DataFrameが生成できるのは直感的です。
例は[公式ドキュメント](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.DataFrame.html)にも載っています。

NumPy配列からDataFrameを作る例を示します。

```python
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を作る例を示します。

```python
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を作る例を示します。

```python
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を作るところですでに出現しています。

```python
import pandas as pd

l = [[1, 2], [3, 4]]

pd.DataFrame(l, columns=["x", "y"])
```
||x|y|
|---|---|---|
|0|1|2|
|1|3|4|

この方法では、列名は自分で与える必要があるのが他の方法との差です。
与え忘れると、適当な列名になります。

```python
import pandas as pd

l = [[1, 2], [3, 4]]

pd.DataFrame(l)
```
||0|1|
|---|---|---|
|0|1|2|
|1|3|4|

#### 2.3.2. 辞書のリストを使う方法

次に思いつくのは、辞書のリストです。

```python
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](https://docs.python.org/ja/3/library/dataclasses.html)はPython3.7で導入された機能で、データを保持するのが主目的のクラスを宣言するときに便利な機能です。
辞書と比べると、型アノテーションが利用出来たり、コードの可読性が上がったり、値をイミュータブルにするなど制限を付けることができるといったメリットがあります。
自作クラスと比べると、いろいろなメソッドを自分で書かなくてよくなるメリットがあります。例えば、\_\_repr\_\_を実装しなくても、printした際の出力がきれいです。もちろん、\_\_repr\_\_の実装を変更することもできます。

前置きが長くなりましたが、dataclassのリストはどうでしょうか？やってみましょう。

```python
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については、既に素敵な解説記事がたくさんあります。

* [Python3\.7以上のデータ格納はdataclassを活用しよう \- Qiita](https://qiita.com/ttyszk/items/01934dc42cbd4f6665d2)
* [Python3\.7からは「Data Classes」がクラス定義のスタンダードになるかもしれない \- Qiita](https://qiita.com/tag1216/items/13b032348c893667862a)

#### 2.3.4. namedtupleを使う方法

dataclassからDataframeが作ることができるなら、namedtupleからも作ることができるのではないか、そう考えるのは自然なことでしょう。

```python
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については、既に素敵な解説記事がたくさんあります。

* [namedtupleで美しいpythonを書く！（翻訳） \- Qiita](https://qiita.com/Seny/items/add4d03876f505442136)
* [namedtuple\(名前付きタプル\)で簡易クラスを作る \- Qiita](https://qiita.com/tag1216/items/19fbc1a4f6a24dd7861b)

### 2.4. ちょっとだけ深堀り

dataclassやnamedtupleを使わずに、単にクラスを定義したらどうなるでしょうか。

#### 2.4.1. 自作クラスを使う方法(単純な実装)

とりあえず、メソッドなどを定義せずシンプルなクラスを作ってみます。

```python
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](https://github.com/pandas-dev/pandas/blob/v1.3.5/pandas/core/frame.py#L573-L776)です。
第一引数のdataがどんな型やクラスなのか判定して、処理を分岐しています。
DataFrameなのか辞書なのかNumPy配列なのかなどを判定した後に、[frame.py#L682-L726](https://github.com/pandas-dev/pandas/blob/v1.3.5/pandas/core/frame.py#L682-L726)でここまで判定したもの以外のIterableに対する処理が記述されています。

##### dataclassの場合
すこし横道にそれて、dataclassに対する処理は、[frame.py#L686-L687](https://github.com/pandas-dev/pandas/blob/v1.3.5/pandas/core/frame.py#L686-L687)です。
特別扱いされていますね。
上記の延長線上で、list(map(asdict, data))が呼ばれて、これ以降は辞書のリストと同じ扱いです。

##### namedtupleの場合
さらに横道にそれて、namedtupleに対する処理は、[frame.py#L694](https://github.com/pandas-dev/pandas/blob/v1.3.5/pandas/core/frame.py#L694)で呼び出されている[nested_data_to_arrays](https://github.com/pandas-dev/pandas/blob/v1.3.5/pandas/core/internals/construction.py#L469-L495)の[construction.py#L480-L481](https://github.com/pandas-dev/pandas/blob/v1.3.5/pandas/core/internals/construction.py#L480-L481)です。
列名さえ取れてしまえば、あとはタプルのリストと同じ扱いです。

##### classの場合

元の道に戻ります。
ただのクラスの場合、コードのどこを通るでしょうか。

[frame.py#L682](https://github.com/pandas-dev/pandas/blob/v1.3.5/pandas/core/frame.py#L682)のis_list_like(data)は、クラスのリストでもTrueになるはずです。
[frame.py#L683-L684](https://github.com/pandas-dev/pandas/blob/v1.3.5/pandas/core/frame.py#L683-L684)は、リストでないものをリスト化しているだけなのでスルーします。
[frame.py#L685](https://github.com/pandas-dev/pandas/blob/v1.3.5/pandas/core/frame.py#L685)は、空でないリストを渡しているのでTrueです。
[frame.py#L686-L687](https://github.com/pandas-dev/pandas/blob/v1.3.5/pandas/core/frame.py#L686-L687)は、データクラスではないのでifの中には入りません。

[frame.py#L688](https://github.com/pandas-dev/pandas/blob/v1.3.5/pandas/core/frame.py#L688)の[tread_as_nested](https://github.com/pandas-dev/pandas/blob/v1.3.5/pandas/core/internals/construction.py#L498-L507)は、4つないし5つの条件をチェックしています。
重要なのはis_list_like(data[0])です。
これは、クラスのリストの0番目(=結局クラス)がis_list_likeかをチェックしています。
[is_list_likeの公式ドキュメント](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.api.types.is_list_like.html)や[コードのコメント](https://github.com/pandas-dev/pandas/blob/v1.3.5/pandas/core/frame.py#L681)を見ると、is_list_likeは文字列以外のIterableであればTrueを返します。
つまり、値をそれ以上展開すべきかどうかを判定しているということですね。

今回はクラスの中身、Pointクラスのx座標とy座標を展開してほしいとすると、tread_as_nestedがTureとなりその配下のブロックを通るようになってほしいということになります。
よって、まずはIterableにしてみましょう。

#### 2.4.3. 自作クラスを使う方法(リスト劣化版)

Iterableにするために、\_\_iter\_\_と\_\_next\_\_を実装します。

```python
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](https://github.com/pandas-dev/pandas/blob/66e3805b8cabe977f40c05259cc3fcf7ead5687d/pandas/core/internals/construction.py#L796-L805)の中で列名を解決してくれそうでpandasの型ではないものは、[abc.Mapping](https://github.com/pandas-dev/pandas/blob/66e3805b8cabe977f40c05259cc3fcf7ead5687d/pandas/core/internals/construction.py#L798-L799)くらいでしょうか。
公式ドキュメントによると[abc.Mapping](https://docs.python.org/ja/3/library/collections.abc.html#collections.abc.Mapping)は辞書のようなオブジェクトのための抽象クラスで、実装するのはなかなか大変です。
興味のある方は実装してみていただければと思います。

ここまでの調査結果を見ても、**dataclassとして宣言してから拡張していく**のがおすすめです。

## 3. 関数にデータを渡す方法

pandas.DataFrameやpandasには、データを読み込むのに便利な関数が定義されています。
これらの関数は、第一引数はおおむねデータの所在であるファイルパスかファイルオブジェクトです。
ファイルパスにはURLも指定出来たり、ファイルオブジェクトにExcelFileオブジェクトを指定してファイルを開いたままシートごとに処理をしたりすることもできます。

|#|名前|説明|
|---|---|---|
|1|[pandas\.DataFrame\.from\_dict](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.DataFrame.from_dict.html#pandas.DataFrame.from_dict)|辞書からDataFrameを作る関数。辞書のバリューが行データなのか列データなのかを指定できるが、指定しない場合はDataFrameコンストラクタを呼び出す。 [frame.py#L1510-L1593](https://github.com/pandas-dev/pandas/blob/v1.3.5/pandas/core/frame.py#L1510-L1593)。|
|2|[pandas\.DataFrame\.from\_records](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.DataFrame.from_records.html#pandas.DataFrame.from_records)|行データからDataFrameを作る関数。from_dict以外のコンストラクタ[frame.py#L1943-L2124](https://github.com/pandas-dev/pandas/blob/v1.3.5/pandas/core/frame.py#L1943-L2124)。|
|3|[pandas\.read\_pickle](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_pickle.html#pandas.read_pickle)|Python独自のシリアライズ型式であるpickle形式のファイルでDataFrameを保存する。|
|4|[pandas\.read\_table](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_table.html#pandas.read_table)|tsvファイルからデータを取り込むときに使う。区切り文字は変えることができる。read_csvとおおむね一緒。|
|5|[pandas\.read\_csv](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_csv.html#pandas.read_csv)|csvファイルからデータを取り込むときに使う。区切り文字は変えることができる。read_tsvとおおむね一緒。|
|6|[pandas\.read\_fwf](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_fwf.html#pandas.read_fwf)|固定長レコードのデータを取り込むときに使う。|
|7|[pandas\.read\_clipboard](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_clipboard.html#pandas.read_clipboard)|クリップボードからcsvファイルを読み込むときに使う。read_csvファイルのオプションをキーワード引数で指定できる。|
|8|[pandas\.read\_excel](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_excel.html#pandas.read_excel)|xls形式やxlsx形式のファイルからデータを取り込むときに使う。シートを指定したり取り込むに使うエンジンを指定することもできる。|
|9|[pandas\.read\_json](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_json.html#pandas.read_json)|jsonファイルからデータを取り込むときに使う。orient引数で読み込み方を調整できる。|
|10|[pandas\.read\_html](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_html.html#pandas.read_html)|html形式の表からデータを取り込むときに使う。html内のテーブルをすべて対象とするため戻り値はDataFrameのリストである。htmlをパースするエンジンを指定できる。|
|11|[pandas\.read\_xml](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_xml.html#pandas.read_xml)|xml形式のファイルからデータを取り込むときに使う。XPathを使ったり、xmlをパースするエンジンを指定できる。|
|12|[pandas\.read\_hdf](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_hdf.html#pandas.read_hdf)|Hierarchical Data Format形式のファイルからデータを取り込むときに使う。|
|13|[pandas\.read\_feather](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_feather.html#pandas.read_feather)|Apache ArrowのFeather形式のファイルからデータを取り込むときに使う。Feather形式は列指向のデータ格納形式である。|
|14|[pandas\.read\_parquet](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_parquet.html#pandas.read_parquet)|Apache Parquetのparquet形式のファイルからデータを取り込むときに使う。parquet形式をパースするエンジンを指定できる。parquet形式は列指向のデータ格納形式である。|
|15|[pandas\.read\_orc](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_orc.html#pandas.read_orc)|ORC形式のファイルからデータを取り込むときに使う。ORC形式は列指向のデータ格納形式である。|
|16|[pandas\.read\_sas](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_sas.html#pandas.read_sas)|XPORTやSAS7BDAT形式のファイルからデータを取り込むときに使う。|
|17|[pandas\.read\_spss](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_spss.html#pandas.read_spss)|SPSSのファイルからデータを取り込むときに使う。|
|18|[pandas\.read\_sql\_table](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_sql_table.html#pandas.read_sql_table)|RDBMSからデータを取り込むときに使う。テーブル名とコネクションを指定する。コネクションにはコネクションオブジェクトかURLを指定できるが、[SQLALchemyがサポートしているRDBMS](https://docs.sqlalchemy.org/en/14/core/engines.html#supported-databases)でないと使えないので注意が必要。|
|19|[pandas\.read\_sql\_query](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_sql_query.html#pandas.read_sql_query)|RDBMSからデータを取り込むときに使う。SQLとコネクションを指定する。コネクションにはコネクションオブジェクトかURLを指定できる。SQLite3のコネクションしかサポートしていないとあるが、[DBAPIv2](https://www.python.org/dev/peps/pep-0249/)に則っていれば動作はする。|
|20|[pandas\.read\_sql](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_sql.html#pandas.read_sql)|RDBMSからデータを取り込むときに使う。read_sql_tableおよびread_sql_queryのラッパーである。テーブル名が与えられた場合はread_sql_table、SQLが与えられた場合はread_sql_queryを呼び出す。|
|21|[pandas\.read\_gbq](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_gbq.html#pandas.read_gbq)|Google BigQueryからデータを取り込むときに使う。SQLの実行結果をDataFrameとして取り込むことができる。|
|22|[pandas\.read\_stata](https://pandas.pydata.org/pandas-docs/version/1.3.5/reference/api/pandas.read_stata.html#pandas.read_stata)|Stata形式のファイルからデータを取り込むときに使う。|

## まとめ

冒頭の結論をご覧ください。