IOツール (テキスト, CSV, HDF5, ...) #

pandasのI/O APIは、pandas.read_csv()のようにアクセスできるトップレベルのreader関数群であり、一般的にpandasオブジェクトを返します。対応するwriter関数は、DataFrame.to_csv()のようにアクセスできるオブジェクトメソッドです。以下は、利用可能なreaderswritersを含む表です。

フォーマットタイプ

データの説明

Reader

Writer

テキスト

CSV

read_csv

to_csv

テキスト

固定幅テキストファイル

read_fwf

テキスト

JSON

read_json

to_json

テキスト

HTML

read_html

to_html

テキスト

LaTeX

Styler.to_latex

テキスト

XML

read_xml

to_xml

テキスト

ローカルクリップボード

read_clipboard

to_clipboard

バイナリ

MS Excel

read_excel

to_excel

バイナリ

OpenDocument

read_excel

バイナリ

HDF5 フォーマット

read_hdf

to_hdf

バイナリ

Feather フォーマット

read_feather

to_feather

バイナリ

Parquet フォーマット

read_parquet

to_parquet

バイナリ

ORC フォーマット

read_orc

to_orc

バイナリ

Stata

read_stata

to_stata

バイナリ

SAS

read_sas

バイナリ

SPSS

read_spss

バイナリ

Python Pickle フォーマット

read_pickle

to_pickle

SQL

SQL

read_sql

to_sql

SQL

Google BigQuery

read_gbq

to_gbq

こちらは、これらのIOメソッドのいくつかに関する非公式なパフォーマンス比較です。

注釈

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

CSVとテキストファイル#

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

パースオプション#

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

基本#

filepath_or_buffervarious

ファイルへのパス(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_whitespaceboolean、デフォルトは 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']] を使用します。

callable の場合、callable 関数は列名に対して評価され、callable 関数が 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型名または列 -> 型の辞書、デフォルト None

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

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

dtype_backend{"numpy_nullable", "pyarrow"}、デフォルトは NumPy を基盤とする DataFrame

使用する 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 とみなす値。

skipinitialspaceboolean、デフォルト False

デリミター後のスペースをスキップします。

skiprowsリストライクまたは整数、デフォルト None

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

callable の場合、callable 関数は行インデックスに対して評価され、行をスキップする必要がある場合は 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_memoryboolean、デフォルト True

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

memory_mapboolean、デフォルト False

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

NA および欠損値の処理#

na_valuesスカラー、文字列、リストライク、または辞書、デフォルト None

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

keep_default_naboolean、デフォルト 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_na および na_values パラメーターは無視されることに注意してください。

na_filterboolean、デフォルト True

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

verboseboolean、デフォルト False

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

skip_blank_linesboolean、デフォルト True

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

日付時刻の処理#

parse_datesboolean または整数のリストまたは名前のリストまたはリストのリストまたは辞書、デフォルト False
  • True の場合 -> インデックスの解析を試みます。

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

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

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

注釈

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

infer_datetime_formatboolean、デフォルト False

True で、parse_dates が列に対して有効になっている場合、処理を高速化するために日時形式の推論を試みます。

バージョン 2.0.0 から非推奨: この引数の厳密なバージョンがデフォルトになりました。渡しても効果はありません。

keep_date_colboolean、デフォルト False

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

date_parser関数、デフォルト None

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

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

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

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

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

dayfirstboolean、デフォルトは False

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

cache_datesboolean、デフォルトは True

Trueの場合、datetime変換を適用するために、一意の変換された日付のキャッシュを使用します。特にタイムゾーンオフセットを持つ重複する日付文字列を解析する場合、大幅な高速化が期待できます。

イテレーション#

iteratorboolean、デフォルトは 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.ZipFile, gzip.GzipFile, bz2.BZ2File, または zstandard.ZstdDecompressor に転送されます。例として、より高速な圧縮と再現可能なgzipアーカイブを作成するために、次のようなものを渡すことができます: compression={'method': 'gzip', 'compresslevel': 1, 'mtime': 1}

バージョン 1.2.0 で変更: 以前のバージョンでは、’gzip’ の辞書エントリを 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) のいずれかを使用します。

