IO ツール (text, CSV, HDF5, ...)#

pandas の I/O API は、pandas.read_csv() のようにアクセスされ、通常 pandas オブジェクトを返す一連のトップレベルの reader 関数です。対応する writer 関数は、DataFrame.to_csv() のようにアクセスされるオブジェクトメソッドです。以下に利用可能な readerwriter の表を示します。

フォーマットタイプ

データの説明

リーダー

ライター

テキスト

CSV

read_csv

to_csv

テキスト

固定幅テキストファイル

read_fwf

NA

テキスト

JSON

read_json

to_json

テキスト

HTML

read_html

to_html

テキスト

LaTeX

Styler.to_latex

NA

テキスト

XML

read_xml

to_xml

テキスト

ローカルクリップボード

read_clipboard

to_clipboard

binary

MS Excel

read_excel

to_excel

binary

OpenDocument

read_excel

NA

binary

HDF5 フォーマット

read_hdf

to_hdf

binary

Feather フォーマット

read_feather

to_feather

binary

Parquet フォーマット

read_parquet

to_parquet

binary

ORC フォーマット

read_orc

to_orc

binary

Stata

read_stata

to_stata

binary

SAS

read_sas

NA

binary

SPSS

read_spss

NA

binary

Python Pickle フォーマット

read_pickle

to_pickle

SQL

SQL

read_sql

to_sql

SQL

Google BigQuery;:ref:read_gbq<io.bigquery>;:ref:to_gbq<io.bigquery>

これらの IO メソッドの一部について、非公式なパフォーマンス比較をここに示します。

StringIO クラスを使用する例では、Python 3 の場合は from io import StringIO でインポートするようにしてください。

CSV & テキストファイル#

テキストファイル (別名フラットファイル) を読み込むための主要な関数は read_csv() です。高度な戦略についてはクックブックを参照してください。

パースオプション#

read_csv() は以下の一般的な引数を受け入れます。

基本#

filepath_or_buffer各種

ファイルへのパス (strpathlib.Path、または py:py._path.local.LocalPath)、URL (http、ftp、S3 ロケーションを含む)、または read() メソッドを持つ任意のオブジェクト (開かれたファイルや StringIO など) のいずれか。

sepstr, read_csv() のデフォルトは ','read_table() のデフォルトは \t

使用する区切り文字。sepNone の場合、C エンジンは区切り文字を自動的に検出できませんが、Python パースエンジンは検出できます。つまり、後者が使用され、Python の組み込みスニファーツール csv.Sniffer によって区切り文字が自動的に検出されます。さらに、1 文字より長く '\s+' と異なる区切り文字は正規表現として解釈され、Python パースエンジンの使用も強制されます。正規表現の区切り文字は引用符付きデータを無視する傾向があることに注意してください。正規表現の例: '\\r\\t'

delimiterstr, デフォルトは None

sep の代替引数名。

delim_whitespaceブール値、デフォルトは False

空白文字 (例: ' ' または '\t') を区切り文字として使用するかどうかを指定します。sep='\s+' を設定するのと同等です。このオプションが True に設定されている場合、delimiter パラメータには何も渡すべきではありません。

列とインデックスの位置と名前#

headerint または int のリスト、デフォルトは 'infer'

列名およびデータの開始に使用する行番号。デフォルトの動作は列名を推論することです。名前が渡されない場合、動作は header=0 と同じになり、列名はファイルの最初の行から推論されます。列名が明示的に渡された場合、動作は header=None と同じになります。既存の名前を置き換えるには、header=0 を明示的に渡します。

ヘッダーは、列の MultiIndex の行位置を指定する int のリストにすることができます (例: [0,1,3])。指定されていない途中の行はスキップされます (例: この例では 2 がスキップされます)。このパラメータは、skip_blank_lines=True の場合、コメント行と空行を無視することに注意してください。したがって、header=0 はファイルの最初の行ではなくデータの最初の行を意味します。

names配列のような、デフォルトは None

使用する列名のリスト。ファイルにヘッダー行が含まれていない場合は、header=None を明示的に渡す必要があります。このリスト内の重複は許可されません。

index_colint, str, int / str のシーケンス、または False, オプション, デフォルトは None

文字列名または列インデックスとして指定された、DataFrame の行ラベルとして使用する列。int / str のシーケンスが与えられた場合、MultiIndex が使用されます。

index_col=False は、pandas に最初の列をインデックスとして*使用しない*ように強制するために使用できます。例えば、各行の末尾に区切り文字がある不正な形式のファイルがある場合など。

デフォルト値の None は、pandas に推測するように指示します。列ヘッダー行のフィールド数がデータファイルの本文のフィールド数と等しい場合、デフォルトのインデックスが使用されます。より大きい場合、残りの本文のフィールド数がヘッダーのフィールド数と等しくなるように、最初の列がインデックスとして使用されます。

ヘッダーの後の最初の行が列数を決定するために使用され、それがインデックスになります。その後の行が最初の行よりも少ない列数を含む場合、それらは NaN で埋められます。

これは usecols を使用することで回避できます。これにより、列がそのまま使用され、末尾のデータは無視されます。

usecolsリスト形式または呼び出し可能、デフォルトは None

列のサブセットを返します。リスト形式の場合、すべての要素は位置 (つまり、ドキュメント列への整数インデックス) または、ユーザーが names で提供した、またはドキュメントヘッダー行から推論された列名に対応する文字列でなければなりません。names が指定されている場合、ドキュメントヘッダー行は考慮されません。たとえば、有効なリスト形式の usecols パラメータは [0, 1, 2] または ['foo', 'bar', 'baz'] となります。

要素の順序は無視されます。そのため、usecols=[0, 1][1, 0] と同じです。data から要素の順序を保持したまま DataFrame をインスタンス化するには、['foo', 'bar'] の列順序の場合は pd.read_csv(data, usecols=['foo', 'bar'])[['foo', 'bar']] を使用し、['bar', 'foo'] の順序の場合は pd.read_csv(data, usecols=['foo', 'bar'])[['bar', 'foo']] を使用します。

呼び出し可能な場合、呼び出し可能な関数は列名に対して評価され、呼び出し可能な関数が True と評価される名前を返します。

In [1]: import pandas as pd

In [2]: from io import StringIO

In [3]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3"

In [4]: pd.read_csv(StringIO(data))
Out[4]: 
  col1 col2  col3
0    a    b     1
1    a    b     2
2    c    d     3

In [5]: pd.read_csv(StringIO(data), usecols=lambda x: x.upper() in ["COL1", "COL3"])
Out[5]: 
  col1  col3
0    a     1
1    a     2
2    c     3

C エンジンを使用する場合、このパラメーターを使用すると、解析時間が大幅に短縮され、メモリ使用量が削減されます。Python エンジンは、どの列を削除するかを決定する前にデータを最初にロードします。

一般的な解析設定#

dtype型名または column -> type の辞書、デフォルトは None

データまたは列のデータ型。例: {'a': np.float64, 'b': np.int32, 'c': 'Int64'}。適切な na_values 設定と組み合わせて str または object を使用すると、dtype を保持し、解釈しないことができます。コンバーターが指定されている場合、dtype 変換の**代わりに**適用されます。

バージョン 1.5.0 で追加: defaultdict のサポートが追加されました。明示的にリストされていない列の dtype を決定するデフォルトを含む defaultdict を入力として指定します。

dtype_backend{"numpy_nullable", "pyarrow"}, デフォルトは NumPy バックの DataFrames

使用する dtype_backend。たとえば、DataFrame が NumPy 配列を持つべきかどうか、"numpy_nullable" が設定されている場合は、null 許容の実装を持つすべての dtype に null 許容 dtype が使用され、"pyarrow" が設定されている場合は、すべての dtype に pyarrow が使用されます。

dtype_backend はまだ実験段階です。

バージョン 2.0 で追加されました。

engine{'c', 'python', 'pyarrow'}

使用するパーサーエンジン。C および pyarrow エンジンは高速ですが、python エンジンは現在、より多くの機能を備えています。マルチスレッドは現在、pyarrow エンジンのみがサポートしています。

バージョン 1.4.0 で追加: "pyarrow" エンジンが*実験的な*エンジンとして追加され、一部の機能はこのエンジンではサポートされていないか、正しく機能しない場合があります。

convertersdict, デフォルトは None

特定の列の値を変換するための関数の辞書。キーは整数または列ラベルのいずれかです。

true_valuesリスト、デフォルトは None

True とみなす値。

false_valuesリスト、デフォルトは None

False とみなす値。

skipinitialspaceブール値、デフォルトは False

区切り文字の後のスペースをスキップします。

skiprowsリスト形式または整数、デフォルトは None

スキップする行番号 (0 から始まる) またはファイルの先頭でスキップする行数 (int)。

呼び出し可能の場合、呼び出し可能関数は行インデックスに対して評価され、行をスキップする場合は True、それ以外の場合は False を返します。

In [6]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3"

In [7]: pd.read_csv(StringIO(data))
Out[7]: 
  col1 col2  col3
0    a    b     1
1    a    b     2
2    c    d     3

In [8]: pd.read_csv(StringIO(data), skiprows=lambda x: x % 2 != 0)
Out[8]: 
  col1 col2  col3
0    a    b     2
skipfooterint, デフォルトは 0

ファイルの末尾でスキップする行数 (engine='c' ではサポートされていません)。

nrowsint, デフォルトは None

読み込むファイルの行数。大きなファイルの断片を読み込むのに便利です。

low_memoryブール値、デフォルトは True

内部的にファイルをチャンクで処理することで、解析中のメモリ使用量を削減しますが、型推論が混在する可能性があります。混合型がないことを保証するには、False を設定するか、dtype パラメーターで型を指定します。ファイル全体は、いずれにしても単一の DataFrame に読み込まれることに注意してください。chunksize または iterator パラメーターを使用してデータをチャンクで返します。(C パーサーでのみ有効)

memory_mapブール値、デフォルトは False

filepath_or_buffer にファイルパスが提供されている場合、ファイルオブジェクトを直接メモリにマップし、そこから直接データにアクセスします。このオプションを使用すると、I/O オーバーヘッドがなくなるため、パフォーマンスが向上する可能性があります。

NA と欠損データの処理#

na_valuesスカラー、str、リスト形式、または dict、デフォルトは None

NA/NaN として認識する追加の文字列。辞書が渡された場合、列ごとの NA 値を指定します。デフォルトで NaN として解釈される値のリストについては、以下のNA 値の定数を参照してください。

keep_default_naブール値、デフォルトは True

データを解析する際に、デフォルトの NaN 値を含めるかどうか。 na_values が渡されるかどうかによって、動作は次のようになります。

  • keep_default_naTrue で、na_values が指定されている場合、na_values は解析に使用されるデフォルトの NaN 値に追加されます。

  • keep_default_naTrue で、na_values が指定されていない場合、デフォルトの NaN 値のみが解析に使用されます。

  • keep_default_naFalse で、na_values が指定されている場合、na_values で指定された NaN 値のみが解析に使用されます。

  • keep_default_naFalse で、na_values が指定されていない場合、文字列は NaN として解析されません。

na_filterFalse として渡された場合、keep_default_nana_values パラメーターは無視されることに注意してください。

na_filterブール値、デフォルトは True

欠損値マーカー (空文字列と na_values の値) を検出します。NA が含まれていないデータの場合、na_filter=False を渡すと、大きなファイルを読み込むパフォーマンスが向上する可能性があります。

verboseブール値、デフォルトは False

非数値列に配置された NA 値の数を示します。

skip_blank_linesブール値、デフォルトは True

True の場合、空行を NaN 値として解釈するのではなく、スキップします。

日時処理#

parse_datesブール値、int または名前のリスト、リストのリスト、または dict、デフォルトは False
  • True の場合 -> インデックスの解析を試みます。

  • [1, 2, 3] の場合 -> 列 1, 2, 3 をそれぞれ個別の日付列として解析しようとします。

  • [[1, 3]] の場合 -> 列 1 と 3 を結合して、単一の日付列として解析します。

  • {'foo': [1, 3]} の場合 -> 列 1, 3 を日付として解析し、結果を 'foo' とします。

ISO8601 形式の日付には高速パスが存在します。

infer_datetime_formatブール値、デフォルトは False

True で、列に対して parse_dates が有効になっている場合、処理を高速化するために datetime 形式を推測しようとします。

バージョン 2.0.0 以降非推奨: この引数の厳密なバージョンが現在デフォルトであり、渡しても効果はありません。

keep_date_colブール値、デフォルトは False

True で、parse_dates が複数の列の結合を指定する場合、元の列を保持します。

date_parser関数、デフォルトは None

文字列列のシーケンスを datetime インスタンスの配列に変換するために使用する関数。デフォルトは dateutil.parser.parser を使用して変換を行います。pandas は date_parser を 3 つの異なる方法で呼び出しを試み、例外が発生した場合は次に進みます。1) 1 つ以上の配列 (parse_dates で定義されたもの) を引数として渡す。2) parse_dates で定義された列からの文字列値を単一の配列に (行方向に) 連結し、それを渡す。3) 1 つ以上の文字列 (parse_dates で定義された列に対応する) を引数として、各行に対して date_parser を 1 回呼び出す。

バージョン 2.0.0 以降非推奨: 代わりに date_format を使用するか、object として読み込み、必要に応じて to_datetime() を適用してください。

date_formatstr または column -> format の辞書、デフォルトは None

parse_dates と組み合わせて使用すると、このフォーマットに従って日付が解析されます。より複雑な場合は、object として読み込み、必要に応じて to_datetime() を適用してください。

バージョン 2.0.0 で追加されました。

dayfirstブール値、デフォルトは False

DD/MM 形式の日付、国際およびヨーロッパ形式。

cache_datesブール値、デフォルトは True

True の場合、ユニークな変換済み日付のキャッシュを使用して datetime 変換を適用します。特にタイムゾーンオフセットのある重複する日付文字列を解析する場合、大幅な高速化をもたらす可能性があります。

反復処理#

iteratorブール値、デフォルトは False

反復処理または get_chunk() でチャンクを取得するための TextFileReader オブジェクトを返します。

chunksizeint, デフォルトは None

反復処理用の TextFileReader オブジェクトを返します。以下の反復処理とチャンク化を参照してください。

クォーティング、圧縮、ファイル形式#

compression{'infer', 'gzip', 'bz2', 'zip', 'xz', 'zstd', None, dict}, デフォルトは 'infer'

ディスク上のデータのオンザフライでの解凍用。 'infer' の場合、filepath_or_buffer が '.gz'、'.bz2'、'.zip'、'.xz'、'.zst' で終わるパスのようなものであれば、それぞれ gzip、bz2、zip、xz、または zstandard を使用し、それ以外の場合は解凍を行いません。 'zip' を使用する場合、ZIP ファイルには読み込むデータファイルが 1 つだけ含まれている必要があります。解凍しない場合は None を設定します。キー 'method' が {'zip', 'gzip', 'bz2', 'zstd'} のいずれかに設定され、その他のキーと値のペアが zipfile.ZipFilegzip.GzipFilebz2.BZ2File、または zstandard.ZstdDecompressor に転送される dict にすることもできます。例として、より高速な圧縮と再現性のある gzip アーカイブを作成するために、次のように渡すことができます: compression={'method': 'gzip', 'compresslevel': 1, 'mtime': 1}

バージョン 1.2.0 で変更: 以前のバージョンでは、'gzip' の dict エントリは gzip.open に転送されていました。

thousandsstr, デフォルトは None

桁区切り文字。

decimalstr, デフォルトは '.'

小数点として認識する文字。例: ヨーロッパのデータには ',' を使用します。

float_precisionstring, デフォルトは None

C エンジンが浮動小数点値に使用するコンバーターを指定します。オプションは、通常のコンバーターの場合は None、高精度コンバーターの場合は high、ラウンドトリップコンバーターの場合は round_trip です。

lineterminatorstr (長さ 1)、デフォルトは None

ファイルを改行で区切る文字。C パーサーでのみ有効です。

quotecharstr (長さ 1)

引用符で囲まれた項目の開始と終了を示すために使用される文字。引用符で囲まれた項目には区切り文字を含めることができ、その場合は無視されます。

quotingint または csv.QUOTE_* インスタンス、デフォルトは 0

csv.QUOTE_* 定数に従ってフィールドのクォーティング動作を制御します。QUOTE_MINIMAL (0)、QUOTE_ALL (1)、QUOTE_NONNUMERIC (2)、または QUOTE_NONE (3) のいずれかを使用します。

doublequoteブール値、デフォルトは True

quotechar が指定され、quotingQUOTE_NONE でない場合、フィールド**内**の連続する 2 つの quotechar 要素を単一の quotechar 要素として解釈するかどうかを示します。

escapecharstr (長さ 1)、デフォルトは None

quotingQUOTE_NONE の場合に区切り文字をエスケープするために使用される 1 文字の文字列。

commentstr, デフォルトは None

行の残りを解析すべきでないことを示します。行の先頭で見つかった場合、その行は完全に無視されます。このパラメータは単一の文字でなければなりません。空行 (skip_blank_lines=True である限り) と同様に、完全にコメント化された行は header パラメータでは無視されますが、skiprows では無視されません。たとえば、comment='#' の場合、header=0 で ' #empty\na,b,c\n1,2,3' を解析すると、'a,b,c' がヘッダーとして扱われます。

encodingstr, デフォルトは None

読み書き時に UTF に使用するエンコーディング (例: 'utf-8')。Python の標準エンコーディングのリスト

dialectstr または csv.Dialect インスタンス、デフォルトは None

提供された場合、このパラメータは次のパラメータの値を (デフォルトであろうとなかろうと) 上書きします: delimiterdoublequoteescapecharskipinitialspacequotechar、および quoting。値を上書きする必要がある場合、ParserWarning が発行されます。詳細については、csv.Dialect ドキュメントを参照してください。

エラー処理#

on_bad_lines('error', 'warn', 'skip'), デフォルトは 'error'

不正な行 (フィールドが多すぎる行) に遭遇した場合の動作を指定します。許可される値は次のとおりです。

  • 'error'、不正な行に遭遇した場合に ParserError を発生させます。

  • 'warn'、不正な行に遭遇した場合に警告を印刷し、その行をスキップします。

  • 'skip'、不正な行に遭遇してもエラーや警告を発生させずにスキップします。

バージョン 1.3.0 で追加。

列のデータ型の指定#

DataFrame 全体または個々の列のデータ型を示すことができます。

In [9]: import numpy as np

In [10]: data = "a,b,c,d\n1,2,3,4\n5,6,7,8\n9,10,11"

In [11]: print(data)
a,b,c,d
1,2,3,4
5,6,7,8
9,10,11

In [12]: df = pd.read_csv(StringIO(data), dtype=object)

In [13]: df
Out[13]: 
   a   b   c    d
0  1   2   3    4
1  5   6   7    8
2  9  10  11  NaN

In [14]: df["a"][0]
Out[14]: '1'

In [15]: df = pd.read_csv(StringIO(data), dtype={"b": object, "c": np.float64, "d": "Int64"})

In [16]: df.dtypes
Out[16]: 
a      int64
b     object
c    float64
d      Int64
dtype: object

幸いなことに、pandas は列に 1 つの dtype のみを含めることを保証するための複数の方法を提供します。これらの概念に慣れていない場合は、dtypes の詳細についてはこちらを、pandas での object 変換の詳細についてはこちらを参照してください。

例えば、read_csv()converters 引数を使用できます。

In [17]: data = "col_1\n1\n2\n'A'\n4.22"

In [18]: df = pd.read_csv(StringIO(data), converters={"col_1": str})

In [19]: df
Out[19]: 
  col_1
0     1
1     2
2   'A'
3  4.22

In [20]: df["col_1"].apply(type).value_counts()
Out[20]: 
col_1
<class 'str'>    4
Name: count, dtype: int64

または、データを読み込んだ後に to_numeric() 関数を使用して dtypes を強制することができます。

In [21]: df2 = pd.read_csv(StringIO(data))

In [22]: df2["col_1"] = pd.to_numeric(df2["col_1"], errors="coerce")

In [23]: df2
Out[23]: 
   col_1
0   1.00
1   2.00
2    NaN
3   4.22

In [24]: df2["col_1"].apply(type).value_counts()
Out[24]: 
col_1
<class 'float'>    4
Name: count, dtype: int64

これにより、有効な解析はすべて浮動小数点数に変換され、無効な解析は NaN のままになります。

最終的に、混合 dtype を含む列を読み込む方法をどのように処理するかは、特定のニーズによって異なります。上記の場合、データ異常を NaN にしたい場合、to_numeric() がおそらく最良の選択肢です。ただし、型に関係なくすべてのデータを強制したい場合は、read_csv()converters 引数を使用することは間違いなく試す価値があります。

場合によっては、混合 dtype を含む列を持つ異常なデータを読み込むと、一貫性のないデータセットになることがあります。pandas に列の dtype を推測させる場合、解析エンジンはデータセット全体を一度にではなく、データの異なるチャンクの dtype を推測します。その結果、混合 dtype を持つ列が生じることがあります。例えば、

In [25]: col_1 = list(range(500000)) + ["a", "b"] + list(range(500000))

In [26]: df = pd.DataFrame({"col_1": col_1})

In [27]: df.to_csv("foo.csv")

In [28]: mixed_df = pd.read_csv("foo.csv")

In [29]: mixed_df["col_1"].apply(type).value_counts()
Out[29]: 
col_1
<class 'int'>    737858
<class 'str'>    262144
Name: count, dtype: int64

In [30]: mixed_df["col_1"].dtype
Out[30]: dtype('O')

結果として mixed_df は、読み込まれたデータの混合 dtype のために、列の特定のチャンクでは int dtype、他のチャンクでは str を含むことになります。全体的な列は、混合 dtype の列に使用される objectdtype でマークされることに注意することが重要です。

dtype_backend="numpy_nullable" を設定すると、すべての列にnull許容なデータ型が適用されます。

In [31]: data = """a,b,c,d,e,f,g,h,i,j
   ....: 1,2.5,True,a,,,,,12-31-2019,
   ....: 3,4.5,False,b,6,7.5,True,a,12-31-2019,
   ....: """
   ....: 

In [32]: df = pd.read_csv(StringIO(data), dtype_backend="numpy_nullable", parse_dates=["i"])

In [33]: df
Out[33]: 
   a    b      c  d     e     f     g     h          i     j
0  1  2.5   True  a  <NA>  <NA>  <NA>  <NA> 2019-12-31  <NA>
1  3  4.5  False  b     6   7.5  True     a 2019-12-31  <NA>

In [34]: df.dtypes
Out[34]: 
a             Int64
b           Float64
c           boolean
d    string[python]
e             Int64
f           Float64
g           boolean
h    string[python]
i    datetime64[ns]
j             Int64
dtype: object

カテゴリカル dtype の指定#

Categorical 列は、dtype='category' または dtype=CategoricalDtype(categories, ordered) を指定することで直接解析できます。

In [35]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3"

In [36]: pd.read_csv(StringIO(data))
Out[36]: 
  col1 col2  col3
0    a    b     1
1    a    b     2
2    c    d     3

In [37]: pd.read_csv(StringIO(data)).dtypes
Out[37]: 
col1    object
col2    object
col3     int64
dtype: object

In [38]: pd.read_csv(StringIO(data), dtype="category").dtypes
Out[38]: 
col1    category
col2    category
col3    category
dtype: object

個々の列は、辞書指定を使用して Categorical として解析できます。

In [39]: pd.read_csv(StringIO(data), dtype={"col1": "category"}).dtypes
Out[39]: 
col1    category
col2      object
col3       int64
dtype: object

dtype='category' を指定すると、データのユニークな値が categories となる、順序付けされていない Categorical が生成されます。カテゴリと順序をより詳細に制御するには、事前に CategoricalDtype を作成し、その列の dtype として渡します。

In [40]: from pandas.api.types import CategoricalDtype

In [41]: dtype = CategoricalDtype(["d", "c", "b", "a"], ordered=True)

In [42]: pd.read_csv(StringIO(data), dtype={"col1": dtype}).dtypes
Out[42]: 
col1    category
col2      object
col3       int64
dtype: object

dtype=CategoricalDtype を使用する場合、dtype.categories の範囲外の「予期しない」値は欠損値として扱われます。

In [43]: dtype = CategoricalDtype(["a", "b", "d"])  # No 'c'

In [44]: pd.read_csv(StringIO(data), dtype={"col1": dtype}).col1
Out[44]: 
0      a
1      a
2    NaN
Name: col1, dtype: category
Categories (3, object): ['a', 'b', 'd']

これは Categorical.set_categories() の動作と一致します。

dtype='category' の場合、結果のカテゴリは常に文字列 (オブジェクト dtype) として解析されます。カテゴリが数値である場合、to_numeric() 関数、または必要に応じて to_datetime() などの別のコンバーターを使用して変換できます。

dtype が同種の categories (すべて数値、すべて日時など) を持つ CategoricalDtype の場合、変換は自動的に行われます。

In [45]: df = pd.read_csv(StringIO(data), dtype="category")

In [46]: df.dtypes
Out[46]: 
col1    category
col2    category
col3    category
dtype: object

In [47]: df["col3"]
Out[47]: 
0    1
1    2
2    3
Name: col3, dtype: category
Categories (3, object): ['1', '2', '3']

In [48]: new_categories = pd.to_numeric(df["col3"].cat.categories)

In [49]: df["col3"] = df["col3"].cat.rename_categories(new_categories)

In [50]: df["col3"]
Out[50]: 
0    1
1    2
2    3
Name: col3, dtype: category
Categories (3, int64): [1, 2, 3]

列の命名と使用#

列名の処理#

ファイルにはヘッダー行がある場合とない場合があります。pandas は最初の行が列名として使用されると仮定します。

In [51]: data = "a,b,c\n1,2,3\n4,5,6\n7,8,9"

In [52]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9

In [53]: pd.read_csv(StringIO(data))
Out[53]: 
   a  b  c
0  1  2  3
1  4  5  6
2  7  8  9

names 引数を header と組み合わせて指定することで、使用する別の名前と、ヘッダー行 (もしあれば) を破棄するかどうかを示すことができます。

In [54]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9

In [55]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=0)
Out[55]: 
   foo  bar  baz
0    1    2    3
1    4    5    6
2    7    8    9

In [56]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=None)
Out[56]: 
  foo bar baz
0   a   b   c
1   1   2   3
2   4   5   6
3   7   8   9

ヘッダーが最初の行以外の行にある場合は、行番号を header に渡します。これにより、前の行はスキップされます。

In [57]: data = "skip this skip it\na,b,c\n1,2,3\n4,5,6\n7,8,9"

In [58]: pd.read_csv(StringIO(data), header=1)
Out[58]: 
   a  b  c
0  1  2  3
1  4  5  6
2  7  8  9

デフォルトの動作は列名を推論することです。名前が渡されない場合、動作は header=0 と同じになり、列名はファイルの最初の空白でない行から推論されます。列名が明示的に渡された場合、動作は header=None と同じになります。

重複名の解析#

ファイルまたはヘッダーに重複する名前が含まれている場合、pandas はデフォルトでデータの重複を防ぐためにそれらを区別します。

In [59]: data = "a,b,a\n0,1,2\n3,4,5"

In [60]: pd.read_csv(StringIO(data))
Out[60]: 
   a  b  a.1
0  0  1    2
1  3  4    5

重複する列 'X', ..., 'X' が 'X', 'X.1', ..., 'X.N' となるため、重複データは存在しません。

列のフィルタリング (usecols)#

usecols 引数を使用すると、列名、位置番号、または呼び出し可能オブジェクトを使用して、ファイル内の列の任意のサブセットを選択できます。

In [61]: data = "a,b,c,d\n1,2,3,foo\n4,5,6,bar\n7,8,9,baz"

In [62]: pd.read_csv(StringIO(data))
Out[62]: 
   a  b  c    d
0  1  2  3  foo
1  4  5  6  bar
2  7  8  9  baz

In [63]: pd.read_csv(StringIO(data), usecols=["b", "d"])
Out[63]: 
   b    d
0  2  foo
1  5  bar
2  8  baz

In [64]: pd.read_csv(StringIO(data), usecols=[0, 2, 3])
Out[64]: 
   a  c    d
0  1  3  foo
1  4  6  bar
2  7  9  baz

In [65]: pd.read_csv(StringIO(data), usecols=lambda x: x.upper() in ["A", "C"])
Out[65]: 
   a  c
0  1  3
1  4  6
2  7  9

usecols 引数は、最終結果で使用しない列を指定するためにも使用できます。

In [66]: pd.read_csv(StringIO(data), usecols=lambda x: x not in ["a", "c"])
Out[66]: 
   b    d
0  2  foo
1  5  bar
2  8  baz

この場合、呼び出し可能オブジェクトは、出力から "a" および "c" 列を除外するように指定しています。

コメントと空行#

行コメントと空行の無視#

comment パラメータが指定されている場合、完全にコメント化された行は無視されます。デフォルトでは、完全に空白の行も無視されます。

In [67]: data = "\na,b,c\n  \n# commented line\n1,2,3\n\n4,5,6"

In [68]: print(data)

a,b,c
  
# commented line
1,2,3

4,5,6

In [69]: pd.read_csv(StringIO(data), comment="#")
Out[69]: 
   a  b  c
0  1  2  3
1  4  5  6

skip_blank_lines=False の場合、read_csv は空行を無視しません。

In [70]: data = "a,b,c\n\n1,2,3\n\n\n4,5,6"

In [71]: pd.read_csv(StringIO(data), skip_blank_lines=False)
Out[71]: 
     a    b    c
0  NaN  NaN  NaN
1  1.0  2.0  3.0
2  NaN  NaN  NaN
3  NaN  NaN  NaN
4  4.0  5.0  6.0

警告

無視された行の存在は、行番号に関連する曖昧さを生じさせる可能性があります。header パラメータは行番号を使用し (コメント/空行は無視)、skiprows は行番号を使用します (コメント/空行も含む)。

In [72]: data = "#comment\na,b,c\nA,B,C\n1,2,3"

In [73]: pd.read_csv(StringIO(data), comment="#", header=1)
Out[73]: 
   A  B  C
0  1  2  3

In [74]: data = "A,B,C\n#comment\na,b,c\n1,2,3"

In [75]: pd.read_csv(StringIO(data), comment="#", skiprows=2)
Out[75]: 
   a  b  c
0  1  2  3

headerskiprows の両方が指定されている場合、headerskiprows の終わりを基準とします。例えば、

In [76]: data = (
   ....:     "# empty\n"
   ....:     "# second empty line\n"
   ....:     "# third emptyline\n"
   ....:     "X,Y,Z\n"
   ....:     "1,2,3\n"
   ....:     "A,B,C\n"
   ....:     "1,2.,4.\n"
   ....:     "5.,NaN,10.0\n"
   ....: )
   ....: 

In [77]: print(data)
# empty
# second empty line
# third emptyline
X,Y,Z
1,2,3
A,B,C
1,2.,4.
5.,NaN,10.0


In [78]: pd.read_csv(StringIO(data), comment="#", skiprows=4, header=1)
Out[78]: 
     A    B     C
0  1.0  2.0   4.0
1  5.0  NaN  10.0

コメント#

ファイルにはコメントやメタデータが含まれる場合があります。

In [79]: data = (
   ....:     "ID,level,category\n"
   ....:     "Patient1,123000,x # really unpleasant\n"
   ....:     "Patient2,23000,y # wouldn't take his medicine\n"
   ....:     "Patient3,1234018,z # awesome"
   ....: )
   ....: 

In [80]: with open("tmp.csv", "w") as fh:
   ....:     fh.write(data)
   ....: 

In [81]: print(open("tmp.csv").read())
ID,level,category
Patient1,123000,x # really unpleasant
Patient2,23000,y # wouldn't take his medicine
Patient3,1234018,z # awesome

デフォルトでは、パーサーはコメントを出力に含めます。

In [82]: df = pd.read_csv("tmp.csv")

In [83]: df
Out[83]: 
         ID    level                        category
0  Patient1   123000           x # really unpleasant
1  Patient2    23000  y # wouldn't take his medicine
2  Patient3  1234018                     z # awesome

comment キーワードを使用してコメントを抑制できます。

In [84]: df = pd.read_csv("tmp.csv", comment="#")

In [85]: df
Out[85]: 
         ID    level category
0  Patient1   123000       x 
1  Patient2    23000       y 
2  Patient3  1234018       z 

Unicode データの処理#

encoding 引数は、エンコードされたユニコードデータに使用すべきであり、その結果、バイト文字列は結果でユニコードにデコードされます。

In [86]: from io import BytesIO

In [87]: data = b"word,length\n" b"Tr\xc3\xa4umen,7\n" b"Gr\xc3\xbc\xc3\x9fe,5"

In [88]: data = data.decode("utf8").encode("latin-1")

In [89]: df = pd.read_csv(BytesIO(data), encoding="latin-1")

In [90]: df
Out[90]: 
      word  length
0  Träumen       7
1    Grüße       5

In [91]: df["word"][1]
Out[91]: 'Grüße'

UTF-16 のようにすべての文字を複数のバイトでエンコードする一部の形式は、エンコーディングを指定しないとまったく正しく解析されません。Python の標準エンコーディングの全リスト

インデックス列と末尾の区切り文字#

ファイルに列名よりもデータ列が 1 つ多い場合、最初の列は DataFrame の行名として使用されます。

In [92]: data = "a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"

In [93]: pd.read_csv(StringIO(data))
Out[93]: 
        a    b     c
4   apple  bat   5.7
8  orange  cow  10.0
In [94]: data = "index,a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"

In [95]: pd.read_csv(StringIO(data), index_col=0)
Out[95]: 
            a    b     c
index                   
4       apple  bat   5.7
8      orange  cow  10.0

通常、index_col オプションを使用してこの動作を実現できます。

各データ行の末尾に区切り文字があるようにファイルが準備されており、パーサーを混乱させる例外的なケースがあります。インデックス列の推論を明示的に無効にし、最後の列を破棄するには、index_col=False を渡します。