doublequoteboolean、デフォルトは True

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

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

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

commentstr、デフォルトは None

行の残りの部分を解析しないことを示します。行の先頭に見つかった場合、行全体が無視されます。このパラメータは、1つの文字である必要があります。空行と同様に(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()関数を使用してdtypeを強制変換することもできます。

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')

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

dtype_backend="numpy_nullable"を設定すると、すべての列でnullable dtypeが生成されます。

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'の場合、結果のカテゴリは常に文字列(object dtype)として解析されます。カテゴリが数値の場合は、to_numeric()関数、または必要に応じてto_datetime()などの別のコンバーターを使用して変換できます。

dtypeが均一なcategories(すべて数値、すべてdatetimeなど)を持つ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引数を使用すると、列名、位置番号、またはcallableを使用して、ファイル内の列の任意のサブセットを選択できます。

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

この場合、callableは、出力から「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引数は、エンコードされたunicodeデータに使用する必要があります。これにより、結果ではバイト文字列がunicodeにデコードされます。

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

日付処理#

日付列の指定#

datetimeデータの操作をより容易にするために、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

注釈

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

注釈

read_csvには、iso8601形式(例: "2000-01-01T00:01:02+00:00" および同様のバリエーション)の日付時刻文字列を解析するための高速パスがあります。データがこの形式で日付時刻を保存するように手配できる場合、ロード時間が大幅に速くなり、〜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 を使用しても、デフォルトの結果は文字列を持つ object-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]

datetime 形式の推論#

以下は、推測可能な datetime 文字列の例です(すべて 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

同じ列内に混在する datetime 形式がある場合は、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

または、datetime 形式がすべて 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 を書き込むことができます。ほとんどの場合、Pandas はファイルオブジェクトがテキストモードで開かれているかバイナリモードで開かれているかを自動検出するため、mode を指定する必要はありません。

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.0NaN として認識されます。文字列は最初に数値の 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 parsers.pyx:838, in pandas._libs.parsers.TextReader.read_low_memory()

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

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

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

File 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:979, in ParserBase._validate_usecols_names(self, usecols, names)
    977 missing = [c for c in usecols if c not in names]
    978 if len(missing) > 0:
--> 979     raise ValueError(
    980         f"Usecols do not match columns, columns expected but not found: "
    981         f"{missing}"
    982     )
    984 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

もう1つの一般的な方言オプションは、区切り文字の後の空白をスキップする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オブジェクトと同様にobject 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の関数パラメーターは、2つの追加パラメーターと、delimiterパラメーターの異なる使用法を除いて、read_csvとほぼ同じです。

  • 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 0x7fac5f067a60>
          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

  • delim_whitespace=Falseのときのsep=None

上記のいずれかのオプションを指定すると、engine='python'を使用して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 で追加されました。

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

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:内容が非ASCIIの場合に使用するエンコーディングを表す文字列(Python 3より前のバージョンの場合)

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

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

  • quotechar:フィールドを引用するために使用される文字(デフォルトは ' " ')

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

  • escapechar:必要に応じて sep および quotechar をエスケープするために使用される文字(デフォルトは 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 内の float に適用されます。

  • 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 は、有効なJSON文字列に変換できます。to_json をオプションのパラメータとともに使用します。

  • path_or_buf:出力先となるパス名またはバッファ。これが None の場合、JSON文字列が返されます。

  • orient :

    Series:
    • デフォルトは index

    • 使用可能な値は {split, records, index}

    DataFrame:
    • デフォルトは columns

    • 使用可能な値は {split, records, index, columns, values, table}

    JSON文字列の形式

    split

    {index -> [index], columns -> [columns], data -> [values]} のような辞書

    records

    [{column -> value}, ... , {column -> value}] のようなリスト

    index

    {index -> {column -> value}} のような辞書

    columns

    {column -> {index -> value}} のような辞書

    values

    値の配列のみ

    table

    JSON テーブルスキーマ に準拠

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

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

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

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

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

  • linesrecords orientの場合、各レコードをJSONとして1行ずつ書き込みます。

  • mode:文字列、パスへの書き込み時のライターモード。書き込みの場合は 'w'、追加の場合は 'a'。デフォルトは 'w'

NaNNaTNonenull に変換され、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ファイル/文字列の形式には、いくつかの異なるオプションがあります。次の DataFrame および Series を考えてみましょう。

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 テーブルスキーマにシリアライズし、dtypesやインデックス名を含むがこれらに限定されないメタデータの保持を可能にします。

注釈

JSONオブジェクトにエンコードするorientオプションでは、ラウンドトリップシリアル化中にインデックスと列ラベルの順序が保持されません。ラベルの順序を保持したい場合は、順序付けられたコンテナを使用する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メソッドはdictを返す必要があります。その後、これがJSONシリアライズされます。

    • 提供されている場合は、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、およびファイルが含まれます。ファイルURLの場合、ホストが予期されます。たとえば、ローカルファイルはfile ://localhost/path/to/table.jsonになります。

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

  • orient :

    Series
    • デフォルトは index

    • 使用可能な値は {split, records, index}

    DataFrame
    • デフォルトは columns

    • 使用可能な値は {split, records, index, columns, values, table}

    JSON文字列の形式

    split

    {index -> [index], columns -> [columns], data -> [values]} のような辞書

    records

    [{column -> value}, ... , {column -> value}] のようなリスト

    index

    {index -> {column -> value}} のような辞書

    columns

    {column -> {index -> value}} のような辞書

    values

    値の配列のみ

    table

    JSON テーブルスキーマ に準拠

  • dtype : Trueの場合、dtypesを推論します。列からdtypeへのdictの場合、それを使用します。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 : py3バイトをデコードするために使用するエンコーディング。

  • chunksize : lines=Trueと組み合わせて使用すると、反復ごとにchunksize行を読み取るpandas.api.typing.JsonReaderを返します。

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

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

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

データ変換#

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

注釈

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

  • '_at'で終わる

  • '_time'で終わる

  • 'timestamp'で始まる

  • 'modified'である

  • 'date'である

警告

JSONデータを読み込むとき、dtypesへの自動強制にはいくつかの癖があります。

  • インデックスはシリアライズとは異なる順序で再構築できます。つまり、返される順序はシリアライズ前と同じであることが保証されません。

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

  • bool列は、再構築時に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に使用されるデフォルトのdtypesを制御できます。

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は、dictまたはdictのリストを受け取り、この半構造化データをフラットテーブルに正規化するためのユーティリティ関数を提供します。

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の場合、次のスニペットは、指定されたdictの最初のネストレベルまで正規化します。

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 で追加されました。

テーブルスキーマ#

テーブルスキーマは、表形式のデータセットをJSONオブジェクトとして記述するための仕様です。JSONには、フィールド名、型、およびその他の属性に関する情報が含まれています。orient 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を含む、列名から型のペアのリストが含まれています(型のリストについては、以下を参照)。(マルチ)インデックスが一意の場合、schemaフィールドにはprimaryKeyフィールドも含まれます。

2番目のフィールドであるdataには、recordsオリエントでシリアライズされたデータが含まれています。インデックスが含まれており、日付時刻はすべて、テーブルスキーマ仕様で要求されているようにISO 8601形式でフォーマットされています。

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

pandas型

テーブルスキーマ型

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'}
    
  • (シリアライズ前の)タイムゾーン付きの日付時刻には、タイムゾーン名(例:'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'}
    
  • 期間はシリアライズ前にタイムスタンプに変換されるため、UTCに変換されるという同じ動作をします。さらに、期間には、期間の頻度を示す freq という追加フィールド(例:'A-DEC')が含まれます。

    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 の動作は MultiIndex の場合も同じですが、この場合、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'}
    
  • デフォルトの命名規則は、おおむね以下のルールに従います。

    • シリーズの場合、object.name が使用されます。それが None の場合、名前は values になります。

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

    • IndexMultiIndex ではない)の場合、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

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

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 page で報告してください。

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つの文字列を含むリストを渡すことを推奨します。使用できるのは

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 を使用します

    • したがって、BeautifulSoup4html5lib の両方をインストールすることを強くお勧めします。これにより、lxml が失敗した場合でも(他のすべてが有効である限り)有効な結果が得られます。

lxml をバックエンドとして使用する BeautifulSoup4 の問題点

  • 上記の課題は、BeautifulSoup4 が本質的にパーサーバックエンドのラッパーに過ぎないため、ここでも同様に当てはまります。

html5libをバックエンドとして使用するBeautifulSoup4に関する問題点

  • 利点

    • html5liblxmlよりも寛容であり、その結果、実際のマークアップを、例えば要素を通知なしに削除するのではなく、はるかに健全な方法で処理します。

    • html5libは、無効なマークアップから自動的に有効なHTML5マークアップを生成します。これは、有効なドキュメントを保証するため、HTMLテーブルを解析する上で非常に重要です。ただし、マークアップを修正するプロセスには単一の定義がないため、これは「正しい」という意味ではありません。

    • html5libは純粋なPythonであり、インストール以外に追加のビルド手順は必要ありません。

  • 短所

    • html5libを使用する最大の欠点は、非常に遅いことです。ただし、Web上の多くのテーブルは、解析アルゴリズムの実行時間が問題にならないほど小さく、ボトルネックはWeb経由でURLから生テキストを読み取るプロセス、つまりIO(入出力)になる可能性が高いという事実を考慮してください。非常に大きなテーブルの場合、これは当てはまらない可能性があります。

LaTeX#

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

現在、LaTeXから読み取るメソッドはなく、出力メソッドのみです。

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

注釈

DataFrame および Stylerオブジェクトには現在、to_latexメソッドがあります。条件付きスタイリングに対する柔軟性が高く、後者の将来的な廃止の可能性があるため、Styler.to_latex()メソッドをDataFrame.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 Article Datasetsなどの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には、プレフィックスdochttps://example.comのURIを持つ名前空間が含まれています。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要素の子に存在し、row要素自体には存在しないため、期待どおりに解析されなかったことを示しています。言い換えれば、sides属性はrow要素の孫レベルの子孫です。ただし、xpathは、その子と属性のみを対象とするrow要素をターゲットにします。

パーサーとしてlxmlを使用すると、XSLTスクリプトを使用してネストされたXMLドキュメントをフラット化できます。このスクリプトも文字列/ファイル/URLタイプにすることができます。背景として、XSLTは、元のXMLドキュメントをXSLTプロセッサを使用して他の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>

階層的な列を持つDataFrameは、アンダースコアで区切られたレベルを持つ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仕様に準拠しています。etreelxmlの両方のパーサーは、整形式ではない、または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データの操作と似ています。いくつかの高度な戦略については、cookbookを参照してください。

注釈

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に渡すことができます。ファイルが1回だけメモリに読み込まれるため、複数のシートを読み取る場合にパフォーマンス上の利点があります。

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ファイルの読み取り方法を制御できます。たとえば、on_demand=Trueを指定してxlrd.open_workbook()を呼び出すことで、シートをオンデマンドでロードできます。

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インデックスと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=Trueto_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)