In [96]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"

In [97]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,

In [98]: pd.read_csv(StringIO(data))
Out[98]: 
        a    b   c
4   apple  bat NaN
8  orange  cow NaN

In [99]: pd.read_csv(StringIO(data), index_col=False)
Out[99]: 
   a       b    c
0  4   apple  bat
1  8  orange  cow

usecols オプションを使用してデータのサブセットが解析されている場合、index_col の指定は元のデータではなく、そのサブセットに基づいています。

In [100]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"

In [101]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,

In [102]: pd.read_csv(StringIO(data), usecols=["b", "c"])
Out[102]: 
     b   c
4  bat NaN
8  cow NaN

In [103]: pd.read_csv(StringIO(data), usecols=["b", "c"], index_col=0)
Out[103]: 
     b   c
4  bat NaN
8  cow NaN

日付の処理#

日付列の指定#

日時データをより簡単に扱えるようにするために、read_csv() はキーワード引数 parse_datesdate_format を使用して、入力テキストデータを datetime オブジェクトに変換するためのさまざまな列と日付/時刻形式を指定できるようにします。

最も単純なケースは、単に parse_dates=True を渡すことです。

In [104]: with open("foo.csv", mode="w") as f:
   .....:     f.write("date,A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5")
   .....: 

# Use a column as an index, and parse it as dates.
In [105]: df = pd.read_csv("foo.csv", index_col=0, parse_dates=True)

In [106]: df
Out[106]: 
            A  B  C
date               
2009-01-01  a  1  2
2009-01-02  b  3  4
2009-01-03  c  4  5

# These are Python datetime objects
In [107]: df.index
Out[107]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', name='date', freq=None)

日付と時刻のデータを個別に保存したり、さまざまな日付フィールドを個別に保存したりしたい場合がよくあります。parse_dates キーワードは、日付や時刻を解析する列の組み合わせを指定するために使用できます。

parse_dates に列リストのリストを指定できます。結果の日付列は出力の先頭に追加され (既存の列順序に影響を与えないように)、新しい列名は構成要素の列名の連結になります。

In [108]: data = (
   .....:     "KORD,19990127, 19:00:00, 18:56:00, 0.8100\n"
   .....:     "KORD,19990127, 20:00:00, 19:56:00, 0.0100\n"
   .....:     "KORD,19990127, 21:00:00, 20:56:00, -0.5900\n"
   .....:     "KORD,19990127, 21:00:00, 21:18:00, -0.9900\n"
   .....:     "KORD,19990127, 22:00:00, 21:56:00, -0.5900\n"
   .....:     "KORD,19990127, 23:00:00, 22:56:00, -0.5900"
   .....: )
   .....: 

In [109]: with open("tmp.csv", "w") as fh:
   .....:     fh.write(data)
   .....: 

In [110]: df = pd.read_csv("tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]])

In [111]: df
Out[111]: 
                  1_2                 1_3     0     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59

デフォルトでは、パーサーは構成要素の日付列を削除しますが、keep_date_col キーワードを使用してそれらを保持することを選択できます。

In [112]: df = pd.read_csv(
   .....:     "tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]], keep_date_col=True
   .....: )
   .....: 

In [113]: df
Out[113]: 
                  1_2                 1_3     0  ...          2          3     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  ...   19:00:00   18:56:00  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  ...   20:00:00   19:56:00  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD  ...   21:00:00   20:56:00 -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD  ...   21:00:00   21:18:00 -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD  ...   22:00:00   21:56:00 -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD  ...   23:00:00   22:56:00 -0.59

[6 rows x 7 columns]

複数の列を単一の日付列に結合したい場合は、ネストされたリストを使用する必要があることに注意してください。つまり、parse_dates=[1, 2] は 2 番目と 3 番目の列をそれぞれ個別の日付列として解析する必要があることを示し、parse_dates=[[1, 2]] は 2 つの列を単一の列に解析する必要があることを意味します。

辞書を使用してカスタム名列を指定することもできます。

In [114]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}

In [115]: df = pd.read_csv("tmp.csv", header=None, parse_dates=date_spec)

In [116]: df
Out[116]: 
              nominal              actual     0     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59

複数のテキスト列を単一の日付列に解析する場合、新しい列がデータの先頭に追加されることを忘れてはなりません。index_col の指定は、元のデータ列ではなく、この新しい列のセットに基づいています。

In [117]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}

In [118]: df = pd.read_csv(
   .....:     "tmp.csv", header=None, parse_dates=date_spec, index_col=0
   .....: )  # index is the nominal column
   .....: 

In [119]: df
Out[119]: 
                                 actual     0     4
nominal                                            
1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59

列またはインデックスに解析できない日付が含まれている場合、列またはインデックス全体がオブジェクトデータ型として変更されずに返されます。非標準の日時解析には、pd.read_csv の後に to_datetime() を使用してください。

read_csv には、ISO8601 形式、例: "2000-01-01T00:01:02+00:00" および同様のバリエーションの datetime 文字列を解析するための高速パスがあります。データをこの形式で保存するように手配できれば、読み込み時間は大幅に高速化されます (~20 倍が確認されています)。

バージョン 2.2.0 以降非推奨: read_csv 内での日付列の結合は非推奨です。代わりに、関連する結果列に対して pd.to_datetime を使用してください。

日付解析関数#

最後に、パーサーではカスタムの date_format を指定できます。パフォーマンスの観点から、次の順序で日付解析メソッドを試す必要があります。

  1. 形式がわかっている場合は、date_format を使用します。例: date_format="%d/%m/%Y" または date_format={column_name: "%d/%m/%Y"}

  2. 異なる列に異なるフォーマットを使用する場合、または to_datetime に追加のオプション (例: utc) を渡したい場合は、データを object dtype として読み込み、その後 to_datetime を使用する必要があります。

混合タイムゾーンを持つ CSV の解析#

pandas は、混合タイムゾーンを持つ列またはインデックスをネイティブに表現することはできません。CSV ファイルに混合タイムゾーンを持つ列が含まれている場合、parse_dates を使用しても、デフォルトの結果は文字列を含むオブジェクト dtype の列になります。混合タイムゾーンの値を datetime 列として解析するには、object dtype として読み込み、次に to_datetime()utc=True と共に呼び出します。

In [120]: content = """\
   .....: a
   .....: 2000-01-01T00:00:00+05:00
   .....: 2000-01-01T00:00:00+06:00"""
   .....: 

In [121]: df = pd.read_csv(StringIO(content))

In [122]: df["a"] = pd.to_datetime(df["a"], utc=True)

In [123]: df["a"]
Out[123]: 
0   1999-12-31 19:00:00+00:00
1   1999-12-31 18:00:00+00:00
Name: a, dtype: datetime64[ns, UTC]

日時フォーマットの推測#

以下は、推測できる日時文字列のいくつかの例です (すべて 2011 年 12 月 30 日 00:00:00 を表します)。

  • "20111230"

  • "2011/12/30"

  • "20111230 00:00:00"

  • "12/30/2011 00:00:00"

  • "30/Dec/2011 00:00:00"

  • "30/December/2011 00:00:00"

フォーマットの推論は dayfirst の影響を受けることに注意してください。dayfirst=True の場合、"01/12/2011" は 12 月 1 日と推測されます。dayfirst=False (デフォルト) の場合、"01/12/2011" は 1 月 12 日と推測されます。

日付文字列の列を解析しようとすると、pandas は最初の NaN 以外の要素からフォーマットを推測し、そのフォーマットで列の残りを解析します。pandas がフォーマットを推測できない場合 (たとえば、最初の文字列が '01 December US/Pacific 2000' の場合など)、警告が発生し、各行は dateutil.parser.parse によって個別に解析されます。日付を解析する最も安全な方法は、format= を明示的に設定することです。

In [124]: df = pd.read_csv(
   .....:     "foo.csv",
   .....:     index_col=0,
   .....:     parse_dates=True,
   .....: )
   .....: 

In [125]: df
Out[125]: 
            A  B  C
date               
2009-01-01  a  1  2
2009-01-02  b  3  4
2009-01-03  c  4  5

同じ列内に混合された日時フォーマットがある場合は、format='mixed' を渡すことができます。

In [126]: data = StringIO("date\n12 Jan 2000\n2000-01-13\n")

In [127]: df = pd.read_csv(data)

In [128]: df['date'] = pd.to_datetime(df['date'], format='mixed')

In [129]: df
Out[129]: 
        date
0 2000-01-12
1 2000-01-13

または、日時フォーマットがすべて ISO8601 である場合 (必ずしも同一のフォーマットではない可能性があります)

In [130]: data = StringIO("date\n2020-01-01\n2020-01-01 03:00\n")

In [131]: df = pd.read_csv(data)

In [132]: df['date'] = pd.to_datetime(df['date'], format='ISO8601')

In [133]: df
Out[133]: 
                 date
0 2020-01-01 00:00:00
1 2020-01-01 03:00:00

国際的な日付形式#

米国の日付形式は MM/DD/YYYY である傾向がありますが、多くの国際的な形式では DD/MM/YYYY が使用されます。便宜上、dayfirst キーワードが提供されています。

In [134]: data = "date,value,cat\n1/6/2000,5,a\n2/6/2000,10,b\n3/6/2000,15,c"

In [135]: print(data)
date,value,cat
1/6/2000,5,a
2/6/2000,10,b
3/6/2000,15,c

In [136]: with open("tmp.csv", "w") as fh:
   .....:     fh.write(data)
   .....: 

In [137]: pd.read_csv("tmp.csv", parse_dates=[0])
Out[137]: 
        date  value cat
0 2000-01-06      5   a
1 2000-02-06     10   b
2 2000-03-06     15   c

In [138]: pd.read_csv("tmp.csv", dayfirst=True, parse_dates=[0])
Out[138]: 
        date  value cat
0 2000-06-01      5   a
1 2000-06-02     10   b
2 2000-06-03     15   c

CSV をバイナリファイルオブジェクトに書き込む#

バージョン 1.2.0 で追加されました。

df.to_csv(..., mode="wb") を使用すると、CSV をバイナリモードで開いたファイルオブジェクトに書き込むことができます。ほとんどの場合、mode を指定する必要はありません。Pandas はファイルオブジェクトがテキストモードで開かれているかバイナリモードで開かれているかを自動的に検出するためです。

In [139]: import io

In [140]: data = pd.DataFrame([0, 1, 2])

In [141]: buffer = io.BytesIO()

In [142]: data.to_csv(buffer, encoding="utf-8", compression="gzip")

浮動小数点変換のメソッド指定#

C エンジンで解析中に特定の浮動小数点コンバーターを使用するために、float_precision パラメーターを指定できます。オプションは、通常のコンバーター、高精度コンバーター、およびラウンドトリップコンバーター (ファイルに書き込んだ後に値をラウンドトリップすることが保証されます) です。例:

In [143]: val = "0.3066101993807095471566981359501369297504425048828125"

In [144]: data = "a,b,c\n1,2,{0}".format(val)

In [145]: abs(
   .....:     pd.read_csv(
   .....:         StringIO(data),
   .....:         engine="c",
   .....:         float_precision=None,
   .....:     )["c"][0] - float(val)
   .....: )
   .....: 
Out[145]: 5.551115123125783e-17

In [146]: abs(
   .....:     pd.read_csv(
   .....:         StringIO(data),
   .....:         engine="c",
   .....:         float_precision="high",
   .....:     )["c"][0] - float(val)
   .....: )
   .....: 
Out[146]: 5.551115123125783e-17

In [147]: abs(
   .....:     pd.read_csv(StringIO(data), engine="c", float_precision="round_trip")["c"][0]
   .....:     - float(val)
   .....: )
   .....: 
Out[147]: 0.0

千単位の区切り文字#

千単位の区切り文字で書かれた大きな数字の場合、thousands キーワードに長さ 1 の文字列を設定することで、整数を正しく解析できます。

デフォルトでは、千単位の区切り文字を持つ数値は文字列として解析されます。

In [148]: data = (
   .....:     "ID|level|category\n"
   .....:     "Patient1|123,000|x\n"
   .....:     "Patient2|23,000|y\n"
   .....:     "Patient3|1,234,018|z"
   .....: )
   .....: 

In [149]: with open("tmp.csv", "w") as fh:
   .....:     fh.write(data)
   .....: 

In [150]: df = pd.read_csv("tmp.csv", sep="|")

In [151]: df
Out[151]: 
         ID      level category
0  Patient1    123,000        x
1  Patient2     23,000        y
2  Patient3  1,234,018        z

In [152]: df.level.dtype
Out[152]: dtype('O')

thousands キーワードを使用すると、整数が正しく解析されます。

In [153]: df = pd.read_csv("tmp.csv", sep="|", thousands=",")

In [154]: df
Out[154]: 
         ID    level category
0  Patient1   123000        x
1  Patient2    23000        y
2  Patient3  1234018        z

In [155]: df.level.dtype
Out[155]: dtype('int64')

NA 値#

欠損値 (これは NaN で示されます) として解析される値を制御するには、na_values に文字列を指定します。文字列のリストを指定した場合、その中のすべての値が欠損値とみなされます。数値 (5.0 のような float5 のような integer) を指定した場合、対応する同等の値も欠損値を意味します (この場合、事実上 [5.0, 5]NaN として認識されます)。

欠損値として認識されるデフォルトの値を完全に上書きするには、keep_default_na=False を指定します。

デフォルトで NaN として認識される値は ['-1.#IND', '1.#QNAN', '1.#IND', '-1.#QNAN', '#N/A N/A', '#N/A', 'N/A', 'n/a', 'NA', '<NA>', '#NA', 'NULL', 'null', 'NaN', '-NaN', 'nan', '-nan', 'None', ''] です。

いくつかの例を見てみましょう。

pd.read_csv("path_to_file.csv", na_values=[5])

上記の例では、55.0 はデフォルトに加えて NaN として認識されます。文字列は最初に数値の 5 として解釈され、次に NaN として解釈されます。

pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=[""])

上記では、空のフィールドのみが NaN として認識されます。

pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=["NA", "0"])

上記では、文字列としての NA0 の両方が NaN です。

pd.read_csv("path_to_file.csv", na_values=["Nope"])

デフォルト値に加えて、文字列 "Nope"NaN として認識されます。

無限大#

inf のような値は np.inf (正の無限大) として、-inf-np.inf (負の無限大) として解析されます。これらは値のケースを無視するため、Infnp.inf として解析されます。

ブール値#

一般的な値 TrueFalseTRUEFALSE はすべてブール値として認識されます。場合によっては、他の値をブール値として認識させたい場合があります。これを行うには、次のように true_values および false_values オプションを使用します。

In [156]: data = "a,b,c\n1,Yes,2\n3,No,4"

In [157]: print(data)
a,b,c
1,Yes,2
3,No,4

In [158]: pd.read_csv(StringIO(data))
Out[158]: 
   a    b  c
0  1  Yes  2
1  3   No  4

In [159]: pd.read_csv(StringIO(data), true_values=["Yes"], false_values=["No"])
Out[159]: 
   a      b  c
0  1   True  2
1  3  False  4

「不正な」行の処理#

一部のファイルには、フィールドが少なすぎたり多すぎたりする不正な形式の行が含まれている場合があります。フィールドが少なすぎる行は、末尾のフィールドに NA 値が埋められます。フィールドが多すぎる行は、デフォルトでエラーを発生させます。

In [160]: data = "a,b,c\n1,2,3\n4,5,6,7\n8,9,10"

In [161]: pd.read_csv(StringIO(data))
---------------------------------------------------------------------------
ParserError                               Traceback (most recent call last)
Cell In[161], line 1
----> 1 pd.read_csv(StringIO(data))

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1026, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
   1013 kwds_defaults = _refine_defaults_read(
   1014     dialect,
   1015     delimiter,
   (...)
   1022     dtype_backend=dtype_backend,
   1023 )
   1024 kwds.update(kwds_defaults)
-> 1026 return _read(filepath_or_buffer, kwds)

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:626, in _read(filepath_or_buffer, kwds)
    623     return parser
    625 with parser:
--> 626     return parser.read(nrows)

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1923, in TextFileReader.read(self, nrows)
   1916 nrows = validate_integer("nrows", nrows)
   1917 try:
   1918     # error: "ParserBase" has no attribute "read"
   1919     (
   1920         index,
   1921         columns,
   1922         col_dict,
-> 1923     ) = self._engine.read(  # type: ignore[attr-defined]
   1924         nrows
   1925     )
   1926 except Exception:
   1927     self.close()

File ~/work/pandas/pandas/pandas/io/parsers/c_parser_wrapper.py:234, in CParserWrapper.read(self, nrows)
    232 try:
    233     if self.low_memory:
--> 234         chunks = self._reader.read_low_memory(nrows)
    235         # destructive to chunks
    236         data = _concatenate_chunks(chunks)

File ~/work/pandas/pandas/pandas/_libs/parsers.pyx:838, in pandas._libs.parsers.TextReader.read_low_memory()

File ~/work/pandas/pandas/pandas/_libs/parsers.pyx:905, in pandas._libs.parsers.TextReader._read_rows()

File ~/work/pandas/pandas/pandas/_libs/parsers.pyx:874, in pandas._libs.parsers.TextReader._tokenize_rows()

File ~/work/pandas/pandas/pandas/_libs/parsers.pyx:891, in pandas._libs.parsers.TextReader._check_tokenize_status()

File ~/work/pandas/pandas/pandas/_libs/parsers.pyx:2061, in pandas._libs.parsers.raise_parser_error()

ParserError: Error tokenizing data. C error: Expected 3 fields in line 3, saw 4

不正な行をスキップすることを選択できます。

In [162]: data = "a,b,c\n1,2,3\n4,5,6,7\n8,9,10"

In [163]: pd.read_csv(StringIO(data), on_bad_lines="skip")
Out[163]: 
   a  b   c
0  1  2   3
1  8  9  10

バージョン 1.4.0 で追加されました。

または、engine="python" の場合、不正な行を処理するために呼び出し可能な関数を渡します。不正な行は、sep で分割された文字列のリストになります。

In [164]: external_list = []

In [165]: def bad_lines_func(line):
   .....:     external_list.append(line)
   .....:     return line[-3:]
   .....: 

In [166]: external_list
Out[166]: []

呼び出し可能な関数は、フィールドが多すぎる行のみを処理します。その他のエラーによって引き起こされる不正な行は、静かにスキップされます。

In [167]: bad_lines_func = lambda line: print(line)

In [168]: data = 'name,type\nname a,a is of type a\nname b,"b\" is of type b"'

In [169]: data
Out[169]: 'name,type\nname a,a is of type a\nname b,"b" is of type b"'

In [170]: pd.read_csv(StringIO(data), on_bad_lines=bad_lines_func, engine="python")
Out[170]: 
     name            type
0  name a  a is of type a

この場合、行は処理されませんでした。ここでの「不正な行」はエスケープ文字によって引き起こされたためです。

usecols パラメータを使用して、一部の行に表示されるが他の行には表示されない余分な列データを削除することもできます。

In [171]: pd.read_csv(StringIO(data), usecols=[0, 1, 2])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[171], line 1
----> 1 pd.read_csv(StringIO(data), usecols=[0, 1, 2])

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1026, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
   1013 kwds_defaults = _refine_defaults_read(
   1014     dialect,
   1015     delimiter,
   (...)
   1022     dtype_backend=dtype_backend,
   1023 )
   1024 kwds.update(kwds_defaults)
-> 1026 return _read(filepath_or_buffer, kwds)

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:620, in _read(filepath_or_buffer, kwds)
    617 _validate_names(kwds.get("names", None))
    619 # Create the parser.
--> 620 parser = TextFileReader(filepath_or_buffer, **kwds)
    622 if chunksize or iterator:
    623     return parser

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1620, in TextFileReader.__init__(self, f, engine, **kwds)
   1617     self.options["has_index_names"] = kwds["has_index_names"]
   1619 self.handles: IOHandles | None = None
-> 1620 self._engine = self._make_engine(f, self.engine)

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1898, in TextFileReader._make_engine(self, f, engine)
   1895     raise ValueError(msg)
   1897 try:
-> 1898     return mapping[engine](f, **self.options)
   1899 except Exception:
   1900     if self.handles is not None:

File ~/work/pandas/pandas/pandas/io/parsers/c_parser_wrapper.py:155, in CParserWrapper.__init__(self, src, **kwds)
    152     # error: Cannot determine type of 'names'
    153     if len(self.names) < len(usecols):  # type: ignore[has-type]
    154         # error: Cannot determine type of 'names'
--> 155         self._validate_usecols_names(
    156             usecols,
    157             self.names,  # type: ignore[has-type]
    158         )
    160 # error: Cannot determine type of 'names'
    161 self._validate_parse_dates_presence(self.names)  # type: ignore[has-type]

File ~/work/pandas/pandas/pandas/io/parsers/base_parser.py:988, in ParserBase._validate_usecols_names(self, usecols, names)
    986 missing = [c for c in usecols if c not in names]
    987 if len(missing) > 0:
--> 988     raise ValueError(
    989         f"Usecols do not match columns, columns expected but not found: "
    990         f"{missing}"
    991     )
    993 return usecols

ValueError: Usecols do not match columns, columns expected but not found: [0, 1, 2]

フィールドが多すぎる行を含むすべてのデータを保持したい場合は、十分な数の names を指定できます。これにより、フィールドが不足している行が NaN で埋められます。

In [172]: pd.read_csv(StringIO(data), names=['a', 'b', 'c', 'd'])
Out[172]: 
        a                b   c   d
0    name             type NaN NaN
1  name a   a is of type a NaN NaN
2  name b  b is of type b" NaN NaN

ダイアレクト#

dialect キーワードは、ファイル形式を指定する際に柔軟性を提供します。デフォルトでは Excel ダイアレクトを使用しますが、ダイアレクト名または csv.Dialect インスタンスのいずれかを指定できます。

囲まれていない引用符を持つデータがあったとします。

In [173]: data = "label1,label2,label3\n" 'index1,"a,c,e\n' "index2,b,d,f"

In [174]: print(data)
label1,label2,label3
index1,"a,c,e
index2,b,d,f

デフォルトでは、read_csv は Excel ダイアレクトを使用し、二重引用符を引用符文字として扱います。これにより、閉じ二重引用符が見つかる前に改行が見つかると失敗します。

これは dialect を使用して回避できます。

In [175]: import csv

In [176]: dia = csv.excel()

In [177]: dia.quoting = csv.QUOTE_NONE

In [178]: pd.read_csv(StringIO(data), dialect=dia)
Out[178]: 
       label1 label2 label3
index1     "a      c      e
index2      b      d      f

すべてのダイアレクトオプションは、キーワード引数で個別に指定できます。

In [179]: data = "a,b,c~1,2,3~4,5,6"

In [180]: pd.read_csv(StringIO(data), lineterminator="~")
Out[180]: 
   a  b  c
0  1  2  3
1  4  5  6

もう一つの一般的なダイアレクトオプションは、区切り文字の後の空白をスキップするための skipinitialspace です。

In [181]: data = "a, b, c\n1, 2, 3\n4, 5, 6"

In [182]: print(data)
a, b, c
1, 2, 3
4, 5, 6

In [183]: pd.read_csv(StringIO(data), skipinitialspace=True)
Out[183]: 
   a  b  c
0  1  2  3
1  4  5  6

パーサーは「正しいことをする」ためにあらゆる試みを行い、脆弱にならないようにします。型推論は非常に重要です。列が内容を変更せずに整数 dtype に強制できる場合、パーサーはそうします。非数値列は、残りの pandas オブジェクトと同様にオブジェクト dtype として渡されます。

クォーティングとエスケープ文字#

埋め込みフィールド内の引用符 (およびその他のエスケープ文字) は、さまざまな方法で処理できます。1 つの方法はバックスラッシュを使用することです。このデータを適切に解析するには、escapechar オプションを渡す必要があります。

In [184]: data = 'a,b\n"hello, \\"Bob\\", nice to see you",5'

In [185]: print(data)
a,b
"hello, \"Bob\", nice to see you",5

In [186]: pd.read_csv(StringIO(data), escapechar="\\")
Out[186]: 
                               a  b
0  hello, "Bob", nice to see you  5

固定幅の列を持つファイル#