1 つの 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.writer および io.excel.xls.writer を使用して、デフォルトのエンジンを設定できます。 Xlsxwriter が利用できない場合、pandas は .xlsx ファイルに対して openpyxl にフォールバックします。

使用するライターを指定するには、to_excel および ExcelWriter に engine キーワード引数を渡すことができます。組み込みのエンジンは次のとおりです。

  • 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 ファイル の IO メソッドは、odfpy モジュールを使用した OpenDocument スプレッドシートの読み取りと書き込みもサポートしています。 OpenDocument スプレッドシートの読み書きに関するセマンティクスと機能は、engine='odf' を使用して Excel ファイル で実行できるものと一致します。オプションの依存関係である '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 型を認識せず、代わりに float を返します (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 メソッドに渡します。たとえば、次のテキストをクリップボードにコピーできます(多くのオペレーティングシステムでは 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 関数を使用すると、ピクル化された pandas オブジェクト(またはその他のピクル化されたオブジェクト)をファイルからロードできます。

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() の後方互換性は、数回のマイナーリリースまでしか保証されていません。

圧縮されたpickleファイル#

read_pickle()DataFrame.to_pickle()、および Series.to_pickle() は、圧縮された pickle ファイルの読み書きができます。gzipbz2xzzstd の圧縮タイプは、読み書きでサポートされています。zip ファイル形式は、読み込みのみをサポートし、読み込むにはデータファイルが 1 つだけ含まれている必要があります。

圧縮タイプは、明示的なパラメータであるか、ファイル拡張子から推測できます。「infer」の場合、ファイル名がそれぞれ '.gz''.bz2''.zip''.xz'、または '.zst' で終わる場合は、gzipbz2zipxzzstd を使用します。

圧縮パラメータは、圧縮プロトコルにオプションを渡すために dict にすることもできます。これには、圧縮プロトコルの名前('zip''gzip''bz2''xz''zstd' のいずれかである必要があります)に設定された 'method' キーが必要です。その他すべてのキーと値のペアは、基礎となる圧縮ライブラリに渡されます。

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]

デフォルトは「infer」です

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 を使用して object-dtype データをシリアル化できます。信頼できないソースから受信したピクル化されたデータをロードすることは安全でない場合があります。

詳細については、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 を使用して格納する方法を示しています。これは、fixed 形式と呼ばれる固定配列形式で HDF5 を PyTables に書き込みます。これらのタイプのストアは、一度書き込むと追加できません(ただし、単純に削除して書き直すことはできます)。また、クエリ可能でもありません。全体をまとめて取得する必要があります。また、一意でない列名を持つデータフレームもサポートしていません。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:452, in read_hdf(path_or_buf, key, mode, errors, where, start, stop, columns, iterator, chunksize, **kwargs)
    447                 raise ValueError(
    448                     "key must be provided when HDF5 "
    449                     "file contains multiple datasets."
    450                 )
    451         key = candidate_only_group._v_pathname
--> 452     return store.select(
    453         key,
    454         where=where,
    455         start=start,
    456         stop=stop,
    457         columns=columns,
    458         iterator=iterator,
    459         chunksize=chunksize,
    460         auto_close=auto_close,
    461     )
    462 except (ValueError, TypeError, LookupError):
    463     if not isinstance(path_or_buf, HDFStore):
    464         # if there is an error, close the store if we opened it.

File ~/work/pandas/pandas/pandas/io/pytables.py:906, in HDFStore.select(self, key, where, start, stop, columns, iterator, chunksize, auto_close)
    892 # create the iterator
    893 it = TableIterator(
    894     self,
    895     s,
   (...)
    903     auto_close=auto_close,
    904 )
--> 906 return it.get_result()

File ~/work/pandas/pandas/pandas/io/pytables.py:2029, in TableIterator.get_result(self, coordinates)
   2026     where = self.where
   2028 # directly return the result
-> 2029 results = self.func(self.start, self.stop, where)
   2030 self.close()
   2031 return results

File ~/work/pandas/pandas/pandas/io/pytables.py:890, in HDFStore.select.<locals>.func(_start, _stop, _where)
    889 def func(_start, _stop, _where):
--> 890     return s.read(start=_start, stop=_stop, where=_where, columns=columns)

File ~/work/pandas/pandas/pandas/io/pytables.py:3278, in BlockManagerFixed.read(self, where, columns, start, stop)
   3270 def read(
   3271     self,
   3272     where=None,
   (...)
   3276 ) -> DataFrame:
   3277     # start, stop applied to rows, so 0th axis only
-> 3278     self.validate_read(columns, where)
   3279     select_axis = self.obj_type()._get_block_manager_axis(0)
   3281     axes = []

File ~/work/pandas/pandas/pandas/io/pytables.py:2922, in GenericFixed.validate_read(self, columns, where)
   2917     raise TypeError(
   2918         "cannot pass a column specification when reading "
   2919         "a Fixed format store. this store must be selected in its entirety"
   2920     )
   2921 if where is not None:
-> 2922     raise TypeError(
   2923         "cannot pass a where specification when reading "
   2924         "from a Fixed format store. this store must be selected in its entirety"
   2925     )

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 は、同じまたは他のセッションで追加できます。さらに、削除およびクエリタイプの操作がサポートされています。この形式は、format='table' または format='t'appendput、または to_hdf に指定することで指定されます。