read_csv() は区切りデータを読み込みますが、read_fwf() 関数は、既知の固定列幅を持つデータファイルで機能します。read_fwf の関数パラメータは read_csv とほぼ同じですが、2 つの追加パラメータと delimiter パラメータの使い方が異なります。

  • colspecs: 各行の固定幅フィールドの範囲を半開区間 (つまり、[from, to[ ) で与えるペア (タプル) のリスト。文字列値 'infer' を使用して、データの最初の 100 行から列の仕様を検出するようにパーサーに指示できます。指定されていない場合のデフォルトの動作は推測することです。

  • widths: 区間が連続している場合、'colspecs' の代わりに使用できるフィールド幅のリスト。

  • delimiter: 固定幅ファイル内の埋め文字として考慮する文字。スペースではない場合 (例: '~')、フィールドの埋め文字を指定するために使用できます。

典型的な固定幅データファイルを考えます。

In [187]: data1 = (
   .....:     "id8141    360.242940   149.910199   11950.7\n"
   .....:     "id1594    444.953632   166.985655   11788.4\n"
   .....:     "id1849    364.136849   183.628767   11806.2\n"
   .....:     "id1230    413.836124   184.375703   11916.8\n"
   .....:     "id1948    502.953953   173.237159   12468.3"
   .....: )
   .....: 

In [188]: with open("bar.csv", "w") as f:
   .....:     f.write(data1)
   .....: 

このファイルを DataFrame に解析するには、ファイル名とともに read_fwf 関数に列の仕様を提供するだけで済みます。

# Column specifications are a list of half-intervals
In [189]: colspecs = [(0, 6), (8, 20), (21, 33), (34, 43)]

In [190]: df = pd.read_fwf("bar.csv", colspecs=colspecs, header=None, index_col=0)

In [191]: df
Out[191]: 
                 1           2        3
0                                      
id8141  360.242940  149.910199  11950.7
id1594  444.953632  166.985655  11788.4
id1849  364.136849  183.628767  11806.2
id1230  413.836124  184.375703  11916.8
id1948  502.953953  173.237159  12468.3

header=None 引数が指定されている場合、パーサーが自動的に列名 X.<列番号> を選択することに注目してください。あるいは、連続する列の列幅のみを指定することもできます。

# Widths are a list of integers
In [192]: widths = [6, 14, 13, 10]

In [193]: df = pd.read_fwf("bar.csv", widths=widths, header=None)

In [194]: df
Out[194]: 
        0           1           2        3
0  id8141  360.242940  149.910199  11950.7
1  id1594  444.953632  166.985655  11788.4
2  id1849  364.136849  183.628767  11806.2
3  id1230  413.836124  184.375703  11916.8
4  id1948  502.953953  173.237159  12468.3

パーサーが列の周りの余分な空白を処理するため、ファイル内の列間に余分な区切りがあっても問題ありません。

デフォルトでは、read_fwf はファイルの最初の 100 行を使用してファイルの colspecs を推測しようとします。これは、列が整列され、指定された delimiter (デフォルトの区切り文字は空白) で正しく区切られている場合にのみ可能です。

In [195]: df = pd.read_fwf("bar.csv", header=None, index_col=0)

In [196]: df
Out[196]: 
                 1           2        3
0                                      
id8141  360.242940  149.910199  11950.7
id1594  444.953632  166.985655  11788.4
id1849  364.136849  183.628767  11806.2
id1230  413.836124  184.375703  11916.8
id1948  502.953953  173.237159  12468.3

read_fwf は、解析された列の型を推測された型と異なるように指定するための dtype パラメーターをサポートしています。

In [197]: pd.read_fwf("bar.csv", header=None, index_col=0).dtypes
Out[197]: 
1    float64
2    float64
3    float64
dtype: object

In [198]: pd.read_fwf("bar.csv", header=None, dtype={2: "object"}).dtypes
Out[198]: 
0     object
1    float64
2     object
3    float64
dtype: object

インデックス#

「暗黙の」インデックス列を持つファイル#

ヘッダーの項目数がデータ列の数より 1 つ少ないファイルを考えます。

In [199]: data = "A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5"

In [200]: print(data)
A,B,C
20090101,a,1,2
20090102,b,3,4
20090103,c,4,5

In [201]: with open("foo.csv", "w") as f:
   .....:     f.write(data)
   .....: 

この特殊なケースでは、read_csv は最初の列が DataFrame のインデックスとして使用されると仮定します。

In [202]: pd.read_csv("foo.csv")
Out[202]: 
          A  B  C
20090101  a  1  2
20090102  b  3  4
20090103  c  4  5

日付が自動的に解析されなかったことに注意してください。この場合、以前と同様に処理する必要があります。

In [203]: df = pd.read_csv("foo.csv", parse_dates=True)

In [204]: df.index
Out[204]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', freq=None)

MultiIndex を持つインデックスの読み込み#

2つの列でインデックス付けされたデータがあるとします。

In [205]: data = 'year,indiv,zit,xit\n1977,"A",1.2,.6\n1977,"B",1.5,.5'

In [206]: print(data)
year,indiv,zit,xit
1977,"A",1.2,.6
1977,"B",1.5,.5

In [207]: with open("mindex_ex.csv", mode="w") as f:
   .....:     f.write(data)
   .....: 

read_csvindex_col 引数には、複数の列を返されたオブジェクトのインデックスの MultiIndex に変換するための列番号のリストを指定できます。

In [208]: df = pd.read_csv("mindex_ex.csv", index_col=[0, 1])

In [209]: df
Out[209]: 
            zit  xit
year indiv          
1977 A      1.2  0.6
     B      1.5  0.5

In [210]: df.loc[1977]
Out[210]: 
       zit  xit
indiv          
A      1.2  0.6
B      1.5  0.5

MultiIndex を持つ列の読み込み#

header 引数に行位置のリストを指定することで、列の MultiIndex を読み込むことができます。連続しない行を指定すると、途中の行はスキップされます。

In [211]: mi_idx = pd.MultiIndex.from_arrays([[1, 2, 3, 4], list("abcd")], names=list("ab"))

In [212]: mi_col = pd.MultiIndex.from_arrays([[1, 2], list("ab")], names=list("cd"))

In [213]: df = pd.DataFrame(np.ones((4, 2)), index=mi_idx, columns=mi_col)

In [214]: df.to_csv("mi.csv")

In [215]: print(open("mi.csv").read())
c,,1,2
d,,a,b
a,b,,
1,a,1.0,1.0
2,b,1.0,1.0
3,c,1.0,1.0
4,d,1.0,1.0


In [216]: pd.read_csv("mi.csv", header=[0, 1, 2, 3], index_col=[0, 1])
Out[216]: 
c                    1                  2
d                    a                  b
a   Unnamed: 2_level_2 Unnamed: 3_level_2
1                  1.0                1.0
2 b                1.0                1.0
3 c                1.0                1.0
4 d                1.0                1.0

read_csv は、より一般的な多列インデックス形式も解釈できます。

In [217]: data = ",a,a,a,b,c,c\n,q,r,s,t,u,v\none,1,2,3,4,5,6\ntwo,7,8,9,10,11,12"

In [218]: print(data)
,a,a,a,b,c,c
,q,r,s,t,u,v
one,1,2,3,4,5,6
two,7,8,9,10,11,12

In [219]: with open("mi2.csv", "w") as fh:
   .....:     fh.write(data)
   .....: 

In [220]: pd.read_csv("mi2.csv", header=[0, 1], index_col=0)
Out[220]: 
     a         b   c    
     q  r  s   t   u   v
one  1  2  3   4   5   6
two  7  8  9  10  11  12

index_col が指定されていない場合 (例: インデックスがない場合、または df.to_csv(..., index=False) で書き込まれた場合)、列インデックスの names は*失われます*。

区切り文字の自動「スニッフィング」#

read_csv は、区切り文字付き (必ずしもコンマ区切りとは限らない) ファイルを推測することができます。これは、pandas が csv モジュールの csv.Sniffer クラスを使用するためです。このためには、sep=None を指定する必要があります。

In [221]: df = pd.DataFrame(np.random.randn(10, 4))

In [222]: df.to_csv("tmp2.csv", sep=":", index=False)

In [223]: pd.read_csv("tmp2.csv", sep=None, engine="python")
Out[223]: 
          0         1         2         3
0  0.469112 -0.282863 -1.509059 -1.135632
1  1.212112 -0.173215  0.119209 -1.044236
2 -0.861849 -2.104569 -0.494929  1.071804
3  0.721555 -0.706771 -1.039575  0.271860
4 -0.424972  0.567020  0.276232 -1.087401
5 -0.673690  0.113648 -1.478427  0.524988
6  0.404705  0.577046 -1.715002 -1.039268
7 -0.370647 -1.157892 -1.344312  0.844885
8  1.075770 -0.109050  1.643563 -1.469388
9  0.357021 -0.674600 -1.776904 -0.968914

複数のファイルを読み込んで単一の DataFrame を作成する#

複数のファイルを結合するには、concat() を使用するのが最適です。例についてはクックブックを参照してください。

ファイルをチャンクごとに反復処理する#

以下のように、(潜在的に非常に大きな) ファイルをメモリ全体に読み込むのではなく、遅延的に反復処理したい場合を考えます。

In [224]: df = pd.DataFrame(np.random.randn(10, 4))

In [225]: df.to_csv("tmp.csv", index=False)

In [226]: table = pd.read_csv("tmp.csv")

In [227]: table
Out[227]: 
          0         1         2         3
0 -1.294524  0.413738  0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2  0.895717  0.805244 -1.206412  2.565646
3  1.431256  1.340309 -1.170299 -0.226169
4  0.410835  0.813850  0.132003 -0.827317
5 -0.076467 -1.187678  1.130127 -1.436737
6 -1.413681  1.607920  1.024180  0.569605
7  0.875906 -2.211372  0.974466 -2.006747
8 -0.410001 -0.078638  0.545952 -1.219217
9 -1.226825  0.769804 -1.281247 -0.727707

read_csvchunksize を指定すると、返り値は TextFileReader 型のイテラブルオブジェクトになります。

In [228]: with pd.read_csv("tmp.csv", chunksize=4) as reader:
   .....:     print(reader)
   .....:     for chunk in reader:
   .....:         print(chunk)
   .....: 
<pandas.io.parsers.readers.TextFileReader object at 0x7f9452705a80>
          0         1         2         3
0 -1.294524  0.413738  0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2  0.895717  0.805244 -1.206412  2.565646
3  1.431256  1.340309 -1.170299 -0.226169
          0         1         2         3
4  0.410835  0.813850  0.132003 -0.827317
5 -0.076467 -1.187678  1.130127 -1.436737
6 -1.413681  1.607920  1.024180  0.569605
7  0.875906 -2.211372  0.974466 -2.006747
          0         1         2         3
8 -0.410001 -0.078638  0.545952 -1.219217
9 -1.226825  0.769804 -1.281247 -0.727707

バージョン 1.2 で変更: read_csv/json/sas はファイルを反復処理するときにコンテキストマネージャーを返します。

iterator=True を指定すると、TextFileReader オブジェクトも返されます。

In [229]: with pd.read_csv("tmp.csv", iterator=True) as reader:
   .....:     print(reader.get_chunk(5))
   .....: 
          0         1         2         3
0 -1.294524  0.413738  0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2  0.895717  0.805244 -1.206412  2.565646
3  1.431256  1.340309 -1.170299 -0.226169
4  0.410835  0.813850  0.132003 -0.827317

パーサーエンジンの指定#

Pandas は現在、C エンジン、Python エンジン、および実験的な pyarrow エンジン (pyarrow パッケージが必要) の 3 つのエンジンをサポートしています。一般的に、pyarrow エンジンは大規模なワークロードで最速であり、ほとんどの他のワークロードでは C エンジンと同等の速度です。Python エンジンは、ほとんどのワークロードで pyarrow エンジンや C エンジンよりも遅い傾向があります。ただし、pyarrow エンジンは C エンジンよりもはるかに堅牢性が低く、Python エンジンと比較していくつかの機能が不足しています。

可能な限り、pandas は C パーサー (engine='c' と指定) を使用しますが、C でサポートされていないオプションが指定されている場合は Python にフォールバックする場合があります。

現在、C および pyarrow エンジンでサポートされていないオプションは次のとおりです。

  • 単一文字以外の sep (例: 正規表現区切り文字)

  • skipfooter

  • sep=Nonedelim_whitespace=False

上記のオプションのいずれかを指定すると、engine='python' を明示的に選択しない限り、ParserWarning が発生します。

pyarrow エンジンでサポートされていない、上記のリストに含まれないオプションは次のとおりです。

  • float_precision

  • chunksize

  • comment

  • nrows

  • thousands

  • memory_map

  • dialect

  • on_bad_lines

  • delim_whitespace

  • quoting

  • lineterminator

  • converters

  • decimal

  • iterator

  • dayfirst

  • infer_datetime_format

  • verbose

  • skipinitialspace

  • low_memory

engine='pyarrow' でこれらのオプションを指定すると、ValueError が発生します。

リモートファイルの読み書き#

pandas の多くの IO 関数にリモートファイルを読み書きするための URL を渡すことができます。以下の例は CSV ファイルの読み込みを示しています。

df = pd.read_csv("https://download.bls.gov/pub/time.series/cu/cu.item", sep="\t")

バージョン 1.3.0 で追加。

カスタムヘッダーは、以下に示すように、ヘッダーキーと値のマッピングの辞書を storage_options キーワード引数に渡すことで、HTTP(s) リクエストと共に送信できます。

headers = {"User-Agent": "pandas"}
df = pd.read_csv(
    "https://download.bls.gov/pub/time.series/cu/cu.item",
    sep="\t",
    storage_options=headers
)

ローカルファイルまたは HTTP(s) 以外のすべての URL は、インストールされていれば fsspec とそのさまざまなファイルシステム実装 (Amazon S3、Google Cloud、SSH、FTP、webHDFS など) によって処理されます。これらの実装の一部では、追加のパッケージのインストールが必要になります。たとえば、S3 URL には s3fs ライブラリが必要です。

df = pd.read_json("s3://pandas-test/adatafile.json")

リモートストレージシステムを扱う場合、環境変数や特定の場所の構成ファイルで追加の構成が必要になる場合があります。たとえば、S3 バケットのデータにアクセスするには、S3Fs ドキュメントに記載されているいくつかの方法のいずれかで資格情報を定義する必要があります。これはいくつかのストレージバックエンドにも当てはまり、fsspec に組み込まれている実装については fsimpl1 のリンクを、主要な fsspec ディストリビューションに含まれていない実装については fsimpl2 のリンクに従う必要があります。

パラメータを直接バックエンドドライバに渡すこともできます。fsspecAWS_S3_HOST環境変数を活用しないため、endpoint_urlを含む辞書を直接定義し、そのオブジェクトをストレージオプションパラメータに渡すことができます。

storage_options = {"client_kwargs": {"endpoint_url": "http://127.0.0.1:5555"}}}
df = pd.read_json("s3://pandas-test/test-1", storage_options=storage_options)

その他のサンプル設定とドキュメントは、S3Fsドキュメントで確認できます。

S3認証情報が**ない**場合でも、匿名接続を指定することで公開データにアクセスできます。

バージョン 1.2.0 で追加されました。

pd.read_csv(
    "s3://ncei-wcsd-archive/data/processed/SH1305/18kHz/SaKe2013"
    "-D20130523-T080854_to_SaKe2013-D20130523-T085643.csv",
    storage_options={"anon": True},
)

fsspecは、圧縮アーカイブ内のデータへのアクセス、ファイルのローカルキャッシュなど、複雑なURLも許可します。上記の例をローカルでキャッシュするには、呼び出しを次のように変更します。

pd.read_csv(
    "simplecache::s3://ncei-wcsd-archive/data/processed/SH1305/18kHz/"
    "SaKe2013-D20130523-T080854_to_SaKe2013-D20130523-T085643.csv",
    storage_options={"s3": {"anon": True}},
)

ここで、「anon」パラメータは実装の「s3」部分を意図しており、キャッシュ実装を意図していないことを指定します。これはセッション期間中のみ一時ディレクトリにキャッシュされますが、永続的なストアを指定することもできます。

データの書き出し#

CSV形式への書き込み#

SeriesおよびDataFrameオブジェクトには、オブジェクトの内容をカンマ区切り値ファイルとして保存できるインスタンスメソッドto_csvがあります。この関数は多くの引数を取ります。最初の引数のみが必須です。

  • path_or_buf: 書き込むファイルへの文字列パス、またはファイルオブジェクト。ファイルオブジェクトの場合、newline=''で開く必要があります

  • sep : 出力ファイルのフィールド区切り文字 (デフォルト “,”)

  • na_rep: 欠損値の文字列表現 (デフォルト ‘’)

  • float_format: 浮動小数点数の書式指定文字列

  • columns: 書き込む列 (デフォルト None)

  • header: 列名を書き出すかどうか (デフォルト True)

  • index: 行 (インデックス) 名を書き出すかどうか (デフォルト True)

  • index_label: 必要に応じて、インデックス列の列ラベル。None (デフォルト) で、headerindex が True の場合、インデックス名が使用されます。(DataFrameがMultiIndexを使用している場合はシーケンスを指定する必要があります)。

  • mode : Pythonの書き込みモード、デフォルトは 'w'

  • encoding: Python 3より前のバージョンで、コンテンツが非ASCIIの場合に使用するエンコーディングを表す文字列

  • lineterminator: 行末を表す文字シーケンス (デフォルト os.linesep)

  • quoting: csvモジュールのように引用規則を設定します (デフォルト csv.QUOTE_MINIMAL)。float_formatを設定している場合、浮動小数点数は文字列に変換され、csv.QUOTE_NONNUMERICはそれらを非数値として扱います

  • quotechar: フィールドを引用符で囲むために使用される文字 (デフォルト ‘”’)

  • doublequote: フィールド内のquotecharの引用符を制御します (デフォルト True)

  • escapechar: 必要に応じてsepquotecharをエスケープするために使用される文字 (デフォルト None)

  • chunksize: 一度に書き込む行数

  • date_format: datetimeオブジェクトのフォーマット文字列

書式付き文字列の書き込み#

DataFrameオブジェクトには、オブジェクトの文字列表現を制御できるインスタンスメソッドto_stringがあります。すべての引数はオプションです。

  • buf デフォルト None、例えばStringIOオブジェクト

  • columns デフォルト None、書き込む列

  • col_space デフォルト None、各列の最小幅。

  • na_rep デフォルト NaN、NA値の表現

  • formatters デフォルト None、各々が単一の引数を受け取り、書式付き文字列を返す関数の辞書 (列ごと)

  • float_format デフォルト None、単一の (float) 引数を受け取り、書式付き文字列を返す関数。DataFrame内の浮動小数点数に適用されます。

  • sparsify デフォルト True。DataFrameが階層的インデックスを持つ場合、すべてのMultiIndexキーを行ごとに表示するにはFalseに設定します。

  • index_names デフォルト True、インデックス名を出力します

  • index デフォルト True、インデックス (つまり、行ラベル) を出力します

  • header デフォルト True、列ラベルを出力します

  • justify デフォルト left、列ヘッダーを左揃えまたは右揃えで出力します

Seriesオブジェクトにもto_stringメソッドがありますが、bufna_repfloat_format引数のみです。length引数もあり、Trueに設定すると、Seriesの長さも出力されます。

JSON#

JSON形式のファイルと文字列を読み書きします。

JSONの書き込み#

SeriesまたはDataFrameは、オプションのパラメータと共にto_jsonを使用して、有効なJSON文字列に変換できます。

  • path_or_buf : 出力を書き込むパス名またはバッファ。これはNoneにすることができ、その場合JSON文字列が返されます。

  • orient :

    Series:
    • デフォルトはindex

    • 許容値は {split, records, index}

    DataFrame:
    • デフォルトはcolumns

    • 許容値は {split, records, index, columns, values, table}

    JSON文字列の形式

    split

    dict like {index -> [index]; columns -> [columns]; data -> [values]}

    records

    list like [{column -> value}; … ]

    index

    dict like {index -> {column -> value}}

    columns

    dict like {column -> {index -> value}}

    values

    値の配列のみ

    table

    JSON Table Schema に準拠

  • date_format : 文字列、日付変換のタイプ、タイムスタンプの場合は 'epoch'、ISO8601の場合は 'iso'。

  • double_precision : 浮動小数点値をエンコードする際に使用する小数点以下の桁数、デフォルト10。

  • force_ascii : エンコードされた文字列をASCIIに強制するかどうか、デフォルトTrue。

  • date_unit : エンコードする時間単位。タイムスタンプとISO8601の精度を決定します。秒、ミリ秒、マイクロ秒、ナノ秒に対してそれぞれ 's'、'ms'、'us'、'ns' のいずれか。デフォルト 'ms'。

  • default_handler : オブジェクトをJSONに適した形式に変換できない場合に呼び出すハンドラ。変換するオブジェクトを単一の引数として受け取り、シリアル化可能なオブジェクトを返します。

  • lines : recordsオリエントの場合、各レコードをJSONとして1行に書き込みます。

  • mode : 文字列、パスに書き込む際の書き込みモード。書き込みは 'w'、追記は 'a'。デフォルト 'w'。

注: NaNNaT、およびNonenullに変換され、datetimeオブジェクトはdate_formatおよびdate_unitパラメータに基づいて変換されます。

In [230]: dfj = pd.DataFrame(np.random.randn(5, 2), columns=list("AB"))

In [231]: json = dfj.to_json()

In [232]: json
Out[232]: '{"A":{"0":-0.1213062281,"1":0.6957746499,"2":0.9597255933,"3":-0.6199759194,"4":-0.7323393705},"B":{"0":-0.0978826728,"1":0.3417343559,"2":-1.1103361029,"3":0.1497483186,"4":0.6877383895}}'

Orientオプション#

生成されるJSONファイル/文字列の形式には、いくつかの異なるオプションがあります。以下のDataFrameSeriesを考慮してください。

In [233]: dfjo = pd.DataFrame(
   .....:     dict(A=range(1, 4), B=range(4, 7), C=range(7, 10)),
   .....:     columns=list("ABC"),
   .....:     index=list("xyz"),
   .....: )
   .....: 

In [234]: dfjo
Out[234]: 
   A  B  C
x  1  4  7
y  2  5  8
z  3  6  9

In [235]: sjo = pd.Series(dict(x=15, y=16, z=17), name="D")

In [236]: sjo
Out[236]: 
x    15
y    16
z    17
Name: D, dtype: int64

列指向 (DataFrameのデフォルト) は、列ラベルをプライマリインデックスとして機能する入れ子になったJSONオブジェクトとしてデータをシリアル化します。

In [237]: dfjo.to_json(orient="columns")
Out[237]: '{"A":{"x":1,"y":2,"z":3},"B":{"x":4,"y":5,"z":6},"C":{"x":7,"y":8,"z":9}}'

# Not available for Series

インデックス指向 (Seriesのデフォルト) は列指向と似ていますが、インデックスラベルがプライマリになります。

In [238]: dfjo.to_json(orient="index")
Out[238]: '{"x":{"A":1,"B":4,"C":7},"y":{"A":2,"B":5,"C":8},"z":{"A":3,"B":6,"C":9}}'

In [239]: sjo.to_json(orient="index")
Out[239]: '{"x":15,"y":16,"z":17}'

レコード指向 はデータを列 -> 値レコードのJSON配列にシリアル化し、インデックスラベルは含まれません。これは、例えばJavaScriptライブラリd3.jsなどのプロットライブラリにDataFrameデータを渡すのに役立ちます。

In [240]: dfjo.to_json(orient="records")
Out[240]: '[{"A":1,"B":4,"C":7},{"A":2,"B":5,"C":8},{"A":3,"B":6,"C":9}]'

In [241]: sjo.to_json(orient="records")
Out[241]: '[15,16,17]'

値指向 は、値のみの入れ子になったJSON配列にシリアル化する最小限のオプションであり、列ラベルとインデックスラベルは含まれません。

In [242]: dfjo.to_json(orient="values")
Out[242]: '[[1,4,7],[2,5,8],[3,6,9]]'

# Not available for Series

分割指向 は、値、インデックス、列の個別のエントリを含むJSONオブジェクトにシリアル化します。Seriesの場合、名前も含まれます。

In [243]: dfjo.to_json(orient="split")
Out[243]: '{"columns":["A","B","C"],"index":["x","y","z"],"data":[[1,4,7],[2,5,8],[3,6,9]]}'

In [244]: sjo.to_json(orient="split")
Out[244]: '{"name":"D","index":["x","y","z"],"data":[15,16,17]}'

テーブル指向 は、JSON Table Schemaにシリアル化され、dtypesやインデックス名を含むがこれらに限定されないメタデータの保持を可能にします。

JSONオブジェクトにエンコードするすべてのオリエントオプションは、ラウンドトリップシリアル化中にインデックスおよび列ラベルの順序を保持しません。ラベルの順序を保持したい場合は、順序付きコンテナを使用するため、splitオプションを使用してください。

日付処理#

ISO日付形式で書き込み

In [245]: dfd = pd.DataFrame(np.random.randn(5, 2), columns=list("AB"))

In [246]: dfd["date"] = pd.Timestamp("20130101")

In [247]: dfd = dfd.sort_index(axis=1, ascending=False)

In [248]: json = dfd.to_json(date_format="iso")

In [249]: json
Out[249]: '{"date":{"0":"2013-01-01T00:00:00.000","1":"2013-01-01T00:00:00.000","2":"2013-01-01T00:00:00.000","3":"2013-01-01T00:00:00.000","4":"2013-01-01T00:00:00.000"},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'

マイクロ秒を含むISO日付形式で書き込み

In [250]: json = dfd.to_json(date_format="iso", date_unit="us")

In [251]: json
Out[251]: '{"date":{"0":"2013-01-01T00:00:00.000000","1":"2013-01-01T00:00:00.000000","2":"2013-01-01T00:00:00.000000","3":"2013-01-01T00:00:00.000000","4":"2013-01-01T00:00:00.000000"},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'

エポックタイムスタンプ (秒単位)

In [252]: json = dfd.to_json(date_format="epoch", date_unit="s")

In [253]: json
Out[253]: '{"date":{"0":1,"1":1,"2":1,"3":1,"4":1},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'

日付インデックスと日付列を含むファイルへの書き込み

In [254]: dfj2 = dfj.copy()

In [255]: dfj2["date"] = pd.Timestamp("20130101")

In [256]: dfj2["ints"] = list(range(5))

In [257]: dfj2["bools"] = True

In [258]: dfj2.index = pd.date_range("20130101", periods=5)

In [259]: dfj2.to_json("test.json")

In [260]: with open("test.json") as fh:
   .....:     print(fh.read())
   .....: 
{"A":{"1356998400000":-0.1213062281,"1357084800000":0.6957746499,"1357171200000":0.9597255933,"1357257600000":-0.6199759194,"1357344000000":-0.7323393705},"B":{"1356998400000":-0.0978826728,"1357084800000":0.3417343559,"1357171200000":-1.1103361029,"1357257600000":0.1497483186,"1357344000000":0.6877383895},"date":{"1356998400000":1356,"1357084800000":1356,"1357171200000":1356,"1357257600000":1356,"1357344000000":1356},"ints":{"1356998400000":0,"1357084800000":1,"1357171200000":2,"1357257600000":3,"1357344000000":4},"bools":{"1356998400000":true,"1357084800000":true,"1357171200000":true,"1357257600000":true,"1357344000000":true}}

フォールバック動作#

JSONシリアライザがコンテナの内容を直接処理できない場合、次のようにフォールバックします。

  • dtypeがサポートされていない場合 (例: np.complex_) は、default_handlerが提供されていれば各値に対して呼び出され、それ以外の場合は例外が発生します。

  • オブジェクトがサポートされていない場合は、次を試行します。

    • オブジェクトにtoDictメソッドが定義されているかチェックし、呼び出す。toDictメソッドは、JSONシリアル化されるdictを返す必要があります。

    • default_handlerが提供されていれば呼び出す。

    • オブジェクトの内容をたどってdictに変換する。ただし、これはOverflowErrorで失敗したり、予期しない結果になったりすることがよくあります。

一般的に、サポートされていないオブジェクトやdtypeに対しては、default_handlerを提供するのが最善の方法です。例えば、

>>> DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json()  # raises
RuntimeError: Unhandled numpy dtype 15

は、シンプルなdefault_handlerを指定することで対処できます。

In [261]: pd.DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json(default_handler=str)
Out[261]: '{"0":{"0":"(1+0j)","1":"(2+0j)","2":"(1+2j)"}}'

JSONの読み込み#

JSON文字列をpandasオブジェクトに読み込む際には、いくつかのパラメータを使用できます。typが指定されていないかNoneの場合、パーサーはDataFrameを解析しようとします。Seriesの解析を明示的に強制するには、typ=seriesを渡します。

  • filepath_or_buffer : 有効な JSON文字列またはファイルハンドル / StringIO。文字列はURLでも構いません。有効なURLスキームには、http、ftp、S3、およびfileが含まれます。file URLの場合、ホストが期待されます。例えば、ローカルファイルは file:///path/to/table.json のようになります。

  • typ : 復元するオブジェクトのタイプ (series または frame)、デフォルト 'frame'

  • orient :

    Series
    • デフォルトはindex

    • 許容値は {split, records, index}

    DataFrame
    • デフォルトはcolumns

    • 許容値は {split, records, index, columns, values, table}

    JSON文字列の形式

    split

    dict like {index -> [index]; columns -> [columns]; data -> [values]}

    records

    list like [{column -> value} …]

    index

    dict like {index -> {column -> value}}

    columns

    dict like {column -> {index -> value}}

    values

    値の配列のみ

    table

    JSON Table Schema に準拠

  • dtype : Trueの場合、dtypesを推論します。列からdtypeへの辞書の場合、それらを使用します。Falseの場合、dtypesをまったく推論しません。デフォルトはTrueで、データにのみ適用されます。

  • convert_axes : ブール値。軸を適切なdtypesに変換しようとします。デフォルトはTrueです。

  • convert_dates : 日付として解析する列のリスト。Trueの場合、日付のような列を解析しようとします。デフォルトはTrueです。

  • keep_default_dates : ブール値、デフォルトTrue。日付を解析する場合、デフォルトの日付のような列も解析します。

  • precise_float : ブール値、デフォルトFalse。文字列をdouble値にデコードする際に、高精度 (strtod) 関数を使用できるように設定します。デフォルト (False) は、高速だが精度が低い組み込み機能を使用します。

  • date_unit : 文字列、日付を変換する場合に検出するタイムスタンプ単位。デフォルト None。デフォルトではタイムスタンプの精度が検出されますが、これを望まない場合は 's'、'ms'、'us'、'ns' のいずれかを渡して、それぞれ秒、ミリ秒、マイクロ秒、ナノ秒にタイムスタンプの精度を強制します。

  • lines : ファイルを1行に1つのjsonオブジェクトとして読み取ります。

  • encoding : Python 3のバイトをデコードするために使用するエンコーディング。

  • chunksize : lines=Trueと組み合わせて使用する場合、イテレーションごとにchunksize行を読み込むpandas.api.typing.JsonReaderを返します。

  • engine: "ujson" (組み込みJSONパーサー)、またはpyarrowのpyarrow.json.read_jsonにディスパッチする"pyarrow"のいずれか。"pyarrow"lines=Trueの場合にのみ利用可能です。

JSONが解析できない場合、パーサーはValueError/TypeError/AssertionErrorのいずれかを発生させます。

JSONエンコード時にデフォルト以外のorientを使用した場合、デコード時に適切な結果が得られるように、ここで同じオプションを渡すようにしてください。概要についてはOrient Optionsを参照してください。

データ変換#

convert_axes=Truedtype=Trueconvert_dates=Trueのデフォルト設定は、軸とすべてのデータを、日付を含む適切な型に解析しようとします。特定のdtypesをオーバーライドする必要がある場合は、dtypeに辞書を渡します。convert_axesは、軸内の文字列のような数字 (例: '1'、'2') を保持する必要がある場合にのみFalseに設定する必要があります。

convert_dates=Trueの場合、データおよび/または列ラベルが「日付のような」場合、大きな整数値は日付に変換されることがあります。正確な閾値は指定されたdate_unitによって異なります。「日付のような」とは、列ラベルが以下のいずれかの基準を満たすことを意味します。

  • 末尾が'_at'であること

  • 末尾が'_time'であること

  • 先頭が'timestamp'であること

  • 'modified'であること

  • 'date'であること

警告

JSONデータを読み込む際、dtypesへの自動型変換にはいくつかの癖があります。

  • インデックスはシリアル化とは異なる順序で再構築される可能性があり、つまり、返される順序はシリアル化前と同じであるとは限りません。

  • 以前floatデータだった列は、安全に実行できる場合、例えば1.の列などはintegerに変換されます。

  • ブール列は再構築時にintegerに変換されます

したがって、dtypeキーワード引数を使用して特定のdtypesを指定したい場合があります。

JSON文字列からの読み込み

In [262]: from io import StringIO

In [263]: pd.read_json(StringIO(json))
Out[263]: 
   date         B         A
0     1  0.403310  0.176444
1     1  0.301624 -0.154951
2     1 -1.369849 -2.179861
3     1  1.462696 -0.954208
4     1 -0.826591 -1.743161

ファイルからの読み込み

In [264]: pd.read_json("test.json")
Out[264]: 
                   A         B  date  ints  bools
2013-01-01 -0.121306 -0.097883  1356     0   True
2013-01-02  0.695775  0.341734  1356     1   True
2013-01-03  0.959726 -1.110336  1356     2   True
2013-01-04 -0.619976  0.149748  1356     3   True
2013-01-05 -0.732339  0.687738  1356     4   True

データを変換しない (ただし、軸と日付は変換)

In [265]: pd.read_json("test.json", dtype=object).dtypes
Out[265]: 
A        object
B        object
date     object
ints     object
bools    object
dtype: object

変換のdtypesを指定する

In [266]: pd.read_json("test.json", dtype={"A": "float32", "bools": "int8"}).dtypes
Out[266]: 
A        float32
B        float64
date       int64
ints       int64
bools       int8
dtype: object

文字列インデックスを保持する

In [267]: from io import StringIO

In [268]: si = pd.DataFrame(
   .....:     np.zeros((4, 4)), columns=list(range(4)), index=[str(i) for i in range(4)]
   .....: )
   .....: 

In [269]: si
Out[269]: 
     0    1    2    3
0  0.0  0.0  0.0  0.0
1  0.0  0.0  0.0  0.0
2  0.0  0.0  0.0  0.0
3  0.0  0.0  0.0  0.0

In [270]: si.index
Out[270]: Index(['0', '1', '2', '3'], dtype='object')

In [271]: si.columns
Out[271]: Index([0, 1, 2, 3], dtype='int64')

In [272]: json = si.to_json()

In [273]: sij = pd.read_json(StringIO(json), convert_axes=False)

In [274]: sij
Out[274]: 
   0  1  2  3
0  0  0  0  0
1  0  0  0  0
2  0  0  0  0
3  0  0  0  0

In [275]: sij.index
Out[275]: Index(['0', '1', '2', '3'], dtype='object')

In [276]: sij.columns
Out[276]: Index(['0', '1', '2', '3'], dtype='object')

ナノ秒で書き込まれた日付はナノ秒で読み込む必要があります。

In [277]: from io import StringIO

In [278]: json = dfj2.to_json(date_unit="ns")

# Try to parse timestamps as milliseconds -> Won't Work
In [279]: dfju = pd.read_json(StringIO(json), date_unit="ms")

In [280]: dfju
Out[280]: 
                            A         B        date  ints  bools
1356998400000000000 -0.121306 -0.097883  1356998400     0   True
1357084800000000000  0.695775  0.341734  1356998400     1   True
1357171200000000000  0.959726 -1.110336  1356998400     2   True
1357257600000000000 -0.619976  0.149748  1356998400     3   True
1357344000000000000 -0.732339  0.687738  1356998400     4   True

# Let pandas detect the correct precision
In [281]: dfju = pd.read_json(StringIO(json))

In [282]: dfju
Out[282]: 
                   A         B       date  ints  bools
2013-01-01 -0.121306 -0.097883 2013-01-01     0   True
2013-01-02  0.695775  0.341734 2013-01-01     1   True
2013-01-03  0.959726 -1.110336 2013-01-01     2   True
2013-01-04 -0.619976  0.149748 2013-01-01     3   True
2013-01-05 -0.732339  0.687738 2013-01-01     4   True

# Or specify that all timestamps are in nanoseconds
In [283]: dfju = pd.read_json(StringIO(json), date_unit="ns")

In [284]: dfju
Out[284]: 
                   A         B        date  ints  bools
2013-01-01 -0.121306 -0.097883  1356998400     0   True
2013-01-02  0.695775  0.341734  1356998400     1   True
2013-01-03  0.959726 -1.110336  1356998400     2   True
2013-01-04 -0.619976  0.149748  1356998400     3   True
2013-01-05 -0.732339  0.687738  1356998400     4   True

dtype_backend引数を設定することで、結果のDataFrameに使用されるデフォルトのdtypeを制御できます。

In [285]: data = (
   .....:  '{"a":{"0":1,"1":3},"b":{"0":2.5,"1":4.5},"c":{"0":true,"1":false},"d":{"0":"a","1":"b"},'
   .....:  '"e":{"0":null,"1":6.0},"f":{"0":null,"1":7.5},"g":{"0":null,"1":true},"h":{"0":null,"1":"a"},'
   .....:  '"i":{"0":"12-31-2019","1":"12-31-2019"},"j":{"0":null,"1":null}}'
   .....: )
   .....: 

In [286]: df = pd.read_json(StringIO(data), dtype_backend="pyarrow")

In [287]: df
Out[287]: 
   a    b      c  d     e     f     g     h           i     j
0  1  2.5   True  a  <NA>  <NA>  <NA>  <NA>  12-31-2019  None
1  3  4.5  False  b     6   7.5  True     a  12-31-2019  None

In [288]: df.dtypes
Out[288]: 
a     int64[pyarrow]
b    double[pyarrow]
c      bool[pyarrow]
d    string[pyarrow]
e     int64[pyarrow]
f    double[pyarrow]
g      bool[pyarrow]
h    string[pyarrow]
i    string[pyarrow]
j      null[pyarrow]
dtype: object

正規化#

pandasは、辞書または辞書のリストを取り、この半構造化データをフラットなテーブルに**正規化**するユーティリティ関数を提供します。

In [289]: data = [
   .....:     {"id": 1, "name": {"first": "Coleen", "last": "Volk"}},
   .....:     {"name": {"given": "Mark", "family": "Regner"}},
   .....:     {"id": 2, "name": "Faye Raker"},
   .....: ]
   .....: 

In [290]: pd.json_normalize(data)
Out[290]: 
    id name.first name.last name.given name.family        name
0  1.0     Coleen      Volk        NaN         NaN         NaN
1  NaN        NaN       NaN       Mark      Regner         NaN
2  2.0        NaN       NaN        NaN         NaN  Faye Raker
In [291]: data = [
   .....:     {
   .....:         "state": "Florida",
   .....:         "shortname": "FL",
   .....:         "info": {"governor": "Rick Scott"},
   .....:         "county": [
   .....:             {"name": "Dade", "population": 12345},
   .....:             {"name": "Broward", "population": 40000},
   .....:             {"name": "Palm Beach", "population": 60000},
   .....:         ],
   .....:     },
   .....:     {
   .....:         "state": "Ohio",
   .....:         "shortname": "OH",
   .....:         "info": {"governor": "John Kasich"},
   .....:         "county": [
   .....:             {"name": "Summit", "population": 1234},
   .....:             {"name": "Cuyahoga", "population": 1337},
   .....:         ],
   .....:     },
   .....: ]
   .....: 

In [292]: pd.json_normalize(data, "county", ["state", "shortname", ["info", "governor"]])
Out[292]: 
         name  population    state shortname info.governor
0        Dade       12345  Florida        FL    Rick Scott
1     Broward       40000  Florida        FL    Rick Scott
2  Palm Beach       60000  Florida        FL    Rick Scott
3      Summit        1234     Ohio        OH   John Kasich
4    Cuyahoga        1337     Ohio        OH   John Kasich

max_levelパラメータは、正規化を終了するレベルをより詳細に制御します。max_level=1の場合、以下のスニペットは、提供された辞書の最初のネストレベルまで正規化します。

In [293]: data = [
   .....:     {
   .....:         "CreatedBy": {"Name": "User001"},
   .....:         "Lookup": {
   .....:             "TextField": "Some text",
   .....:             "UserField": {"Id": "ID001", "Name": "Name001"},
   .....:         },
   .....:         "Image": {"a": "b"},
   .....:     }
   .....: ]
   .....: 

In [294]: pd.json_normalize(data, max_level=1)
Out[294]: 
  CreatedBy.Name Lookup.TextField                    Lookup.UserField Image.a
0        User001        Some text  {'Id': 'ID001', 'Name': 'Name001'}       b

行区切りJSON#

pandasは、HadoopやSparkを使用したデータ処理パイプラインで一般的な行区切りJSONファイルの読み書きができます。

行区切りJSONファイルの場合、pandasは一度にchunksize行を読み込むイテレータを返すこともできます。これは、大きなファイルやストリームから読み込む場合に便利です。

In [295]: from io import StringIO

In [296]: jsonl = """
   .....:     {"a": 1, "b": 2}
   .....:     {"a": 3, "b": 4}
   .....: """
   .....: 

In [297]: df = pd.read_json(StringIO(jsonl), lines=True)

In [298]: df
Out[298]: 
   a  b
0  1  2
1  3  4

In [299]: df.to_json(orient="records", lines=True)
Out[299]: '{"a":1,"b":2}\n{"a":3,"b":4}\n'

# reader is an iterator that returns ``chunksize`` lines each iteration
In [300]: with pd.read_json(StringIO(jsonl), lines=True, chunksize=1) as reader:
   .....:     reader
   .....:     for chunk in reader:
   .....:         print(chunk)
   .....: 
Empty DataFrame
Columns: []
Index: []
   a  b
0  1  2
   a  b
1  3  4

行区切りJSONは、engine="pyarrow"を指定することでpyarrowリーダーを使用して読み込むこともできます。

In [301]: from io import BytesIO

In [302]: df = pd.read_json(BytesIO(jsonl.encode()), lines=True, engine="pyarrow")

In [303]: df
Out[303]: 
   a  b
0  1  2
1  3  4

バージョン 2.0.0 で追加されました。

テーブルスキーマ#

Table Schemaは、表形式のデータセットをJSONオブジェクトとして記述するための仕様です。JSONには、フィールド名、型、その他の属性に関する情報が含まれます。tableオリエントを使用して、schemadataの2つのフィールドを持つJSON文字列を構築できます。

In [304]: df = pd.DataFrame(
   .....:     {
   .....:         "A": [1, 2, 3],
   .....:         "B": ["a", "b", "c"],
   .....:         "C": pd.date_range("2016-01-01", freq="d", periods=3),
   .....:     },
   .....:     index=pd.Index(range(3), name="idx"),
   .....: )
   .....: 

In [305]: df
Out[305]: 
     A  B          C
idx                 
0    1  a 2016-01-01
1    2  b 2016-01-02
2    3  c 2016-01-03

In [306]: df.to_json(orient="table", date_format="iso")
Out[306]: '{"schema":{"fields":[{"name":"idx","type":"integer"},{"name":"A","type":"integer"},{"name":"B","type":"string"},{"name":"C","type":"datetime"}],"primaryKey":["idx"],"pandas_version":"1.4.0"},"data":[{"idx":0,"A":1,"B":"a","C":"2016-01-01T00:00:00.000"},{"idx":1,"A":2,"B":"b","C":"2016-01-02T00:00:00.000"},{"idx":2,"A":3,"B":"c","C":"2016-01-03T00:00:00.000"}]}'

schemaフィールドにはfieldsキーが含まれ、これ自体にはIndexまたはMultiIndexを含む列名と型のペアのリストが含まれます(型のリストについては後述)。また、(Multi)インデックスが一意である場合は、schemaフィールドにはprimaryKeyフィールドも含まれます。

2番目のフィールドdataには、recordsオリエントでシリアル化されたデータが含まれます。インデックスも含まれ、Table Schemaの仕様で要求されているように、すべての日付時刻はISO 8601形式です。

サポートされている型の完全なリストは、Table Schemaの仕様に記載されています。この表は、pandasの型からのマッピングを示しています。

pandasの型

Table Schemaの型

int64

integer

float64

number

bool

boolean

datetime64[ns]

datetime

timedelta64[ns]

duration

categorical

any

object

str

生成されたテーブルスキーマに関するいくつかの注意事項

  • schemaオブジェクトにはpandas_versionフィールドが含まれます。これにはpandasの方言のスキーマのバージョンが含まれ、改訂ごとにインクリメントされます。

  • すべての日付はシリアル化時にUTCに変換されます。タイムゾーンに依存しない値も、オフセット0のUTCとして扱われます。

    In [307]: from pandas.io.json import build_table_schema
    
    In [308]: s = pd.Series(pd.date_range("2016", periods=4))
    
    In [309]: build_table_schema(s)
    Out[309]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
      {'name': 'values', 'type': 'datetime'}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'}
    
  • タイムゾーンを持つdatetime (シリアル化前) には、タイムゾーン名 (例: 'US/Central') を含む追加フィールドtzが含まれます。

    In [310]: s_tz = pd.Series(pd.date_range("2016", periods=12, tz="US/Central"))
    
    In [311]: build_table_schema(s_tz)
    Out[311]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
      {'name': 'values', 'type': 'datetime', 'tz': 'US/Central'}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'}
    
  • Periodsはシリアル化前にタイムスタンプに変換されるため、UTCに変換されるのと同じ動作をします。さらに、Periodsにはその期間の頻度、例えば'A-DEC'を含む追加フィールドfreqが含まれます。

    In [312]: s_per = pd.Series(1, index=pd.period_range("2016", freq="Y-DEC", periods=4))
    
    In [313]: build_table_schema(s_per)
    Out[313]: 
    {'fields': [{'name': 'index', 'type': 'datetime', 'freq': 'YE-DEC'},
      {'name': 'values', 'type': 'integer'}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'}
    
  • カテゴリカル型はany型と、可能な値のセットを列挙するenum制約を使用します。さらに、orderedフィールドも含まれます。

    In [314]: s_cat = pd.Series(pd.Categorical(["a", "b", "a"]))
    
    In [315]: build_table_schema(s_cat)
    Out[315]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
      {'name': 'values',
       'type': 'any',
       'constraints': {'enum': ['a', 'b']},
       'ordered': False}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'}
    
  • primaryKeyフィールド (ラベルの配列を含む) は、インデックスが一意の場合にのみ含まれます。

    In [316]: s_dupe = pd.Series([1, 2], index=[1, 1])
    
    In [317]: build_table_schema(s_dupe)
    Out[317]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
      {'name': 'values', 'type': 'integer'}],
     'pandas_version': '1.4.0'}
    
  • primaryKeyの動作はMultiIndexesと同じですが、この場合primaryKeyは配列です。

    In [318]: s_multi = pd.Series(1, index=pd.MultiIndex.from_product([("a", "b"), (0, 1)]))
    
    In [319]: build_table_schema(s_multi)
    Out[319]: 
    {'fields': [{'name': 'level_0', 'type': 'string'},
      {'name': 'level_1', 'type': 'integer'},
      {'name': 'values', 'type': 'integer'}],
     'primaryKey': FrozenList(['level_0', 'level_1']),
     'pandas_version': '1.4.0'}
    
  • デフォルトの命名は、おおよそ以下のルールに従います。

    • Seriesの場合、object.nameが使用されます。それがNoneの場合、名前はvaluesです。

    • DataFramesの場合、列名の文字列化されたバージョンが使用されます。

    • Index (ただしMultiIndexではない) の場合、index.nameが使用され、それがNoneの場合はindexにフォールバックします。

    • MultiIndexの場合、mi.namesが使用されます。いずれかのレベルに名前がない場合、level_<i>が使用されます。

read_jsonは、引数としてorient='table'も受け入れます。これにより、dtypesやインデックス名などのメタデータを、ラウンドトリップ可能な方法で保持できます。

In [320]: df = pd.DataFrame(
   .....:     {
   .....:         "foo": [1, 2, 3, 4],
   .....:         "bar": ["a", "b", "c", "d"],
   .....:         "baz": pd.date_range("2018-01-01", freq="d", periods=4),
   .....:         "qux": pd.Categorical(["a", "b", "c", "c"]),
   .....:     },
   .....:     index=pd.Index(range(4), name="idx"),
   .....: )
   .....: 

In [321]: df
Out[321]: 
     foo bar        baz qux
idx                        
0      1   a 2018-01-01   a
1      2   b 2018-01-02   b
2      3   c 2018-01-03   c
3      4   d 2018-01-04   c

In [322]: df.dtypes
Out[322]: 
foo             int64
bar            object
baz    datetime64[ns]
qux          category
dtype: object

In [323]: df.to_json("test.json", orient="table")

In [324]: new_df = pd.read_json("test.json", orient="table")

In [325]: new_df
Out[325]: 
     foo bar        baz qux
idx                        
0      1   a 2018-01-01   a
1      2   b 2018-01-02   b
2      3   c 2018-01-03   c
3      4   d 2018-01-04   c

In [326]: new_df.dtypes
Out[326]: 
foo             int64
bar            object
baz    datetime64[ns]
qux          category
dtype: object

Indexの名前としてのリテラル文字列 'index' はラウンドトリップ可能ではありません。MultiIndex内の'level_'で始まる名前も同様です。これらはDataFrame.to_json()でデフォルトで欠損値を示すために使用され、その後の読み込みでは意図を区別できません。

In [327]: df.index.name = "index"

In [328]: df.to_json("test.json", orient="table")

In [329]: new_df = pd.read_json("test.json", orient="table")

In [330]: print(new_df.index.name)
None

orient='table'をユーザー定義のExtensionArrayと一緒に使用する場合、生成されるスキーマには、それぞれのfields要素にextDtypeキーが追加されます。この追加キーは標準ではありませんが、拡張型 (例: read_json(df.to_json(orient="table"), orient="table")) のJSONラウンドトリップを可能にします。

extDtypeキーは拡張機能の名前を保持します。ExtensionDtypeを適切に登録している場合、pandasはその名前を使用してレジストリを検索し、シリアル化されたデータをカスタムdtypeに再変換します。

HTML#

HTMLコンテンツの読み込み#

警告

BeautifulSoup4/html5lib/lxmlパーサーを取り巻く問題に関する以下のHTMLテーブル解析の落とし穴を読むことを**強く推奨します**。

トップレベルのread_html()関数は、HTML文字列/ファイル/URLを受け入れ、HTMLテーブルをpandas DataFramesのリストに解析します。いくつかの例を見てみましょう。

read_htmlは、HTMLコンテンツに1つのテーブルしか含まれていない場合でも、DataFrameオブジェクトのlistを返します。

オプションなしでURLを読み込む

In [320]: url = "https://www.fdic.gov/resources/resolutions/bank-failures/failed-bank-list"
In [321]: pd.read_html(url)
Out[321]:
[                         Bank NameBank           CityCity StateSt  ...              Acquiring InstitutionAI Closing DateClosing FundFund
 0                    Almena State Bank             Almena      KS  ...                          Equity Bank    October 23, 2020    10538
 1           First City Bank of Florida  Fort Walton Beach      FL  ...            United Fidelity Bank, fsb    October 16, 2020    10537
 2                 The First State Bank      Barboursville      WV  ...                       MVB Bank, Inc.       April 3, 2020    10536
 3                   Ericson State Bank            Ericson      NE  ...           Farmers and Merchants Bank   February 14, 2020    10535
 4     City National Bank of New Jersey             Newark      NJ  ...                      Industrial Bank    November 1, 2019    10534
 ..                                 ...                ...     ...  ...                                  ...                 ...      ...
 558                 Superior Bank, FSB           Hinsdale      IL  ...                Superior Federal, FSB       July 27, 2001     6004
 559                Malta National Bank              Malta      OH  ...                    North Valley Bank         May 3, 2001     4648
 560    First Alliance Bank & Trust Co.         Manchester      NH  ...  Southern New Hampshire Bank & Trust    February 2, 2001     4647
 561  National State Bank of Metropolis         Metropolis      IL  ...              Banterra Bank of Marion   December 14, 2000     4646
 562                   Bank of Honolulu           Honolulu      HI  ...                   Bank of the Orient    October 13, 2000     4645

 [563 rows x 7 columns]]

上記のURLからのデータは毎週月曜日に変更されるため、上記の結果データは若干異なる場合があります。

HTTPリクエストにヘッダーを渡しながらURLを読み込む

In [322]: url = 'https://www.sump.org/notes/request/' # HTTP request reflector
In [323]: pd.read_html(url)
Out[323]:
[                   0                    1
 0     Remote Socket:  51.15.105.256:51760
 1  Protocol Version:             HTTP/1.1
 2    Request Method:                  GET
 3       Request URI:      /notes/request/
 4     Request Query:                  NaN,
 0   Accept-Encoding:             identity
 1              Host:         www.sump.org
 2        User-Agent:    Python-urllib/3.8
 3        Connection:                close]
In [324]: headers = {
In [325]:    'User-Agent':'Mozilla Firefox v14.0',
In [326]:    'Accept':'application/json',
In [327]:    'Connection':'keep-alive',
In [328]:    'Auth':'Bearer 2*/f3+fe68df*4'
In [329]: }
In [340]: pd.read_html(url, storage_options=headers)
Out[340]:
[                   0                    1
 0     Remote Socket:  51.15.105.256:51760
 1  Protocol Version:             HTTP/1.1
 2    Request Method:                  GET
 3       Request URI:      /notes/request/
 4     Request Query:                  NaN,
 0        User-Agent: Mozilla Firefox v14.0
 1    AcceptEncoding:   gzip,  deflate,  br
 2            Accept:      application/json
 3        Connection:             keep-alive
 4              Auth:  Bearer 2*/f3+fe68df*4]

上記で、渡したヘッダーがHTTPリクエストに反映されていることがわかります。

上記のURLからファイルの内容を読み込み、文字列としてread_htmlに渡します

In [331]: html_str = """
   .....:          <table>
   .....:              <tr>
   .....:                  <th>A</th>
   .....:                  <th colspan="1">B</th>
   .....:                  <th rowspan="1">C</th>
   .....:              </tr>
   .....:              <tr>
   .....:                  <td>a</td>
   .....:                  <td>b</td>
   .....:                  <td>c</td>
   .....:              </tr>
   .....:          </table>
   .....:      """
   .....: 

In [332]: with open("tmp.html", "w") as f:
   .....:     f.write(html_str)
   .....: 

In [333]: df = pd.read_html("tmp.html")

In [334]: df[0]
Out[334]: 
   A  B  C
0  a  b  c

必要に応じて、StringIOのインスタンスを渡すこともできます。

In [335]: dfs = pd.read_html(StringIO(html_str))

In [336]: dfs[0]
Out[336]: 
   A  B  C
0  a  b  c

以下の例は、多くのネットワークアクセス機能がドキュメントのビルドを遅くするため、IPythonエバリュエーターでは実行されません。エラーや実行されない例を見つけた場合は、pandas GitHub issuesページまで遠慮なくご報告ください。

URLを読み込み、特定のテキストを含むテーブルと一致させる

match = "Metcalf Bank"
df_list = pd.read_html(url, match=match)

ヘッダー行を指定します(デフォルトでは、<thead>内にある<th>または<td>要素が列インデックスを形成するために使用され、複数の行が<thead>内に含まれる場合はMultiIndexが作成されます)。指定された場合、ヘッダー行は解析されたヘッダー要素(<th>要素)を差し引いたデータから取得されます。

dfs = pd.read_html(url, header=0)

インデックス列を指定する

dfs = pd.read_html(url, index_col=0)

スキップする行数を指定する

dfs = pd.read_html(url, skiprows=0)

リストを使用してスキップする行数を指定する (rangeも機能します)

dfs = pd.read_html(url, skiprows=range(2))

HTML属性を指定する

dfs1 = pd.read_html(url, attrs={"id": "table"})
dfs2 = pd.read_html(url, attrs={"class": "sortable"})
print(np.array_equal(dfs1[0], dfs2[0]))  # Should be True

NaNに変換すべき値を指定する

dfs = pd.read_html(url, na_values=["No Acquirer"])

NaN値のデフォルトセットを維持するかどうかを指定します。

dfs = pd.read_html(url, keep_default_na=False)

列のコンバーターを指定します。これは、先行ゼロを持つ数値テキストデータに役立ちます。デフォルトでは、数値の列は数値型にキャストされ、先行ゼロは失われます。これを避けるために、これらの列を文字列に変換できます。

url_mcc = "https://en.wikipedia.org/wiki/Mobile_country_code?oldid=899173761"
dfs = pd.read_html(
    url_mcc,
    match="Telekom Albania",
    header=0,
    converters={"MNC": str},
)

上記を組み合わせて使用する

dfs = pd.read_html(url, match="Metcalf Bank", index_col=0)

pandasのto_html出力 (浮動小数点精度の一部損失あり) を読み込む

df = pd.DataFrame(np.random.randn(2, 2))
s = df.to_html(float_format="{0:.40g}".format)
dfin = pd.read_html(s, index_col=0)

lxmlバックエンドは、提供されたパーサーがそれだけの場合、解析に失敗するとエラーを発生させます。パーサーが1つしかない場合は文字列を渡すこともできますが、例えば関数が文字列のシーケンスを期待している場合は、1つの文字列を含むリストを渡すのが良い習慣とされています。以下を使用できます。

dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor=["lxml"])