この形式は、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'

注釈

format='table' または format='t'put 操作に渡すことによって、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:613, in HDFStore.__getattr__(self, name)
    611 """allow attribute access to get stores"""
    612 try:
--> 613     return self.get(name)
    614 except (KeyError, ClosedFileError):
    615     pass

File ~/work/pandas/pandas/pandas/io/pytables.py:813, in HDFStore.get(self, key)
    811 if group is None:
    812     raise KeyError(f"No object named {key} in the file")
--> 813 return self._read_group(group)

File ~/work/pandas/pandas/pandas/io/pytables.py:1878, in HDFStore._read_group(self, group)
   1877 def _read_group(self, group: Node):
-> 1878     s = self._create_storer(group)
   1879     s.infer_axes()
   1880     return s.read()

File ~/work/pandas/pandas/pandas/io/pytables.py:1752, in HDFStore._create_storer(self, group, format, value, encoding, errors)
   1750         tt = "generic_table"
   1751     else:
-> 1752         raise TypeError(
   1753             "cannot create a storer if the object is not existing "
   1754             "nor a value are passed"
   1755         )
   1756 else:
   1757     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 が発生します。

append のパラメータとして min_itemsize={`values`: size} を渡すと、文字列カラムの最小サイズをより大きく設定します。現在、floats, strings, ints, bools, datetime64 の格納がサポートされています。文字列カラムの場合、append に nan_rep = 'nan' を渡すと、ディスク上のデフォルトの 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 DataFrame をテーブルとして格納する方法は、均質なインデックスを持つ DataFrame の格納/選択と非常によく似ています。

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 クラスを使用して指定されます。

  • index および columns は、DataFrame のサポートされているインデクサーです。

  • data_columns が指定されている場合、これらは追加のインデクサーとして使用できます。

  • MultiIndex のレベル名。指定されていない場合、デフォルト名は level_0, level_1, … です。

有効な比較演算子は次のとおりです。

=, ==, !=, >, >=, <, <=

有効なブール式は次のもので結合されます。

  • | : or

  • & : and

  • ( および ) : グループ化

これらのルールは、pandas でインデックス作成にブール式を使用する方法と似ています。

注釈

  • = は、比較演算子 == に自動的に展開されます。

  • ~ は not 演算子ですが、非常に限られた状況でのみ使用できます。

  • 式のリスト/タプルが渡された場合、それらは & で結合されます。

以下は有効な式です。

  • '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 パラメータを指定して、検索空間の合計を制限できます。これらは、テーブル内の行の合計数で表されます。

注釈

クエリ式に不明な変数参照がある場合、selectValueError を発生させます。通常、これは、data_column **ではない**列を選択しようとしていることを意味します。

クエリ式が有効でない場合、selectSyntaxError を発生させます。

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 で作成/変更できます。テーブルインデックスを作成することを**強く**お勧めします。これにより、インデックス付きの次元を where として select を使用する場合、クエリが大幅に高速化されます。

注釈

インデックスは、インデックス可能な列と指定したデータ列に自動的に作成されます。この動作は、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')

多くの場合、大量のデータをストアに追加する場合、append ごとのインデックス作成をオフにし、最後に再作成すると便利です。

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,)

次に、append が完了したらインデックスを作成します。

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 操作の後、データ列(またはインデックス可能列)を変更することはできません(もちろん、データを読み込んで新しいテーブルを作成することはできます!)。

イテレーター#

select および select_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)

chunksize キーワードは、**ソース**行に適用されることに注意してください。したがって、クエリを実行している場合、chunksize はテーブル内の合計行を細分化し、クエリが適用されて、サイズが等しくない可能性のあるチャンクのイテレーターが返されます。

以下は、クエリを生成し、それを使用して同じサイズの返却チャンクを作成するためのレシピです。

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_multiple および select_as_multiple は、複数のテーブルからの追加/選択を一度に実行できます。基本的な考え方は、ほとんど/すべての列にインデックスを付け、クエリを実行する 1 つのテーブル(セレクタテーブルと呼ぶ)を用意することです。他のテーブルは、セレクタテーブルのインデックスと一致するインデックスを持つデータテーブルです。これにより、セレクタテーブルで非常に高速なクエリを実行できますが、多くのデータを取り戻すことができます。この方法は、非常に幅の広いテーブルを持つことに似ていますが、より効率的なクエリを可能にします。

append_to_multiple メソッドは、指定された単一の DataFrame を、テーブル名をテーブルに含める「列」のリストにマッピングする辞書 d に従って、複数のテーブルに分割します。リストの代わりに None を使用した場合、そのテーブルには、指定された DataFrame の残りの未指定の列が含まれます。引数 selector は、クエリを実行できるセレクタテーブル(テーブルに対してクエリを実行できます)を定義します。引数 dropna は、テーブルが同期されていることを確認するために、入力 DataFrame から行を削除します。これは、書き込まれるテーブルのいずれかの行が完全に 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 を使用してください。

警告

列名を属性セレクタとして使用できない場合、PyTablesNaturalNameWarning を表示します。自然な識別子には、文字、数字、アンダースコアのみが含まれ、数字で始めることはできません。他の識別子は 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 は整数、または列名を整数にマッピングする辞書にすることができます。すべてのインデックス可能またはデータ列にこの min_itemsize を適用するために、values をキーとして渡すことができます。

min_itemsize 辞書を渡すと、渡されたすべての列が自動的にデータ列として作成されます。

注釈

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 は、データフレーム用のバイナリの列指向シリアライゼーションを提供します。データフレームの読み取りと書き込みを効率的にし、データ分析言語間でデータを簡単に共有できるように設計されています。

Feather は、カテゴリ型やタイムゾーン付きの日時などの拡張 dtype を含む、pandas のすべての dtype をサポートして、DataFrame を忠実にシリアライズおよびデシリアライズするように設計されています。

いくつかの注意点

  • この形式では、DataFrameIndex または MultiIndex は書き込まれず、デフォルトでないものが指定された場合はエラーが発生します。インデックスを保存するには .reset_index() を使用するか、インデックスを無視するには .reset_index(drop=True) を使用できます。

  • 重複する列名と文字列以外の列名はサポートされていません。

  • object 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

Feather ファイルへの書き込み。

In [609]: df.to_feather("example.feather")

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

Parquet#

Apache Parquet は、データフレーム用のパーティション化されたバイナリの列指向シリアライゼーションを提供します。データフレームの読み取りと書き込みを効率的にし、データ分析言語間でデータを簡単に共有できるように設計されています。Parquet は、さまざまな圧縮技術を使用して、優れた読み取りパフォーマンスを維持しながら、ファイルサイズを可能な限り縮小できます。

Parquet は、タイムゾーン付きの日時などの拡張 dtype を含む、pandas のすべての dtype をサポートして、DataFrame を忠実にシリアライズおよびデシリアライズするように設計されています。

いくつかの注意点。

  • 重複する列名と文字列以外の列名はサポートされていません。

  • pyarrow エンジンは常にインデックスを出力に書き込みますが、fastparquet はデフォルトでないインデックスのみを書き込みます。この余分な列は、予期していない pandas 以外のコンシューマーに問題を引き起こす可能性があります。基盤となるエンジンに関係なく、index 引数を使用して、インデックスを含めるか省略するかを強制できます。

  • インデックスレベルの名前を指定する場合は、文字列である必要があります。

  • pyarrow エンジンでは、文字列以外の型のカテゴリ型 dtype を parquet にシリアライズできますが、プリミティブ dtype としてデシリアライズされます。

  • pyarrow エンジンは、文字列型のカテゴリ型 dtype の ordered フラグを保持します。fastparquetordered フラグを保持しません。

  • サポートされていないタイプには、Interval および実際の Python オブジェクトタイプが含まれます。これらは、シリアライゼーションを試みると役立つエラーメッセージが表示されます。Period タイプは pyarrow >= 0.16.0 でサポートされています。

  • pyarrow エンジンは、null 許容整数や文字列データ型などの拡張データ型を保持します (pyarrow >= 0.16.0 が必要で、拡張型が必要なプロトコルを実装している必要があります。拡張型ドキュメントを参照してください)。