または、リストなしでflavor='lxml'を渡すこともできます。

dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor="lxml")

ただし、bs4とhtml5libがインストールされており、Noneまたは['lxml', 'bs4']を渡した場合、解析はほぼ確実に成功します。解析が成功するとすぐに、関数は戻ることに注意してください。

dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor=["lxml", "bs4"])

extract_links="all"を使用すると、テキストと一緒にセルからリンクを抽出できます。

In [337]: html_table = """
   .....: <table>
   .....:   <tr>
   .....:     <th>GitHub</th>
   .....:   </tr>
   .....:   <tr>
   .....:     <td><a href="https://github.com/pandas-dev/pandas">pandas</a></td>
   .....:   </tr>
   .....: </table>
   .....: """
   .....: 

In [338]: df = pd.read_html(
   .....:     StringIO(html_table),
   .....:     extract_links="all"
   .....: )[0]
   .....: 

In [339]: df
Out[339]: 
                                   (GitHub, None)
0  (pandas, https://github.com/pandas-dev/pandas)

In [340]: df[("GitHub", None)]
Out[340]: 
0    (pandas, https://github.com/pandas-dev/pandas)
Name: (GitHub, None), dtype: object

In [341]: df[("GitHub", None)].str[1]
Out[341]: 
0    https://github.com/pandas-dev/pandas
Name: (GitHub, None), dtype: object

バージョン1.5.0で追加。

HTMLファイルへの書き込み#

DataFrameオブジェクトには、DataFrameの内容をHTMLテーブルとしてレンダリングするインスタンスメソッドto_htmlがあります。関数引数は、上記のto_stringメソッドと同様です。

DataFrame.to_htmlの可能なオプションすべてをここでは簡潔さのために示していません。すべてのオプションについてはDataFrame.to_html()を参照してください。

Jupyter NotebookのようなHTMLレンダリングをサポートする環境では、display(HTML(...))`は生HTMLを環境にレンダリングします。

In [342]: from IPython.display import display, HTML

In [343]: df = pd.DataFrame(np.random.randn(2, 2))

In [344]: df
Out[344]: 
          0         1
0 -0.345352  1.314232
1  0.690579  0.995761

In [345]: html = df.to_html()

In [346]: print(html)  # raw html
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>0</th>
      <th>1</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>-0.345352</td>
      <td>1.314232</td>
    </tr>
    <tr>
      <th>1</th>
      <td>0.690579</td>
      <td>0.995761</td>
    </tr>
  </tbody>
</table>

In [347]: display(HTML(html))
<IPython.core.display.HTML object>

columns引数は表示される列を制限します

In [348]: html = df.to_html(columns=[0])

In [349]: print(html)
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>0</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>-0.345352</td>
    </tr>
    <tr>
      <th>1</th>
      <td>0.690579</td>
    </tr>
  </tbody>
</table>

In [350]: display(HTML(html))
<IPython.core.display.HTML object>

float_formatは、浮動小数点値の精度を制御するためのPython呼び出し可能オブジェクトを受け入れます。

In [351]: html = df.to_html(float_format="{0:.10f}".format)

In [352]: print(html)
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>0</th>
      <th>1</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>-0.3453521949</td>
      <td>1.3142323796</td>
    </tr>
    <tr>
      <th>1</th>
      <td>0.6905793352</td>
      <td>0.9957609037</td>
    </tr>
  </tbody>
</table>

In [353]: display(HTML(html))
<IPython.core.display.HTML object>

bold_rowsはデフォルトで行ラベルを太字にしますが、これをオフにすることもできます。

In [354]: html = df.to_html(bold_rows=False)

In [355]: print(html)
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>0</th>
      <th>1</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td>-0.345352</td>
      <td>1.314232</td>
    </tr>
    <tr>
      <td>1</td>
      <td>0.690579</td>
      <td>0.995761</td>
    </tr>
  </tbody>
</table>

In [356]: display(HTML(html))
<IPython.core.display.HTML object>

classes引数を使用すると、生成されるHTMLテーブルにCSSクラスを与えることができます。これらのクラスは、既存の'dataframe'クラスに追加されることに注意してください。

In [357]: print(df.to_html(classes=["awesome_table_class", "even_more_awesome_class"]))
<table border="1" class="dataframe awesome_table_class even_more_awesome_class">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>0</th>
      <th>1</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>-0.345352</td>
      <td>1.314232</td>
    </tr>
    <tr>
      <th>1</th>
      <td>0.690579</td>
      <td>0.995761</td>
    </tr>
  </tbody>
</table>

render_links引数は、URLを含むセルにハイパーリンクを追加する機能を提供します。

In [358]: url_df = pd.DataFrame(
   .....:     {
   .....:         "name": ["Python", "pandas"],
   .....:         "url": ["https://www.python.org/", "https://pandas.dokyumento.jp"],
   .....:     }
   .....: )
   .....: 

In [359]: html = url_df.to_html(render_links=True)

In [360]: print(html)
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>name</th>
      <th>url</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>Python</td>
      <td><a href="https://www.python.org/" target="_blank">https://www.python.org/</a></td>
    </tr>
    <tr>
      <th>1</th>
      <td>pandas</td>
      <td><a href="https://pandas.dokyumento.jp" target="_blank">https://pandas.dokyumento.jp</a></td>
    </tr>
  </tbody>
</table>

In [361]: display(HTML(html))
<IPython.core.display.HTML object>

最後に、escape引数を使用すると、結果のHTMLで「<」、「>」、「&」の文字がエスケープされるかどうかを制御できます(デフォルトはTrue)。したがって、エスケープされていないHTMLを取得するには、escape=Falseを渡します。

In [362]: df = pd.DataFrame({"a": list("&<>"), "b": np.random.randn(3)})

エスケープ済み

In [363]: html = df.to_html()

In [364]: print(html)
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>a</th>
      <th>b</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>&amp;</td>
      <td>2.396780</td>
    </tr>
    <tr>
      <th>1</th>
      <td>&lt;</td>
      <td>0.014871</td>
    </tr>
    <tr>
      <th>2</th>
      <td>&gt;</td>
      <td>3.357427</td>
    </tr>
  </tbody>
</table>

In [365]: display(HTML(html))
<IPython.core.display.HTML object>

エスケープなし

In [366]: html = df.to_html(escape=False)

In [367]: print(html)
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>a</th>
      <th>b</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>&</td>
      <td>2.396780</td>
    </tr>
    <tr>
      <th>1</th>
      <td><</td>
      <td>0.014871</td>
    </tr>
    <tr>
      <th>2</th>
      <td>></td>
      <td>3.357427</td>
    </tr>
  </tbody>
</table>

In [368]: display(HTML(html))
<IPython.core.display.HTML object>

一部のブラウザでは、前の2つのHTMLテーブルのレンダリングに違いが表示されない場合があります。

HTMLテーブル解析の落とし穴#

トップレベルのpandas io関数read_htmlでHTMLテーブルを解析するために使用されるライブラリには、バージョン管理に関するいくつかの問題があります。

**lxml**に関する問題

  • 利点

    • **lxml**は非常に高速です。

    • **lxml**の正しいインストールにはCythonが必要です。

  • 欠点

    • **lxml**は、**厳密に有効なマークアップ**が与えられない限り、解析結果についていかなる保証も**しません**。

    • 上記を考慮し、弊社はユーザーが**lxml**バックエンドを使用することを許可しましたが、**lxml**が解析に失敗した場合、**このバックエンドは****html5lib**を**使用します**。

    • したがって、**lxml**が失敗した場合でも有効な結果(他のすべてが有効であれば)が得られるように、**BeautifulSoup4****html5lib**の両方をインストールすることを**強くお勧めします**。

****lxml**をバックエンドとして使用する**BeautifulSoup4**に関する問題**

  • **BeautifulSoup4**は本質的にパーサーバックエンドのラッパーに過ぎないため、上記の問題もここにも当てはまります。

****html5lib**をバックエンドとして使用する**BeautifulSoup4**に関する問題**

  • 利点

    • **html5lib****lxml**よりもはるかに寛容であり、その結果、現実のマークアップを、例えば通知なしに要素を削除するようなことではなく、はるかに健全な方法で処理します。

    • **html5lib**は、無効なマークアップから自動的に有効なHTML5マークアップを生成します。これは有効なドキュメントを保証するため、HTMLテーブルを解析する上で非常に重要です。しかし、マークアップを修正するプロセスには単一の定義がないため、それが「正しい」ことを意味するわけではありません。

    • **html5lib**は純粋なPythonであり、自身のインストール以外の追加のビルド手順は不要です。

  • 欠点

    • **html5lib**を使用する最大の欠点は、非常に遅いことです。しかし、ウェブ上の多くのテーブルは解析アルゴリズムの実行時間に影響するほど大きくないという事実を考慮してください。ボトルネックは、ウェブ経由でURLから生のテキストを読み取るプロセス、つまりI/O(入出力)にある可能性が高いです。非常に大きなテーブルの場合は、そうではないかもしれません。

LaTeX#

バージョン 1.3.0 で追加。

現在、LaTeXからの読み込みメソッドはなく、出力メソッドのみです。

LaTeXファイルへの書き込み#

DataFrame および Styler オブジェクトには現在、to_latexメソッドがあります。条件付きスタイリングの柔軟性が高い前者のStyler.to_latex()メソッドを、後者の将来的な非推奨の可能性を考慮して、使用することを推奨します。

Styler.to_latexのドキュメントを確認してください。条件付きスタイリングの例と、キーワード引数の動作について説明されています。

単純なアプリケーションには、以下のパターンで十分です。

In [369]: df = pd.DataFrame([[1, 2], [3, 4]], index=["a", "b"], columns=["c", "d"])

In [370]: print(df.style.to_latex())
\begin{tabular}{lrr}
 & c & d \\
a & 1 & 2 \\
b & 3 & 4 \\
\end{tabular}

出力前に値をフォーマットするには、Styler.formatメソッドを連結します。

In [371]: print(df.style.format("€ {}").to_latex())
\begin{tabular}{lrr}
 & c & d \\
a & € 1 & € 2 \\
b & € 3 & € 4 \\
\end{tabular}

XML#

XMLの読み込み#

バージョン 1.3.0 で追加。

トップレベルのread_xml()関数は、XML文字列/ファイル/URLを受け入れ、ノードと属性をpandas DataFrameに解析します。

デザインタイプが多様な標準XML構造がないため、read_xmlはフラットで浅いバージョンで最適に機能します。XMLドキュメントが深くネストされている場合は、stylesheet機能を使用してXMLをよりフラットなバージョンに変換します。

いくつかの例を見てみましょう。

XML文字列を読み込む

In [372]: from io import StringIO

In [373]: xml = """<?xml version="1.0" encoding="UTF-8"?>
   .....: <bookstore>
   .....:   <book category="cooking">
   .....:     <title lang="en">Everyday Italian</title>
   .....:     <author>Giada De Laurentiis</author>
   .....:     <year>2005</year>
   .....:     <price>30.00</price>
   .....:   </book>
   .....:   <book category="children">
   .....:     <title lang="en">Harry Potter</title>
   .....:     <author>J K. Rowling</author>
   .....:     <year>2005</year>
   .....:     <price>29.99</price>
   .....:   </book>
   .....:   <book category="web">
   .....:     <title lang="en">Learning XML</title>
   .....:     <author>Erik T. Ray</author>
   .....:     <year>2003</year>
   .....:     <price>39.95</price>
   .....:   </book>
   .....: </bookstore>"""
   .....: 

In [374]: df = pd.read_xml(StringIO(xml))

In [375]: df
Out[375]: 
   category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95

オプションなしでURLを読み込む

In [376]: df = pd.read_xml("https://www.w3schools.com/xml/books.xml")

In [377]: df
Out[377]: 
   category              title                  author  year  price      cover
0   cooking   Everyday Italian     Giada De Laurentiis  2005  30.00       None
1  children       Harry Potter            J K. Rowling  2005  29.99       None
2       web  XQuery Kick Start  Vaidyanathan Nagarajan  2003  49.99       None
3       web       Learning XML             Erik T. Ray  2003  39.95  paperback

「books.xml」ファイルの内容を読み込み、文字列としてread_xmlに渡します。

In [378]: file_path = "books.xml"

In [379]: with open(file_path, "w") as f:
   .....:     f.write(xml)
   .....: 

In [380]: with open(file_path, "r") as f:
   .....:     df = pd.read_xml(StringIO(f.read()))
   .....: 

In [381]: df
Out[381]: 
   category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95

「books.xml」の内容をStringIOまたはBytesIOのインスタンスとして読み込み、read_xmlに渡します。

In [382]: with open(file_path, "r") as f:
   .....:     sio = StringIO(f.read())
   .....: 

In [383]: df = pd.read_xml(sio)

In [384]: df
Out[384]: 
   category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95
In [385]: with open(file_path, "rb") as f:
   .....:     bio = BytesIO(f.read())
   .....: 

In [386]: df = pd.read_xml(bio)

In [387]: df
Out[387]: 
   category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95

NIH NCBI PMCの記事データセットなど、AWS S3バケットからバイオメディカルおよび生命科学ジャーナルを提供するXMLを読み込むことさえできます。

In [388]: df = pd.read_xml(
   .....:     "s3://pmc-oa-opendata/oa_comm/xml/all/PMC1236943.xml",
   .....:     xpath=".//journal-meta",
   .....: )
   .....: 

In [389]: df
Out[389]: 
              journal-id              journal-title       issn  publisher
0  Cardiovasc Ultrasound  Cardiovascular Ultrasound  1476-7120        NaN

デフォルトのparserとしてlxmlを使用すると、PythonのElementTree APIを拡張したフル機能のXMLライブラリにアクセスできます。強力なツールの1つは、より表現力豊かなXPathを使用してノードを選択的または条件的にクエリする機能です。

In [390]: df = pd.read_xml(file_path, xpath="//book[year=2005]")

In [391]: df
Out[391]: 
   category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99

解析する要素または属性のみを指定する

In [392]: df = pd.read_xml(file_path, elems_only=True)

In [393]: df
Out[393]: 
              title               author  year  price
0  Everyday Italian  Giada De Laurentiis  2005  30.00
1      Harry Potter         J K. Rowling  2005  29.99
2      Learning XML          Erik T. Ray  2003  39.95
In [394]: df = pd.read_xml(file_path, attrs_only=True)

In [395]: df
Out[395]: 
   category
0   cooking
1  children
2       web

XMLドキュメントは、プレフィックスを持つ名前空間とプレフィックスを持たないデフォルトの名前空間の両方を持つことができ、どちらも特殊な属性xmlnsで示されます。名前空間コンテキスト内のノードで解析するためには、xpathがプレフィックスを参照する必要があります。

たとえば、以下のXMLには、プレフィックスdocとURIがhttps://example.comにある名前空間が含まれています。doc:rowノードを解析するには、namespacesを使用する必要があります。

In [396]: xml = """<?xml version='1.0' encoding='utf-8'?>
   .....: <doc:data xmlns:doc="https://example.com">
   .....:   <doc:row>
   .....:     <doc:shape>square</doc:shape>
   .....:     <doc:degrees>360</doc:degrees>
   .....:     <doc:sides>4.0</doc:sides>
   .....:   </doc:row>
   .....:   <doc:row>
   .....:     <doc:shape>circle</doc:shape>
   .....:     <doc:degrees>360</doc:degrees>
   .....:     <doc:sides/>
   .....:   </doc:row>
   .....:   <doc:row>
   .....:     <doc:shape>triangle</doc:shape>
   .....:     <doc:degrees>180</doc:degrees>
   .....:     <doc:sides>3.0</doc:sides>
   .....:   </doc:row>
   .....: </doc:data>"""
   .....: 

In [397]: df = pd.read_xml(StringIO(xml),
   .....:                  xpath="//doc:row",
   .....:                  namespaces={"doc": "https://example.com"})
   .....: 

In [398]: df
Out[398]: 
      shape  degrees  sides
0    square      360    4.0
1    circle      360    NaN
2  triangle      180    3.0

同様に、XMLドキュメントはプレフィックスのないデフォルトの名前空間を持つことができます。一時的なプレフィックスを割り当てないとノードが返されず、ValueErrorが発生します。しかし、正しいURIに任意の一時的な名前を割り当てると、ノードによる解析が可能になります。

In [399]: xml = """<?xml version='1.0' encoding='utf-8'?>
   .....: <data xmlns="https://example.com">
   .....:  <row>
   .....:    <shape>square</shape>
   .....:    <degrees>360</degrees>
   .....:    <sides>4.0</sides>
   .....:  </row>
   .....:  <row>
   .....:    <shape>circle</shape>
   .....:    <degrees>360</degrees>
   .....:    <sides/>
   .....:  </row>
   .....:  <row>
   .....:    <shape>triangle</shape>
   .....:    <degrees>180</degrees>
   .....:    <sides>3.0</sides>
   .....:  </row>
   .....: </data>"""
   .....: 

In [400]: df = pd.read_xml(StringIO(xml),
   .....:                  xpath="//pandas:row",
   .....:                  namespaces={"pandas": "https://example.com"})
   .....: 

In [401]: df
Out[401]: 
      shape  degrees  sides
0    square      360    4.0
1    circle      360    NaN
2  triangle      180    3.0

ただし、XPathがデフォルト、/*などのノード名を参照しない場合、namespacesは不要です。

xpathは解析されるコンテンツの親を識別するため、子ノードまたは現在の属性を含む直接の子孫のみが解析されます。したがって、read_xmlは孫やその他の子孫のテキストを解析せず、子孫の属性も解析しません。下位レベルのコンテンツを取得するには、xpathを下位レベルに調整します。たとえば、

In [402]: xml = """
   .....: <data>
   .....:   <row>
   .....:     <shape sides="4">square</shape>
   .....:     <degrees>360</degrees>
   .....:   </row>
   .....:   <row>
   .....:     <shape sides="0">circle</shape>
   .....:     <degrees>360</degrees>
   .....:   </row>
   .....:   <row>
   .....:     <shape sides="3">triangle</shape>
   .....:     <degrees>180</degrees>
   .....:   </row>
   .....: </data>"""
   .....: 

In [403]: df = pd.read_xml(StringIO(xml), xpath="./row")

In [404]: df
Out[404]: 
      shape  degrees
0    square      360
1    circle      360
2  triangle      180

shape要素のsides属性は、この属性がrow要素の直接の子ではなくその子に存在するため、期待通りに解析されませんでした。言い換えれば、sides属性はrow要素の孫レベルの子孫です。しかし、xpathrow要素をターゲットとしているため、その子と属性のみをカバーします。

lxmlをパーサーとして使用すると、XSLTスクリプト(文字列/ファイル/URLタイプも可)でネストされたXMLドキュメントをフラット化できます。背景として、XSLTは、XSLTプロセッサを使用して元のXMLドキュメントを別のXML、HTML、さらにはテキスト(CSV、JSONなど)に変換できる特殊なXMLファイルで記述された特殊な言語です。

たとえば、シカゴの「L」ライドの多少ネストされた構造を考えてみましょう。ここでは、ステーションとライドの要素が独自のセクションにデータをカプセル化しています。以下のXSLTを使用すると、lxmlは元のネストされたドキュメントをよりフラットな出力(デモンストレーションのために以下に示す)に変換し、DataFrameへの解析を容易にすることができます。

In [405]: xml = """<?xml version='1.0' encoding='utf-8'?>
   .....:  <response>
   .....:   <row>
   .....:     <station id="40850" name="Library"/>
   .....:     <month>2020-09-01T00:00:00</month>
   .....:     <rides>
   .....:       <avg_weekday_rides>864.2</avg_weekday_rides>
   .....:       <avg_saturday_rides>534</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
   .....:     </rides>
   .....:   </row>
   .....:   <row>
   .....:     <station id="41700" name="Washington/Wabash"/>
   .....:     <month>2020-09-01T00:00:00</month>
   .....:     <rides>
   .....:       <avg_weekday_rides>2707.4</avg_weekday_rides>
   .....:       <avg_saturday_rides>1909.8</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
   .....:     </rides>
   .....:   </row>
   .....:   <row>
   .....:     <station id="40380" name="Clark/Lake"/>
   .....:     <month>2020-09-01T00:00:00</month>
   .....:     <rides>
   .....:       <avg_weekday_rides>2949.6</avg_weekday_rides>
   .....:       <avg_saturday_rides>1657</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
   .....:     </rides>
   .....:   </row>
   .....:  </response>"""
   .....: 

In [406]: xsl = """<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   .....:    <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
   .....:    <xsl:strip-space elements="*"/>
   .....:    <xsl:template match="/response">
   .....:       <xsl:copy>
   .....:         <xsl:apply-templates select="row"/>
   .....:       </xsl:copy>
   .....:    </xsl:template>
   .....:    <xsl:template match="row">
   .....:       <xsl:copy>
   .....:         <station_id><xsl:value-of select="station/@id"/></station_id>
   .....:         <station_name><xsl:value-of select="station/@name"/></station_name>
   .....:         <xsl:copy-of select="month|rides/*"/>
   .....:       </xsl:copy>
   .....:    </xsl:template>
   .....:  </xsl:stylesheet>"""
   .....: 

In [407]: output = """<?xml version='1.0' encoding='utf-8'?>
   .....:  <response>
   .....:    <row>
   .....:       <station_id>40850</station_id>
   .....:       <station_name>Library</station_name>
   .....:       <month>2020-09-01T00:00:00</month>
   .....:       <avg_weekday_rides>864.2</avg_weekday_rides>
   .....:       <avg_saturday_rides>534</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
   .....:    </row>
   .....:    <row>
   .....:       <station_id>41700</station_id>
   .....:       <station_name>Washington/Wabash</station_name>
   .....:       <month>2020-09-01T00:00:00</month>
   .....:       <avg_weekday_rides>2707.4</avg_weekday_rides>
   .....:       <avg_saturday_rides>1909.8</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
   .....:    </row>
   .....:    <row>
   .....:       <station_id>40380</station_id>
   .....:       <station_name>Clark/Lake</station_name>
   .....:       <month>2020-09-01T00:00:00</month>
   .....:       <avg_weekday_rides>2949.6</avg_weekday_rides>
   .....:       <avg_saturday_rides>1657</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
   .....:    </row>
   .....:  </response>"""
   .....: 

In [408]: df = pd.read_xml(StringIO(xml), stylesheet=xsl)

In [409]: df
Out[409]: 
   station_id       station_name  ... avg_saturday_rides  avg_sunday_holiday_rides
0       40850            Library  ...              534.0                     417.2
1       41700  Washington/Wabash  ...             1909.8                    1438.6
2       40380         Clark/Lake  ...             1657.0                    1453.8

[3 rows x 6 columns]

数百メガバイトからギガバイトに及ぶ非常に大きなXMLファイルの場合、pandas.read_xml()は、lxmlのiterparseおよびetreeのiterparseを使用して、このようなサイズのファイルを解析することをサポートしています。これらは、XMLツリーを反復処理して特定の要素と属性を抽出するためのメモリ効率の良い方法であり、ツリー全体をメモリに保持することはありません。

バージョン1.5.0で追加。

この機能を使用するには、物理的なXMLファイルパスをread_xmlに渡し、iterparse引数を使用する必要があります。ファイルは圧縮されておらず、オンラインソースを指すのではなく、ローカルディスクに保存されている必要があります。iterparseは、ドキュメント内の繰り返しノード(行となる)がキーで、その値が繰り返しノードの子孫(つまり、子、孫)である要素または属性のリストである辞書である必要があります。このメソッドではXPathは使用されないため、子孫が互いに同じ関係を共有する必要はありません。以下に、Wikipediaの非常に大きな(12 GB以上)最新記事データダンプを読み込む例を示します。

In [1]: df = pd.read_xml(
...         "/path/to/downloaded/enwikisource-latest-pages-articles.xml",
...         iterparse = {"page": ["title", "ns", "id"]}
...     )
...     df
Out[2]:
                                                     title   ns        id
0                                       Gettysburg Address    0     21450
1                                                Main Page    0     42950
2                            Declaration by United Nations    0      8435
3             Constitution of the United States of America    0      8435
4                     Declaration of Independence (Israel)    0     17858
...                                                    ...  ...       ...
3578760               Page:Black cat 1897 07 v2 n10.pdf/17  104    219649
3578761               Page:Black cat 1897 07 v2 n10.pdf/43  104    219649
3578762               Page:Black cat 1897 07 v2 n10.pdf/44  104    219649
3578763      The History of Tom Jones, a Foundling/Book IX    0  12084291
3578764  Page:Shakespeare of Stratford (1926) Yale.djvu/91  104     21450

[3578765 rows x 3 columns]

XMLの書き込み#

バージョン 1.3.0 で追加。

DataFrameオブジェクトには、DataFrameの内容をXMLドキュメントとしてレンダリングするインスタンスメソッドto_xmlがあります。

このメソッドは、DTD、CData、XSDスキーマ、処理命令、コメントなど、XMLの特殊なプロパティをサポートしていません。ルートレベルの名前空間のみがサポートされています。ただし、stylesheetを使用すると、初期出力後にデザインを変更できます。

いくつかの例を見てみましょう。

オプションなしでXMLを書き込む

In [410]: geom_df = pd.DataFrame(
   .....:     {
   .....:         "shape": ["square", "circle", "triangle"],
   .....:         "degrees": [360, 360, 180],
   .....:         "sides": [4, np.nan, 3],
   .....:     }
   .....: )
   .....: 

In [411]: print(geom_df.to_xml())
<?xml version='1.0' encoding='utf-8'?>
<data>
  <row>
    <index>0</index>
    <shape>square</shape>
    <degrees>360</degrees>
    <sides>4.0</sides>
  </row>
  <row>
    <index>1</index>
    <shape>circle</shape>
    <degrees>360</degrees>
    <sides/>
  </row>
  <row>
    <index>2</index>
    <shape>triangle</shape>
    <degrees>180</degrees>
    <sides>3.0</sides>
  </row>
</data>

新しいルートと行名でXMLを書き込む

In [412]: print(geom_df.to_xml(root_name="geometry", row_name="objects"))
<?xml version='1.0' encoding='utf-8'?>
<geometry>
  <objects>
    <index>0</index>
    <shape>square</shape>
    <degrees>360</degrees>
    <sides>4.0</sides>
  </objects>
  <objects>
    <index>1</index>
    <shape>circle</shape>
    <degrees>360</degrees>
    <sides/>
  </objects>
  <objects>
    <index>2</index>
    <shape>triangle</shape>
    <degrees>180</degrees>
    <sides>3.0</sides>
  </objects>
</geometry>

属性中心のXMLを書き込む

In [413]: print(geom_df.to_xml(attr_cols=geom_df.columns.tolist()))
<?xml version='1.0' encoding='utf-8'?>
<data>
  <row index="0" shape="square" degrees="360" sides="4.0"/>
  <row index="1" shape="circle" degrees="360"/>
  <row index="2" shape="triangle" degrees="180" sides="3.0"/>
</data>

要素と属性の混在を書き込む

In [414]: print(
   .....:     geom_df.to_xml(
   .....:         index=False,
   .....:         attr_cols=['shape'],
   .....:         elem_cols=['degrees', 'sides'])
   .....: )
   .....: 
<?xml version='1.0' encoding='utf-8'?>
<data>
  <row shape="square">
    <degrees>360</degrees>
    <sides>4.0</sides>
  </row>
  <row shape="circle">
    <degrees>360</degrees>
    <sides/>
  </row>
  <row shape="triangle">
    <degrees>180</degrees>
    <sides>3.0</sides>
  </row>
</data>

階層列を持つすべてのDataFramesは、アンダースコアで区切られたレベルを持つXML要素名にフラット化されます。

In [415]: ext_geom_df = pd.DataFrame(
   .....:     {
   .....:         "type": ["polygon", "other", "polygon"],
   .....:         "shape": ["square", "circle", "triangle"],
   .....:         "degrees": [360, 360, 180],
   .....:         "sides": [4, np.nan, 3],
   .....:     }
   .....: )
   .....: 

In [416]: pvt_df = ext_geom_df.pivot_table(index='shape',
   .....:                                  columns='type',
   .....:                                  values=['degrees', 'sides'],
   .....:                                  aggfunc='sum')
   .....: 

In [417]: pvt_df
Out[417]: 
         degrees         sides        
type       other polygon other polygon
shape                                 
circle     360.0     NaN   0.0     NaN
square       NaN   360.0   NaN     4.0
triangle     NaN   180.0   NaN     3.0

In [418]: print(pvt_df.to_xml())
<?xml version='1.0' encoding='utf-8'?>
<data>
  <row>
    <shape>circle</shape>
    <degrees_other>360.0</degrees_other>
    <degrees_polygon/>
    <sides_other>0.0</sides_other>
    <sides_polygon/>
  </row>
  <row>
    <shape>square</shape>
    <degrees_other/>
    <degrees_polygon>360.0</degrees_polygon>
    <sides_other/>
    <sides_polygon>4.0</sides_polygon>
  </row>
  <row>
    <shape>triangle</shape>
    <degrees_other/>
    <degrees_polygon>180.0</degrees_polygon>
    <sides_other/>
    <sides_polygon>3.0</sides_polygon>
  </row>
</data>

デフォルトの名前空間でXMLを書き込む

In [419]: print(geom_df.to_xml(namespaces={"": "https://example.com"}))
<?xml version='1.0' encoding='utf-8'?>
<data xmlns="https://example.com">
  <row>
    <index>0</index>
    <shape>square</shape>
    <degrees>360</degrees>
    <sides>4.0</sides>
  </row>
  <row>
    <index>1</index>
    <shape>circle</shape>
    <degrees>360</degrees>
    <sides/>
  </row>
  <row>
    <index>2</index>
    <shape>triangle</shape>
    <degrees>180</degrees>
    <sides>3.0</sides>
  </row>
</data>

名前空間プレフィックスでXMLを書き込む

In [420]: print(
   .....:     geom_df.to_xml(namespaces={"doc": "https://example.com"},
   .....:                    prefix="doc")
   .....: )
   .....: 
<?xml version='1.0' encoding='utf-8'?>
<doc:data xmlns:doc="https://example.com">
  <doc:row>
    <doc:index>0</doc:index>
    <doc:shape>square</doc:shape>
    <doc:degrees>360</doc:degrees>
    <doc:sides>4.0</doc:sides>
  </doc:row>
  <doc:row>
    <doc:index>1</doc:index>
    <doc:shape>circle</doc:shape>
    <doc:degrees>360</doc:degrees>
    <doc:sides/>
  </doc:row>
  <doc:row>
    <doc:index>2</doc:index>
    <doc:shape>triangle</doc:shape>
    <doc:degrees>180</doc:degrees>
    <doc:sides>3.0</doc:sides>
  </doc:row>
</doc:data>

宣言なしでXMLを書き込む、またはきれいに出力する

In [421]: print(
   .....:     geom_df.to_xml(xml_declaration=False,
   .....:                    pretty_print=False)
   .....: )
   .....: 
<data><row><index>0</index><shape>square</shape><degrees>360</degrees><sides>4.0</sides></row><row><index>1</index><shape>circle</shape><degrees>360</degrees><sides/></row><row><index>2</index><shape>triangle</shape><degrees>180</degrees><sides>3.0</sides></row></data>

XMLを書き込み、スタイルシートで変換する

In [422]: xsl = """<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   .....:    <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
   .....:    <xsl:strip-space elements="*"/>
   .....:    <xsl:template match="/data">
   .....:      <geometry>
   .....:        <xsl:apply-templates select="row"/>
   .....:      </geometry>
   .....:    </xsl:template>
   .....:    <xsl:template match="row">
   .....:      <object index="{index}">
   .....:        <xsl:if test="shape!='circle'">
   .....:            <xsl:attribute name="type">polygon</xsl:attribute>
   .....:        </xsl:if>
   .....:        <xsl:copy-of select="shape"/>
   .....:        <property>
   .....:            <xsl:copy-of select="degrees|sides"/>
   .....:        </property>
   .....:      </object>
   .....:    </xsl:template>
   .....:  </xsl:stylesheet>"""
   .....: 

In [423]: print(geom_df.to_xml(stylesheet=xsl))
<?xml version="1.0"?>
<geometry>
  <object index="0" type="polygon">
    <shape>square</shape>
    <property>
      <degrees>360</degrees>
      <sides>4.0</sides>
    </property>
  </object>
  <object index="1">
    <shape>circle</shape>
    <property>
      <degrees>360</degrees>
      <sides/>
    </property>
  </object>
  <object index="2" type="polygon">
    <shape>triangle</shape>
    <property>
      <degrees>180</degrees>
      <sides>3.0</sides>
    </property>
  </object>
</geometry>

XMLの最終ノート#

  • すべてのXMLドキュメントはW3Cの仕様に準拠しています。etreeおよびlxmlパーサーはどちらも、整形式でない、またはXML構文ルールに従わないマークアップドキュメントを解析できません。HTMLはXHTML仕様に従わない限りXMLドキュメントではないことに注意してください。ただし、KML、XAML、RSS、MusicML、MathMLなどの他の一般的なマークアップタイプは準拠したXMLスキーマです。

  • 上記の理由により、アプリケーションがpandas操作の前にXMLを構築する場合、文字列連結や正規表現の調整ではなく、etreelxmlなどの適切なDOMライブラリを使用して必要なドキュメントを構築してください。XMLはマークアップルールを持つ特別なテキストファイルであることを常に忘れないでください。

  • 非常に大きなXMLファイル(数百MBからGB単位)の場合、XPathとXSLTはメモリを大量に消費する操作になる可能性があります。大きなXMLファイルの読み書きには、十分なRAMがあることを確認してください(テキストサイズの約5倍程度)。

  • XSLTはプログラミング言語であるため、このようなスクリプトは環境にセキュリティ上のリスクをもたらしたり、大規模または無限の再帰操作を実行したりする可能性があるため、注意して使用してください。常に、完全な実行の前に小さな断片でスクリプトをテストしてください。

  • etreeパーサーは、複雑なXPathとすべてのXSLTを除く、read_xmlto_xmlの両方のすべての機能をサポートしています。機能は限られていますが、etreeは依然として信頼性が高く有能なパーサーおよびツリービルダーです。そのパフォーマンスは、大きなファイルではlxmlに多少劣るかもしれませんが、中小規模のファイルでは比較的目立ちません。

Excelファイル#

read_excel()メソッドは、openpyxl Pythonモジュールを使用してExcel 2007+ (.xlsx) ファイルを読み取ることができます。Excel 2003 (.xls) ファイルはxlrdを使用して読み取ることができます。バイナリExcel (.xlsb) ファイルはpyxlsbを使用して読み取ることができます。すべての形式はcalamineエンジンを使用して読み取ることができます。to_excel()インスタンスメソッドは、DataFrameをExcelに保存するために使用されます。一般的に、セマンティクスはcsvデータでの作業に似ています。高度な戦略については、クックブックを参照してください。

engine=Noneの場合、エンジンを決定するために次のロジックが使用されます。

  • path_or_bufferがOpenDocument形式 (.odf, .ods, .odt) の場合、odfが使用されます。

  • それ以外の場合、path_or_bufferがxls形式の場合、xlrdが使用されます。

  • それ以外の場合、path_or_bufferがxlsb形式の場合、pyxlsbが使用されます。

  • それ以外の場合、openpyxlが使用されます。

Excelファイルの読み込み#

最も基本的なユースケースでは、read_excelはExcelファイルへのパスと、解析するシートを示すsheet_nameを受け取ります。

engine_kwargsパラメータを使用する場合、pandasはこれらの引数をエンジンに渡します。このため、pandasが内部的にどの関数を使用しているかを知ることが重要です。

  • openpyxlエンジンについては、pandasはopenpyxl.load_workbook()を使用して(.xlsx)および(.xlsm)ファイルを読み込んでいます。

  • xlrdエンジンについては、pandasはxlrd.open_workbook()を使用して(.xls)ファイルを読み込んでいます。

  • pyxlsbエンジンについては、pandasはpyxlsb.open_workbook()を使用して(.xlsb)ファイルを読み込んでいます。

  • odfエンジンについては、pandasはodf.opendocument.load()を使用して(.ods)ファイルを読み込んでいます。

  • calamineエンジンについては、pandasはpython_calamine.load_workbook()を使用して(.xlsx)、(.xlsm)、(.xls)、(.xlsb)、(.ods)ファイルを読み込んでいます。

# Returns a DataFrame
pd.read_excel("path_to_file.xls", sheet_name="Sheet1")

ExcelFileクラス#

同じファイルから複数のシートを扱いやすくするために、ExcelFileクラスを使用してファイルをラップし、read_excelに渡すことができます。ファイルがメモリに一度だけ読み込まれるため、複数のシートを読み込む際のパフォーマンスが向上します。

xlsx = pd.ExcelFile("path_to_file.xls")
df = pd.read_excel(xlsx, "Sheet1")

ExcelFileクラスはコンテキストマネージャとしても使用できます。

with pd.ExcelFile("path_to_file.xls") as xls:
    df1 = pd.read_excel(xls, "Sheet1")
    df2 = pd.read_excel(xls, "Sheet2")

sheet_namesプロパティは、ファイル内のシート名のリストを生成します。

ExcelFileの主なユースケースは、異なるパラメータで複数のシートを解析することです。

data = {}
# For when Sheet1's format differs from Sheet2
with pd.ExcelFile("path_to_file.xls") as xls:
    data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
    data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=1)

すべてのシートに同じ解析パラメータを使用する場合、シート名のリストをread_excelに渡すだけで、パフォーマンスの低下はありません。

# using the ExcelFile class
data = {}
with pd.ExcelFile("path_to_file.xls") as xls:
    data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
    data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=None, na_values=["NA"])

# equivalent using the read_excel function
data = pd.read_excel(
    "path_to_file.xls", ["Sheet1", "Sheet2"], index_col=None, na_values=["NA"]
)

ExcelFileは、xlrd.book.Bookオブジェクトをパラメータとして呼び出すこともできます。これにより、ユーザーはExcelファイルの読み込み方法を制御できます。たとえば、xlrd.open_workbook()on_demand=Trueで呼び出すことで、シートをオンデマンドでロードできます。

import xlrd

xlrd_book = xlrd.open_workbook("path_to_file.xls", on_demand=True)
with pd.ExcelFile(xlrd_book) as xls:
    df1 = pd.read_excel(xls, "Sheet1")
    df2 = pd.read_excel(xls, "Sheet2")

シートの指定#

2番目の引数はsheet_nameであり、ExcelFile.sheet_namesと混同しないでください。

ExcelFileの属性sheet_namesは、シートのリストへのアクセスを提供します。

  • 引数sheet_nameを使用すると、読み込むシートを指定できます。

  • sheet_nameのデフォルト値は0で、最初のシートを読み込むことを示します。

  • ブック内の特定のシート名を参照するには、文字列を渡します。

  • シートのインデックスを参照するには、整数を渡します。インデックスはPythonの慣例に従い、0から始まります。

  • 指定されたシートの辞書を返すには、文字列または整数のリストを渡します。

  • 利用可能なすべてのシートの辞書を返すには、Noneを渡します。

# Returns a DataFrame
pd.read_excel("path_to_file.xls", "Sheet1", index_col=None, na_values=["NA"])

シートインデックスの使用

# Returns a DataFrame
pd.read_excel("path_to_file.xls", 0, index_col=None, na_values=["NA"])

すべてのデフォルト値を使用

# Returns a DataFrame
pd.read_excel("path_to_file.xls")

すべてのシートを取得するためにNoneを使用

# Returns a dictionary of DataFrames
pd.read_excel("path_to_file.xls", sheet_name=None)

複数のシートを取得するためにリストを使用

# Returns the 1st and 4th sheet, as a dictionary of DataFrames.
pd.read_excel("path_to_file.xls", sheet_name=["Sheet1", 3])

read_excelは、sheet_nameをシート名のリスト、シート位置のリスト、またはすべてのシートを読み込むためのNoneに設定することで、複数のシートを読み込むことができます。シートは、それぞれ整数または文字列を使用して、シートインデックスまたはシート名で指定できます。

MultiIndexの読み込み#

read_excelは、index_colに列のリストを渡し、headerに行のリストを渡すことで、MultiIndexインデックスを読み込むことができます。indexまたはcolumnsにシリアル化されたレベル名がある場合、レベルを構成する行/列を指定することで、それらも読み込まれます。

たとえば、名前のないMultiIndexインデックスを読み込むには

In [424]: df = pd.DataFrame(
   .....:     {"a": [1, 2, 3, 4], "b": [5, 6, 7, 8]},
   .....:     index=pd.MultiIndex.from_product([["a", "b"], ["c", "d"]]),
   .....: )
   .....: 

In [425]: df.to_excel("path_to_file.xlsx")

In [426]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1])

In [427]: df
Out[427]: 
     a  b
a c  1  5
  d  2  6
b c  3  7
  d  4  8

インデックスにレベル名がある場合、それらも同じパラメータを使用して解析されます。

In [428]: df.index = df.index.set_names(["lvl1", "lvl2"])

In [429]: df.to_excel("path_to_file.xlsx")

In [430]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1])

In [431]: df
Out[431]: 
           a  b
lvl1 lvl2      
a    c     1  5
     d     2  6
b    c     3  7
     d     4  8

ソースファイルにMultiIndexインデックスと列の両方がある場合、それぞれを指定するリストをindex_colheaderに渡す必要があります。

In [432]: df.columns = pd.MultiIndex.from_product([["a"], ["b", "d"]], names=["c1", "c2"])

In [433]: df.to_excel("path_to_file.xlsx")

In [434]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1], header=[0, 1])

In [435]: df
Out[435]: 
c1         a   
c2         b  d
lvl1 lvl2      
a    c     1  5
     d     2  6
b    c     3  7
     d     4  8

index_colで指定された列の欠損値は、merged_cells=Trueの場合にto_excelとのラウンドトリップを可能にするために前方補完されます。欠損値の前方補完を避けるには、データの読み込み後にindex_colの代わりにset_indexを使用してください。

特定の列の解析#

ユーザーがExcelで一時的な計算を行うために列を挿入し、それらの列を読み込みたくないというケースがよくあります。read_excelは、解析する列のサブセットを指定できるようにusecolsキーワードを受け入れます。

カンマ区切りのExcel列と範囲のセットを文字列として指定できます。

pd.read_excel("path_to_file.xls", "Sheet1", usecols="A,C:E")

usecolsが整数のリストの場合、解析されるファイル列のインデックスと見なされます。

pd.read_excel("path_to_file.xls", "Sheet1", usecols=[0, 2, 3])

要素の順序は無視されるため、usecols=[0, 1][1, 0]と同じです。

usecolsが文字列のリストの場合、各文字列は、ユーザーがnamesで提供した、またはドキュメントのヘッダー行から推測された列名に対応すると見なされます。これらの文字列は、解析される列を定義します。

pd.read_excel("path_to_file.xls", "Sheet1", usecols=["foo", "bar"])

要素の順序は無視されるため、usecols=['baz', 'joe']['joe', 'baz']と同じです。

usecolsが呼び出し可能オブジェクトの場合、その呼び出し可能関数は列名に対して評価され、呼び出し可能関数がTrueと評価される名前を返します。

pd.read_excel("path_to_file.xls", "Sheet1", usecols=lambda x: x.isalpha())

日付の解析#

日付のような値は、Excelファイルを読み込む際に通常自動的に適切なdtypeに変換されます。しかし、日付**のように見える**文字列の列(ただし、Excelで実際に日付としてフォーマットされていない)がある場合、parse_datesキーワードを使用してそれらの文字列をdatetimeに解析できます。

pd.read_excel("path_to_file.xls", "Sheet1", parse_dates=["date_strings"])

セルコンバーター#

convertersオプションを使用すると、Excelセルの内容を変換できます。たとえば、列をブール型に変換するには、

pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyBools": bool})

このオプションは欠損値を処理し、コンバータでの例外を欠損データとして扱います。変換は列全体ではなくセルごとに適用されるため、配列のdtypeは保証されません。例えば、欠損値を含む整数の列は、NaNが厳密には浮動小数点数であるため、整数dtypeの配列に変換できません。整数dtypeを回復するには、欠損データを手動でマスクする必要があります。

def cfun(x):
    return int(x) if x else -1


pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyInts": cfun})

Dtype の仕様#

コンバータの代替として、列全体の型は、列名を型にマッピングする辞書を受け取るdtypeキーワードを使用して指定できます。型推論なしでデータを解釈するには、strまたはobject型を使用します。

pd.read_excel("path_to_file.xls", dtype={"MyInts": "int64", "MyText": str})

Excel ファイルの書き込み#

ディスクへの Excel ファイルの書き込み#

DataFrameオブジェクトをExcelファイルのシートに書き込むには、to_excelインスタンスメソッドを使用できます。引数は、上記のto_csvとほぼ同じで、最初の引数がExcelファイルの名前、オプションの2番目の引数がDataFrameを書き込むシートの名前です。例:

df.to_excel("path_to_file.xlsx", sheet_name="Sheet1")

.xlsx拡張子を持つファイルは、xlsxwriter(利用可能な場合)またはopenpyxlを使用して書き込まれます。

DataFrameはREPL出力を模倣するように書き込まれます。index_labelは最初の行ではなく2番目の行に配置されます。to_excel()merge_cellsオプションをFalseに設定することで、最初の行に配置できます。

df.to_excel("path_to_file.xlsx", index_label="label", merge_cells=False)

単一のExcelファイル内の別々のシートに別々のDataFrameを書き込むには、ExcelWriterを渡すことができます。

with pd.ExcelWriter("path_to_file.xlsx") as writer:
    df1.to_excel(writer, sheet_name="Sheet1")
    df2.to_excel(writer, sheet_name="Sheet2")

engine_kwargsパラメータを使用する場合、pandasはこれらの引数をエンジンに渡します。このため、pandasが内部的にどの関数を使用しているかを知ることが重要です。

  • openpyxlエンジンでは、pandasは新しいシートを作成するためにopenpyxl.Workbook()を使用し、既存のシートにデータを追加するためにopenpyxl.load_workbook()を使用します。openpyxlエンジンは(.xlsx)および(.xlsm)ファイルに書き込みます。

  • xlsxwriterエンジンでは、pandasはxlsxwriter.Workbook()を使用して(.xlsx)ファイルに書き込みます。

  • odfエンジンでは、pandasはodf.opendocument.OpenDocumentSpreadsheet()を使用して(.ods)ファイルに書き込みます。

メモリへのExcelファイルの書き込み#

pandasは、ExcelWriterを使用して、StringIOBytesIOのようなバッファ状オブジェクトへのExcelファイルの書き込みをサポートしています。

from io import BytesIO

bio = BytesIO()

# By setting the 'engine' in the ExcelWriter constructor.
writer = pd.ExcelWriter(bio, engine="xlsxwriter")
df.to_excel(writer, sheet_name="Sheet1")

# Save the workbook
writer.save()

# Seek to the beginning and read to copy the workbook to a variable in memory
bio.seek(0)
workbook = bio.read()

engineはオプションですが推奨されます。エンジンを設定すると、生成されるワークブックのバージョンが決まります。engine='xlrd'を設定するとExcel 2003形式のワークブック (xls) が生成されます。'openpyxl'または'xlsxwriter'を使用するとExcel 2007形式のワークブック (xlsx) が生成されます。省略した場合、Excel 2007形式のワークブックが生成されます。

Excel ライターエンジン#

pandas は2つの方法で Excel ライターを選択します。

  1. engine キーワード引数

  2. ファイル名の拡張子 (設定オプションで指定されたデフォルトによる)

デフォルトでは、pandas は.xlsxにはXlsxWriterを、.xlsmにはopenpyxlを使用します。複数のエンジンがインストールされている場合は、設定オプションio.excel.xlsx.writerio.excel.xls.writerを設定することでデフォルトエンジンを設定できます。Xlsxwriterが利用できない場合、pandas は.xlsxファイルに対してopenpyxlにフォールバックします。

使用するライターを指定するには、to_excelExcelWriterにエンジンキーワード引数を渡すことができます。組み込みエンジンは次のとおりです。

  • openpyxl: バージョン 2.4 以降が必要です

  • xlsxwriter

# By setting the 'engine' in the DataFrame 'to_excel()' methods.
df.to_excel("path_to_file.xlsx", sheet_name="Sheet1", engine="xlsxwriter")

# By setting the 'engine' in the ExcelWriter constructor.
writer = pd.ExcelWriter("path_to_file.xlsx", engine="xlsxwriter")

# Or via pandas configuration.
from pandas import options  # noqa: E402

options.io.excel.xlsx.writer = "xlsxwriter"

df.to_excel("path_to_file.xlsx", sheet_name="Sheet1")

スタイルと書式設定#

pandas から作成される Excel ワークシートの見た目と体裁は、DataFrameto_excelメソッドで以下のパラメータを使用して変更できます。

  • float_format: 浮動小数点数の書式指定文字列 (デフォルト None)。

  • freeze_panes: 固定する最下行と最右列を表す2つの整数のタプル。これらのパラメータはそれぞれ1から始まるため、(1, 1) は最初の行と最初の列を固定します (デフォルト None)。

Xlsxwriterエンジンを使用すると、to_excelメソッドで作成されたExcelワークシートの書式を制御するための多くのオプションが提供されます。Xlsxwriterのドキュメントに優れた例があります。こちらをご覧ください: https://xlsxwriter.readthedocs.io/working_with_pandas.html

OpenDocument スプレッドシート#

ExcelファイルのI/Oメソッドは、odfpyモジュールを使用してOpenDocumentスプレッドシートの読み書きもサポートしています。engine='odf'を使用してExcelファイルに対して行えることと同様に、OpenDocumentスプレッドシートの読み書きのセマンティクスと機能も一致します。オプションの依存関係 'odfpy' をインストールする必要があります。

read_excel()メソッドはOpenDocumentスプレッドシートを読み取ることができます。

# Returns a DataFrame
pd.read_excel("path_to_file.ods", engine="odf")

同様に、to_excel()メソッドはOpenDocumentスプレッドシートを書き込むことができます。

# Writes DataFrame to a .ods file
df.to_excel("path_to_file.ods", engine="odf")

バイナリ Excel (.xlsb) ファイル#

read_excel()メソッドは、pyxlsbモジュールを使用してバイナリExcelファイルを読み取ることもできます。バイナリExcelファイルの読み取りのセマンティクスと機能は、engine='pyxlsb'を使用してExcelファイルに対して行えることとほぼ一致します。pyxlsbはファイル内のdatetime型を認識せず、代わりに浮動小数点数を返します(datetime型を認識する必要がある場合はcalamineを使用できます)。

# Returns a DataFrame
pd.read_excel("path_to_file.xlsb", engine="pyxlsb")

現在、pandasはバイナリExcelファイルの*読み取り*のみをサポートしています。書き込みは実装されていません。

Calamine (Excel および ODS ファイル)#

read_excel()メソッドは、python-calamineモジュールを使用してExcelファイル(.xlsx.xlsm.xls.xlsb)およびOpenDocumentスプレッドシート(.ods)を読み取ることができます。このモジュールはRustライブラリcalamineのバインディングであり、ほとんどの場合、他のエンジンよりも高速です。オプションの依存関係 'python-calamine' をインストールする必要があります。

# Returns a DataFrame
pd.read_excel("path_to_file.xlsb", engine="calamine")

クリップボード#

データを取得する便利な方法は、read_clipboard()メソッドを使用することです。これはクリップボードバッファの内容を受け取り、read_csvメソッドに渡します。たとえば、以下のテキストをクリップボードにコピーできます(多くのOSではCTRL-C)。

  A B C
x 1 4 p
y 2 5 q
z 3 6 r

そして、呼び出しによってデータをDataFrameに直接インポートします。

>>> clipdf = pd.read_clipboard()
>>> clipdf
  A B C
x 1 4 p
y 2 5 q
z 3 6 r

to_clipboardメソッドは、DataFrameの内容をクリップボードに書き込むために使用できます。その後、クリップボードの内容を他のアプリケーションに貼り付けることができます(多くのオペレーティングシステムではCTRL-V)。ここでは、DataFrameをクリップボードに書き込み、それを読み戻す例を示します。

>>> df = pd.DataFrame(
...     {"A": [1, 2, 3], "B": [4, 5, 6], "C": ["p", "q", "r"]}, index=["x", "y", "z"]
... )

>>> df
  A B C
x 1 4 p
y 2 5 q
z 3 6 r
>>> df.to_clipboard()
>>> pd.read_clipboard()
  A B C
x 1 4 p
y 2 5 q
z 3 6 r

以前クリップボードに書き込んだのと同じ内容が返ってきたことがわかります。

これらのメソッドを使用するには、Linuxにxclipまたはxsel (PyQt5、PyQt4、またはqtpyとともに) をインストールする必要がある場合があります。

ピクリング#

すべてのpandasオブジェクトには、PythonのcPickleモジュールを使用してデータ構造をpickle形式でディスクに保存するto_pickleメソッドが装備されています。

In [436]: df
Out[436]: 
c1         a   
c2         b  d
lvl1 lvl2      
a    c     1  5
     d     2  6
b    c     3  7
     d     4  8

In [437]: df.to_pickle("foo.pkl")

pandas名前空間のread_pickle関数は、pickle化されたpandasオブジェクト(またはその他のpickle化されたオブジェクト)をファイルからロードするために使用できます。

In [438]: pd.read_pickle("foo.pkl")
Out[438]: 
c1         a   
c2         b  d
lvl1 lvl2      
a    c     1  5
     d     2  6
b    c     3  7
     d     4  8

警告

信頼できないソースから受け取ったピクルデータは安全にロードできない可能性があります。

参照: https://docs.python.org/3/library/pickle.html

警告

read_pickle() は、いくつかのマイナーリリースまでの後方互換性が保証されています。

圧縮ピクルファイル#

read_pickle(), DataFrame.to_pickle() および Series.to_pickle() は、圧縮されたピクルファイルを読み書きできます。gzip, bz2, xz, zstd の圧縮タイプが読み書きでサポートされています。zip ファイル形式は読み取りのみをサポートしており、読み取るデータファイルは1つのみである必要があります。

圧縮タイプは明示的なパラメータとして指定することも、ファイル拡張子から推論することもできます。'infer' の場合、ファイル名がそれぞれ '.gz', '.bz2', '.zip', '.xz', '.zst' で終わる場合は、gzip, bz2, zip, xz, zstd を使用します。

圧縮パラメータは、圧縮プロトコルにオプションを渡すためのdictにすることもできます。'method'キーを圧縮プロトコルの名前('zip''gzip''bz2''xz''zstd'のいずれか)に設定する必要があります。その他のキーと値のペアは、基となる圧縮ライブラリに渡されます。

In [439]: df = pd.DataFrame(
   .....:     {
   .....:         "A": np.random.randn(1000),
   .....:         "B": "foo",
   .....:         "C": pd.date_range("20130101", periods=1000, freq="s"),
   .....:     }
   .....: )
   .....: 

In [440]: df
Out[440]: 
            A    B                   C
0   -0.317441  foo 2013-01-01 00:00:00
1   -1.236269  foo 2013-01-01 00:00:01
2    0.896171  foo 2013-01-01 00:00:02
3   -0.487602  foo 2013-01-01 00:00:03
4   -0.082240  foo 2013-01-01 00:00:04
..        ...  ...                 ...
995 -0.171092  foo 2013-01-01 00:16:35
996  1.786173  foo 2013-01-01 00:16:36
997 -0.575189  foo 2013-01-01 00:16:37
998  0.820750  foo 2013-01-01 00:16:38
999 -1.256530  foo 2013-01-01 00:16:39

[1000 rows x 3 columns]

明示的な圧縮タイプを使用する

In [441]: df.to_pickle("data.pkl.compress", compression="gzip")

In [442]: rt = pd.read_pickle("data.pkl.compress", compression="gzip")

In [443]: rt
Out[443]: 
            A    B                   C
0   -0.317441  foo 2013-01-01 00:00:00
1   -1.236269  foo 2013-01-01 00:00:01
2    0.896171  foo 2013-01-01 00:00:02
3   -0.487602  foo 2013-01-01 00:00:03
4   -0.082240  foo 2013-01-01 00:00:04
..        ...  ...                 ...
995 -0.171092  foo 2013-01-01 00:16:35
996  1.786173  foo 2013-01-01 00:16:36
997 -0.575189  foo 2013-01-01 00:16:37
998  0.820750  foo 2013-01-01 00:16:38
999 -1.256530  foo 2013-01-01 00:16:39

[1000 rows x 3 columns]

拡張子からの圧縮タイプの推論

In [444]: df.to_pickle("data.pkl.xz", compression="infer")

In [445]: rt = pd.read_pickle("data.pkl.xz", compression="infer")

In [446]: rt
Out[446]: 
            A    B                   C
0   -0.317441  foo 2013-01-01 00:00:00
1   -1.236269  foo 2013-01-01 00:00:01
2    0.896171  foo 2013-01-01 00:00:02
3   -0.487602  foo 2013-01-01 00:00:03
4   -0.082240  foo 2013-01-01 00:00:04
..        ...  ...                 ...
995 -0.171092  foo 2013-01-01 00:16:35
996  1.786173  foo 2013-01-01 00:16:36
997 -0.575189  foo 2013-01-01 00:16:37
998  0.820750  foo 2013-01-01 00:16:38
999 -1.256530  foo 2013-01-01 00:16:39

[1000 rows x 3 columns]

デフォルトは「推論」です。

In [447]: df.to_pickle("data.pkl.gz")

In [448]: rt = pd.read_pickle("data.pkl.gz")

In [449]: rt
Out[449]: 
            A    B                   C
0   -0.317441  foo 2013-01-01 00:00:00
1   -1.236269  foo 2013-01-01 00:00:01
2    0.896171  foo 2013-01-01 00:00:02
3   -0.487602  foo 2013-01-01 00:00:03
4   -0.082240  foo 2013-01-01 00:00:04
..        ...  ...                 ...
995 -0.171092  foo 2013-01-01 00:16:35
996  1.786173  foo 2013-01-01 00:16:36
997 -0.575189  foo 2013-01-01 00:16:37
998  0.820750  foo 2013-01-01 00:16:38
999 -1.256530  foo 2013-01-01 00:16:39

[1000 rows x 3 columns]

In [450]: df["A"].to_pickle("s1.pkl.bz2")

In [451]: rt = pd.read_pickle("s1.pkl.bz2")

In [452]: rt
Out[452]: 
0     -0.317441
1     -1.236269
2      0.896171
3     -0.487602
4     -0.082240
         ...   
995   -0.171092
996    1.786173
997   -0.575189
998    0.820750
999   -1.256530
Name: A, Length: 1000, dtype: float64

圧縮を高速化するために、圧縮プロトコルにオプションを渡す

In [453]: df.to_pickle("data.pkl.gz", compression={"method": "gzip", "compresslevel": 1})

msgpack#

pandas のmsgpackサポートはバージョン 1.0.0 で削除されました。pickleを使用することをお勧めします。

あるいは、pandasオブジェクトのワイヤー上での転送にはArrow IPCシリアライゼーション形式を使用することもできます。pyarrowのドキュメントについては、こちらを参照してください。

HDF5 (PyTables)#

HDFStoreは、優れたPyTablesライブラリを使用して、高性能なHDF5形式でpandasを読み書きする辞書のようなオブジェクトです。高度な戦略についてはクックブックを参照してください。

警告

pandas は HDF5 ファイルの読み書きに PyTables を使用し、これにより pickle を使用してオブジェクト dtype データをシリアライズできます。信頼できないソースから受け取った pickle データをロードすることは安全ではありません。

詳細は、https://docs.python.org/3/library/pickle.htmlを参照してください。

In [454]: store = pd.HDFStore("store.h5")

In [455]: print(store)
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

辞書にキーと値のペアを追加するように、オブジェクトをファイルに書き込むことができます。

In [456]: index = pd.date_range("1/1/2000", periods=8)

In [457]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])

In [458]: df = pd.DataFrame(np.random.randn(8, 3), index=index, columns=["A", "B", "C"])

# store.put('s', s) is an equivalent method
In [459]: store["s"] = s

In [460]: store["df"] = df

In [461]: store
Out[461]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

現在の、またはそれ以降の Python セッションで、保存されたオブジェクトを取得できます。

# store.get('df') is an equivalent method
In [462]: store["df"]
Out[462]: 
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

# dotted (attribute) access provides get as well
In [463]: store.df
Out[463]: 
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

キーで指定されたオブジェクトの削除

# store.remove('df') is an equivalent method
In [464]: del store["df"]

In [465]: store
Out[465]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

ストアを閉じてコンテキストマネージャーを使用する

In [466]: store.close()

In [467]: store
Out[467]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

In [468]: store.is_open
Out[468]: False

# Working with, and automatically closing the store using a context manager
In [469]: with pd.HDFStore("store.h5") as store:
   .....:     store.keys()
   .....: 

読み書き API#

HDFStore は、read_csvto_csv の動作と同様に、読み取り用の read_hdf と書き込み用の to_hdf を使用するトップレベル API をサポートしています。

In [470]: df_tl = pd.DataFrame({"A": list(range(5)), "B": list(range(5))})

In [471]: df_tl.to_hdf("store_tl.h5", key="table", append=True)

In [472]: pd.read_hdf("store_tl.h5", "table", where=["index>2"])
Out[472]: 
   A  B
3  3  3
4  4  4

HDFStoreはデフォルトでは、すべての行が欠損している行は削除しません。dropna=Trueを設定することで、この動作を変更できます。

In [473]: df_with_missing = pd.DataFrame(
   .....:     {
   .....:         "col1": [0, np.nan, 2],
   .....:         "col2": [1, np.nan, np.nan],
   .....:     }
   .....: )
   .....: 

In [474]: df_with_missing
Out[474]: 
   col1  col2
0   0.0   1.0
1   NaN   NaN
2   2.0   NaN

In [475]: df_with_missing.to_hdf("file.h5", key="df_with_missing", format="table", mode="w")

In [476]: pd.read_hdf("file.h5", "df_with_missing")
Out[476]: 
   col1  col2
0   0.0   1.0
1   NaN   NaN
2   2.0   NaN

In [477]: df_with_missing.to_hdf(
   .....:     "file.h5", key="df_with_missing", format="table", mode="w", dropna=True
   .....: )
   .....: 

In [478]: pd.read_hdf("file.h5", "df_with_missing")
Out[478]: 
   col1  col2
0   0.0   1.0
2   2.0   NaN

固定形式#

上記の例は、putを使用した保存を示しており、HDF5をfixed形式と呼ばれる固定配列形式でPyTablesに書き込みます。このタイプのストアは、一度書き込むと追加**できません**(ただし、単に削除して書き直すことはできます)。また、クエリ**もできません**。全体を一度に取得する必要があります。非一意な列名を持つDataFrameもサポートしていません。fixed形式のストアは、tableストアよりも書き込みが非常に高速で、読み取りもわずかに高速です。この形式は、putまたはto_hdfを使用する場合、またはformat='fixed'またはformat='f'でデフォルトで指定されます。

警告

fixed形式は、whereを使用して取得しようとするとTypeErrorを発生させます。

In [479]: pd.DataFrame(np.random.randn(10, 2)).to_hdf("test_fixed.h5", key="df")

In [480]: pd.read_hdf("test_fixed.h5", "df", where="index>5")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[480], line 1
----> 1 pd.read_hdf("test_fixed.h5", "df", where="index>5")

File ~/work/pandas/pandas/pandas/io/pytables.py:457, in read_hdf(path_or_buf, key, mode, errors, where, start, stop, columns, iterator, chunksize, **kwargs)
    452                 raise ValueError(
    453                     "key must be provided when HDF5 "
    454                     "file contains multiple datasets."
    455                 )
    456         key = candidate_only_group._v_pathname
--> 457     return store.select(
    458         key,
    459         where=where,
    460         start=start,
    461         stop=stop,
    462         columns=columns,
    463         iterator=iterator,
    464         chunksize=chunksize,
    465         auto_close=auto_close,
    466     )
    467 except (ValueError, TypeError, LookupError):
    468     if not isinstance(path_or_buf, HDFStore):
    469         # if there is an error, close the store if we opened it.

File ~/work/pandas/pandas/pandas/io/pytables.py:911, in HDFStore.select(self, key, where, start, stop, columns, iterator, chunksize, auto_close)
    897 # create the iterator
    898 it = TableIterator(
    899     self,
    900     s,
   (...)
    908     auto_close=auto_close,
    909 )
--> 911 return it.get_result()

File ~/work/pandas/pandas/pandas/io/pytables.py:2034, in TableIterator.get_result(self, coordinates)
   2031     where = self.where
   2033 # directly return the result
-> 2034 results = self.func(self.start, self.stop, where)
   2035 self.close()
   2036 return results

File ~/work/pandas/pandas/pandas/io/pytables.py:895, in HDFStore.select.<locals>.func(_start, _stop, _where)
    894 def func(_start, _stop, _where):
--> 895     return s.read(start=_start, stop=_stop, where=_where, columns=columns)

File ~/work/pandas/pandas/pandas/io/pytables.py:3295, in BlockManagerFixed.read(self, where, columns, start, stop)
   3287 def read(
   3288     self,
   3289     where=None,
   (...)
   3293 ) -> DataFrame:
   3294     # start, stop applied to rows, so 0th axis only
-> 3295     self.validate_read(columns, where)
   3296     select_axis = self.obj_type()._get_block_manager_axis(0)
   3298     axes = []

File ~/work/pandas/pandas/pandas/io/pytables.py:2927, in GenericFixed.validate_read(self, columns, where)
   2922     raise TypeError(
   2923         "cannot pass a column specification when reading "
   2924         "a Fixed format store. this store must be selected in its entirety"
   2925     )
   2926 if where is not None:
-> 2927     raise TypeError(
   2928         "cannot pass a where specification when reading "
   2929         "from a Fixed format store. this store must be selected in its entirety"
   2930     )

TypeError: cannot pass a where specification when reading from a Fixed format store. this store must be selected in its entirety

テーブル形式#

HDFStoreは、ディスク上の別のPyTables形式、table形式をサポートしています。概念的に、tableは行と列を持つDataFrameと非常によく似た形状をしています。tableは、同じセッションまたは他のセッションで追加することができます。さらに、削除およびクエリタイプの操作がサポートされています。この形式は、appendまたはputまたはto_hdfformat='table'またはformat='t'として指定されます。

この形式は、pd.set_option('io.hdf.default_format','table')のようにオプションとして設定することもでき、put/append/to_hdfがデフォルトでtable形式で保存されるようになります。

In [481]: store = pd.HDFStore("store.h5")

In [482]: df1 = df[0:4]

In [483]: df2 = df[4:]

# append data (creates a table automatically)
In [484]: store.append("df", df1)

In [485]: store.append("df", df2)

In [486]: store
Out[486]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

# select the entire object
In [487]: store.select("df")
Out[487]: 
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

# the type of stored data
In [488]: store.root.df._v_attrs.pandas_type
Out[488]: 'frame_table'

put操作にformat='table'またはformat='t'を渡すことでもtableを作成できます。

階層キー#

ストアへのキーは文字列として指定できます。これらは階層的なパス名のような形式(例:foo/bar/bah)で指定でき、これによりサブストア(PyTablesの用語ではGroups)の階層が生成されます。キーは先頭の'/'なしで指定できますが、**常に**絶対パスです(例:'foo'は'/foo'を指します)。削除操作はサブストアとその**下**のすべてを削除できるため、注意が必要です。

In [489]: store.put("foo/bar/bah", df)

In [490]: store.append("food/orange", df)

In [491]: store.append("food/apple", df)

In [492]: store
Out[492]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

# a list of keys are returned
In [493]: store.keys()
Out[493]: ['/df', '/food/apple', '/food/orange', '/foo/bar/bah']

# remove all nodes under this level
In [494]: store.remove("food")

In [495]: store
Out[495]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

walkメソッドを使用してグループ階層をたどることができます。このメソッドは、各グループキーとその内容の相対キーを含むタプルを生成します。

In [496]: for (path, subgroups, subkeys) in store.walk():
   .....:     for subgroup in subgroups:
   .....:         print("GROUP: {}/{}".format(path, subgroup))
   .....:     for subkey in subkeys:
   .....:         key = "/".join([path, subkey])
   .....:         print("KEY: {}".format(key))
   .....:         print(store.get(key))
   .....: 
GROUP: /foo
KEY: /df
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517
GROUP: /foo/bar
KEY: /foo/bar/bah
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

警告

階層キーは、上記で説明したルートノードの下に格納された項目に対して、ドット(属性)アクセスとして取得することはできません。

In [497]: store.foo.bar.bah
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[497], line 1
----> 1 store.foo.bar.bah

File ~/work/pandas/pandas/pandas/io/pytables.py:618, in HDFStore.__getattr__(self, name)
    616 """allow attribute access to get stores"""
    617 try:
--> 618     return self.get(name)
    619 except (KeyError, ClosedFileError):
    620     pass

File ~/work/pandas/pandas/pandas/io/pytables.py:818, in HDFStore.get(self, key)
    816 if group is None:
    817     raise KeyError(f"No object named {key} in the file")
--> 818 return self._read_group(group)

File ~/work/pandas/pandas/pandas/io/pytables.py:1883, in HDFStore._read_group(self, group)
   1882 def _read_group(self, group: Node):
-> 1883     s = self._create_storer(group)
   1884     s.infer_axes()
   1885     return s.read()

File ~/work/pandas/pandas/pandas/io/pytables.py:1757, in HDFStore._create_storer(self, group, format, value, encoding, errors)
   1755         tt = "generic_table"
   1756     else:
-> 1757         raise TypeError(
   1758             "cannot create a storer if the object is not existing "
   1759             "nor a value are passed"
   1760         )
   1761 else:
   1762     if isinstance(value, Series):

TypeError: cannot create a storer if the object is not existing nor a value are passed
# you can directly access the actual PyTables node but using the root node
In [498]: store.root.foo.bar.bah
Out[498]: 
/foo/bar/bah (Group) ''
  children := ['axis0' (Array), 'axis1' (Array), 'block0_items' (Array), 'block0_values' (Array)]

代わりに、明示的な文字列ベースのキーを使用します。

In [499]: store["foo/bar/bah"]
Out[499]: 
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

型の保存#

テーブルへの混在型の保存#

混在するdtypeデータの保存がサポートされています。文字列は、追加された列の最大サイズを使用して固定幅で保存されます。後続のより長い文字列の追加試行は、ValueErrorを発生させます。

min_itemsize={`values`: size}をappendのパラメータとして渡すと、文字列列の最小値が大きくなります。floats, strings, ints, bools, datetime64の保存が現在サポートされています。文字列列の場合、nan_rep = 'nan'をappendに渡すと、ディスク上のデフォルトのnan表現が変更されます(これはnp.nanとの間で変換されます)。これはデフォルトでnanになります。

In [500]: df_mixed = pd.DataFrame(
   .....:     {
   .....:         "A": np.random.randn(8),
   .....:         "B": np.random.randn(8),
   .....:         "C": np.array(np.random.randn(8), dtype="float32"),
   .....:         "string": "string",
   .....:         "int": 1,
   .....:         "bool": True,
   .....:         "datetime64": pd.Timestamp("20010102"),
   .....:     },
   .....:     index=list(range(8)),
   .....: )
   .....: 

In [501]: df_mixed.loc[df_mixed.index[3:5], ["A", "B", "string", "datetime64"]] = np.nan

In [502]: store.append("df_mixed", df_mixed, min_itemsize={"values": 50})

In [503]: df_mixed1 = store.select("df_mixed")

In [504]: df_mixed1
Out[504]: 
          A         B         C  ... int  bool                    datetime64
0  0.013747 -1.166078 -1.292080  ...   1  True 1970-01-01 00:00:00.978393600
1 -0.712009  0.247572  1.526911  ...   1  True 1970-01-01 00:00:00.978393600
2 -0.645096  1.687406  0.288504  ...   1  True 1970-01-01 00:00:00.978393600
3       NaN       NaN  0.097771  ...   1  True                           NaT
4       NaN       NaN  1.536408  ...   1  True                           NaT
5 -0.023202  0.043702  0.926790  ...   1  True 1970-01-01 00:00:00.978393600
6  2.359782  0.088224 -0.676448  ...   1  True 1970-01-01 00:00:00.978393600
7 -0.143428 -0.813360 -0.179724  ...   1  True 1970-01-01 00:00:00.978393600

[8 rows x 7 columns]

In [505]: df_mixed1.dtypes.value_counts()
Out[505]: 
float64           2
float32           1
object            1
int64             1
bool              1
datetime64[ns]    1
Name: count, dtype: int64

# we have provided a minimum string column size
In [506]: store.root.df_mixed.table
Out[506]: 
/df_mixed/table (Table(8,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": Float64Col(shape=(2,), dflt=0.0, pos=1),
  "values_block_1": Float32Col(shape=(1,), dflt=0.0, pos=2),
  "values_block_2": StringCol(itemsize=50, shape=(1,), dflt=b'', pos=3),
  "values_block_3": Int64Col(shape=(1,), dflt=0, pos=4),
  "values_block_4": BoolCol(shape=(1,), dflt=False, pos=5),
  "values_block_5": Int64Col(shape=(1,), dflt=0, pos=6)}
  byteorder := 'little'
  chunkshape := (689,)
  autoindex := True
  colindexes := {
    "index": Index(6, mediumshuffle, zlib(1)).is_csi=False}

MultiIndex DataFrame の保存#

MultiIndex DataFramesをテーブルとして保存することは、均質なインデックスDataFramesからの保存/選択と非常によく似ています。

In [507]: index = pd.MultiIndex(
   .....:    levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
   .....:    codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
   .....:    names=["foo", "bar"],
   .....: )
   .....: 

In [508]: df_mi = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])

In [509]: df_mi
Out[509]: 
                  A         B         C
foo bar                                
foo one   -1.303456 -0.642994 -0.649456
    two    1.012694  0.414147  1.950460
    three  1.094544 -0.802899 -0.583343
bar one    0.410395  0.618321  0.560398
    two    1.434027 -0.033270  0.343197
baz two   -1.646063 -0.695847 -0.429156
    three -0.244688 -1.428229 -0.138691
qux one    1.866184 -1.446617  0.036660
    two   -1.660522  0.929553 -1.298649
    three  3.565769  0.682402  1.041927

In [510]: store.append("df_mi", df_mi)

In [511]: store.select("df_mi")
Out[511]: 
                  A         B         C
foo bar                                
foo one   -1.303456 -0.642994 -0.649456
    two    1.012694  0.414147  1.950460
    three  1.094544 -0.802899 -0.583343
bar one    0.410395  0.618321  0.560398
    two    1.434027 -0.033270  0.343197
baz two   -1.646063 -0.695847 -0.429156
    three -0.244688 -1.428229 -0.138691
qux one    1.866184 -1.446617  0.036660
    two   -1.660522  0.929553 -1.298649
    three  3.565769  0.682402  1.041927

# the levels are automatically included as data columns
In [512]: store.select("df_mi", "foo=bar")
Out[512]: 
                A         B         C
foo bar                              
bar one  0.410395  0.618321  0.560398
    two  1.434027 -0.033270  0.343197

indexキーワードは予約されており、レベル名として使用することはできません。

クエリ#

テーブルのクエリ#

selectおよびdelete操作には、データのサブセットのみを選択/削除するために指定できるオプションの条件があります。これにより、非常に大きなディスク上のテーブルを持ち、データの一部のみを取得することができます。

クエリは内部的にTermクラスを使用して、ブール式として指定されます。

  • indexcolumnsDataFrame のサポートされているインデクサーです。

  • data_columnsが指定されている場合、これらを追加のインデクサーとして使用できます。

  • MultiIndex のレベル名。指定されていない場合は、デフォルト名 level_0level_1、... となります。

有効な比較演算子は

=, ==, !=, >, >=, <, <=

有効なブール式は、次と組み合わせて使用されます。

  • |: または

  • &: かつ

  • (): グループ化用

これらのルールは、pandasでインデックス作成のためにブール式が使用される方法と似ています。

  • = は比較演算子 == に自動的に展開されます。

  • ~ は否定演算子ですが、非常に限られた状況でのみ使用できます。

  • 式のリスト/タプルが渡された場合、それらは&を介して結合されます。

以下は有効な式です。

  • 'index >= date'

  • "columns = ['A', 'D']"

  • "columns in ['A', 'D']"

  • 'columns = A'

  • 'columns == A'

  • "~(columns = ['A', 'B'])"

  • 'index > df.index[3] & string = "bar"'

  • '(index > df.index[3] & index <= df.index[6]) | string = "bar"'

  • "ts >= Timestamp('2012-02-01')"

  • "major_axis>=20130101"

indexers はサブ式の左側にあります。

columns, major_axis, ts

サブ式の右側(比較演算子の後)は、次のいずれかになります。

  • 評価される関数、例: Timestamp('2012-02-01')

  • 文字列、例:"bar"

  • 日付形式、例: 20130101 または "20130101"

  • リスト、例:"['A', 'B']"

  • ローカル名前空間で定義された変数、例:date

文字列をクエリ式に補間してクエリに渡すことは推奨されません。目的の文字列を変数に代入し、その変数を式で使用してください。たとえば、次のようにします。

string = "HolyMoly'"
store.select("df", "index == string")

これではなく

string = "HolyMoly'"
store.select('df', f'index == {string}')

後者は**機能せず**、SyntaxErrorを発生させます。string変数にはシングルクォートの後にダブルクォートがあることに注意してください。

もし補間を*しなければならない*場合は、'%r'書式指定子を使用してください。

store.select("df", "index == %r" % string)

stringを引用符で囲みます。

以下にいくつかの例を示します。

In [513]: dfq = pd.DataFrame(
   .....:     np.random.randn(10, 4),
   .....:     columns=list("ABCD"),
   .....:     index=pd.date_range("20130101", periods=10),
   .....: )
   .....: 

In [514]: store.append("dfq", dfq, format="table", data_columns=True)

インライン関数評価を使用してブール式を使用します。

In [515]: store.select("dfq", "index>pd.Timestamp('20130104') & columns=['A', 'B']")
Out[515]: 
                   A         B
2013-01-05 -0.830545 -0.457071
2013-01-06  0.431186  1.049421
2013-01-07  0.617509 -0.811230
2013-01-08  0.947422 -0.671233
2013-01-09 -0.183798 -1.211230
2013-01-10  0.361428  0.887304

インライン列参照を使用します。

In [516]: store.select("dfq", where="A>0 or C>0")
Out[516]: 
                   A         B         C         D
2013-01-02  0.658179  0.362814 -0.917897  0.010165
2013-01-03  0.905122  1.848731 -1.184241  0.932053
2013-01-05 -0.830545 -0.457071  1.565581  1.148032
2013-01-06  0.431186  1.049421  0.383309  0.595013
2013-01-07  0.617509 -0.811230 -2.088563 -1.393500
2013-01-08  0.947422 -0.671233 -0.847097 -1.187785
2013-01-10  0.361428  0.887304  0.266457 -0.399641

columnsキーワードを指定して返される列のリストを選択できます。これは'columns=list_of_columns_to_filter'を渡すのと同等です。

In [517]: store.select("df", "columns=['A', 'B']")
Out[517]: 
                   A         B
2000-01-01  0.858644 -0.851236
2000-01-02 -0.080372 -1.268121
2000-01-03  0.816983  1.965656
2000-01-04  0.712795 -0.062433
2000-01-05 -0.298721 -1.988045
2000-01-06  1.103675  1.382242
2000-01-07 -0.729161 -0.142928
2000-01-08 -1.005977  0.465222

startおよびstopパラメータは、合計検索空間を制限するために指定できます。これらはテーブル内の総行数で表されます。

selectは、クエリ式に不明な変数参照がある場合、ValueErrorを発生させます。これは通常、**データ列ではない**列に対して選択しようとしていることを意味します。

selectは、クエリ式が無効な場合、SyntaxErrorを発生させます。

timedelta64[ns] のクエリ#

timedelta64[ns]型を使用して保存およびクエリを実行できます。項は<float>(<unit>)の形式で指定できます。ここでfloatは符号付き(および小数)で、unitはtimedeltaのD,s,ms,us,nsのいずれかになります。例を示します。

In [518]: from datetime import timedelta

In [519]: dftd = pd.DataFrame(
   .....:     {
   .....:         "A": pd.Timestamp("20130101"),
   .....:         "B": [
   .....:             pd.Timestamp("20130101") + timedelta(days=i, seconds=10)
   .....:             for i in range(10)
   .....:         ],
   .....:     }
   .....: )
   .....: 

In [520]: dftd["C"] = dftd["A"] - dftd["B"]

In [521]: dftd
Out[521]: 
           A                   B                  C
0 2013-01-01 2013-01-01 00:00:10  -1 days +23:59:50
1 2013-01-01 2013-01-02 00:00:10  -2 days +23:59:50
2 2013-01-01 2013-01-03 00:00:10  -3 days +23:59:50
3 2013-01-01 2013-01-04 00:00:10  -4 days +23:59:50
4 2013-01-01 2013-01-05 00:00:10  -5 days +23:59:50
5 2013-01-01 2013-01-06 00:00:10  -6 days +23:59:50
6 2013-01-01 2013-01-07 00:00:10  -7 days +23:59:50
7 2013-01-01 2013-01-08 00:00:10  -8 days +23:59:50
8 2013-01-01 2013-01-09 00:00:10  -9 days +23:59:50
9 2013-01-01 2013-01-10 00:00:10 -10 days +23:59:50

In [522]: store.append("dftd", dftd, data_columns=True)

In [523]: store.select("dftd", "C<'-3.5D'")
Out[523]: 
                              A                   B                  C
4 1970-01-01 00:00:01.356998400 2013-01-05 00:00:10  -5 days +23:59:50
5 1970-01-01 00:00:01.356998400 2013-01-06 00:00:10  -6 days +23:59:50
6 1970-01-01 00:00:01.356998400 2013-01-07 00:00:10  -7 days +23:59:50
7 1970-01-01 00:00:01.356998400 2013-01-08 00:00:10  -8 days +23:59:50
8 1970-01-01 00:00:01.356998400 2013-01-09 00:00:10  -9 days +23:59:50
9 1970-01-01 00:00:01.356998400 2013-01-10 00:00:10 -10 days +23:59:50

MultiIndex のクエリ#

MultiIndexからの選択は、レベルの名前を使用することで実現できます。

In [524]: df_mi.index.names
Out[524]: FrozenList(['foo', 'bar'])

In [525]: store.select("df_mi", "foo=baz and bar=two")
Out[525]: 
                A         B         C
foo bar                              
baz two -1.646063 -0.695847 -0.429156

MultiIndexレベルの名前がNoneの場合、選択したいMultiIndexのレベルnを用いて、level_nキーワードにより自動的に利用可能になります。

In [526]: index = pd.MultiIndex(
   .....:     levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
   .....:     codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
   .....: )
   .....: 

In [527]: df_mi_2 = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])

In [528]: df_mi_2
Out[528]: 
                  A         B         C
foo one   -0.219582  1.186860 -1.437189
    two    0.053768  1.872644 -1.469813
    three -0.564201  0.876341  0.407749
bar one   -0.232583  0.179812  0.922152
    two   -1.820952 -0.641360  2.133239
baz two   -0.941248 -0.136307 -1.271305
    three -0.099774 -0.061438 -0.845172
qux one    0.465793  0.756995 -0.541690
    two   -0.802241  0.877657 -2.553831
    three  0.094899 -2.319519  0.293601

In [529]: store.append("df_mi_2", df_mi_2)

# the levels are automatically included as data columns with keyword level_n
In [530]: store.select("df_mi_2", "level_0=foo and level_1=two")
Out[530]: 
                A         B         C
foo two  0.053768  1.872644 -1.469813

インデックス付け#

データがすでにテーブルにある場合(append/put操作の後)に、create_table_indexを使用してテーブルのインデックスを作成/変更できます。テーブルインデックスの作成は**強く推奨されます**。selectをインデックス付きディメンションをwhereとして使用すると、クエリが大幅に高速化されます。

インデックスは、インデックス可能なオブジェクトと指定したデータ列に自動的に作成されます。appendindex=Falseを渡すことで、この動作をオフにできます。

# we have automagically already created an index (in the first section)
In [531]: i = store.root.df.table.cols.index.index

In [532]: i.optlevel, i.kind
Out[532]: (6, 'medium')

# change an index by passing new parameters
In [533]: store.create_table_index("df", optlevel=9, kind="full")

In [534]: i = store.root.df.table.cols.index.index

In [535]: i.optlevel, i.kind
Out[535]: (9, 'full')

大量のデータをストアに追加する場合、各追加時にインデックス作成をオフにし、最後に再作成すると便利なことがよくあります。

In [536]: df_1 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [537]: df_2 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [538]: st = pd.HDFStore("appends.h5", mode="w")

In [539]: st.append("df", df_1, data_columns=["B"], index=False)

In [540]: st.append("df", df_2, data_columns=["B"], index=False)

In [541]: st.get_storer("df").table
Out[541]: 
/df/table (Table(20,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
  "B": Float64Col(shape=(), dflt=0.0, pos=2)}
  byteorder := 'little'
  chunkshape := (2730,)

追加が完了したらインデックスを作成します。

In [542]: st.create_table_index("df", columns=["B"], optlevel=9, kind="full")

In [543]: st.get_storer("df").table
Out[543]: 
/df/table (Table(20,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
  "B": Float64Col(shape=(), dflt=0.0, pos=2)}
  byteorder := 'little'
  chunkshape := (2730,)
  autoindex := True
  colindexes := {
    "B": Index(9, fullshuffle, zlib(1)).is_csi=True}

In [544]: st.close()

既存のストアで完全にソートされたインデックス(CSI)を作成する方法については、こちらを参照してください。

データ列によるクエリ#

クエリを実行したい特定の列を(常にクエリできるindexable列以外に)指定(およびインデックス化)できます。例えば、ディスク上でこの一般的な操作を実行し、このクエリに一致するフレームだけを返したいとします。data_columns = Trueを指定すると、すべての列がdata_columnsになります。

In [545]: df_dc = df.copy()

In [546]: df_dc["string"] = "foo"

In [547]: df_dc.loc[df_dc.index[4:6], "string"] = np.nan

In [548]: df_dc.loc[df_dc.index[7:9], "string"] = "bar"

In [549]: df_dc["string2"] = "cool"

In [550]: df_dc.loc[df_dc.index[1:3], ["B", "C"]] = 1.0

In [551]: df_dc
Out[551]: 
                   A         B         C string string2
2000-01-01  0.858644 -0.851236  1.058006    foo    cool
2000-01-02 -0.080372  1.000000  1.000000    foo    cool
2000-01-03  0.816983  1.000000  1.000000    foo    cool
2000-01-04  0.712795 -0.062433  0.736755    foo    cool
2000-01-05 -0.298721 -1.988045  1.475308    NaN    cool
2000-01-06  1.103675  1.382242 -0.650762    NaN    cool
2000-01-07 -0.729161 -0.142928 -1.063038    foo    cool
2000-01-08 -1.005977  0.465222 -0.094517    bar    cool

# on-disk operations
In [552]: store.append("df_dc", df_dc, data_columns=["B", "C", "string", "string2"])

In [553]: store.select("df_dc", where="B > 0")
Out[553]: 
                   A         B         C string string2
2000-01-02 -0.080372  1.000000  1.000000    foo    cool
2000-01-03  0.816983  1.000000  1.000000    foo    cool
2000-01-06  1.103675  1.382242 -0.650762    NaN    cool
2000-01-08 -1.005977  0.465222 -0.094517    bar    cool

# getting creative
In [554]: store.select("df_dc", "B > 0 & C > 0 & string == foo")
Out[554]: 
                   A    B    C string string2
2000-01-02 -0.080372  1.0  1.0    foo    cool
2000-01-03  0.816983  1.0  1.0    foo    cool

# this is in-memory version of this type of selection
In [555]: df_dc[(df_dc.B > 0) & (df_dc.C > 0) & (df_dc.string == "foo")]
Out[555]: 
                   A    B    C string string2
2000-01-02 -0.080372  1.0  1.0    foo    cool
2000-01-03  0.816983  1.0  1.0    foo    cool

# we have automagically created this index and the B/C/string/string2
# columns are stored separately as ``PyTables`` columns
In [556]: store.root.df_dc.table
Out[556]: 
/df_dc/table (Table(8,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
  "B": Float64Col(shape=(), dflt=0.0, pos=2),
  "C": Float64Col(shape=(), dflt=0.0, pos=3),
  "string": StringCol(itemsize=3, shape=(), dflt=b'', pos=4),
  "string2": StringCol(itemsize=4, shape=(), dflt=b'', pos=5)}
  byteorder := 'little'
  chunkshape := (1680,)
  autoindex := True
  colindexes := {
    "index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
    "B": Index(6, mediumshuffle, zlib(1)).is_csi=False,
    "C": Index(6, mediumshuffle, zlib(1)).is_csi=False,
    "string": Index(6, mediumshuffle, zlib(1)).is_csi=False,
    "string2": Index(6, mediumshuffle, zlib(1)).is_csi=False}

多くの列をdata columnsにすると、パフォーマンスがいくらか低下するため、これらを指定するかどうかはユーザー次第です。さらに、最初のappend/put操作の後にはデータ列(およびインデックス可能な列)を変更できません(もちろん、データを読み込んで新しいテーブルを作成することはできます!)。

イテレータ#

selectselect_as_multipleiterator=Trueまたはchunksize=number_in_a_chunkを渡すと、結果のイテレータが返されます。デフォルトはチャンクあたり50,000行です。

In [557]: for df in store.select("df", chunksize=3):
   .....:     print(df)
   .....: 
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
                   A         B         C
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
                   A         B         C
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

read_hdfでイテレータを使用することもできます。これはストアを開き、イテレーションが完了すると自動的に閉じます。

for df in pd.read_hdf("store.h5", "df", chunksize=3):
    print(df)

チャンクサイズのキーワードは、**ソース**行に適用されることに注意してください。つまり、クエリを実行している場合、チャンクサイズはテーブルの総行数を細分化し、クエリが適用され、潜在的に不均一なサイズのチャンクでイテレータを返します。

クエリを生成し、それを使用して均等サイズの戻りチャンクを作成するためのレシピを以下に示します。

In [558]: dfeq = pd.DataFrame({"number": np.arange(1, 11)})

In [559]: dfeq
Out[559]: 
   number
0       1
1       2
2       3
3       4
4       5
5       6
6       7
7       8
8       9
9      10

In [560]: store.append("dfeq", dfeq, data_columns=["number"])

In [561]: def chunks(l, n):
   .....:     return [l[i: i + n] for i in range(0, len(l), n)]
   .....: 

In [562]: evens = [2, 4, 6, 8, 10]

In [563]: coordinates = store.select_as_coordinates("dfeq", "number=evens")

In [564]: for c in chunks(coordinates, 2):
   .....:     print(store.select("dfeq", where=c))
   .....: 
   number
1       2
3       4
   number
5       6
7       8
   number
9      10

高度なクエリ#

単一列の選択#

単一のインデックス可能またはデータ列を取得するには、select_columnメソッドを使用します。これにより、たとえば、インデックスを非常に迅速に取得できます。これらは、行番号でインデックス付けされた結果のSeriesを返します。これらは現在、whereセレクタを受け入れません。

In [565]: store.select_column("df_dc", "index")
Out[565]: 
0   2000-01-01
1   2000-01-02
2   2000-01-03
3   2000-01-04
4   2000-01-05
5   2000-01-06
6   2000-01-07
7   2000-01-08
Name: index, dtype: datetime64[ns]

In [566]: store.select_column("df_dc", "string")
Out[566]: 
0    foo
1    foo
2    foo
3    foo
4    NaN
5    NaN
6    foo
7    bar
Name: string, dtype: object
座標の選択#

クエリの座標(インデックス位置)を取得したい場合があります。これは、結果の場所のIndexを返します。これらの座標は、後続のwhere操作にも渡すことができます。

In [567]: df_coord = pd.DataFrame(
   .....:     np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
   .....: )
   .....: 

In [568]: store.append("df_coord", df_coord)

In [569]: c = store.select_as_coordinates("df_coord", "index > 20020101")

In [570]: c
Out[570]: 
Index([732, 733, 734, 735, 736, 737, 738, 739, 740, 741,
       ...
       990, 991, 992, 993, 994, 995, 996, 997, 998, 999],
      dtype='int64', length=268)

In [571]: store.select("df_coord", where=c)
Out[571]: 
                   0         1
2002-01-02  0.007717  1.168386
2002-01-03  0.759328 -0.638934
2002-01-04 -1.154018 -0.324071
2002-01-05 -0.804551 -1.280593
2002-01-06 -0.047208  1.260503
...              ...       ...
2002-09-22 -1.139583  0.344316
2002-09-23 -0.760643 -1.306704
2002-09-24  0.059018  1.775482
2002-09-25  1.242255 -0.055457
2002-09-26  0.410317  2.194489

[268 rows x 2 columns]
where マスクを使用した選択#

クエリが、選択する行のリストを作成する必要がある場合があります。通常、このmaskは、インデックス操作の結果のindexになります。この例では、datetimeindexの月が5であるものを選択します。

In [572]: df_mask = pd.DataFrame(
   .....:     np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
   .....: )
   .....: 

In [573]: store.append("df_mask", df_mask)

In [574]: c = store.select_column("df_mask", "index")

In [575]: where = c[pd.DatetimeIndex(c).month == 5].index

In [576]: store.select("df_mask", where=where)
Out[576]: 
                   0         1
2000-05-01  1.479511  0.516433
2000-05-02 -0.334984 -1.493537
2000-05-03  0.900321  0.049695
2000-05-04  0.614266 -1.077151
2000-05-05  0.233881  0.493246
...              ...       ...
2002-05-27  0.294122  0.457407
2002-05-28 -1.102535  1.215650
2002-05-29 -0.432911  0.753606
2002-05-30 -1.105212  2.311877
2002-05-31  2.567296  2.610691

[93 rows x 2 columns]
Storer オブジェクト#

保存されたオブジェクトを検査したい場合は、get_storerで取得します。これにより、オブジェクトの行数をプログラムで取得できます。

In [577]: store.get_storer("df_dc").nrows
Out[577]: 8

複数テーブルクエリ#

append_to_multipleselect_as_multipleメソッドは、複数のテーブルに対して一度に追記/選択を実行できます。これは、1つのテーブル(セレクターテーブルと呼ぶ)にほとんど/すべての列をインデックス付けし、クエリを実行するという考え方です。他のテーブルは、セレクターテーブルのインデックスと一致するインデックスを持つデータテーブルです。これにより、セレクターテーブルに対して非常に高速なクエリを実行しつつ、大量のデータを取得できます。この方法は、非常に幅の広いテーブルを持つのと似ていますが、より効率的なクエリを可能にします。

append_to_multipleメソッドは、指定された単一のDataFrameを、テーブル名をそのテーブルに必要な「列」のリストにマップする辞書dに従って複数のテーブルに分割します。リストの代わりにNoneを使用すると、そのテーブルには指定されていない残りのDataFrameの列が含まれます。selector引数は、どのテーブルがセレクターテーブルであるか(クエリを作成できるテーブル)を定義します。dropna引数は、テーブルが同期されるように、入力DataFrameから行を削除します。これは、書き込まれるテーブルの1つが行全体がnp.nanである場合、その行はすべてのテーブルから削除されることを意味します。

dropnaがFalseの場合、**テーブルの同期はユーザーの責任です**。完全にnp.Nanの行はHDFStoreに書き込まれないため、dropna=Falseを呼び出すと、一部のテーブルが行数が他のテーブルより多くなる可能性があり、したがってselect_as_multipleが機能しないか、予期しない結果を返す可能性があります。

In [578]: df_mt = pd.DataFrame(
   .....:     np.random.randn(8, 6),
   .....:     index=pd.date_range("1/1/2000", periods=8),
   .....:     columns=["A", "B", "C", "D", "E", "F"],
   .....: )
   .....: 

In [579]: df_mt["foo"] = "bar"

In [580]: df_mt.loc[df_mt.index[1], ("A", "B")] = np.nan

# you can also create the tables individually
In [581]: store.append_to_multiple(
   .....:     {"df1_mt": ["A", "B"], "df2_mt": None}, df_mt, selector="df1_mt"
   .....: )
   .....: 

In [582]: store
Out[582]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

# individual tables were created
In [583]: store.select("df1_mt")
Out[583]: 
                   A         B
2000-01-01  0.162291 -0.430489
2000-01-02       NaN       NaN
2000-01-03  0.429207 -1.099274
2000-01-04  1.869081 -1.466039
2000-01-05  0.092130 -1.726280
2000-01-06  0.266901 -0.036854
2000-01-07 -0.517871 -0.990317
2000-01-08 -0.231342  0.557402

In [584]: store.select("df2_mt")
Out[584]: 
                   C         D         E         F  foo
2000-01-01 -2.502042  0.668149  0.460708  1.834518  bar
2000-01-02  0.130441 -0.608465  0.439872  0.506364  bar
2000-01-03 -1.069546  1.236277  0.116634 -1.772519  bar
2000-01-04  0.137462  0.313939  0.748471 -0.943009  bar
2000-01-05  0.836517  2.049798  0.562167  0.189952  bar
2000-01-06  1.112750 -0.151596  1.503311  0.939470  bar
2000-01-07 -0.294348  0.335844 -0.794159  1.495614  bar
2000-01-08  0.860312 -0.538674 -0.541986 -1.759606  bar

# as a multiple
In [585]: store.select_as_multiple(
   .....:     ["df1_mt", "df2_mt"],
   .....:     where=["A>0", "B>0"],
   .....:     selector="df1_mt",
   .....: )
   .....: 
Out[585]: 
Empty DataFrame
Columns: [A, B, C, D, E, F, foo]
Index: []

テーブルからの削除#

whereを指定することで、テーブルから選択的に削除できます。行を削除する際には、PyTablesが、行を消去し、その後続のデータを**移動**して行を削除することを理解することが重要です。したがって、削除はデータの向きによっては非常にコストの高い操作になる可能性があります。最適なパフォーマンスを得るには、削除するディメンションをindexablesの最初にするのが良いでしょう。

データはindexablesの観点から(ディスク上で)順序付けされています。簡単な使用例を次に示します。パネル型のデータを保存し、日付をmajor_axisに、IDをminor_axisに配置します。データは次のようになります。

  • date_1
    • id_1

    • id_2

    • .

    • id_n

  • date_2
    • id_1

    • .

    • id_n

major_axisに対する削除操作は、1つのチャンクが削除され、その後のデータが移動するため、かなり高速であることが明確でしょう。一方、minor_axisに対する削除操作は非常に高価になります。この場合、欠損データ以外のすべてを選択するwhereを使用してテーブルを書き直す方が、ほぼ確実に高速でしょう。

警告

HDF5 は h5 ファイル内のスペースを自動的に**再利用しない**ことに注意してください。したがって、削除(またはノードの削除)と追加を繰り返すと、**ファイルサイズが増加する傾向があります**。

ファイルを*再梱包してクリーンアップ*するには、ptrepackを使用してください。

注意と警告#

圧縮#

PyTablesは、保存されたデータを圧縮することを可能にします。これはテーブルだけでなく、あらゆる種類のストアに適用されます。圧縮を制御するために2つのパラメータが使用されます。complevelcomplibです。

  • complevelは、データが圧縮されるかどうか、およびその強度を指定します。complevel=0およびcomplevel=Noneは圧縮を無効にし、0<complevel<10は圧縮を有効にします。

  • complibは、使用する圧縮ライブラリを指定します。何も指定しない場合、デフォルトライブラリのzlibが使用されます。圧縮ライブラリは通常、良好な圧縮率または速度のいずれかを最適化し、結果はデータの種類によって異なります。どの種類の圧縮を選択するかは、特定のニーズとデータによって異なります。サポートされている圧縮ライブラリのリストは次のとおりです。

    • zlib: デフォルトの圧縮ライブラリ。圧縮の古典であり、優れた圧縮率を達成しますが、やや遅いです。

    • lzo: 高速な圧縮と解凍。

    • bzip2: 高い圧縮率。

    • blosc: 高速な圧縮と解凍。

      代替 blosc コンプレッサーのサポート

      • blosc:blosclz これは blosc のデフォルトのコンプレッサーです。

      • blosc:lz4: コンパクトで非常に人気のある高速コンプレッサー。

      • blosc:lz4hc: LZ4の調整版で、速度を犠牲にしてより良い圧縮率を実現します。

      • blosc:snappy: 多くの場所で使われている人気のあるコンプレッサー。

      • blosc:zlib: 古典的なもので、上記の他のものよりもやや遅いですが、より良い圧縮率を達成します。

      • blosc:zstd: 非常にバランスの取れたコーデックで、上記の他のものの中で最高の圧縮率を、合理的な高速さで提供します。

    complibがリストされたライブラリ以外のものとして定義されている場合、ValueError例外が発行されます。

complibオプションで指定されたライブラリがプラットフォームにない場合、圧縮は特に告知なくzlibにデフォルト設定されます。

ファイル内のすべてのオブジェクトに対して圧縮を有効にする

store_compressed = pd.HDFStore(
    "store_compressed.h5", complevel=9, complib="blosc:blosclz"
)

または、圧縮が有効になっていないストアで、その場での圧縮(これはテーブルにのみ適用されます)

store.append("df", df, complib="zlib", complevel=5)

ptrepack#

PyTablesは、テーブルを書き込み後に圧縮する方が、最初から圧縮を有効にするよりも優れた書き込みパフォーマンスを提供します。PyTables付属のユーティリティptrepackを使用できます。さらに、ptrepackは後から圧縮レベルを変更できます。

ptrepack --chunkshape=auto --propindexes --complevel=9 --complib=blosc in.h5 out.h5

さらに、ptrepack in.h5 out.h5はファイルを*再パック*して、以前に削除されたスペースを再利用できるようにします。あるいは、ファイルを単に削除して再度書き込むか、copyメソッドを使用することもできます。

注意点#

警告

HDFStoreは**書き込み時にスレッドセーフではありません**。基盤となるPyTablesは、同時読み取り(スレッドまたはプロセス経由)のみをサポートしています。読み取りと書き込みを**同時に**行う必要がある場合は、これらの操作を単一プロセス内の単一スレッドでシリアライズする必要があります。そうしないとデータが破損します。詳細については(GH 2397)を参照してください。

  • 複数のプロセス間で書き込みアクセスを管理するためにロックを使用する場合、書き込みロックを解放する前にfsync()を使用することをお勧めします。便宜上、store.flush(fsync=True)を使用すると、この操作が実行されます。

  • 一度tableが作成されると、列(DataFrame)は固定され、まったく同じ列のみが追加できます。

  • タイムゾーン(例: pytz.timezone('US/Eastern'))は、タイムゾーンのバージョン間で必ずしも等しいとは限らないことに注意してください。そのため、あるタイムゾーンライブラリのバージョンを使用してHDFStoreに特定のタイムゾーンにローカライズされたデータがあり、そのデータが別のバージョンで更新された場合、これらのタイムゾーンは等しいと見なされないため、データはUTCに変換されます。同じバージョンのタイムゾーンライブラリを使用するか、更新されたタイムゾーン定義でtz_convertを使用してください。

警告

PyTablesは、列名が属性セレクタとして使用できない場合にNaturalNameWarningを表示します。*自然な*識別子には文字、数字、アンダースコアのみが含まれ、数字で始まることはできません。その他の識別子はwhere句で使用できず、一般的に推奨されません。

データ型#

HDFStoreは、オブジェクトdtypeをPyTablesの基盤となるdtypeにマッピングします。これは、以下の型が機能することがわかっていることを意味します。

欠損値を表す

浮動小数点数 : float64, float32, float16

np.nan

整数 : int64, int32, int8, uint64,uint32, uint8

boolean

datetime64[ns]

NaT

timedelta64[ns]

NaT

カテゴリカル : 下記のセクションを参照

オブジェクト : strings

np.nan

unicode列はサポートされておらず、**失敗します**。

カテゴリデータ#

category dtype を含むデータをHDFStoreに書き込むことができます。クエリはオブジェクト配列の場合と同じように機能します。ただし、category dtype データはより効率的な方法で格納されます。

In [586]: dfcat = pd.DataFrame(
   .....:     {"A": pd.Series(list("aabbcdba")).astype("category"), "B": np.random.randn(8)}
   .....: )
   .....: 

In [587]: dfcat
Out[587]: 
   A         B
0  a -1.520478
1  a -1.069391
2  b -0.551981
3  b  0.452407
4  c  0.409257
5  d  0.301911
6  b -0.640843
7  a -2.253022

In [588]: dfcat.dtypes
Out[588]: 
A    category
B     float64
dtype: object

In [589]: cstore = pd.HDFStore("cats.h5", mode="w")

In [590]: cstore.append("dfcat", dfcat, format="table", data_columns=["A"])

In [591]: result = cstore.select("dfcat", where="A in ['b', 'c']")

In [592]: result
Out[592]: 
   A         B
2  b -0.551981
3  b  0.452407
4  c  0.409257
6  b -0.640843

In [593]: result.dtypes
Out[593]: 
A    category
B     float64
dtype: object

文字列カラム#

min_itemsize

HDFStoreの基盤となる実装では、文字列列に固定の列幅(itemsize)を使用します。文字列列のitemsizeは、**最初の追加時**にHDFStoreに渡されるデータ(その列用)の長さの最大値として計算されます。その後の追加で、列が保持できるよりも**長い**文字列が列に導入されると、例外がスローされます(そうでなければ、これらの列のサイレントな切り捨てが発生し、情報が失われる可能性があります)。将来的にはこれを緩和し、ユーザーが指定した切り捨てを許可する可能性があります。

最初のテーブル作成時にmin_itemsizeを渡すことで、特定の文字列列の最小長を事前に指定できます。min_itemsizeは整数、または列名を整数にマッピングする辞書にすることができます。valuesをキーとして渡すと、すべての*インデックス可能*または*データ列*にこのmin_itemsizeを適用できます。

min_itemsize辞書を渡すと、渡されたすべての列が自動的に*data_columns*として作成されます。

data_columnsを渡さない場合、min_itemsizeは渡された任意の文字列の最大長になります。

In [594]: dfs = pd.DataFrame({"A": "foo", "B": "bar"}, index=list(range(5)))

In [595]: dfs
Out[595]: 
     A    B
0  foo  bar
1  foo  bar
2  foo  bar
3  foo  bar
4  foo  bar

# A and B have a size of 30
In [596]: store.append("dfs", dfs, min_itemsize=30)

In [597]: store.get_storer("dfs").table
Out[597]: 
/dfs/table (Table(5,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": StringCol(itemsize=30, shape=(2,), dflt=b'', pos=1)}
  byteorder := 'little'
  chunkshape := (963,)
  autoindex := True
  colindexes := {
    "index": Index(6, mediumshuffle, zlib(1)).is_csi=False}

# A is created as a data_column with a size of 30
# B is size is calculated
In [598]: store.append("dfs2", dfs, min_itemsize={"A": 30})

In [599]: store.get_storer("dfs2").table
Out[599]: 
/dfs2/table (Table(5,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": StringCol(itemsize=3, shape=(1,), dflt=b'', pos=1),
  "A": StringCol(itemsize=30, shape=(), dflt=b'', pos=2)}
  byteorder := 'little'
  chunkshape := (1598,)
  autoindex := True
  colindexes := {
    "index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
    "A": Index(6, mediumshuffle, zlib(1)).is_csi=False}

nan_rep

文字列列は、np.nan(欠損値)をnan_rep文字列表現でシリアライズします。これはデフォルトで文字列値nanになります。誤って実際のnan値を欠損値にしてしまう可能性があります。

In [600]: dfss = pd.DataFrame({"A": ["foo", "bar", "nan"]})

In [601]: dfss
Out[601]: 
     A
0  foo
1  bar
2  nan

In [602]: store.append("dfss", dfss)

In [603]: store.select("dfss")
Out[603]: 
     A
0  foo
1  bar
2  NaN

# here you need to specify a different nan rep
In [604]: store.append("dfss2", dfss, nan_rep="_nan_")

In [605]: store.select("dfss2")
Out[605]: 
     A
0  foo
1  bar
2  nan

パフォーマンス#

  • tables形式は、fixedストアと比較して書き込みパフォーマンスのペナルティがあります。利点は、追加/削除およびクエリ(潜在的に非常に大量のデータ)の機能です。書き込み時間は、通常のストアと比較して一般的に長くなります。クエリ時間は、特にインデックス付き軸では非常に高速になる可能性があります。

  • appendchunksize=<int>を渡すことで、書き込みチャンクサイズを指定できます(デフォルトは50000)。これにより、書き込み時のメモリ使用量が大幅に削減されます。

  • 最初のappendexpectedrows=<int>を渡して、PyTablesが期待する**合計**行数を設定できます。これにより、読み書きのパフォーマンスが最適化されます。

  • 重複行はテーブルに書き込まれる可能性がありますが、選択時にはフィルタリングされます(最後の項目が選択されます。したがって、テーブルはメジャー、マイナーのペアで一意です)。

  • PyTablesによってpickleされる型(固有の型として格納されるのではなく)を格納しようとすると、PerformanceWarningが発行されます。詳細と解決策についてはこちらを参照してください。

フェザー#

Featherは、データフレームのバイナリカラムナーシリアル化を提供します。データフレームの読み書きを効率的にし、データ分析言語間でのデータ共有を容易にすることを目的としています。

Feather は、DataFrame を忠実にシリアライズおよびデシリアライズするように設計されており、カテゴリカルやタイムゾーン付き datetime などの拡張 dtype を含む、すべての pandas の dtype をサポートしています。

いくつかの注意点

  • この形式は、DataFrameIndexまたはMultiIndexを**書き込みません**。非デフォルトのものが提供された場合、エラーが発生します。インデックスを保存するには.reset_index()を使用し、無視するには.reset_index(drop=True)を使用できます。

  • 重複する列名と非文字列の列名はサポートされていません。

  • オブジェクト dtype の列内の実際の Python オブジェクトはサポートされていません。これらはシリアライズを試みると、役立つエラーメッセージを発生させます。

詳細は完全なドキュメントを参照してください。

In [606]: df = pd.DataFrame(
   .....:     {
   .....:         "a": list("abc"),
   .....:         "b": list(range(1, 4)),
   .....:         "c": np.arange(3, 6).astype("u1"),
   .....:         "d": np.arange(4.0, 7.0, dtype="float64"),
   .....:         "e": [True, False, True],
   .....:         "f": pd.Categorical(list("abc")),
   .....:         "g": pd.date_range("20130101", periods=3),
   .....:         "h": pd.date_range("20130101", periods=3, tz="US/Eastern"),
   .....:         "i": pd.date_range("20130101", periods=3, freq="ns"),
   .....:     }
   .....: )
   .....: 

In [607]: df
Out[607]: 
   a  b  c  ...          g                         h                             i
0  a  1  3  ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1  b  2  4  ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2  c  3  5  ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002

[3 rows x 9 columns]

In [608]: df.dtypes
Out[608]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                      category
g                datetime64[ns]
h    datetime64[ns, US/Eastern]
i                datetime64[ns]
dtype: object

フェザーファイルに書き込む。

In [609]: df.to_feather("example.feather")

フェザーファイルから読み込む。

In [610]: result = pd.read_feather("example.feather")

In [611]: result
Out[611]: 
   a  b  c  ...          g                         h                             i
0  a  1  3  ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1  b  2  4  ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2  c  3  5  ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002

[3 rows x 9 columns]

# we preserve dtypes
In [612]: result.dtypes
Out[612]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                      category
g                datetime64[ns]
h    datetime64[ns, US/Eastern]
i                datetime64[ns]
dtype: object

パルケ#

Apache Parquetは、データフレームのパーティション分割されたバイナリカラムナーシリアル化を提供します。データフレームの読み書きを効率的にし、データ分析言語間でのデータ共有を容易にすることを目的としています。Parquetは、良好な読み取りパフォーマンスを維持しながら、ファイルサイズを可能な限り縮小するために、さまざまな圧縮技術を使用できます。

Parquet は、DataFrame を忠実にシリアライズおよびデシリアライズするように設計されており、タイムゾーン付き datetime などの拡張 dtype を含む、すべての pandas の dtype をサポートしています。

いくつかの注意点。

  • 重複する列名と非文字列の列名はサポートされていません。

  • pyarrowエンジンは常にインデックスを出力に書き込みますが、fastparquetは非デフォルトのインデックスのみを書き込みます。この余分な列は、それを予期しない非pandasコンシューマーにとって問題を引き起こす可能性があります。基盤となるエンジンに関係なく、index引数を使用してインデックスを含めるか除外するかを強制できます。

  • インデックスレベル名は、指定されている場合、文字列である必要があります。

  • pyarrowエンジンでは、非文字列型のカテゴリカルdtypeはparquetにシリアライズできますが、プリミティブdtypeとしてデシリアライズされます。

  • pyarrowエンジンは、文字列型を持つカテゴリカルdtypeのorderedフラグを保持します。fastparquetorderedフラグを保持しません。

  • サポートされていない型には、Intervalおよび実際のPythonオブジェクト型が含まれます。これらはシリアライズしようとすると、役立つエラーメッセージが表示されます。Period型はpyarrow >= 0.16.0でサポートされています。

  • pyarrowエンジンは、nullable整数型や文字列データ型などの拡張データ型を保持します(pyarrow >= 0.16.0が必要であり、拡張型が必要なプロトコルを実装している必要があります。拡張型ドキュメントを参照してください)。

シリアライズを指示するためにengineを指定できます。これはpyarrowfastparquet、またはautoのいずれかになります。エンジンが指定されていない場合、pd.options.io.parquet.engineオプションがチェックされます。これもautoの場合、pyarrowが試行され、fastparquetにフォールバックします。

pyarrowfastparquetのドキュメントを参照してください。

これらのエンジンは非常に似ており、ほぼ同一のパルケ形式ファイルを読み書きするはずです。pyarrow>=8.0.0はtimedeltaデータをサポートし、fastparquet>=0.1.4はタイムゾーン対応のdatetimeをサポートします。これらのライブラリは、異なる基盤となる依存関係を持つ点で異なります(fastparquetnumbaを使用し、pyarrowはCライブラリを使用します)。

In [613]: df = pd.DataFrame(
   .....:     {
   .....:         "a": list("abc"),
   .....:         "b": list(range(1, 4)),
   .....:         "c": np.arange(3, 6).astype("u1"),
   .....:         "d": np.arange(4.0, 7.0, dtype="float64"),
   .....:         "e": [True, False, True],
   .....:         "f": pd.date_range("20130101", periods=3),
   .....:         "g": pd.date_range("20130101", periods=3, tz="US/Eastern"),
   .....:         "h": pd.Categorical(list("abc")),
   .....:         "i": pd.Categorical(list("abc"), ordered=True),
   .....:     }
   .....: )
   .....: 

In [614]: df
Out[614]: 
   a  b  c    d      e          f                         g  h  i
0  a  1  3  4.0   True 2013-01-01 2013-01-01 00:00:00-05:00  a  a
1  b  2  4  5.0  False 2013-01-02 2013-01-02 00:00:00-05:00  b  b
2  c  3  5  6.0   True 2013-01-03 2013-01-03 00:00:00-05:00  c  c

In [615]: df.dtypes
Out[615]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                datetime64[ns]
g    datetime64[ns, US/Eastern]
h                      category
i                      category
dtype: object

パルケファイルに書き込む。

In [616]: df.to_parquet("example_pa.parquet", engine="pyarrow")

In [617]: df.to_parquet("example_fp.parquet", engine="fastparquet")

パルケファイルから読み込む。

In [618]: result = pd.read_parquet("example_fp.parquet", engine="fastparquet")

In [619]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow")

In [620]: result.dtypes
Out[620]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                datetime64[ns]
g    datetime64[ns, US/Eastern]
h                      category
i                      category
dtype: object

dtype_backend引数を設定することで、結果のDataFrameに使用されるデフォルトのdtypeを制御できます。

In [621]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow", dtype_backend="pyarrow")

In [622]: result.dtypes
Out[622]: 
a                                      string[pyarrow]
b                                       int64[pyarrow]
c                                       uint8[pyarrow]
d                                      double[pyarrow]
e                                        bool[pyarrow]
f                               timestamp[ns][pyarrow]
g                timestamp[ns, tz=US/Eastern][pyarrow]
h    dictionary<values=string, indices=int8, ordere...
i    dictionary<values=string, indices=int8, ordere...
dtype: object

これはfastparquetではサポートされていません。

パルケファイルから特定の列のみを読み込む。

In [623]: result = pd.read_parquet(
   .....:     "example_fp.parquet",
   .....:     engine="fastparquet",
   .....:     columns=["a", "b"],
   .....: )
   .....: 

In [624]: result = pd.read_parquet(
   .....:     "example_pa.parquet",
   .....:     engine="pyarrow",
   .....:     columns=["a", "b"],
   .....: )
   .....: 

In [625]: result.dtypes
Out[625]: 
a    object
b     int64
dtype: object

インデックスの扱い#

DataFrameをParquetにシリアライズすると、暗黙的なインデックスが出力ファイル内の1つ以上の列として含まれる場合があります。したがって、このコードは

In [626]: df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})

In [627]: df.to_parquet("test.parquet", engine="pyarrow")

シリアライゼーションにpyarrowを使用した場合、ab、および__index_level_0__の*3つ*の列を持つParquetファイルを作成します。fastparquetを使用している場合、インデックスはファイルに書き込まれる**こともあれば書き込まれないこともあります**。

この予期しない余分な列は、Amazon Redshiftなどの一部のデータベースがファイルを拒否する原因となります。その列がターゲットテーブルに存在しないためです。

書き込み時にデータフレームのインデックスを省略したい場合は、to_parquet()index=Falseを渡します。

In [628]: df.to_parquet("test.parquet", index=False)

これにより、期待される2つの列abのみを含むパルケファイルが作成されます。DataFrameにカスタムインデックスがある場合、このファイルをDataFrameにロードしてもインデックスは戻りません。

index=Trueを渡すと、基盤となるエンジンのデフォルトの動作でなくても、インデックスが*常に*書き込まれます。

Parquet ファイルのパーティショニング#

Parquetは、1つ以上の列の値に基づいてデータのパーティショニングをサポートしています。

In [629]: df = pd.DataFrame({"a": [0, 0, 1, 1], "b": [0, 1, 0, 1]})

In [630]: df.to_parquet(path="test", engine="pyarrow", partition_cols=["a"], compression=None)

pathはデータが保存される親ディレクトリを指定します。partition_colsはデータセットがパーティション分割される列名です。列は指定された順序でパーティション分割されます。パーティション分割はパーティション列の一意な値によって決定されます。上記の例は、次のようなパーティション分割されたデータセットを作成します。

test
├── a=0
│   ├── 0bac803e32dc42ae83fddfd029cbdebc.parquet
│   └──  ...
└── a=1
    ├── e6ab24a4f45147b49b54a662f0c412a3.parquet
    └── ...

ORC#

parquet形式と同様に、ORC形式はデータフレームのバイナリカラムナーシリアル化です。データフレームの読み取りを効率的にすることを目的としています。pandasは、ORC形式のリーダーとライターの両方、read_orc()to_orc()を提供します。これにはpyarrowライブラリが必要です。

警告

  • pyarrowで発生するいくつかの問題のため、condaを使用してpyarrowをインストールすることを*強く推奨します*。

  • to_orc()にはpyarrow>=7.0.0が必要です。

  • read_orc()およびto_orc()はWindowsではまだサポートされていません。有効な環境についてはオプションの依存関係のインストールを参照してください。

  • サポートされているdtypeについては、ArrowでサポートされているORC機能を参照してください。

  • 現在、データフレームがORCファイルに変換される際、datetimeカラムのタイムゾーンは保持されません。

In [631]: df = pd.DataFrame(
   .....:     {
   .....:         "a": list("abc"),
   .....:         "b": list(range(1, 4)),
   .....:         "c": np.arange(4.0, 7.0, dtype="float64"),
   .....:         "d": [True, False, True],
   .....:         "e": pd.date_range("20130101", periods=3),
   .....:     }
   .....: )
   .....: 

In [632]: df
Out[632]: 
   a  b    c      d          e
0  a  1  4.0   True 2013-01-01
1  b  2  5.0  False 2013-01-02
2  c  3  6.0   True 2013-01-03

In [633]: df.dtypes
Out[633]: 
a            object
b             int64
c           float64
d              bool
e    datetime64[ns]
dtype: object

ORCファイルに書き込む。

In [634]: df.to_orc("example_pa.orc", engine="pyarrow")

ORCファイルから読み込む。

In [635]: result = pd.read_orc("example_pa.orc")

In [636]: result.dtypes
Out[636]: 
a            object
b             int64
c           float64
d              bool
e    datetime64[ns]
dtype: object

ORCファイルから特定のカラムのみを読み込む。

In [637]: result = pd.read_orc(
   .....:     "example_pa.orc",
   .....:     columns=["a", "b"],
   .....: )
   .....: 

In [638]: result.dtypes
Out[638]: 
a    object
b     int64
dtype: object

SQLクエリ#

pandas.io.sqlモジュールは、データ取得を容易にし、DB固有のAPIへの依存を減らすために、クエリラッパーのコレクションを提供します。

利用可能な場合、ユーザーはまずApache Arrow ADBCドライバーを選択することをお勧めします。これらのドライバーは、最高のパフォーマンス、NULL処理、および型検出を提供します。

バージョン 2.2.0 で追加: ADBCドライバーのネイティブサポートが追加されました。

ADBCドライバーの完全なリストと開発状況については、ADBC Driver Implementation Statusのドキュメントを参照してください。

ADBCドライバーが利用できない、または機能が不足している場合、ユーザーはデータベースドライバーライブラリと並行してSQLAlchemyをインストールすることを選択すべきです。そのようなドライバーの例としては、PostgreSQL用のpsycopg2やMySQL用のpymysqlがあります。SQLiteの場合、これはPythonの標準ライブラリにデフォルトで含まれています。各SQL方言でサポートされているドライバーの概要は、SQLAlchemy docsで見つけることができます。

SQLAlchemyがインストールされていない場合は、SQLAlchemyのエンジン、接続、またはURI文字列の代わりにsqlite3.Connectionを使用できます。

高度な戦略については、クックブックの例も参照してください。

主な機能は次のとおりです。

read_sql_table(table_name, con[, schema, ...])

SQLデータベーステーブルをDataFrameに読み込みます。

read_sql_query(sql, con[, index_col, ...])

SQLクエリをDataFrameに読み込みます。

read_sql(sql, con[, index_col, ...])

SQLクエリまたはデータベーステーブルをDataFrameに読み込みます。

DataFrame.to_sql(name, con, *[, schema, ...])

DataFrameに格納されているレコードをSQLデータベースに書き込みます。

関数read_sql()は、read_sql_table()read_sql_query()の便利なラッパーであり(後方互換性のためにも)、提供された入力(データベーステーブル名またはSQLクエリ)に応じて特定の関数に処理を委譲します。特殊文字を含むテーブル名も引用符で囲む必要はありません。

以下の例では、SQlite SQLデータベースエンジンを使用します。データを「メモリ」に保存する一時的なSQLiteデータベースを使用できます。

ADBCドライバーを使用して接続するには、パッケージマネージャーを使用してadbc_driver_sqliteをインストールします。インストール後、ADBCドライバーが提供するDBAPIインターフェースを使用してデータベースに接続できます。

import adbc_driver_sqlite.dbapi as sqlite_dbapi

# Create the connection
with sqlite_dbapi.connect("sqlite:///:memory:") as conn:
     df = pd.read_sql_table("data", conn)

SQLAlchemyで接続するには、create_engine()関数を使用してデータベースURIからエンジンオブジェクトを作成します。接続するデータベースごとにエンジンを一度だけ作成する必要があります。create_engine()とURIの書式設定の詳細については、以下の例とSQLAlchemyのドキュメントを参照してください。

In [639]: from sqlalchemy import create_engine

# Create your engine.
In [640]: engine = create_engine("sqlite:///:memory:")

自分で接続を管理したい場合は、代わりにそれらのいずれかを渡すことができます。以下の例では、Pythonのコンテキストマネージャーを使用してデータベースへの接続を開き、ブロックが完了すると自動的に接続を閉じます。データベース接続の処理方法については、SQLAlchemyドキュメントを参照してください。

with engine.connect() as conn, conn.begin():
    data = pd.read_sql_table("data", conn)

警告

データベースへの接続を開いた場合は、閉じる責任もあります。接続を開いたままにすると、データベースのロックなどの問題が発生する可能性があります。

DataFrameの書き込み#

次のデータがDataFrame dataに含まれていると仮定して、to_sql()を使用してデータベースに挿入できます。

id

日付

Col_1

Col_2

Col_3

26

2012-10-18

X

25.7

True

42

2012-10-19

Y

-12.4

False

63

2012-10-20

Z

5.73

True

In [641]: import datetime

In [642]: c = ["id", "Date", "Col_1", "Col_2", "Col_3"]

In [643]: d = [
   .....:     (26, datetime.datetime(2010, 10, 18), "X", 27.5, True),
   .....:     (42, datetime.datetime(2010, 10, 19), "Y", -12.5, False),
   .....:     (63, datetime.datetime(2010, 10, 20), "Z", 5.73, True),
   .....: ]
   .....: 

In [644]: data = pd.DataFrame(d, columns=c)

In [645]: data
Out[645]: 
   id       Date Col_1  Col_2  Col_3
0  26 2010-10-18     X  27.50   True
1  42 2010-10-19     Y -12.50  False
2  63 2010-10-20     Z   5.73   True

In [646]: data.to_sql("data", con=engine)
Out[646]: 3

一部のデータベースでは、大きなDataFrameを書き込むと、パケットサイズの制限を超過するためにエラーが発生する可能性があります。これは、to_sqlを呼び出すときにchunksizeパラメータを設定することで回避できます。たとえば、次のコードはdataを1000行ずつバッチでデータベースに書き込みます。

In [647]: data.to_sql("data_chunked", con=engine, chunksize=1000)
Out[647]: 3

SQLデータ型#

SQLデータベース全体で一貫したデータ型管理を確保することは困難です。すべてのSQLデータベースが同じ型を提供するわけではなく、同じ型であっても、特定の型の実装は微妙な方法で型が保持される方法に影響を与える可能性があります。

データベース型を保持する最善の機会を得るには、ADBCドライバーが利用可能な場合はそれを使用することをお勧めします。Arrow型システムは、従来のpandas/NumPy型システムよりもデータベース型に密接に一致する幅広い型の配列を提供します。例として、さまざまなデータベースとpandasバックエンドで利用可能な型(網羅的ではありません)のリストを示します。

numpy/pandas

arrow

postgres

sqlite

int16/Int16

int16

SMALLINT

INTEGER

int32/Int32

int32

INTEGER

INTEGER

int64/Int64

int64

BIGINT

INTEGER

float32

float32

REAL

REAL

float64

float64

DOUBLE PRECISION

REAL

object

string

TEXT

TEXT

bool

bool_

BOOLEAN

datetime64[ns]

timestamp(us)

TIMESTAMP

datetime64[ns,tz]

timestamp(us,tz)

TIMESTAMPTZ

date32

DATE

month_day_nano_interval

INTERVAL

binary

BINARY

BLOB

decimal128

DECIMAL [1]

list

ARRAY [1]

struct

COMPOSITE TYPE

[1]

脚注

DataFrameのライフサイクル全体でデータベース型を可能な限り維持することに関心がある場合は、read_sql()dtype_backend="pyarrow"引数を活用することをお勧めします。

# for roundtripping
with pg_dbapi.connect(uri) as conn:
    df2 = pd.read_sql("pandas_table", conn, dtype_backend="pyarrow")

これにより、データが従来のpandas/NumPy型システムに変換されるのを防ぎます。従来のシステムでは、SQL型がラウンドトリップできない方法で変換されることがよくあります。

ADBCドライバーが利用できない場合、to_sql()は、データのdtypeに基づいて、データを適切なSQLデータ型にマッピングしようとします。objectdtypeの列がある場合、pandasはデータ型を推測しようとします。

dtype引数を使用して、任意の列の目的のSQL型を指定することで、常にデフォルトの型を上書きできます。この引数には、列名をSQLAlchemy型(またはsqlite3フォールバックモードの場合は文字列)にマッピングする辞書が必要です。たとえば、文字列列のデフォルトのText型ではなく、sqlalchemyのString型を使用するように指定する場合などです。

In [648]: from sqlalchemy.types import String

In [649]: data.to_sql("data_dtype", con=engine, dtype={"Col_1": String})
Out[649]: 3

さまざまなデータベースフレーバーでtimedeltaがサポートされる範囲が限られているため、timedelta64型の列はナノ秒単位の整数値としてデータベースに書き込まれ、警告が発生します。唯一の例外は、ADBC PostgreSQLドライバーを使用する場合です。この場合、timedeltaはINTERVALとしてデータベースに書き込まれます。

category dtypeの列は、np.asarray(categorical)で得られる密な表現に変換されます(例:文字列カテゴリの場合、文字列の配列が得られます)。このため、データベーステーブルを読み戻してもカテゴリカルは生成され**ません**。

日時データ型#

ADBCまたはSQLAlchemyを使用すると、to_sql()は、タイムゾーンに依存しない、またはタイムゾーンを意識した日時データを書き込むことができます。ただし、データベースに保存される最終的なデータは、使用されているデータベースシステムの日時データに対してサポートされているデータ型に最終的に依存します。

次の表は、一般的なデータベースの日時データに対してサポートされているデータ型を示しています。他のデータベース方言では、日時データに対して異なるデータ型を持つ場合があります。

データベース

SQL日時型

タイムゾーンサポート

SQLite

TEXT

いいえ

MySQL

TIMESTAMP または DATETIME

いいえ

PostgreSQL

TIMESTAMP または TIMESTAMP WITH TIME ZONE

はい

タイムゾーンをサポートしていないデータベースにタイムゾーンを意識したデータを書き込む場合、データはタイムゾーンに関してローカルタイムのタイムゾーンに依存しないタイムスタンプとして書き込まれます。

read_sql_table()は、タイムゾーンを意識した、またはタイムゾーンに依存しない日時データを読み込むこともできます。TIMESTAMP WITH TIME ZONE型を読み込む場合、pandasはデータをUTCに変換します。

挿入方法#

パラメータmethodは、使用されるSQL挿入句を制御します。可能な値は次のとおりです。

  • None:標準SQL INSERT句(行ごとに1つ)を使用します。

  • 'multi':単一のINSERT句に複数の値を渡します。これは、すべてのバックエンドでサポートされているわけではない特殊なSQL構文を使用します。これは通常、PrestoRedshiftのような分析データベースではより良いパフォーマンスを提供しますが、テーブルに多くの列が含まれている場合、従来のSQLバックエンドではパフォーマンスが低下します。詳細については、SQLAlchemyのドキュメントを確認してください。

  • シグネチャ(pd_table, conn, keys, data_iter)を持つcallable:これは、特定のバックエンド方言機能に基づいて、より高性能な挿入方法を実装するために使用できます。

PostgreSQLのCOPY句を使用するcallableの例

# Alternative to_sql() *method* for DBs that support COPY FROM
import csv
from io import StringIO

def psql_insert_copy(table, conn, keys, data_iter):
    """
    Execute SQL statement inserting data

    Parameters
    ----------
    table : pandas.io.sql.SQLTable
    conn : sqlalchemy.engine.Engine or sqlalchemy.engine.Connection
    keys : list of str
        Column names
    data_iter : Iterable that iterates the values to be inserted
    """
    # gets a DBAPI connection that can provide a cursor
    dbapi_conn = conn.connection
    with dbapi_conn.cursor() as cur:
        s_buf = StringIO()
        writer = csv.writer(s_buf)
        writer.writerows(data_iter)
        s_buf.seek(0)

        columns = ', '.join(['"{}"'.format(k) for k in keys])
        if table.schema:
            table_name = '{}.{}'.format(table.schema, table.name)
        else:
            table_name = table.name

        sql = 'COPY {} ({}) FROM STDIN WITH CSV'.format(
            table_name, columns)
        cur.copy_expert(sql=sql, file=s_buf)

テーブルの読み込み#

read_sql_table()は、テーブル名とオプションで読み込む列のサブセットを指定して、データベーステーブルを読み込みます。

read_sql_table()を使用するには、ADBCドライバーまたはSQLAlchemyオプションの依存関係がインストールされている**必要があります**。

In [650]: pd.read_sql_table("data", engine)
Out[650]: 
   index  id       Date Col_1  Col_2  Col_3
0      0  26 2010-10-18     X  27.50   True
1      1  42 2010-10-19     Y -12.50  False
2      2  63 2010-10-20     Z   5.73   True

ADBCドライバーは、データベース型を直接Arrow型にマッピングします。他のドライバーについては、pandasはクエリ出力から列のdtypeを推測し、物理データベーススキーマのデータ型を検索しないことに注意してください。たとえば、useridがテーブル内の整数列であると仮定します。この場合、直感的には、select userid ...は整数値のシリーズを返し、select cast(userid as text) ...はオブジェクト値(str)のシリーズを返します。したがって、クエリ出力が空の場合、結果のすべての列はオブジェクト値(最も一般的であるため)として返されます。クエリが時々空の結果を生成する可能性があると予想される場合、dtypeの整合性を確保するために後で明示的に型キャストすることをお勧めします。

列の名前をDataFrameインデックスとして指定し、読み込む列のサブセットを指定することもできます。

In [651]: pd.read_sql_table("data", engine, index_col="id")
Out[651]: 
    index       Date Col_1  Col_2  Col_3
id                                      
26      0 2010-10-18     X  27.50   True
42      1 2010-10-19     Y -12.50  False
63      2 2010-10-20     Z   5.73   True

In [652]: pd.read_sql_table("data", engine, columns=["Col_1", "Col_2"])
Out[652]: 
  Col_1  Col_2
0     X  27.50
1     Y -12.50
2     Z   5.73

そして、列を明示的に日付として解析するように強制できます

In [653]: pd.read_sql_table("data", engine, parse_dates=["Date"])
Out[653]: 
   index  id       Date Col_1  Col_2  Col_3
0      0  26 2010-10-18     X  27.50   True
1      1  42 2010-10-19     Y -12.50  False
2      2  63 2010-10-20     Z   5.73   True

必要に応じて、書式文字列、またはpandas.to_datetime()に渡す引数の辞書を明示的に指定できます。

pd.read_sql_table("data", engine, parse_dates={"Date": "%Y-%m-%d"})
pd.read_sql_table(
    "data",
    engine,
    parse_dates={"Date": {"format": "%Y-%m-%d %H:%M:%S"}},
)

has_table()を使用してテーブルが存在するかどうかを確認できます。

スキーマのサポート#

異なるスキーマへの読み書きは、read_sql_table()およびto_sql()関数のschemaキーワードを通じてサポートされています。ただし、これはデータベースのフレーバーに依存することに注意してください(sqliteにはスキーマがありません)。例:

df.to_sql(name="table", con=engine, schema="other_schema")
pd.read_sql_table("table", engine, schema="other_schema")

クエリ#

read_sql_query()関数で生のSQLを使用してクエリを実行できます。この場合、データベースに適したSQLバリアントを使用する必要があります。SQLAlchemyを使用する場合、データベースに依存しないSQLAlchemy Expression言語の構造を渡すこともできます。

In [654]: pd.read_sql_query("SELECT * FROM data", engine)
Out[654]: 
   index  id                        Date Col_1  Col_2  Col_3
0      0  26  2010-10-18 00:00:00.000000     X  27.50      1
1      1  42  2010-10-19 00:00:00.000000     Y -12.50      0
2      2  63  2010-10-20 00:00:00.000000     Z   5.73      1

もちろん、より「複雑な」クエリを指定することもできます。

In [655]: pd.read_sql_query("SELECT id, Col_1, Col_2 FROM data WHERE id = 42;", engine)
Out[655]: 
   id Col_1  Col_2
0  42     Y  -12.5

read_sql_query()関数はchunksize引数をサポートしています。これを指定すると、クエリ結果のチャンクを介したイテレータが返されます。

In [656]: df = pd.DataFrame(np.random.randn(20, 3), columns=list("abc"))

In [657]: df.to_sql(name="data_chunks", con=engine, index=False)
Out[657]: 20
In [658]: for chunk in pd.read_sql_query("SELECT * FROM data_chunks", engine, chunksize=5):
   .....:     print(chunk)
   .....: 
          a         b         c
0 -0.395347 -0.822726 -0.363777
1  1.676124 -0.908102 -1.391346
2 -1.094269  0.278380  1.205899
3  1.503443  0.932171 -0.709459
4 -0.645944 -1.351389  0.132023
          a         b         c
0  0.210427  0.192202  0.661949
1  1.690629 -1.046044  0.618697
2 -0.013863  1.314289  1.951611
3 -1.485026  0.304662  1.194757
4 -0.446717  0.528496 -0.657575
          a         b         c
0 -0.876654  0.336252  0.172668
1  0.337684 -0.411202 -0.828394
2 -0.244413  1.094948  0.087183
3  1.125934 -1.480095  1.205944
4 -0.451849  0.452214 -2.208192
          a         b         c
0 -2.061019  0.044184 -0.017118
1  1.248959 -0.675595 -1.908296
2 -0.125934  1.491974  0.648726
3  0.391214  0.438609  1.634248
4  1.208707 -1.535740  1.620399

エンジン接続例#

SQLAlchemyと接続するには、create_engine()関数を使用してデータベースURIからエンジンオブジェクトを作成します。接続するデータベースごとにエンジンを一度作成するだけで済みます。

from sqlalchemy import create_engine

engine = create_engine("postgresql://scott:tiger@localhost:5432/mydatabase")

engine = create_engine("mysql+mysqldb://scott:tiger@localhost/foo")

engine = create_engine("oracle://scott:[email protected]:1521/sidname")

engine = create_engine("mssql+pyodbc://mydsn")

# sqlite://<nohostname>/<path>
# where <path> is relative:
engine = create_engine("sqlite:///foo.db")

# or absolute, starting with a slash:
engine = create_engine("sqlite:////absolute/path/to/foo.db")

詳細については、SQLAlchemyのドキュメントの例を参照してください。

高度なSQLAlchemyクエリ#

SQLAlchemyの構造を使用してクエリを記述できます。

sqlalchemy.text()を使用して、バックエンドに依存しない方法でクエリパラメーターを指定します。

In [659]: import sqlalchemy as sa

In [660]: pd.read_sql(
   .....:     sa.text("SELECT * FROM data where Col_1=:col1"), engine, params={"col1": "X"}
   .....: )
   .....: 
Out[660]: 
   index  id                        Date Col_1  Col_2  Col_3
0      0  26  2010-10-18 00:00:00.000000     X   27.5      1

データベースのSQLAlchemy記述がある場合、SQLAlchemy式を使用してwhere条件を表現できます。

In [661]: metadata = sa.MetaData()

In [662]: data_table = sa.Table(
   .....:     "data",
   .....:     metadata,
   .....:     sa.Column("index", sa.Integer),
   .....:     sa.Column("Date", sa.DateTime),
   .....:     sa.Column("Col_1", sa.String),
   .....:     sa.Column("Col_2", sa.Float),
   .....:     sa.Column("Col_3", sa.Boolean),
   .....: )
   .....: 

In [663]: pd.read_sql(sa.select(data_table).where(data_table.c.Col_3 is True), engine)
Out[663]: 
Empty DataFrame
Columns: [index, Date, Col_1, Col_2, Col_3]
Index: []

SQLAlchemy式を、sqlalchemy.bindparam()を使用してread_sql()に渡されるパラメータと組み合わせることができます。

In [664]: import datetime as dt

In [665]: expr = sa.select(data_table).where(data_table.c.Date > sa.bindparam("date"))

In [666]: pd.read_sql(expr, engine, params={"date": dt.datetime(2010, 10, 18)})
Out[666]: 
   index       Date Col_1  Col_2  Col_3
0      1 2010-10-19     Y -12.50  False
1      2 2010-10-20     Z   5.73   True

Sqliteフォールバック#

sqliteの使用はSQLAlchemyを使用せずともサポートされています。このモードでは、Python DB-APIを尊重するPythonデータベースアダプターが必要です。

接続は次のように作成できます。

import sqlite3

con = sqlite3.connect(":memory:")

そして、次のクエリを発行します。

data.to_sql("data", con)
pd.read_sql_query("SELECT * FROM data", con)

Google BigQuery#

pandas-gbqパッケージは、Google BigQueryからの読み書き機能を提供します。

pandasはこの外部パッケージと統合されています。pandas-gbqがインストールされている場合、pandasのメソッドpd.read_gbqDataFrame.to_gbqを使用できます。これらはpandas-gbqのそれぞれの関数を呼び出します。

完全なドキュメントはこちらにあります。

Stata形式#

Stata形式への書き込み#

DataFrame.to_stata()メソッドは、DataFrameを.dtaファイルに書き込みます。このファイルの形式バージョンは常に115(Stata 12)です。

In [667]: df = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [668]: df.to_stata("stata.dta")

Stataデータファイルはデータ型のサポートが限られています。244文字以下の文字列、int8int16int32float32float64のみが.dtaファイルに保存できます。さらに、Stataは欠損データを表すために特定の値を予約しています。Stataで特定のデータ型に対して許可されている範囲外の非欠損値をエクスポートすると、変数は次の大きなサイズに再型付けされます。たとえば、int8の値はStataでは-127から100の間に制限されているため、100を超える値を持つ変数はint16への変換をトリガーします。浮動小数点データ型のnan値は、基本的な欠損データ型(Stataでは.)として保存されます。

整数データ型の欠損データをエクスポートすることはできません。

Stataライターは、int64booluint8uint16uint32などの他のデータ型を、データを表現できる最小のサポートされている型にキャストすることで適切に処理します。たとえば、uint8型のデータは、すべての値が100(Stataの非欠損int8データの範囲の上限)未満の場合はint8にキャストされ、値がこの範囲外の場合は変数はint16にキャストされます。

警告

int64からfloat64への変換は、int64値が2**53より大きい場合に精度が失われる可能性があります。

警告

StataWriterおよびDataFrame.to_stata()は、バージョン115 dtaファイル形式によって課される制限により、最大244文字の固定幅文字列のみをサポートします。244文字を超える文字列を持つStata dtaファイルを書き込もうとすると、ValueErrorが発生します。

Stata形式からの読み込み#

トップレベル関数read_stataはdtaファイルを読み込み、DataFrameまたはファイルをインクリメンタルに読み込むために使用できるpandas.api.typing.StataReaderを返します。

In [669]: pd.read_stata("stata.dta")
Out[669]: 
   index         A         B
0      0 -0.165614  0.490482
1      1 -0.637829  0.067091
2      2 -0.242577  1.348038
3      3  0.647699 -0.644937
4      4  0.625771  0.918376
5      5  0.401781 -1.488919
6      6 -0.981845 -0.046882
7      7 -0.306796  0.877025
8      8 -0.336606  0.624747
9      9 -1.582600  0.806340

chunksizeを指定すると、一度にファイルからchunksize行を読み込むために使用できるpandas.api.typing.StataReaderインスタンスが生成されます。StataReaderオブジェクトはイテレータとして使用できます。

In [670]: with pd.read_stata("stata.dta", chunksize=3) as reader:
   .....:     for df in reader:
   .....:         print(df.shape)
   .....: 
(3, 3)
(3, 3)
(3, 3)
(1, 3)

よりきめ細かな制御のためには、iterator=Trueを使用し、read()の各呼び出しでchunksizeを指定します。

In [671]: with pd.read_stata("stata.dta", iterator=True) as reader:
   .....:     chunk1 = reader.read(5)
   .....:     chunk2 = reader.read(5)
   .....: 

現在、indexは列として取得されます。

パラメータconvert_categoricalsは、値ラベルを読み込み、それらからCategorical変数を作成するために使用するかどうかを示します。値ラベルは、関数value_labelsでも取得できます。これを使用する前にread()を呼び出す必要があります。

パラメータconvert_missingは、Stataの欠損値表現を保持するかどうかを示します。False(デフォルト)の場合、欠損値はnp.nanとして表現されます。Trueの場合、欠損値はStataMissingValueオブジェクトを使用して表現され、欠損値を含む列はobjectデータ型になります。

read_stata()StataReaderは、.dta形式の113-115(Stata 10-12)、117(Stata 13)、118(Stata 14)をサポートしています。

preserve_dtypes=Falseを設定すると、標準のpandasデータ型にアップキャストされます。すべての整数型はint64、浮動小数点データはfloat64になります。デフォルトでは、インポート時にStataのデータ型が保持されます。

read_stata()iterator=Trueまたはchunksizeを使用した場合)によって作成されたか、手動でインスタンス化されたかに関わらず、すべてのStataReaderオブジェクトはコンテキストマネージャー(例:withステートメント)として使用する必要があります。close()メソッドは利用可能ですが、その使用はサポートされていません。これは公開APIの一部ではなく、将来予告なしに削除されます。

カテゴリカルデータ#

Categoricalデータは、値ラベル付きデータとしてStataデータファイルにエクスポートできます。エクスポートされたデータは、整数データ値としての基礎となるカテゴリコードと、値ラベルとしてのカテゴリで構成されます。StataにはCategoricalの明示的な同等物がないため、変数が順序付けられているかどうかに関する情報はエクスポート時に失われます。

警告

Stataは文字列値ラベルのみをサポートしているため、データをエクスポートするときにカテゴリに対してstrが呼び出されます。文字列以外のカテゴリを持つCategorical変数をエクスポートすると警告が発生し、カテゴリのstr表現が一意でない場合、情報が失われる可能性があります。

同様に、ラベル付きデータは、キーワード引数convert_categoricals(デフォルトはTrue)を使用して、StataデータファイルからCategorical変数としてインポートできます。キーワード引数order_categoricals(デフォルトはTrue)は、インポートされたCategorical変数が順序付けられるかどうかを決定します。

カテゴリカルデータをインポートする場合、Categorical変数は常に-1n-1の間の整数データ型を使用するため、Stataデータファイルの変数値は保持されません。nはカテゴリの数です。Stataデータファイル内の元の値が必要な場合は、convert_categoricals=Falseを設定することでインポートできます。これにより、元のデータ(ただし変数ラベルは含まれません)がインポートされます。元のStataデータ値とインポートされたカテゴリカル変数のカテゴリコードの間には単純なマッピングがあるため、元の値はインポートされたカテゴリカルデータと一致させることができます。欠損値にはコード-1が割り当てられ、最小の元の値には0が割り当てられ、2番目に小さい値には1が割り当てられ、最大の元の値にはコードn-1が割り当てられるまで続きます。

Stataは部分的にラベル付けされたシリーズをサポートしています。これらのシリーズには、一部のデータ値には値ラベルがありますが、すべてのデータ値にはありません。部分的にラベル付けされたシリーズをインポートすると、ラベル付けされた値には文字列カテゴリ、ラベルのない値には数値カテゴリを持つCategoricalが生成されます。

SAS形式#

トップレベル関数read_sas()は、SAS XPORT (.xpt)およびSAS7BDAT (.sas7bdat)形式のファイルを読み込むことができます(ただし書き込みはできません)。

SASファイルには、ASCIIテキストと浮動小数点値(通常8バイトですが、時には切り捨てられることもあります)の2種類の値のみが含まれます。xportファイルの場合、整数、日付、またはカテゴリカルへの自動型変換はありません。SAS7BDATファイルの場合、形式コードによって日付変数を自動的に日付に変換できる場合があります。デフォルトでは、ファイル全体が読み込まれ、DataFrameとして返されます。

chunksizeを指定するか、iterator=Trueを使用してリーダーオブジェクト(XportReaderまたはSAS7BDATReader)を取得し、ファイルをインクリメンタルに読み込みます。リーダーオブジェクトには、ファイルとその変数に関する追加情報を含む属性も含まれています。

SAS7BDATファイルを読み込む

df = pd.read_sas("sas_data.sas7bdat")

イテレータを取得し、XPORTファイルを100,000行ずつ読み込む

def do_something(chunk):
    pass


with pd.read_sas("sas_xport.xpt", chunk=100000) as rdr:
    for chunk in rdr:
        do_something(chunk)

xportファイル形式の仕様はSASのウェブサイトで入手できます。

SAS7BDAT形式の公式ドキュメントは入手できません。

SPSS形式#

トップレベル関数read_spss()は、SPSS SAV (.sav)およびZSAV (.zsav)形式ファイルを読み込むことができます(ただし書き込みはできません)。

SPSSファイルには列名が含まれています。デフォルトでは、ファイル全体が読み込まれ、カテゴリカル列はpd.Categoricalに変換され、すべての列を含むDataFrameが返されます。

列のサブセットを取得するにはusecolsパラメータを指定します。カテゴリカル列をpd.Categoricalに変換しないようにするにはconvert_categoricals=Falseを指定します。

SPSSファイルを読み込む

df = pd.read_spss("spss_data.sav")

SPSSファイルからusecolsに含まれる列のサブセットを抽出し、カテゴリカル列をpd.Categoricalに変換しないようにする。

df = pd.read_spss(
    "spss_data.sav",
    usecols=["foo", "bar"],
    convert_categoricals=False,
)

SAVおよびZSAVファイル形式に関する詳細情報はこちらで入手できます。

その他のファイル形式#

pandas自体は、その表形式データモデルにきれいにマッピングされる限られたファイル形式のみをIOでサポートしています。pandasとの間で他のファイル形式を読み書きするには、より広いコミュニティからのこれらのパッケージをお勧めします。

netCDF#

xarrayは、netCDFファイル形式に焦点を当て、pandasとの間で簡単に変換できる、多次元データセットを扱うためのpandasのDataFrameに触発されたデータ構造を提供します。

パフォーマンスに関する考慮事項#

これは、pandas 0.24.2を使用した、さまざまなIOメソッドの非公式な比較です。タイミングはマシンに依存し、小さな違いは無視する必要があります。

In [1]: sz = 1000000
In [2]: df = pd.DataFrame({'A': np.random.randn(sz), 'B': [1] * sz})

In [3]: df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000000 entries, 0 to 999999
Data columns (total 2 columns):
A    1000000 non-null float64
B    1000000 non-null int64
dtypes: float64(1), int64(1)
memory usage: 15.3 MB

いくつかのIOメソッドのパフォーマンスを比較するために、以下のテスト関数が使用されます。

import numpy as np

import os

sz = 1000000
df = pd.DataFrame({"A": np.random.randn(sz), "B": [1] * sz})

sz = 1000000
np.random.seed(42)
df = pd.DataFrame({"A": np.random.randn(sz), "B": [1] * sz})


def test_sql_write(df):
    if os.path.exists("test.sql"):
        os.remove("test.sql")
    sql_db = sqlite3.connect("test.sql")
    df.to_sql(name="test_table", con=sql_db)
    sql_db.close()


def test_sql_read():
    sql_db = sqlite3.connect("test.sql")
    pd.read_sql_query("select * from test_table", sql_db)
    sql_db.close()


def test_hdf_fixed_write(df):
    df.to_hdf("test_fixed.hdf", key="test", mode="w")


def test_hdf_fixed_read():
    pd.read_hdf("test_fixed.hdf", "test")


def test_hdf_fixed_write_compress(df):
    df.to_hdf("test_fixed_compress.hdf", key="test", mode="w", complib="blosc")


def test_hdf_fixed_read_compress():
    pd.read_hdf("test_fixed_compress.hdf", "test")


def test_hdf_table_write(df):
    df.to_hdf("test_table.hdf", key="test", mode="w", format="table")


def test_hdf_table_read():
    pd.read_hdf("test_table.hdf", "test")


def test_hdf_table_write_compress(df):
    df.to_hdf(
        "test_table_compress.hdf", key="test", mode="w", complib="blosc", format="table"
    )


def test_hdf_table_read_compress():
    pd.read_hdf("test_table_compress.hdf", "test")


def test_csv_write(df):
    df.to_csv("test.csv", mode="w")


def test_csv_read():
    pd.read_csv("test.csv", index_col=0)


def test_feather_write(df):
    df.to_feather("test.feather")


def test_feather_read():
    pd.read_feather("test.feather")


def test_pickle_write(df):
    df.to_pickle("test.pkl")


def test_pickle_read():
    pd.read_pickle("test.pkl")


def test_pickle_write_compress(df):
    df.to_pickle("test.pkl.compress", compression="xz")


def test_pickle_read_compress():
    pd.read_pickle("test.pkl.compress", compression="xz")


def test_parquet_write(df):
    df.to_parquet("test.parquet")


def test_parquet_read():
    pd.read_parquet("test.parquet")

書き込みにおいて、速度の面で上位3つの関数は、test_feather_writetest_hdf_fixed_writetest_hdf_fixed_write_compressです。

In [4]: %timeit test_sql_write(df)
3.29 s ± 43.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [5]: %timeit test_hdf_fixed_write(df)
19.4 ms ± 560 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [6]: %timeit test_hdf_fixed_write_compress(df)
19.6 ms ± 308 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [7]: %timeit test_hdf_table_write(df)
449 ms ± 5.61 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [8]: %timeit test_hdf_table_write_compress(df)
448 ms ± 11.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [9]: %timeit test_csv_write(df)
3.66 s ± 26.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [10]: %timeit test_feather_write(df)
9.75 ms ± 117 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [11]: %timeit test_pickle_write(df)
30.1 ms ± 229 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [12]: %timeit test_pickle_write_compress(df)
4.29 s ± 15.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [13]: %timeit test_parquet_write(df)
67.6 ms ± 706 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

読み込みにおいて、速度の面で上位3つの関数は、test_feather_readtest_pickle_readtest_hdf_fixed_readです。

In [14]: %timeit test_sql_read()
1.77 s ± 17.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [15]: %timeit test_hdf_fixed_read()
19.4 ms ± 436 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [16]: %timeit test_hdf_fixed_read_compress()
19.5 ms ± 222 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [17]: %timeit test_hdf_table_read()
38.6 ms ± 857 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [18]: %timeit test_hdf_table_read_compress()
38.8 ms ± 1.49 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [19]: %timeit test_csv_read()
452 ms ± 9.04 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [20]: %timeit test_feather_read()
12.4 ms ± 99.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [21]: %timeit test_pickle_read()
18.4 ms ± 191 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [22]: %timeit test_pickle_read_compress()
915 ms ± 7.48 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [23]: %timeit test_parquet_read()
24.4 ms ± 146 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

ファイルtest.pkl.compresstest.parquettest.featherがディスク上のスペース(バイト単位)を最も消費しませんでした。

29519500 Oct 10 06:45 test.csv
16000248 Oct 10 06:45 test.feather
8281983  Oct 10 06:49 test.parquet
16000857 Oct 10 06:47 test.pkl
7552144  Oct 10 06:48 test.pkl.compress
34816000 Oct 10 06:42 test.sql
24009288 Oct 10 06:43 test_fixed.hdf
24009288 Oct 10 06:43 test_fixed_compress.hdf
24458940 Oct 10 06:44 test_table.hdf
24458940 Oct 10 06:44 test_table_compress.hdf