シリアライゼーションを指示する engine を指定できます。これは、pyarrowfastparquet、または auto のいずれかになります。エンジンが指定されていない場合は、pd.options.io.parquet.engine オプションがチェックされます。これも auto の場合は、pyarrow が試行され、fastparquet にフォールバックします。

pyarrow および fastparquet のドキュメントを参照してください。

注釈

これらのエンジンは非常に似ており、ほぼ同一の parquet 形式ファイルを読み書きする必要があります。pyarrow>=8.0.0 は timedelta データをサポートし、fastparquet>=0.1.4 はタイムゾーンを認識する日時をサポートします。これらのライブラリは、異なる基盤となる依存関係を持つことで異なります (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

Parquet ファイルへの書き込み。

In [616]: df.to_parquet("example_pa.parquet", engine="pyarrow")

In [617]: df.to_parquet("example_fp.parquet", engine="fastparquet")

Parquet ファイルからの読み取り。

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に使用されるデフォルトのdtypesを制御できます。

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=int32, order...
i    dictionary<values=string, indices=int32, order...
dtype: object

注釈

これは fastparquet ではサポートされていないことに注意してください。

Parquet ファイルの特定の列のみを読み取ります。

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 を使用する場合は、3 つの列 (ab、および __index_level_0__) を持つ parquet ファイルを作成します。fastparquet を使用している場合、インデックスは ファイルに書き込まれる場合と書き込まれない場合があります

この予期しない余分な列が原因で、Amazon Redshift などの一部のデータベースで、その列がターゲットテーブルに存在しないため、ファイルが拒否されます。

書き込み時にデータフレームのインデックスを省略する場合は、to_parquet()index=False を渡します。

In [628]: df.to_parquet("test.parquet", index=False)

これにより、予期される 2 つの列 (ab) のみを持つ parquet ファイルが作成されます。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 ドライバーの実装状況ドキュメントを参照してください。

ADBC ドライバーが利用できない場合や、機能が不足している場合は、データベースドライバーライブラリと一緒に SQLAlchemy をインストールすることをお勧めします。このようなドライバーの例としては、PostgreSQL の場合は psycopg2、MySQL の場合は pymysql があります。SQLite の場合は、Python の標準ライブラリにデフォルトで含まれています。各 SQL ダイアレクトでサポートされているドライバーの概要は、SQLAlchemy のドキュメントで確認できます。

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

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 型システムに変換されるのを防ぐことができます。従来の pandas/NumPy 型システムでは、SQL 型が変換されて、ラウンドトリップできなくなることがよくあります。

ADBCドライバーが利用できない場合、to_sql()は、データのdtypeに基づいて適切なSQLデータ型にデータをマッピングしようとします。dtypeがobjectの列がある場合、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) ...はobject値(str)のシリーズを返します。したがって、クエリ出力が空の場合、結果のすべての列はobject値として返されます(最も一般的であるため)。クエリが空の結果を生成する場合があることが予想される場合は、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式言語の構成を渡すこともできます。

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からエンジンオブジェクトを作成します。接続しているデータベースごとに、エンジンを1回だけ作成する必要があります。

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.bindparam()を使用して、SQLAlchemy式と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_gbqおよびDataFrame.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文字以下の文字列、int8int16int32float32、および float64 のみが .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() は、最大 244 文字までの固定幅文字列のみをサポートしています。これは、バージョン 115 の dta ファイル形式による制限です。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 を設定すると、すべての整数型は int64 に、浮動小数点データは float64 に、標準の pandas データ型にアップキャストされます。デフォルトでは、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 変数が順序付けられているかどうかを決定します。

注釈

カテゴリカルデータをインポートする場合、Stata データファイル内の変数の値は保持されません。これは、Categorical 変数は常に -1 から n-1 の間の整数データ型を使用するためです。ここで 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 の Web サイトから入手できます。

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 は、pandas DataFrame に着想を得た多次元データセットを操作するためのデータ構造を提供し、netCDF ファイル形式と pandas との簡単な変換に焦点を当てています。

パフォーマンスに関する考慮事項#

これは、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