IO 工具(文本、CSV、HDF5 等)#

pandas I/O API 是一组顶级的 reader 函数,例如 pandas.read_csv(),通常返回一个 pandas 对象。相应的 writer 函数是对象方法,例如 DataFrame.to_csv()。以下是一个包含可用 readerswriters 的表格。

格式类型

数据描述

Reader

Writer

text

CSV

读取CSV

to_csv

text

固定宽度文本文件

读取fwf

NA

text

JSON

读取JSON

to_json

text

HTML

读取HTML

to_html

text

LaTeX

Styler.to_latex

NA

text

XML

读取XML

to_xml

text

本地剪贴板

读取剪贴板

到剪贴板

binary

MS Excel

读取Excel

to_excel

binary

OpenDocument

读取Excel

NA

binary

HDF5 格式

read_hdf

to_hdf

binary

Feather 格式

读取羽毛文件

to_feather

binary

Parquet 格式

读取Parquet

to_parquet

binary

ORC 格式

读取ORC

to_orc

binary

Stata

读取Stata

到Stata

binary

SAS

读取SAS

NA

binary

SPSS

读取SPSS

NA

binary

Python Pickle 格式

read_pickle

to_pickle

SQL

SQL

read_sql

to_sql

这里 是对这些IO方法中的一些非正式性能比较。

备注

对于使用 StringIO 类的示例,请确保使用 from io import StringIO 进行导入以用于 Python 3。

CSV 和文本文件#

读取文本文件(又称平面文件)的主力函数是 read_csv()。请参阅 cookbook 以获取一些高级策略。

解析选项#

read_csv() 接受以下常见参数:

基本#

filepath_or_buffer各种

要么是一个文件的路径(一个 strpathlib.Path),URL(包括 http、ftp 和 S3 位置),或者是任何带有 read() 方法的对象(例如一个打开的文件或 StringIO)。

sep : str, 默认为 ',' 用于 read_csv(), `` `` 用于 read_table()str, 默认为

使用的分隔符。如果 sep 是 None,C 引擎无法自动检测分隔符,但 Python 解析引擎可以,这意味着将使用后者并通过 Python 的内置嗅探工具 csv.Sniffer 自动检测分隔符。此外,长度超过 1 个字符且不同于 '\s+' 的分隔符将被解释为正则表达式,并将强制使用 Python 解析引擎。请注意,正则表达式分隔符容易忽略带引号的数据。正则表达式示例:'\r\t'

分隔符 : str, 默认 Nonestr, 默认

sep 的替代参数名称。

列和索引的位置和名称#

header : int 或 int 列表, 默认 'infer'整数或整数列表,默认

行号(s) 用作列名和数据的开始。默认行为是推断列名:如果没有传递列名,则行为与 header=0 相同,并且从文件的第一行推断列名;如果显式传递了列名,则行为与 header=None 相同。显式传递 header=0 以能够替换现有名称。

标题可以是一个指定列上MultiIndex的行位置的整数列表,例如 [0,1,3]。未指定的中间行将被跳过(例如,此示例中跳过2)。请注意,如果 skip_blank_lines=True,此参数会忽略注释行和空行,因此 header=0 表示第一行数据而不是文件的第一行。

names类数组, 默认

要使用的列名列表。如果文件不包含标题行,则应显式传递 header=None。此列表中不允许有重复项。

index_col : int, str, int / str 序列, 或 False, 可选, 默认 Noneint, str, int / str 序列, 或 False, 可选, 默认

用于作为 DataFrame 行标签的列,可以给定字符串名称或列索引。如果给定的是一个 int / str 序列,则使用 MultiIndex。

备注

index_col=False 可以用来强制 pandas 使用第一列作为索引,例如当你有一个每行末尾带有分隔符的格式错误文件时。

None 的默认值指示 pandas 进行猜测。如果列标题行中的字段数等于数据文件主体中的字段数,则使用默认索引。如果更大,则使用前几列作为索引,使得主体中剩余的字段数等于标题中的字段数。

标题后的第一行用于确定列数,这些列将进入索引。如果后续行包含的列数少于第一行,则用 NaN 填充。

这可以通过 usecols 来避免。这确保了列按原样获取,并且忽略尾随数据。

usecols : 类似列表或可调用对象, 默认 None类似列表或可调用对象,默认

返回列的子集。如果是列表类型,所有元素必须是位置的(即文档列的整数索引)或与在 names 中提供的或从文档标题行推断出的列名相对应的字符串。如果给出了 names,则不考虑文档标题行。例如,一个有效的列表类型 usecols 参数可以是 [0, 1, 2]['foo', 'bar', 'baz']

元素顺序被忽略,所以 usecols=[0, 1][1, 0] 是一样的。要从 data 实例化一个保留元素顺序的 DataFrame,请对 ['foo', 'bar'] 顺序的列使用 pd.read_csv(data, usecols=['foo', 'bar'])[['foo', 'bar']],或者对 ['bar', 'foo'] 顺序的列使用 pd.read_csv(data, usecols=['foo', 'bar'])[['bar', 'foo']]

如果是可调用的,可调用函数将对列名进行评估,返回可调用函数评估为 True 的名称:

In [1]: import pandas as pd

In [2]: from io import StringIO

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

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

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

使用此参数在使用 c 引擎时会大大加快解析时间并减少内存使用。Python 引擎在决定删除哪些列之前会先加载数据。

通用解析配置#

dtype : 类型名称或列 -> 类型的字典, 默认 None输入列名或列名 -> 类型的字典,默认为

数据或列的数据类型。例如 {'a': np.float64, 'b': np.int32, 'c': 'Int64'} 使用 strobject 以及合适的 na_values 设置来保留并不解释数据类型。如果指定了转换器,它们将代替数据类型转换应用。

Added in version 1.5.0: 添加了对 defaultdict 的支持。将 defaultdict 指定为输入,其中默认值决定了未明确列出的列的 dtype。

dtype_backend{“numpy_nullable”, “pyarrow”},默认为基于NumPy的DataFrame

使用哪个 dtype_backend,例如,当设置为 “numpy_nullable” 时,DataFrame 是否应使用 NumPy 数组,所有具有可空实现的 dtypes 都使用可空 dtypes;如果设置为 “pyarrow”,则所有 dtypes 都使用 pyarrow。

dtype_backends 仍然是实验性的。

Added in version 2.0.

引擎 : {'c', 'python', 'pyarrow'}{}

使用的解析引擎。C 和 pyarrow 引擎更快,而 python 引擎目前功能更全面。多线程目前仅由 pyarrow 引擎支持。

Added in version 1.4.0: “pyarrow” 引擎作为一个 实验性 引擎被添加,并且某些功能在此引擎上不受支持,或者可能无法正常工作。

转换器 : dict, 默认 None字典, 默认

用于转换某些列中值的函数字典。键可以是整数或列标签。

true_values : 列表, 默认 None列表, 默认

视为 True 的值。

false_values : 列表, 默认 None列表, 默认

视为 False 的值。

skipinitialspace : 布尔值, 默认 False布尔值, 默认

在分隔符后跳过空格。

skiprows : 类列表或整数, 默认 None列表类或整数,默认

文件开头要跳过的行号(从0开始索引)或要跳过的行数(整数)。

如果是可调用的,可调用函数将对行索引进行评估,如果该行应被跳过则返回 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
skipfooter : int, 默认 0int, 默认

文件底部要跳过的行数(使用 engine=’c’ 时不支持)。

nrows : int, 默认 Noneint, 默认

要读取的文件行数。对于读取大文件的部分内容非常有用。

low_memory : boolean, 默认 True布尔值, 默认

在内部以块为单位处理文件,从而在解析时减少内存使用,但可能会导致混合类型推断。为确保没有混合类型,可以设置 False,或者使用 dtype 参数指定类型。请注意,无论怎样,整个文件都会被读入一个单一的 DataFrame,使用 chunksizeiterator 参数以块的形式返回数据。(仅对C解析器有效)

memory_map布尔值, 默认 False

如果为 filepath_or_buffer 提供了一个文件路径,则将文件对象直接映射到内存中,并直接从那里访问数据。使用此选项可以提高性能,因为不再有任何 I/O 开销。

NA 和缺失数据处理#

na_values : 标量, 字符串, 类似列表, 或字典, 默认 None标量, 字符串, 类似列表的, 或字典, 默认

额外的字符串识别为 NA/NaN。如果传递的是字典,则为每列指定 NA 值。请参阅下面的 na 值常量 以获取默认解释为 NaN 的值列表。

keep_default_na : 布尔值, 默认为 True布尔值, 默认

在解析数据时是否包含默认的 NaN 值。根据是否传递 na_values ,行为如下:

  • 如果 keep_default_naTrue,并且指定了 na_values,则 na_values 会被附加到用于解析的默认 NaN 值中。

  • 如果 keep_default_naTrue,并且未指定 na_values,则仅使用默认的 NaN 值进行解析。

  • 如果 keep_default_naFalse,并且指定了 na_values,则仅使用指定的 na_values 的 NaN 值进行解析。

  • 如果 keep_default_naFalse,并且 na_values 未指定,则不会将任何字符串解析为 NaN。

请注意,如果 na_filter 被传递为 Falsekeep_default_nana_values 参数将被忽略。

na_filter : boolean, 默认 True布尔值, 默认

检测缺失值标记(空字符串和 na_values 的值)。在没有 NAs 的数据中,传递 na_filter=False 可以提高读取大文件的性能。

verbose : boolean, 默认 False布尔值, 默认

指示放置在非数字列中的NA值的数量。

skip_blank_lines : boolean, 默认 True布尔值, 默认

如果 True ,跳过空白行,而不是解释为 NaN 值。

日期时间处理#

parse_dates : 布尔值或整数列表或名称或列表的列表或字典,默认 False布尔值或整数列表或名称或列表的列表或字典,默认
  • 如果 True -> 尝试解析索引。

  • 如果 [1, 2, 3] -> 尝试将第1、2、3列分别解析为单独的日期列。

备注

对于 iso8601 格式的日期,存在一个快速路径。

date_format : str 或 列 -> 格式的字典, 默认 Nonestr 或 列 -> 格式 的字典,默认

如果在使用 parse_dates 的同时使用,将根据此格式解析日期。对于更复杂的情况,请以 object 形式读入,然后根据需要应用 to_datetime()

Added in version 2.0.0.

dayfirst : 布尔值, 默认 False布尔值, 默认

DD/MM 格式日期,国际和欧洲格式。

cache_dates布尔值,默认为 True

如果为真,使用一个唯一的、转换后的日期缓存来应用日期时间转换。在解析重复的日期字符串时,尤其是带有时区偏移的字符串时,可能会产生显著的速度提升。

迭代#

iterator : boolean, 默认 False布尔值, 默认

返回用于迭代或通过 get_chunk() 获取块的 TextFileReader 对象。

chunksize : int, 默认 Noneint, 默认

返回用于迭代的 TextFileReader 对象。请参见下面的 迭代和分块

引用、压缩和文件格式#

压缩 : {'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 文件必须仅包含一个要读取的数据文件。设置为 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

thousands : str, 默认 Nonestr, 默认

千位分隔符。

decimal : str, 默认 '.'str, 默认

识别为小数点的字符。例如,使用 ',' 表示欧洲数据。

float_precision字符串,默认无

指定 C 引擎应使用哪个转换器来处理浮点值。选项包括 None 表示普通转换器,high 表示高精度转换器,round_trip 表示往返转换器。

lineterminator : str (长度 1), 默认 Nonestr (长度 1), 默认

字符将文件分割成行。仅在C解析器中有效。

quotecharstr (长度为1)

用于表示引用项开始和结束的字符。引用项可以包含分隔符,并且会被忽略。

引用 : int 或 csv.QUOTE_* 实例, 默认 0int 或

控制每个 csv.QUOTE_* 常量的字段引用行为。使用 QUOTE_MINIMAL (0)、QUOTE_ALL (1)、QUOTE_NONNUMERIC (2) 或 QUOTE_NONE (3) 中的一个。

doublequote : boolean, 默认 True布尔值, 默认

quotechar 被指定且 quoting 不是 QUOTE_NONE 时,指示是否将字段内连续的两个 quotechar 元素解释为单个 quotechar 元素。

escapechar : str (长度为1), 默认 Nonestr (长度 1), 默认

用于在引用时转义分隔符的单字符字符串是 QUOTE_NONE

comment : str, 默认 Nonestr, 默认

指示该行剩余部分不应被解析。如果在行首找到,则该行将完全被忽略。此参数必须是一个单一字符。像空行(只要 skip_blank_lines=True)一样,完全注释的行会被参数 header 忽略,但不会被 skiprows 忽略。例如,如果 comment='#',解析 ‘#emptyna,b,cn1,2,3’ 并使用 header=0 将导致 ‘a,b,c’ 被视为标题。

encoding : str, 默认 Nonestr, 默认

读取/写入时使用的编码(例如 'utf-8')。Python 标准编码列表

dialect : str 或 csv.Dialect 实例, 默认 Nonestr 或

如果提供,此参数将覆盖以下参数的值(默认或非默认):delimiterdoublequoteescapecharskipinitialspacequotecharquoting。如果需要覆盖值,将发出 ParserWarning。有关更多详细信息,请参阅 csv.Dialect 文档。

错误处理#

on_bad_lines(‘error’, ‘warn’, ‘skip’), default ‘error’

指定在遇到坏行(字段过多的行)时该怎么做。允许的值有:

  • ‘error’, 当遇到错误行时引发 ParserError。

  • ‘warn’, 当遇到错误行时打印警告并跳过该行。

  • ‘skip’, 遇到错误行时不引发或警告,直接跳过。

Added in version 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 提供了不止一种方法来确保你的列只包含一个 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() 函数在读取数据后强制转换数据类型,

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

最终,如何处理包含混合数据类型的列取决于您的具体需求。在上面的例子中,如果您想将数据异常值替换为 NaN ,那么 to_numeric() 可能是您的最佳选择。然而,如果您希望无论数据类型如何都强制转换所有数据,那么使用 read_csv()converters 参数绝对值得一试。

备注

在某些情况下,读取包含混合dtypes列的异常数据会导致数据集不一致。如果你依赖pandas来推断列的dtypes,解析引擎会去推断数据不同块的dtypes,而不是整个数据集一次。因此,你可能会得到包含混合dtypes的列。例如,

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

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

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

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

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

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

将导致 mixed_df 包含列的某些部分为 int dtype,其他部分为 str,这是由于读入数据中的混合 dtypes 导致的。需要注意的是,整个列将被标记为 dtypeobject,这是用于混合 dtypes 列的。

设置 dtype_backend="numpy_nullable" 将导致每一列都使用可为空的 dtypes。

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[s]
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' 将生成一个无序的 Categorical ,其 categories 是数据中观察到的唯一值。为了对类别和顺序进行更多控制,可以提前创建一个 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' ,结果类别将始终解析为字符串(对象类型)。如果类别是数字,可以使用 to_numeric() 函数或其他适当的转换器(如 to_datetime() )进行转换。

dtype 是具有同质 categories (全部为数值、全部为日期时间等)的 CategoricalDtype 时,转换会自动完成。

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

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

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

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

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

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

命名和使用列#

处理列名#

一个文件可能有也可能没有标题行。pandas 假设第一行应该用作列名:

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

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

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

通过在 header 的同时指定 names 参数,你可以指示使用其他名称,并指示是否丢弃标题行(如果有):

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

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

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

如果标题不在第一行,请将行号传递给 header。这将跳过前面的行:

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

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

备注

默认行为是推断列名:如果没有传递列名,则行为与 header=0 相同,并且从文件的第一行非空白行推断列名,如果明确传递了列名,则行为与 header=None 相同。

重复名称解析#

如果文件或标题包含重复的名称,pandas 默认会区分它们,以防止数据被覆盖:

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

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

不再有重复数据,因为重复列 ‘X’, …, ‘X’ 变成了 ‘X’, ‘X.1’, …, ‘X.N’。

过滤列 (usecols)#

usecols 参数允许你选择文件中任意列的子集,可以使用列名、位置编号或可调用对象:

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

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

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

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

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

usecols 参数也可以用来指定在最终结果中不使用哪些列:

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

在这种情况下,可调用对象指定我们从输出中排除“a”和“c”列。

注释和空行#

忽略行注释和空行#

如果指定了 comment 参数,则完全注释的行将被忽略。默认情况下,完全空白的行也将被忽略。

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

In [68]: print(data)

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

4,5,6

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

如果 skip_blank_lines=False,那么 read_csv 将不会忽略空白行:

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

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

警告

忽略行的存在可能会在涉及行号时产生歧义;参数 header 使用行号(忽略注释/空行),而 skiprows 使用行号(包括注释/空行):

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

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

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

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

如果同时指定了 headerskiprowsheader 将相对于 skiprows 的结束位置。例如:

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 标准编码的完整列表

索引列和尾随分隔符#

如果一个文件的数据列数比列名数量多一列,则第一列将用作 DataFrame 的行名:

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

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

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

通常,您可以使用 index_col 选项来实现此行为。

在某些例外情况下,当一个文件在每行数据末尾都使用了分隔符准备时,会混淆解析器。要显式禁用索引列推断并丢弃最后一列,请传递 index_col=False:

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

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

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

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

如果使用 usecols 选项解析数据的子集,index_col 的指定是基于该子集,而不是原始数据。

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

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

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

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

日期处理#

指定日期列#

为了更好地处理日期时间数据,read_csv() 使用关键字参数 parse_datesdate_format 允许用户指定各种列和日期/时间格式,将输入的文本数据转换为 datetime 对象。

最简单的情况是只需传入 parse_dates=True

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

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

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

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

通常情况下,我们可能希望将日期和时间数据分开存储,或者将各种日期字段分开存储。可以使用 parse_dates 关键字来指定要解析日期和/或时间的列。

备注

如果列或索引包含一个无法解析的日期,整个列或索引将以对象数据类型原样返回。对于非标准的日期时间解析,请在 pd.read_csv 之后使用 to_datetime()

备注

read_csv 有一个快速路径用于解析 iso8601 格式的日期时间字符串,例如 “2000-01-01T00:01:02+00:00” 及其类似变体。如果你能安排你的数据以这种格式存储日期时间,加载时间将显著加快,已观察到约 20 倍的速度提升。

日期解析函数#

最后,解析器允许您指定一个自定义的 date_format。在性能方面,您应该按以下顺序尝试这些日期解析方法:

  1. 如果你知道格式,使用 date_format,例如: date_format="%d/%m/%Y"date_format={column_name: "%d/%m/%Y"}

  2. 如果你对不同的列有不同的格式,或者想传递任何额外的选项(例如 utc)给 to_datetime,那么你应该将数据读取为 object 数据类型,然后使用 to_datetime

解析包含混合时区的CSV#

pandas 不能原生地表示带有混合时区的列或索引。如果你的 CSV 文件包含带有混合时区的列,默认结果将是一个带有字符串的对象类型列,即使使用 parse_dates。要将混合时区值解析为日期时间列,请以 object 类型读取,然后使用 utc=True 调用 to_datetime()

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

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

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

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

推断日期时间格式#

以下是一些可以被猜测的日期时间字符串示例(所有示例都表示2011年12月30日00:00:00):

  • 20111230

  • 2011/12/30

  • “20111230 00:00:00”

  • “12/30/2011 00:00:00”

  • “30/Dec/2011 00:00:00”

  • “2011年12月30日 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 [112]: df = pd.read_csv(
   .....:     "foo.csv",
   .....:     index_col=0,
   .....:     parse_dates=True,
   .....: )
   .....: 

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

如果你在同一个列中混合了日期时间格式,你可以传递 format='mixed'

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

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

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

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

或者,如果你的日期时间格式都是 ISO8601(可能格式不完全相同):

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

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

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

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

国际日期格式#

虽然美国的日期格式倾向于 MM/DD/YYYY,但许多国际格式使用 DD/MM/YYYY。为了方便,提供了一个 dayfirst 关键字:

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

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

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

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

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

将 CSV 写入二进制文件对象#

Added in version 1.2.0.

df.to_csv(..., mode="wb") 允许将CSV写入以二进制模式打开的文件对象。在大多数情况下,不需要指定 mode,因为 pandas 会自动检测文件对象是以文本还是二进制模式打开的。

In [127]: import io

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

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

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

指定浮点数转换的方法#

参数 float_precision 可以在使用 C 引擎解析时指定,以使用特定的浮点转换器。选项包括普通转换器、高精度转换器和往返转换器(保证在写入文件后往返值)。例如:

In [131]: val = "0.3066101993807095471566981359501369297504425048828125"

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

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

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

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

千位分隔符#

对于用千位分隔符写的大数,可以将 thousands 关键字设置为长度为1的字符串,以便正确解析整数:

默认情况下,带有千位分隔符的数字将被解析为字符串:

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

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

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

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

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

thousands 关键字允许正确解析整数:

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

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

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

NA 值#

要控制哪些值被解析为缺失值(这些值用 NaN 表示),请在 na_values 中指定一个字符串。如果你指定一个字符串列表,那么其中的所有值都被视为缺失值。如果你指定一个数字(一个 float,如 5.0 或一个 integer5),相应的等价值也将表示缺失值(在这种情况下,实际上 [5.0, 5] 被识别为 NaN)。

要完全覆盖被识别为缺失的默认值,请指定 keep_default_na=False

默认的 NaN 识别值是 ['-1.#IND', '1.#QNAN', '1.#IND', '-1.#QNAN', '#N/A N/A', '#N/A', 'N/A', 'n/a', 'NA', '<NA>', '#NA', 'NULL', 'null', 'NaN', '-NaN', 'nan', '-nan', 'None', '']

让我们考虑一些例子:

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

在上面的例子中,55.0 将被识别为 NaN,此外还有默认值。一个字符串将首先被解释为数值 5,然后被解释为 NaN

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

上面,只有空字段会被识别为 NaN

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

上面,字符串 NA0 都是 NaN

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

除了字符串 "Nope" 之外,默认值被识别为 NaN

无限#

inf 这样的值将被解析为 np.inf``(正无穷大),而 ``-inf 将被解析为 -np.inf``(负无穷大)。这些将忽略值的大小写,意味着 ``Inf 也将被解析为 np.inf

布尔值#

常见的值 True, False, TRUE, 和 FALSE 都被识别为布尔值。偶尔你可能想将其他值识别为布尔值。为此,请使用 true_valuesfalse_values 选项,如下所示:

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

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

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

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

处理“坏”行#

某些文件可能包含字段过少或过多的格式错误行。字段过少的行将在后续字段中填充NA值。字段过多的行默认会引发错误:

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

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

File /home/pandas/pandas/io/parsers/readers.py:849, 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, skip_blank_lines, parse_dates, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, low_memory, memory_map, float_precision, storage_options, dtype_backend)
    837 kwds_defaults = _refine_defaults_read(
    838     dialect,
    839     delimiter,
   (...)
    845     dtype_backend=dtype_backend,
    846 )
    847 kwds.update(kwds_defaults)
--> 849 return _read(filepath_or_buffer, kwds)

File /home/pandas/pandas/io/parsers/readers.py:716, in _read(filepath_or_buffer, kwds)
    713     return parser
    715 with parser:
--> 716     return parser.read(nrows)

File /home/pandas/pandas/io/parsers/readers.py:1496, in TextFileReader.read(self, nrows)
   1489 nrows = validate_integer("nrows", nrows)
   1490 try:
   1491     # error: "ParserBase" has no attribute "read"
   1492     (
   1493         index,
   1494         columns,
   1495         col_dict,
-> 1496     ) = self._engine.read(  # type: ignore[attr-defined]
   1497         nrows
   1498     )
   1499 except Exception:
   1500     self.close()

File /home/pandas/pandas/io/parsers/c_parser_wrapper.py:237, in CParserWrapper.read(self, nrows)
    235 try:
    236     if self.low_memory:
--> 237         chunks = self._reader.read_low_memory(nrows)
    238         # destructive to chunks
    239         data = _concatenate_chunks(chunks, self.names)  # type: ignore[has-type]

File parsers.pyx:830, in pandas._libs.parsers.TextReader.read_low_memory()

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

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

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

File parsers.pyx:2037, in pandas._libs.parsers.raise_parser_error()

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

你可以选择跳过坏行:

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

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

Added in version 1.4.0.

或者传递一个可调用函数来处理坏行,如果 engine="python"。坏行将是一个由 sep 分割的字符串列表:

In [152]: external_list = []

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

In [154]: external_list
Out[154]: []

备注

可调用函数将仅处理字段过多的行。由其他错误引起的坏行将被静默跳过。

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

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

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

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

在这种情况下,该行未被处理,因为这里的“坏行”是由一个转义字符引起的。

你也可以使用 usecols 参数来消除某些行中出现的无关列数据:

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

File /home/pandas/pandas/io/parsers/readers.py:849, 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, skip_blank_lines, parse_dates, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, low_memory, memory_map, float_precision, storage_options, dtype_backend)
    837 kwds_defaults = _refine_defaults_read(
    838     dialect,
    839     delimiter,
   (...)
    845     dtype_backend=dtype_backend,
    846 )
    847 kwds.update(kwds_defaults)
--> 849 return _read(filepath_or_buffer, kwds)

File /home/pandas/pandas/io/parsers/readers.py:710, in _read(filepath_or_buffer, kwds)
    707 _validate_names(kwds.get("names", None))
    709 # Create the parser.
--> 710 parser = TextFileReader(filepath_or_buffer, **kwds)
    712 if chunksize or iterator:
    713     return parser

File /home/pandas/pandas/io/parsers/readers.py:1192, in TextFileReader.__init__(self, f, engine, **kwds)
   1189     self.options["has_index_names"] = kwds["has_index_names"]
   1191 self.handles: IOHandles | None = None
-> 1192 self._engine = self._make_engine(f, self.engine)

File /home/pandas/pandas/io/parsers/readers.py:1471, in TextFileReader._make_engine(self, f, engine)
   1468     raise ValueError(msg)
   1470 try:
-> 1471     return mapping[engine](f, **self.options)
   1472 except Exception:
   1473     if self.handles is not None:

File /home/pandas/pandas/io/parsers/c_parser_wrapper.py:160, in CParserWrapper.__init__(self, src, **kwds)
    157     # error: Cannot determine type of 'names'
    158     if len(self.names) < len(usecols):  # type: ignore[has-type]
    159         # error: Cannot determine type of 'names'
--> 160         self._validate_usecols_names(
    161             usecols,
    162             self.names,  # type: ignore[has-type]
    163         )
    165 # error: Cannot determine type of 'names'
    166 validate_parse_dates_presence(self.parse_dates, self.names)  # type: ignore[has-type]

File /home/pandas/pandas/io/parsers/base_parser.py:657, in ParserBase._validate_usecols_names(self, usecols, names)
    655 missing = [c for c in usecols if c not in names]
    656 if len(missing) > 0:
--> 657     raise ValueError(
    658         f"Usecols do not match columns, columns expected but not found: "
    659         f"{missing}"
    660     )
    662 return usecols

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

如果你想保留所有数据,包括那些字段过多的行,你可以指定足够数量的 names。这确保了字段不足的行用 NaN 填充。

In [160]: pd.read_csv(StringIO(data), names=['a', 'b', 'c', 'd'])
Out[160]: 
        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#

dialect 关键字在指定文件格式时提供了更大的灵活性。默认情况下,它使用 Excel 方言,但你可以指定方言名称或一个 csv.Dialect 实例。

假设你有包含未封闭引号的数据:

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

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

默认情况下,read_csv 使用 Excel 方言并将双引号视为引号字符,这会导致它在找到换行符之前找到关闭的双引号时失败。

我们可以使用 dialect 来解决这个问题:

In [163]: import csv

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

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

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

所有方言选项可以通过关键字参数分别指定:

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

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

另一个常见的方言选项是 skipinitialspace,用于跳过分隔符后的任何空白字符:

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

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

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

解析器会尽一切努力“做正确的事”,而不是脆弱的。类型推断是一个相当重要的事情。如果一列可以在不改变内容的情况下强制转换为整数类型,解析器会这样做。任何非数字列将作为对象类型通过,就像其他pandas对象一样。

引用和转义字符#

嵌入字段中的引号(和其他转义字符)可以用多种方式处理。一种方法是使用反斜杠;为了正确解析这些数据,你应该传递 escapechar 选项:

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

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

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

固定宽度列的文件#

虽然 read_csv() 读取分隔数据,但 read_fwf() 函数处理的是具有已知和固定列宽的数据文件。read_fwf 的函数参数与 read_csv 大致相同,但多了两个参数,并且 delimiter 参数的使用方式不同:

  • colspecs: 一个给出每行固定宽度字段范围的对列表(元组),作为半开区间(即, [从, 到[ )。字符串值 ‘infer’ 可以用来指示解析器尝试从数据的前 100 行检测列规范。如果未指定,默认行为是推断。

  • widths: 一个字段宽度列表,如果区间是连续的,可以用它来代替 ‘colspecs’。

  • delimiter: 在固定宽度文件中被视为填充字符的字符。如果填充字符不是空格(例如,’~’),可以使用它来指定字段填充字符。

考虑一个典型的固定宽度数据文件:

In [175]: 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 [176]: with open("bar.csv", "w") as f:
   .....:     f.write(data1)
   .....: 

为了将此文件解析为 DataFrame ,我们只需向 read_fwf 函数提供列规范以及文件名:

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

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

In [179]: df
Out[179]: 
                 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 [180]: widths = [6, 14, 13, 10]

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

In [182]: df
Out[182]: 
        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 [183]: df = pd.read_fwf("bar.csv", header=None, index_col=0)

In [184]: df
Out[184]: 
                 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 [185]: pd.read_fwf("bar.csv", header=None, index_col=0).dtypes
Out[185]: 
1    float64
2    float64
3    float64
dtype: object

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

索引#

带有“隐式”索引列的文件#

考虑一个文件,其标题中的条目数比数据列的数量少一个:

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

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

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

在这个特殊情况下,read_csv 假设第一列将用作 DataFrame 的索引:

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

请注意,日期没有自动解析。在这种情况下,你需要像之前那样做:

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

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

使用 MultiIndex 读取索引#

假设你有两列索引的数据:

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

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

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

index_col 参数用于 read_csv 可以接受一个列号列表,将多个列转换为返回对象索引的 MultiIndex

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

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

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

使用 MultiIndex 读取列#

通过为 header 参数指定行位置列表,您可以为列读入一个 MultiIndex 。指定非连续行将跳过中间行。

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

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

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

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

In [203]: 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 [204]: pd.read_csv("mi.csv", header=[0, 1, 2, 3], index_col=[0, 1])
Out[204]: 
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 [205]: 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 [206]: 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 [207]: with open("mi2.csv", "w") as fh:
   .....:     fh.write(data)
   .....: 

In [208]: pd.read_csv("mi2.csv", header=[0, 1], index_col=0)
Out[208]: 
     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 [209]: df = pd.DataFrame(np.random.randn(10, 4))

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

In [211]: pd.read_csv("tmp2.csv", sep=None, engine="python")
Out[211]: 
          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() 来合并多个文件。请参见 cookbook 中的示例。

逐块遍历文件#

假设你希望以惰性方式遍历一个(可能非常大的)文件,而不是将整个文件读入内存,例如以下方式:

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

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

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

In [215]: table
Out[215]: 
          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_csv 指定一个 chunksize ,返回值将是一个类型为 TextFileReader 的可迭代对象:

In [216]: 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 0xffff5928dc30>
          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 [217]: 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 包)。一般来说,pyarrow 引擎在大数据量工作负载上最快,并且在大多数其他工作负载上与 C 引擎速度相当。python 引擎在大多数工作负载上往往比 pyarrow 和 C 引擎慢。然而,pyarrow 引擎比 C 引擎的健壮性差很多,缺少一些与 Python 引擎相比的功能。

如果可能,pandas 使用 C 解析器(指定为 engine='c'),但如果指定了 C 不支持的选项,它可能会回退到 Python。

目前,C 和 pyarrow 引擎不支持的选项包括:

  • sep 除了单个字符外(例如正则表达式分隔符)

  • skipfooter

指定上述任何选项将生成 ParserWarning ,除非使用 engine='python' 显式选择 python 引擎。

pyarrow 引擎不支持的选项,这些选项不在上述列表中,包括:

  • float_precision

  • chunksize

  • comment

  • nrows

  • thousands

  • memory_map

  • dialect

  • on_bad_lines

  • quoting

  • lineterminator

  • converters

  • decimal

  • iterator

  • dayfirst

  • verbose

  • skipinitialspace

  • low_memory

使用 engine='pyarrow' 指定这些选项将引发 ValueError

读取/写入远程文件#

你可以传递一个 URL 到许多 pandas 的 IO 函数中来读取或写入远程文件 - 以下示例展示了读取一个 CSV 文件:

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

Added in version 1.3.0.

可以通过将头键值映射的字典传递给 storage_options 关键字参数,来与 HTTP(s) 请求一起发送自定义头,如下所示:

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

所有不是本地文件或HTTP(s)的URL都由 fsspec 处理,如果已安装,并且它的各种文件系统实现(包括Amazon S3、Google Cloud、SSH、FTP、webHDFS等)。其中一些实现需要安装额外的包,例如S3 URL需要 s3fs 库:

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

在处理远程存储系统时,您可能需要通过环境变量或特殊位置的配置文件进行额外配置。例如,要访问您的S3存储桶中的数据,您需要在 S3Fs文档 中列出的几种方式之一定义凭证。对于多个存储后端也是如此,您应该按照 fsimpl1 中的链接查看内置于 fsspec 的实现,以及 fsimpl2 中未包含在主 fsspec 分发中的实现。

你也可以直接将参数传递给后端驱动。由于 fsspec 不使用 AWS_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 凭证,你仍然可以通过指定一个匿名连接来访问公共数据,例如

Added in version 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格式#

SeriesDataFrame 对象有一个实例方法 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: 用于在适当时候转义 sepquotechar 的字符(默认 None)

  • chunksize: 每次写入的行数

  • date_format: 用于 datetime 对象的格式字符串

编写一个格式化的字符串#

DataFrame 对象有一个实例方法 to_string ,它允许控制对象的字符串表示。所有参数都是可选的:

  • buf 默认 None,例如一个 StringIO 对象

  • columns 默认 None,要写入哪些列

  • col_space 默认 None,每列的最小宽度。

  • na_rep 默认 NaN,NA 值的表示

  • formatters 默认为 None,这是一个按列的字典,每个函数接受一个参数并返回一个格式化的字符串

  • float_format 默认 None,这是一个函数,接受一个(浮点数)参数并返回一个格式化的字符串;应用于 DataFrame 中的浮点数。

  • sparsify 默认值为 True,对于具有分层索引的 DataFrame,设置为 False 以在每行打印每个 MultiIndex 键。

  • index_names 默认 True,将打印索引的名称

  • index 默认为 True,将打印索引(即,行标签)

  • header 默认为 True,将打印列标签

  • justify 默认 left,将打印列标题左对齐或右对齐

Series 对象也有一个 to_string 方法,但只有 bufna_repfloat_format 参数。还有一个 length 参数,如果设置为 True,将额外输出 Series 的长度。

JSON#

读取和写入 JSON 格式文件和字符串。

编写 JSON#

一个 SeriesDataFrame 可以被转换为一个有效的 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}; … ]

    index

    类似字典的 {索引 -> {列 -> 值}}

    columns

    类似字典的结构 {列 -> {索引 -> 值}}

    values

    只是值数组

    table

    遵循 JSON Table Schema

  • date_format : 字符串, 日期转换的类型, ‘epoch’ 表示时间戳, ‘iso’ 表示 ISO8601。

  • double_precision : 编码浮点值时使用的十进制位数,默认 10。

  • force_ascii : 强制编码字符串为 ASCII,默认为 True。

  • date_unit : 编码到的时间单位,控制时间戳和ISO8601精度。可以是 ‘s’, ‘ms’, ‘us’ 或 ‘ns’ 之一,分别表示秒、毫秒、微秒和纳秒。默认 ‘ms’。

  • default_handler : 如果对象无法转换为适合JSON的格式,则调用的处理程序。接受一个参数,即要转换的对象,并返回一个可序列化的对象。

  • lines : 如果 records 方向,那么将以 json 格式每行写入一个记录。

  • mode : 字符串, 写入路径时的写入模式。’w’ 表示写入,’a’ 表示追加。默认为 ‘w’

注意 NaNNaTNone 将被转换为 null,而 datetime 对象将根据 date_formatdate_unit 参数进行转换。

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

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

In [220]: json
Out[220]: '{"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}}'

方向选项#

生成的 JSON 文件/字符串的格式有多种不同的选项。考虑以下 DataFrameSeries

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

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

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

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

**面向列的**(DataFrame 的默认设置)将数据序列化为嵌套的 JSON 对象,其中列标签作为主索引:

In [225]: dfjo.to_json(orient="columns")
Out[225]: '{"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 [226]: dfjo.to_json(orient="index")
Out[226]: '{"x":{"A":1,"B":4,"C":7},"y":{"A":2,"B":5,"C":8},"z":{"A":3,"B":6,"C":9}}'

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

面向记录 将数据序列化为列 -> 值记录的 JSON 数组,不包括索引标签。这对于将 DataFrame 数据传递给绘图库非常有用,例如 JavaScript 库 d3.js

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

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

面向值 是一个基本的选项,它序列化为嵌套的JSON数组,仅包含值,不包括列和索引标签:

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

# Not available for Series

面向分割 序列化为一个包含值、索引和列的单独条目的 JSON 对象。名称也包含在 Series 中:

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

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

面向表格 序列化为 JSON Table Schema ,允许保留包括但不限于 dtypes 和索引名称的元数据。

备注

任何编码为 JSON 对象的 orient 选项在往返序列化过程中都不会保留索引和列标签的顺序。如果您希望保留标签顺序,请使用 split 选项,因为它使用有序容器。

日期处理#

以ISO日期格式书写:

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

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

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

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

In [237]: json
Out[237]: '{"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 [238]: json = dfd.to_json(date_format="iso", date_unit="us")

In [239]: json
Out[239]: '{"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 [240]: dfj2 = dfj.copy()

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

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

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

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

In [245]: dfj2.to_json("test.json", date_format="iso")

In [246]: with open("test.json") as fh:
   .....:     print(fh.read())
   .....: 
{"A":{"2013-01-01T00:00:00.000":-0.1213062281,"2013-01-02T00:00:00.000":0.6957746499,"2013-01-03T00:00:00.000":0.9597255933,"2013-01-04T00:00:00.000":-0.6199759194,"2013-01-05T00:00:00.000":-0.7323393705},"B":{"2013-01-01T00:00:00.000":-0.0978826728,"2013-01-02T00:00:00.000":0.3417343559,"2013-01-03T00:00:00.000":-1.1103361029,"2013-01-04T00:00:00.000":0.1497483186,"2013-01-05T00:00:00.000":0.6877383895},"date":{"2013-01-01T00:00:00.000":"2013-01-01T00:00:00.000","2013-01-02T00:00:00.000":"2013-01-01T00:00:00.000","2013-01-03T00:00:00.000":"2013-01-01T00:00:00.000","2013-01-04T00:00:00.000":"2013-01-01T00:00:00.000","2013-01-05T00:00:00.000":"2013-01-01T00:00:00.000"},"ints":{"2013-01-01T00:00:00.000":0,"2013-01-02T00:00:00.000":1,"2013-01-03T00:00:00.000":2,"2013-01-04T00:00:00.000":3,"2013-01-05T00:00:00.000":4},"bools":{"2013-01-01T00:00:00.000":true,"2013-01-02T00:00:00.000":true,"2013-01-03T00:00:00.000":true,"2013-01-04T00:00:00.000":true,"2013-01-05T00:00:00.000":true}}

回退行为#

如果 JSON 序列化器无法直接处理容器内容,它将按以下方式回退:

  • 如果数据类型不受支持(例如 np.complex_),那么如果提供了 default_handler,将对每个值调用它,否则将引发异常。

  • 如果一个对象不受支持,它将尝试以下操作:

    • 检查对象是否定义了 toDict 方法并调用它。一个 toDict 方法应该返回一个 dict ,然后将被 JSON 序列化。

    • 如果提供了 default_handler,则调用它。

    • 通过遍历对象的内容将其转换为 dict 。然而,这通常会以 OverflowError 失败或给出意外的结果。

一般来说,对于不支持的对象或数据类型,最好的方法是提供一个 default_handler。例如:

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

可以通过指定一个简单的 default_handler 来处理:

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

读取 JSON#

读取一个 JSON 字符串到 pandas 对象可以接受多个参数。如果没有提供 typ 或者 typNone,解析器将尝试解析一个 DataFrame。要显式强制解析 Series,请传递 typ=series

  • filepath_or_buffer : 一个 有效的 JSON 字符串或文件句柄 / StringIO。该字符串可以是一个 URL。有效的 URL 方案包括 http、ftp、S3 和 file。对于文件 URL,需要一个主机。例如,一个本地文件可以是 file ://localhost/path/to/table.json

  • typ : 要恢复的对象类型(序列或框架),默认为 ‘frame’

  • orient :

    系列 :
    • 默认是 index

    • 允许的值是 {split, records, index}

    DataFrame
    • 默认是 columns

    • 允许的值是 {split, records, index, columns, values, table}

    JSON 字符串的格式

    split

    类似字典 {index -> [index]; columns -> [columns]; data -> [values]}

    records

    列表形式 [{column -> value} …]

    index

    类似字典的 {索引 -> {列 -> 值}}

    columns

    类似字典的结构 {列 -> {索引 -> 值}}

    values

    只是值数组

    table

    遵循 JSON Table Schema

  • dtype : 如果为 True,则推断数据类型;如果为列到数据类型的字典,则使用这些数据类型;如果为 False,则完全不推断数据类型;默认为 True,仅适用于数据。

  • convert_axes : 布尔值, 尝试将轴转换为适当的类型, 默认是 True

  • convert_dates : 要解析日期的列的列表;如果 True,则尝试解析类似日期的列,默认是 True

  • keep_default_dates : 布尔值, 默认 True。如果解析日期,则解析默认的类似日期的列。

  • precise_float : 布尔值, 默认为 False。设置为启用在解码字符串到双精度值时使用更高精度的(strtod)函数。默认(False)是使用快速但精度较低的内置功能。

  • date_unit : 字符串, 用于检测是否转换日期的时戳单位。默认为 None。默认情况下会检测时戳精度,如果不希望检测,则传递 ‘s’, ‘ms’, ‘us’ 或 ‘ns’ 之一,分别强制时戳精度为秒、毫秒、微秒或纳秒。

  • lines : 将文件读取为每行一个json对象。

  • encoding : 用于解码 py3 字节的编码。

  • chunksize : 当与 lines=True 结合使用时,返回一个 pandas.api.typing.JsonReader,该读取器每次迭代读取 chunksize 行。

  • engine: 可以是 "ujson",即内置的 JSON 解析器,或者是 "pyarrow",它会分派到 pyarrow 的 pyarrow.json.read_json"pyarrow" 仅在 lines=True 时可用

如果JSON无法解析,解析器将引发 ValueError/TypeError/AssertionError 之一。

如果在编码为 JSON 时使用了非默认的 orient,请确保在这里传递相同的选项,以便解码产生合理的结果,请参阅 Orient 选项 以获取概述。

数据转换#

convert_axes=Truedtype=Trueconvert_dates=True 的默认设置会尝试将轴和所有数据解析为适当的类型,包括日期。如果需要覆盖特定的数据类型,请将字典传递给 dtype。只有在需要保留类似字符串的数字(例如 ‘1’, ‘2’)在轴中时,才应将 convert_axes 设置为 False

备注

如果 convert_dates=True 并且数据和/或列标签看起来像日期,大整数值可能会被转换为日期。确切的阈值取决于指定的 date_unit。’date-like’ 意味着列标签满足以下条件之一:

  • 它以 '_at' 结尾

  • 它以 '_time' 结尾

  • 它以 'timestamp' 开始

  • 它是 'modified'

  • 它是 'date'

警告

在读取JSON数据时,自动转换为数据类型有一些特点:

  • 索引可以在序列化后的不同顺序中重建,也就是说,返回的顺序不保证与序列化前相同。

  • 一个原本是 float 数据的列,如果可以安全转换,将会被转换为 integer,例如一个 1. 的列。

  • 布尔列在重建时将被转换为 integer

因此,有时你可能希望通过 dtype 关键字参数指定特定的数据类型。

从 JSON 字符串读取:

In [248]: from io import StringIO

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

从文件读取:

In [250]: pd.read_json("test.json")
Out[250]: 
                   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

不要转换任何数据(但仍然转换轴和日期):

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

指定转换的数据类型:

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

保留字符串索引:

In [253]: from io import StringIO

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

In [255]: si
Out[255]: 
     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 [256]: si.index
Out[256]: Index(['0', '1', '2', '3'], dtype='object')

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

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

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

In [260]: sij
Out[260]: 
   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 [261]: sij.index
Out[261]: Index(['0', '1', '2', '3'], dtype='object')

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

以纳秒为单位写入的日期需要以纳秒为单位读回:

In [263]: from io import StringIO

In [264]: json = dfj2.to_json(date_format="iso", date_unit="ns")

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

In [266]: dfju
Out[266]: 
                   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

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

In [268]: dfju
Out[268]: 
                   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 [269]: dfju = pd.read_json(StringIO(json), date_unit="ns")

In [270]: dfju
Out[270]: 
                   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

通过设置 dtype_backend 参数,您可以控制生成的 DataFrame 使用的默认数据类型。

In [271]: 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 [272]: df = pd.read_json(StringIO(data), dtype_backend="pyarrow")

In [273]: df
Out[273]: 
   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 [274]: df.dtypes
Out[274]: 
a     int64[pyarrow]
b    double[pyarrow]
c      bool[pyarrow]
d    string[pyarrow]
e     int64[pyarrow]
f    double[pyarrow]
g      bool[pyarrow]
h    string[pyarrow]
i    string[pyarrow]
j      null[pyarrow]
dtype: object

标准化#

pandas 提供了一个实用函数,用于将字典或字典列表 规范化 为扁平表的半结构化数据。

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

In [276]: pd.json_normalize(data)
Out[276]: 
    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 [277]: 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 [278]: pd.json_normalize(data, "county", ["state", "shortname", ["info", "governor"]])
Out[278]: 
         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,以下代码片段将规范化直到提供的字典的第1层嵌套级别。

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

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

行分隔的 json#

pandas 能够读写行分隔的 json 文件,这些文件在数据处理管道中很常见,使用 Hadoop 或 Spark。

对于行分隔的json文件,pandas还可以返回一个迭代器,每次读取 chunksize 行。这对于大文件或从流中读取非常有用。

In [281]: from io import StringIO

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

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

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

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

# reader is an iterator that returns ``chunksize`` lines each iteration
In [286]: 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

使用 pyarrow 读取器通过指定 engine="pyarrow" 也可以读取行限制的 json。

In [287]: from io import BytesIO

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

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

Added in version 2.0.0.

表模式#

Table Schema 是一种用于将表格数据集描述为 JSON 对象的规范。JSON 包含字段名称、类型和其他属性的信息。你可以使用 orient table 来构建一个包含两个字段 schemadata 的 JSON 字符串。

In [290]: 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 [291]: df
Out[291]: 
     A  B          C
idx                 
0    1  a 2016-01-01
1    2  b 2016-01-02
2    3  c 2016-01-03

In [292]: df.to_json(orient="table", date_format="iso")
Out[292]: '{"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 键,该键本身包含列名到类型对的列表,包括 IndexMultiIndex``(类型列表见下文)。如果(多)索引是唯一的,``schema 字段还包含一个 primaryKey 字段。

第二个字段 data 包含以 records 方向序列化的数据。索引包含在内,任何日期时间都按 Table Schema 规范要求的 ISO 8601 格式化。

支持的类型完整列表在 Table Schema 规范中描述。此表显示了从 pandas 类型的映射:

pandas 类型

表模式类型

int64

整数

float64

number

bool

布尔值

datetime64[ns]

datetime

timedelta64[ns]

duration

categorical

任何

对象

str

关于生成的表模式的一些注意事项:

  • schema 对象包含一个 pandas_version 字段。这包含 pandas 模式方言的版本,并且会在每次修订时递增。

  • 所有日期在序列化时都会转换为 UTC。即使是未指定时区的值,也会被视为具有 0 偏移量的 UTC。

    In [293]: from pandas.io.json import build_table_schema
    
    In [294]: s = pd.Series(pd.date_range("2016", periods=4))
    
    In [295]: build_table_schema(s)
    Out[295]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
      {'name': 'values', 'type': 'datetime'}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'}
    
  • 带有时区的日期时间(在序列化之前),包含一个额外的字段 tz ,带有时区名称(例如 'US/Central')。

    In [296]: s_tz = pd.Series(pd.date_range("2016", periods=12, tz="US/Central"))
    
    In [297]: build_table_schema(s_tz)
    Out[297]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
      {'name': 'values', 'type': 'datetime', 'tz': 'US/Central'}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'}
    
  • 句点在序列化之前被转换为时间戳,因此具有相同的转换为UTC的行为。此外,句点将包含一个额外的字段 freq,其中包含句点的频率,例如 'A-DEC'

    In [298]: s_per = pd.Series(1, index=pd.period_range("2016", freq="Y-DEC", periods=4))
    
    In [299]: build_table_schema(s_per)
    Out[299]: 
    {'fields': [{'name': 'index', 'type': 'datetime', 'freq': 'YE-DEC'},
      {'name': 'values', 'type': 'integer'}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'}
    
  • 分类使用 any 类型和一个列出可能值的 enum 约束。此外,还包括一个 ordered 字段:

    In [300]: s_cat = pd.Series(pd.Categorical(["a", "b", "a"]))
    
    In [301]: build_table_schema(s_cat)
    Out[301]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
      {'name': 'values',
       'type': 'any',
       'constraints': {'enum': ['a', 'b']},
       'ordered': False}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'}
    
  • 一个包含标签数组的 primaryKey 字段,如果索引是唯一的,则包含在内:

    In [302]: s_dupe = pd.Series([1, 2], index=[1, 1])
    
    In [303]: build_table_schema(s_dupe)
    Out[303]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
      {'name': 'values', 'type': 'integer'}],
     'pandas_version': '1.4.0'}
    
  • primaryKey 行为与 MultiIndexes 相同,但在这种情况下,primaryKey 是一个数组:

    In [304]: s_multi = pd.Series(1, index=pd.MultiIndex.from_product([("a", "b"), (0, 1)]))
    
    In [305]: build_table_schema(s_multi)
    Out[305]: 
    {'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。如果没有,则名称是 values

    • 对于 DataFrames ,列名的字符串化版本被使用。

    • 对于 Index``(不是 ``MultiIndex),使用 index.name,如果为 None,则回退到 index

    • 对于 MultiIndex,使用 mi.names。如果任何级别没有名称,则使用 level_<i>

read_json 也接受 orient='table' 作为参数。这允许在可往返的方式中保留元数据,如 dtypes 和索引名称。

In [306]: 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 [307]: df
Out[307]: 
     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 [308]: df.dtypes
Out[308]: 
foo             int64
bar            object
baz    datetime64[ns]
qux          category
dtype: object

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

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

In [311]: new_df
Out[311]: 
     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 [312]: new_df.dtypes
Out[312]: 
foo             int64
bar            object
baz    datetime64[ns]
qux          category
dtype: object

请注意,作为 Index 名称的字面字符串 ‘index’ 不是可往返的,也不是 MultiIndex 中以 'level_' 开头的任何名称。这些在 DataFrame.to_json() 中默认用于表示缺失值,随后的读取无法区分意图。

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

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

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

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

使用 orient='table' 以及用户定义的 ExtensionArray 时,生成的模式将在相应的 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 返回一个 DataFrame 对象的 列表 ,即使HTML内容中只包含一个表格。

读取一个没有选项的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的数据每周一都会变化,因此上述结果数据可能略有不同。

在传递头信息的同时读取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 [317]: 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 [318]: with open("tmp.html", "w") as f:
   .....:     f.write(html_str)
   .....: 

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

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

如果你愿意,你甚至可以传入一个 StringIO 的实例:

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

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

备注

以下示例由于许多访问网络的函数导致文档构建速度变慢,因此不会被 IPython 评估器运行。如果你发现错误或无法运行的示例,请不要犹豫,在 pandas GitHub 问题页面 上报告。

读取一个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 后端在解析失败时会引发错误。如果你只有一个解析器,你可以只提供一个字符串,但如果函数期望一个字符串序列,例如,传递一个包含一个字符串的列表被认为是良好的实践。你可以使用:

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 [323]: html_table = """
   .....: <table>
   .....:   <tr>
   .....:     <th>GitHub</th>
   .....:   </tr>
   .....:   <tr>
   .....:     <td><a href="https://github.com/pandas-dev/pandas">pandas</a></td>
   .....:   </tr>
   .....: </table>
   .....: """
   .....: 

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

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

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

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

Added in version 1.5.0.

写入HTML文件#

DataFrame 对象有一个实例方法 to_html ,它将 DataFrame 的内容渲染为 HTML 表格。函数参数与上述 to_string 方法中的相同。

备注

为了简洁起见,这里没有展示 DataFrame.to_html 的所有可能选项。请参阅 DataFrame.to_html() 获取完整的选项集。

备注

在支持 HTML 渲染的环境中,如 Jupyter Notebook,display(HTML(...))` 会将原始 HTML 渲染到环境中。

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

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

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

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

In [332]: 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 [333]: display(HTML(html))
<IPython.core.display.HTML object>

columns 参数将限制显示的列数:

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

In [335]: 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 [336]: display(HTML(html))
<IPython.core.display.HTML object>

float_format 接受一个 Python 可调用对象来控制浮点值的精度:

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

In [338]: 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 [339]: display(HTML(html))
<IPython.core.display.HTML object>

bold_rows 将默认使行标签加粗,但你可以关闭这个功能:

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

In [341]: 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 [342]: display(HTML(html))
<IPython.core.display.HTML object>

classes 参数提供了为生成的 HTML 表格赋予 CSS 类的能力。请注意,这些类是 附加 到现有的 'dataframe' 类上的。

In [343]: 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 [344]: url_df = pd.DataFrame(
   .....:     {
   .....:         "name": ["Python", "pandas"],
   .....:         "url": ["https://www.python.org/", "https://pandas.pydata.org"],
   .....:     }
   .....: )
   .....: 

In [345]: html = url_df.to_html(render_links=True)

In [346]: 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.pydata.org" target="_blank">https://pandas.pydata.org</a></td>
    </tr>
  </tbody>
</table>

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

最后,escape 参数允许你控制是否在生成的 HTML 中转义 “<”, “>” 和 “&” 字符(默认情况下为 True)。因此,要获取不带转义字符的 HTML,请传递 escape=False

In [348]: df = pd.DataFrame({"a": list("&<>"), "b": np.random.randn(3)})

转义:

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

In [350]: 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 [351]: display(HTML(html))
<IPython.core.display.HTML object>

未转义:

In [352]: html = df.to_html(escape=False)

In [353]: 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 [354]: display(HTML(html))
<IPython.core.display.HTML object>

备注

一些浏览器可能不会在渲染前两个HTML表格时显示出差异。

HTML 表格解析的陷阱#

在顶层 pandas io 函数 read_html 中用于解析 HTML 表格的库存在一些版本问题。

问题与 lxml

  • 好处

    • lxml 非常快。

    • lxml 需要 Cython 才能正确安装。

  • 缺点

    • lxml 不对其解析结果做出任何保证,除非它被给予 strictly valid markup

    • 鉴于此,我们决定允许您,用户,使用 lxml 后端,但如果 lxml 无法解析,此后端将使用 html5lib

    • 因此,强烈建议 你安装 BeautifulSoup4html5lib,这样即使 lxml 失败,你仍然可以得到一个有效的结果(前提是其他一切都是有效的)。

BeautifulSoup4 使用 lxml 作为后端的问题

  • 上述问题在这里也同样存在,因为 BeautifulSoup4 本质上只是解析器后端的一个包装。

使用 BeautifulSoup4 时使用 html5lib 作为后端的问题

  • 好处

    • html5liblxml 宽容得多,因此处理 实际标记 的方式更为合理,而不是仅仅例如,在未通知你的情况下删除一个元素。

    • html5lib 自动从无效的标记生成有效的 HTML5 标记。这对于解析 HTML 表格极其重要,因为它保证了有效的文档。然而,这并不意味着它是“正确的”,因为修复标记的过程没有一个单一的定义。

    • html5lib 是纯 Python 编写的,除了自身的安装外,不需要额外的构建步骤。

  • 缺点

    • 使用 html5lib 的最大缺点是它慢如蜗牛。然而,考虑到网络上许多表格并不大,解析算法的运行时间可能并不重要。更有可能的是,瓶颈在于从网络URL读取原始文本的过程,即IO(输入-输出)。对于非常大的表格,这可能不成立。

LaTeX#

Added in version 1.3.0.

目前没有从 LaTeX 读取的方法,只有输出方法。

写入 LaTeX 文件#

备注

DataFrame Styler 对象目前有一个 to_latex 方法。我们推荐使用 Styler.to_latex() 方法而不是 DataFrame.to_latex() 方法,因为前者的条件样式具有更大的灵活性,而后者的未来可能被弃用。

查看 Styler.to_latex 的文档,其中提供了条件样式的示例并解释了其关键字参数的操作。

对于简单的应用程序,以下模式是足够的。

In [355]: df = pd.DataFrame([[1, 2], [3, 4]], index=["a", "b"], columns=["c", "d"])

In [356]: print(df.style.to_latex())
\begin{tabular}{lrr}
 & c & d \\
a & 1 & 2 \\
b & 3 & 4 \\
\end{tabular}

要在输出前格式化值,请链接 Styler.format 方法。

In [357]: print(df.style.format("€ {}").to_latex())
\begin{tabular}{lrr}
 & c & d \\
a & € 1 & € 2 \\
b & € 3 & € 4 \\
\end{tabular}

XML#

读取 XML#

Added in version 1.3.0.

顶级 read_xml() 函数可以接受一个 XML 字符串/文件/URL,并将节点和属性解析为一个 pandas DataFrame

备注

由于没有标准的XML结构,设计类型可以在许多方面有所不同,read_xml 在处理较浅的版本时效果最佳。如果XML文档嵌套很深,请使用 stylesheet 功能将XML转换为较浅的版本。

让我们看几个例子。

读取一个 XML 字符串:

In [358]: from io import StringIO

In [359]: 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 [360]: df = pd.read_xml(StringIO(xml))

In [361]: df
Out[361]: 
   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 [362]: df = pd.read_xml("https://www.w3schools.com/xml/books.xml")

In [363]: df
Out[363]: 
   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 [364]: file_path = "books.xml"

In [365]: with open(file_path, "w") as f:
   .....:     f.write(xml)
   .....: 

In [366]: with open(file_path, "r") as f:
   .....:     df = pd.read_xml(StringIO(f.read()))
   .....: 

In [367]: df
Out[367]: 
   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”的内容读取为 StringIOBytesIO 的实例,并将其传递给 read_xml

In [368]: with open(file_path, "r") as f:
   .....:     sio = StringIO(f.read())
   .....: 

In [369]: df = pd.read_xml(sio)

In [370]: df
Out[370]: 
   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 [371]: with open(file_path, "rb") as f:
   .....:     bio = BytesIO(f.read())
   .....: 

In [372]: df = pd.read_xml(bio)

In [373]: df
Out[373]: 
   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

甚至可以从 AWS S3 存储桶中读取 XML,例如提供生物医学和生命科学期刊的 NIH NCBI PMC 文章数据集:

>>> df = pd.read_xml(
...    "s3://pmc-oa-opendata/oa_comm/xml/all/PMC1236943.xml",
...    xpath=".//journal-meta",
...)
>>> df
      journal-id  journal-title  issn  publisher
0 Cardiovasc Ultrasound Cardiovascular Ultrasound 1476-7120 NaN

使用 lxml 作为默认 解析器 ,你可以访问功能齐全的 XML 库,该库扩展了 Python 的 ElementTree API。一个强大的工具是能够使用更具表现力的 XPath 选择性或条件性地查询节点:

In [374]: df = pd.read_xml(file_path, xpath="//book[year=2005]")

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

仅指定要解析的元素或属性:

In [376]: df = pd.read_xml(file_path, elems_only=True)

In [377]: df
Out[377]: 
              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 [378]: df = pd.read_xml(file_path, attrs_only=True)

In [379]: df
Out[379]: 
   category
0   cooking
1  children
2       web

XML 文档可以有带前缀的命名空间和无前缀的默认命名空间,这两种命名空间都用一个特殊的属性 xmlns 表示。为了在命名空间上下文中按节点解析,xpath 必须引用一个前缀。

例如,下面的 XML 包含一个带有前缀 doc 和 URI 为 https://example.com 的命名空间。为了解析 doc:row 节点,必须使用 namespaces

In [380]: 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 [381]: df = pd.read_xml(StringIO(xml),
   .....:                  xpath="//doc:row",
   .....:                  namespaces={"doc": "https://example.com"})
   .....: 

In [382]: df
Out[382]: 
      shape  degrees  sides
0    square      360    4.0
1    circle      360    NaN
2  triangle      180    3.0

同样地,一个XML文档可以有一个没有前缀的默认命名空间。未能分配一个临时前缀将返回没有节点并引发一个 ValueError 。但为正确的URI分配 任何 临时名称都允许通过节点进行解析。

In [383]: 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 [384]: df = pd.read_xml(StringIO(xml),
   .....:                  xpath="//pandas:row",
   .....:                  namespaces={"pandas": "https://example.com"})
   .....: 

In [385]: df
Out[385]: 
      shape  degrees  sides
0    square      360    4.0
1    circle      360    NaN
2  triangle      180    3.0

然而,如果 XPath 不引用节点名称如 default, /*,那么 namespaces 不是必需的。

备注

由于 xpath 标识了要解析的内容的父级,因此仅解析包含子节点或当前属性的直接后代。因此,read_xml 不会解析孙子节点或其他后代文本,也不会解析任何后代的属性。要检索较低级别的内容,请调整 xpath 到较低级别。例如,

In [386]: 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 [387]: df = pd.read_xml(StringIO(xml), xpath="./row")

In [388]: df
Out[388]: 
      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 文档转换为其他 XML、HTML,甚至是文本(CSV、JSON 等)。

例如,考虑这种芝加哥“L”乘车情况的稍微嵌套结构,其中车站和乘车元素在其自己的部分中封装数据。通过下面的XSLT,lxml 可以将原始的嵌套文档转换为更平坦的输出(如下所示,用于演示),以便更容易解析为 DataFrame

In [389]: 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 [390]: 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 [391]: 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 [392]: df = pd.read_xml(StringIO(xml), stylesheet=StringIO(xsl))

In [393]: df
Out[393]: 
   station_id       station_name                month  avg_weekday_rides  avg_saturday_rides  avg_sunday_holiday_rides
0       40850            Library  2020-09-01T00:00:00              864.2               534.0                     417.2
1       41700  Washington/Wabash  2020-09-01T00:00:00             2707.4              1909.8                    1438.6
2       40380         Clark/Lake  2020-09-01T00:00:00             2949.6              1657.0                    1453.8

对于大小从几百兆字节到千兆字节不等的非常大的XML文件,pandas.read_xml() 支持使用 lxml’s iterparseetree’s iterparse 解析这些大文件,这些是内存高效的方法,用于遍历XML树并提取特定元素和属性,而无需将整个树保留在内存中。

Added in version 1.5.0.

要使用此功能,您必须将物理 XML 文件路径传递给 read_xml 并使用 iterparse 参数。文件不应压缩或指向在线资源,而应存储在本地磁盘上。此外,iterparse 应该是一个字典,其中键是文档中的重复节点(成为行),值是任何元素或属性的列表,这些元素或属性是重复节点的后代(即子节点、孙节点)。由于在此方法中不使用 XPath,因此后代不需要与彼此共享相同的关系。下面显示了读取维基百科非常大的(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#

Added in version 1.3.0.

DataFrame 对象有一个实例方法 to_xml,它将 DataFrame 的内容渲染为 XML 文档。

备注

此方法不支持包括 DTD、CData、XSD 模式、处理指令、注释等在内的 XML 特殊属性。仅支持根级别的命名空间。然而,stylesheet 允许在初始输出后进行设计更改。

让我们看几个例子。

写一个没有选项的XML:

In [394]: geom_df = pd.DataFrame(
   .....:     {
   .....:         "shape": ["square", "circle", "triangle"],
   .....:         "degrees": [360, 360, 180],
   .....:         "sides": [4, np.nan, 3],
   .....:     }
   .....: )
   .....: 

In [395]: 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 [396]: 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 [397]: 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 [398]: print(
   .....:     geom_df.to_xml(
   .....:         index=False,
   .....:         attr_cols=['shape'],
   .....:         elem_cols=['degrees', 'sides'])
   .....: )
   .....: 
<?xml version='1.0' encoding='utf-8'?>
<data>
  <row shape="square">
    <degrees>360</degrees>
    <sides>4.0</sides>
  </row>
  <row shape="circle">
    <degrees>360</degrees>
    <sides/>
  </row>
  <row shape="triangle">
    <degrees>180</degrees>
    <sides>3.0</sides>
  </row>
</data>

任何具有分层列的 DataFrames 将被展平,XML 元素名称的层级用下划线分隔:

In [399]: ext_geom_df = pd.DataFrame(
   .....:     {
   .....:         "type": ["polygon", "other", "polygon"],
   .....:         "shape": ["square", "circle", "triangle"],
   .....:         "degrees": [360, 360, 180],
   .....:         "sides": [4, np.nan, 3],
   .....:     }
   .....: )
   .....: 

In [400]: pvt_df = ext_geom_df.pivot_table(index='shape',
   .....:                                  columns='type',
   .....:                                  values=['degrees', 'sides'],
   .....:                                  aggfunc='sum')
   .....: 

In [401]: pvt_df
Out[401]: 
         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 [402]: 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 [403]: 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 [404]: 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 [405]: 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 [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="/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 [407]: print(geom_df.to_xml(stylesheet=StringIO(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,请使用适当的 DOM 库,如 etreelxml 来构建必要的文档,而不是通过字符串拼接或正则表达式调整。始终记住 XML 是一个带有标记规则的 特殊 文本文件。

  • 对于非常大的 XML 文件(几百 MB 到 GB),XPath 和 XSLT 可能会成为内存密集型操作。确保有足够的可用 RAM 来读取和写入大型 XML 文件(大约是文本大小的 5 倍)。

  • 因为 XSLT 是一种编程语言,使用时要谨慎,因为这种脚本可能会在你的环境中带来安全风险,并且可以运行大型或无限递归操作。在全面运行之前,始终先对小片段测试脚本。

  • etree 解析器支持 read_xmlto_xml 的所有功能,除了复杂的 XPath 和任何 XSLT。尽管功能有限,etree 仍然是一个可靠且有能力的解析器和树构建器。对于较大的文件,它的性能可能在一定程度上落后于 lxml,但在中小型文件上相对不明显。

Excel 文件#

The read_excel() method can read Excel 2007+ (.xlsx) files using the openpyxl Python module. Excel 2003 (.xls) files can be read using xlrd. Binary Excel (.xlsb) files can be read using pyxlsb. All formats can be read using calamine engine. The to_excel() instance method is used for saving a DataFrame to Excel. Generally the semantics are similar to working with csv data. See the cookbook for some advanced strategies.

备注

engine=None 时,将使用以下逻辑来确定引擎:

  • 如果 path_or_buffer 是 OpenDocument 格式(.odf, .ods, .odt),那么将使用 odf

  • 否则,如果 path_or_buffer 是 xls 格式,将使用 xlrd

  • 否则,如果 path_or_buffer 是 xlsb 格式,将使用 pyxlsb

  • 否则将使用 openpyxl

读取 Excel 文件#

在最基本的使用场景中,read_excel 接受一个 Excel 文件的路径,以及指示要解析哪个表的 sheet_name

当使用 engine_kwargs 参数时,pandas 会将这些参数传递给引擎。为此,了解 pandas 内部使用哪个函数是很重要的。

  • 对于 openpyxl 引擎,pandas 使用 openpyxl.load_workbook() 来读取 (.xlsx) 和 (.xlsm) 文件。

  • 对于引擎 xlrd,pandas 使用 xlrd.open_workbook() 来读取 (.xls) 文件。

  • 对于引擎 pyxlsb,pandas 使用 pyxlsb.open_workbook() 来读取 (.xlsb) 文件。

  • 对于 odf 引擎,pandas 使用 odf.opendocument.load() 来读取 (.ods) 文件。

  • 对于引擎 calamine,pandas 使用 python_calamine.load_workbook() 来读取 (.xlsx), (.xlsm), (.xls), (.xlsb), (.ods) 文件。

# Returns a DataFrame
pd.read_excel("path_to_file.xls", sheet_name="Sheet1")

ExcelFile#

为了便于处理同一文件中的多个工作表,可以使用 ExcelFile 类来封装文件,并将其传递给 read_excel。读取多个工作表时会有性能优势,因为文件只读入内存一次。

xlsx = pd.ExcelFile("path_to_file.xls")
df = pd.read_excel(xlsx, "Sheet1")

ExcelFile 类也可以用作上下文管理器。

with pd.ExcelFile("path_to_file.xls") as xls:
    df1 = pd.read_excel(xls, "Sheet1")
    df2 = pd.read_excel(xls, "Sheet2")

sheet_names 属性将生成文件中表单名称的列表。

ExcelFile 的主要用途是使用不同的参数解析多个工作表:

data = {}
# For when Sheet1's format differs from Sheet2
with pd.ExcelFile("path_to_file.xls") as xls:
    data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
    data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=1)

请注意,如果所有工作表使用相同的解析参数,可以简单地将工作表名称列表传递给 read_excel,而不会影响性能。

# using the ExcelFile class
data = {}
with pd.ExcelFile("path_to_file.xls") as xls:
    data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
    data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=None, na_values=["NA"])

# equivalent using the read_excel function
data = pd.read_excel(
    "path_to_file.xls", ["Sheet1", "Sheet2"], index_col=None, na_values=["NA"]
)

ExcelFile 也可以使用 xlrd.book.Book 对象作为参数来调用。这允许用户控制如何读取excel文件。例如,可以通过使用 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")

指定工作表#

备注

第二个参数是 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 传递列列表来读取 MultiIndex 索引,并通过向 header 传递行列表来读取 MultiIndex 列。如果 indexcolumns 具有序列化的级别名称,也可以通过指定构成级别的行/列来读取这些名称。

例如,要读取一个没有名称的 MultiIndex 索引:

In [408]: df = pd.DataFrame(
   .....:     {"a": [1, 2, 3, 4], "b": [5, 6, 7, 8]},
   .....:     index=pd.MultiIndex.from_product([["a", "b"], ["c", "d"]]),
   .....: )
   .....: 

In [409]: df.to_excel("path_to_file.xlsx")

In [410]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1])

In [411]: df
Out[411]: 
     a  b
a c  1  5
  d  2  6
b c  3  7
  d  4  8

如果索引有级别名称,它们也将被解析,使用相同的参数。

In [412]: df.index = df.index.set_names(["lvl1", "lvl2"])

In [413]: df.to_excel("path_to_file.xlsx")

In [414]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1])

In [415]: df
Out[415]: 
           a  b
lvl1 lvl2      
a    c     1  5
     d     2  6
b    c     3  7
     d     4  8

如果源文件同时具有 MultiIndex 索引和列,则应将指定每个的列表传递给 index_colheader

In [416]: df.columns = pd.MultiIndex.from_product([["a"], ["b", "d"]], names=["c1", "c2"])

In [417]: df.to_excel("path_to_file.xlsx")

In [418]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1], header=[0, 1])

In [419]: df
Out[419]: 
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 进行往返操作。为了避免向前填充缺失的值,请在读取数据后使用 set_index 而不是 index_col

解析特定列#

通常情况下,用户会在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 关键字将这些字符串解析为日期时间:

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

此选项处理缺失值并将转换器中的异常视为缺失数据。转换是逐个单元格应用的,而不是对整个列应用,因此不能保证数组的数据类型。例如,一列带有缺失值的整数不能转换为具有整数数据类型的数组,因为 NaN 严格来说是浮点数。您可以手动屏蔽缺失数据以恢复整数数据类型:

def cfun(x):
    return int(x) if x else -1


pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyInts": cfun})

Dtype 规范#

作为转换器的替代方案,可以使用 dtype 关键字为整个列指定类型,该关键字接受一个将列名映射到类型的字典。要解释没有类型推断的数据,请使用类型 strobject

pd.read_excel("path_to_file.xls", dtype={"MyInts": "int64", "MyText": str})

编写 Excel 文件#

将 Excel 文件写入磁盘#

要将 DataFrame 对象写入 Excel 文件的一个工作表,可以使用 to_excel 实例方法。参数与上述 to_csv 大致相同,第一个参数是 Excel 文件的名称,可选的第二个参数是要写入 DataFrame 的工作表名称。例如:

df.to_excel("path_to_file.xlsx", sheet_name="Sheet1")

带有 .xlsx 扩展名的文件将使用 xlsxwriter``(如果可用)或 ``openpyxl 进行写入。

DataFrame 将以模仿 REPL 输出的方式写入。index_label 将被放置在第二行而不是第一行。你可以通过在 to_excel() 中将 merge_cells 选项设置为 False 来将其放置在第一行:

df.to_excel("path_to_file.xlsx", index_label="label", merge_cells=False)

为了将不同的 DataFrames 写入到单个 Excel 文件中的不同工作表,可以传递一个 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 支持将 Excel 文件写入类似缓冲区的对象,例如 StringIOBytesIO,使用 ExcelWriter

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 通过两种方法选择一个 Excel 写入器:

  1. engine 关键字参数

  2. 文件扩展名(通过配置选项中指定的默认值)

默认情况下,pandas 使用 XlsxWriter 用于 .xlsxopenpyxl 用于 .xlsm。如果你安装了多个引擎,可以通过 设置配置选项 io.excel.xlsx.writerio.excel.xls.writer 来设置默认引擎。如果 Xlsxwriter 不可用,pandas 将回退到 openpyxl 用于 .xlsx 文件。

要指定您想要使用的写入器,可以向 to_excelExcelWriter 传递一个 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")

风格和格式化#

通过 DataFrameto_excel 方法上的以下参数,可以修改从 pandas 创建的 Excel 工作表的外观和感觉。

  • float_format : 浮点数的格式字符串(默认 None)。

  • freeze_panes : 一个由两个整数组成的元组,表示要冻结的最底行和最右列。每个参数都是基于1的,因此 (1, 1) 将冻结第一行和第一列(默认 None)。

备注

从 pandas 3.0 开始,默认情况下使用 to_excel 方法创建的电子表格将不包含任何样式。希望在由 to_excel 输出的工作表中加粗文本、添加边框样式等的用户可以使用 Styler.to_excel() 创建样式化的 Excel 文件。有关电子表格样式的文档,请参见 这里

css = "border: 1px solid black; font-weight: bold;"
df.style.map_index(lambda x: css).map_index(lambda x: css, axis=1).to_excel("myfile.xlsx")

使用 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 无法识别文件中的日期时间类型,并将返回浮点数(如果你需要识别日期时间类型,可以使用 calamine)。

# Returns a DataFrame
pd.read_excel("path_to_file.xlsb", engine="pyxlsb")

备注

目前 pandas 仅支持 读取 二进制 Excel 文件。写入功能尚未实现。

Calamine(Excel 和 ODS 文件)#

read_excel() 方法可以读取 Excel 文件(.xlsx.xlsm.xls.xlsb)和 OpenDocument 电子表格(.ods),使用 python-calamine 模块。该模块是 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)。

Pickling#

所有 pandas 对象都配备了 to_pickle 方法,这些方法使用 Python 的 cPickle 模块将数据结构保存到磁盘,使用的是 pickle 格式。

In [420]: df
Out[420]: 
c1         a   
c2         b  d
lvl1 lvl2      
a    c     1  5
     d     2  6
b    c     3  7
     d     4  8

In [421]: df.to_pickle("foo.pkl")

pandas 命名空间中的 read_pickle 函数可以用来从文件加载任何pickled的pandas对象(或其他pickled对象):

In [422]: pd.read_pickle("foo.pkl")
Out[422]: 
c1         a   
c2         b  d
lvl1 lvl2      
a    c     1  5
     d     2  6
b    c     3  7
     d     4  8

警告

从不信任的来源加载经过pickle处理的数据可能是不安全的。

请参见:https://docs.python.org/3/library/pickle.html

警告

read_pickle() 仅保证向后兼容到几个次要版本。

压缩的 pickle 文件#

read_pickle(), DataFrame.to_pickle()Series.to_pickle() 可以读写压缩的 pickle 文件。支持的压缩类型有 gzip, bz2, xz, zstd 用于读写。zip 文件格式仅支持读取,并且必须只包含一个要读取的数据文件。

压缩类型可以是显式参数,也可以从文件扩展名推断。如果为 ‘infer’,则根据文件名以 '.gz', '.bz2', '.zip', '.xz', 或 '.zst' 结尾分别使用 gzip, bz2, zip, xz, zstd

压缩参数也可以是一个 dict ,以便向压缩协议传递选项。它必须有一个 'method' 键,设置为压缩协议的名称,该名称必须是 {'zip', 'gzip', 'bz2', 'xz', 'zstd'} 之一。所有其他键值对都传递给底层的压缩库。

In [423]: df = pd.DataFrame(
   .....:     {
   .....:         "A": np.random.randn(1000),
   .....:         "B": "foo",
   .....:         "C": pd.date_range("20130101", periods=1000, freq="s"),
   .....:     }
   .....: )
   .....: 

In [424]: df
Out[424]: 
            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 [425]: df.to_pickle("data.pkl.compress", compression="gzip")

In [426]: rt = pd.read_pickle("data.pkl.compress", compression="gzip")

In [427]: rt
Out[427]: 
            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 [428]: df.to_pickle("data.pkl.xz", compression="infer")

In [429]: rt = pd.read_pickle("data.pkl.xz", compression="infer")

In [430]: rt
Out[430]: 
            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 [431]: df.to_pickle("data.pkl.gz")

In [432]: rt = pd.read_pickle("data.pkl.gz")

In [433]: rt
Out[433]: 
            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 [434]: df["A"].to_pickle("s1.pkl.bz2")

In [435]: rt = pd.read_pickle("s1.pkl.bz2")

In [436]: rt
Out[436]: 
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 [437]: df.to_pickle("data.pkl.gz", compression={"method": "gzip", "compresslevel": 1})

msgpack#

在版本1.0.0中,pandas对``msgpack``的支持已被移除。建议使用:ref:`pickle <io.pickle>`代替。

或者,你也可以使用 Arrow IPC 序列化格式进行 pandas 对象的在线传输。有关 pyarrow 的文档,请参见 这里

HDF5 (PyTables)#

HDFStore 是一个类似字典的对象,它使用高性能的 HDF5 格式通过优秀的 PyTables 库读写 pandas。请参阅 cookbook 获取一些高级策略

警告

pandas 使用 PyTables 读取和写入 HDF5 文件,这允许使用 pickle 序列化对象类型数据。从不信任的来源加载 pickle 数据可能是不安全的。

更多信息请参见:https://docs.python.org/3/library/pickle.html

In [438]: store = pd.HDFStore("store.h5")

In [439]: print(store)
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

对象可以像向字典添加键值对一样写入文件:

In [440]: index = pd.date_range("1/1/2000", periods=8)

In [441]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])

In [442]: df = pd.DataFrame(np.random.randn(8, 3), index=index, columns=["A", "B", "C"])

# store.put('s', s) is an equivalent method
In [443]: store["s"] = s

In [444]: store["df"] = df

In [445]: store
Out[445]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

在当前或之后的 Python 会话中,您可以检索存储的对象:

# store.get('df') is an equivalent method
In [446]: store["df"]
Out[446]: 
                   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 [447]: store.df
Out[447]: 
                   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 [448]: del store["df"]

In [449]: store
Out[449]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

关闭商店并使用上下文管理器:

In [450]: store.close()

In [451]: store
Out[451]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

In [452]: store.is_open
Out[452]: False

# Working with, and automatically closing the store using a context manager
In [453]: with pd.HDFStore("store.h5") as store:
   .....:     store.keys()
   .....: 

读/写 API#

HDFStore 支持使用 read_hdf 进行读取和 to_hdf 进行写入的顶层API,类似于 read_csvto_csv 的工作方式。

In [454]: df_tl = pd.DataFrame({"A": list(range(5)), "B": list(range(5))})

In [455]: df_tl.to_hdf("store_tl.h5", key="table", append=True)

In [456]: pd.read_hdf("store_tl.h5", "table", where=["index>2"])
Out[456]: 
   A  B
3  3  3
4  4  4

HDFStore 默认情况下不会删除所有缺失的行。可以通过设置 dropna=True 来改变此行为。

In [457]: df_with_missing = pd.DataFrame(
   .....:     {
   .....:         "col1": [0, np.nan, 2],
   .....:         "col2": [1, np.nan, np.nan],
   .....:     }
   .....: )
   .....: 

In [458]: df_with_missing
Out[458]: 
   col1  col2
0   0.0   1.0
1   NaN   NaN
2   2.0   NaN

In [459]: df_with_missing.to_hdf("file.h5", key="df_with_missing", format="table", mode="w")

In [460]: pd.read_hdf("file.h5", "df_with_missing")
Out[460]: 
   col1  col2
0   0.0   1.0
1   NaN   NaN
2   2.0   NaN

In [461]: df_with_missing.to_hdf(
   .....:     "file.h5", key="df_with_missing", format="table", mode="w", dropna=True
   .....: )
   .....: 

In [462]: pd.read_hdf("file.h5", "df_with_missing")
Out[462]: 
   col1  col2
0   0.0   1.0
2   2.0   NaN

固定格式#

上面的例子展示了使用 put 存储,它以固定数组格式将 HDF5 写入 PyTables,称为 fixed 格式。这些类型的存储一旦写入就**不能**追加(尽管你可以简单地删除它们并重写)。它们也**不能**查询;必须完整地检索它们。它们还不支持具有非唯一列名的数据框。fixed 格式存储提供了比 table 存储更快的写入速度和稍快的读取速度。这种格式在使用 putto_hdf 时默认指定,或者通过 format='fixed'format='f' 指定。

警告

一个 fixed 格式如果尝试使用 where 进行检索,将会引发 TypeError

In [463]: pd.DataFrame(np.random.randn(10, 2)).to_hdf("test_fixed.h5", key="df")

In [464]: pd.read_hdf("test_fixed.h5", "df", where="index>5")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[464], line 1
----> 1 pd.read_hdf("test_fixed.h5", "df", where="index>5")

File /home/pandas/pandas/io/pytables.py:444, in read_hdf(path_or_buf, key, mode, errors, where, start, stop, columns, iterator, chunksize, **kwargs)
    439                 raise ValueError(
    440                     "key must be provided when HDF5 "
    441                     "file contains multiple datasets."
    442                 )
    443         key = candidate_only_group._v_pathname
--> 444     return store.select(
    445         key,
    446         where=where,
    447         start=start,
    448         stop=stop,
    449         columns=columns,
    450         iterator=iterator,
    451         chunksize=chunksize,
    452         auto_close=auto_close,
    453     )
    454 except (ValueError, TypeError, LookupError):
    455     if not isinstance(path_or_buf, HDFStore):
    456         # if there is an error, close the store if we opened it.

File /home/pandas/pandas/io/pytables.py:916, in HDFStore.select(self, key, where, start, stop, columns, iterator, chunksize, auto_close)
    902 # create the iterator
    903 it = TableIterator(
    904     self,
    905     s,
   (...)
    913     auto_close=auto_close,
    914 )
--> 916 return it.get_result()

File /home/pandas/pandas/io/pytables.py:2102, in TableIterator.get_result(self, coordinates)
   2099     where = self.where
   2101 # directly return the result
-> 2102 results = self.func(self.start, self.stop, where)
   2103 self.close()
   2104 return results

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

File /home/pandas/pandas/io/pytables.py:3349, in BlockManagerFixed.read(self, where, columns, start, stop)
   3341 def read(
   3342     self,
   3343     where=None,
   (...)
   3347 ) -> DataFrame:
   3348     # start, stop applied to rows, so 0th axis only
-> 3349     self.validate_read(columns, where)
   3350     select_axis = self.obj_type()._get_block_manager_axis(0)
   3352     axes = []

File /home/pandas/pandas/io/pytables.py:2992, in GenericFixed.validate_read(self, columns, where)
   2987     raise TypeError(
   2988         "cannot pass a column specification when reading "
   2989         "a Fixed format store. this store must be selected in its entirety"
   2990     )
   2991 if where is not None:
-> 2992     raise TypeError(
   2993         "cannot pass a where specification when reading "
   2994         "from a Fixed format store. this store must be selected in its entirety"
   2995     )

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' 指定给 appendputto_hdf

这个格式也可以设置为一个选项 pd.set_option('io.hdf.default_format','table') 以启用 put/append/to_hdf 默认存储在 table 格式中。

In [465]: store = pd.HDFStore("store.h5")

In [466]: df1 = df[0:4]

In [467]: df2 = df[4:]

# append data (creates a table automatically)
In [468]: store.append("df", df1)

In [469]: store.append("df", df2)

In [470]: store
Out[470]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

# select the entire object
In [471]: store.select("df")
Out[471]: 
                   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 [472]: store.root.df._v_attrs.pandas_type
Out[472]: 'frame_table'

备注

你也可以通过传递 format='table'format='t'put 操作来创建一个 table

分层键#

商店的键可以指定为一个字符串。这些键可以采用类似分层路径的格式(例如 foo/bar/bah),这将生成一个子商店(或 PyTables 术语中的 Groups)的层次结构。键可以指定为不带前导 ‘/’,并且 总是 绝对路径(例如 ‘foo’ 指的是 ‘/foo’)。删除操作可以删除子商店中 及其以下的所有内容,所以要 小心

In [473]: store.put("foo/bar/bah", df)

In [474]: store.append("food/orange", df)

In [475]: store.append("food/apple", df)

In [476]: store
Out[476]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

# a list of keys are returned
In [477]: store.keys()
Out[477]: ['/df', '/food/apple', '/food/orange', '/foo/bar/bah']

# remove all nodes under this level
In [478]: store.remove("food")

In [479]: store
Out[479]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

你可以使用 walk 方法遍历组层次结构,该方法将为每个组键及其内容的相对键生成一个元组。

In [480]: 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 [481]: store.foo.bar.bah
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[481], line 1
----> 1 store.foo.bar.bah

File /home/pandas/pandas/io/pytables.py:605, in HDFStore.__getattr__(self, name)
    603 """allow attribute access to get stores"""
    604 try:
--> 605     return self.get(name)
    606 except (KeyError, ClosedFileError):
    607     pass

File /home/pandas/pandas/io/pytables.py:817, in HDFStore.get(self, key)
    815 if group is None:
    816     raise KeyError(f"No object named {key} in the file")
--> 817 return self._read_group(group)

File /home/pandas/pandas/io/pytables.py:1951, in HDFStore._read_group(self, group)
   1950 def _read_group(self, group: Node):
-> 1951     s = self._create_storer(group)
   1952     s.infer_axes()
   1953     return s.read()

File /home/pandas/pandas/io/pytables.py:1825, in HDFStore._create_storer(self, group, format, value, encoding, errors)
   1823         tt = "generic_table"
   1824     else:
-> 1825         raise TypeError(
   1826             "cannot create a storer if the object is not existing "
   1827             "nor a value are passed"
   1828         )
   1829 else:
   1830     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 [482]: store.root.foo.bar.bah
Out[482]: 
/foo/bar/bah (Group) ''
  children := ['axis0' (Array), 'axis1' (Array), 'block0_items' (Array), 'block0_values' (Array)]

相反,使用基于字符串的显式键:

In [483]: store["foo/bar/bah"]
Out[483]: 
                   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

存储类型#

在表中存储混合类型#

支持存储混合数据类型的数据。字符串作为固定宽度存储,使用追加列的最大大小。后续尝试追加更长的字符串将引发 ValueError

min_itemsize={`values`: size} 作为参数传递给 append 将设置字符串列的更大最小值。目前支持存储 floats, strings, ints, bools, datetime64。对于字符串列,将 nan_rep = 'nan' 传递给 append 将更改磁盘上默认的 nan 表示(这会转换为/从 np.nan),默认为 nan

In [484]: 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 [485]: df_mixed.loc[df_mixed.index[3:5], ["A", "B", "string", "datetime64"]] = np.nan

In [486]: store.append("df_mixed", df_mixed, min_itemsize={"values": 50})

In [487]: df_mixed1 = store.select("df_mixed")

In [488]: df_mixed1
Out[488]: 
          A         B         C  string  int  bool datetime64
0  0.013747 -1.166078 -1.292080  string    1  True 2001-01-02
1 -0.712009  0.247572  1.526911  string    1  True 2001-01-02
2 -0.645096  1.687406  0.288504  string    1  True 2001-01-02
3       NaN       NaN  0.097771     NaN    1  True        NaT
4       NaN       NaN  1.536408     NaN    1  True        NaT
5 -0.023202  0.043702  0.926790  string    1  True 2001-01-02
6  2.359782  0.088224 -0.676448  string    1  True 2001-01-02
7 -0.143428 -0.813360 -0.179724  string    1  True 2001-01-02

In [489]: df_mixed1.dtypes.value_counts()
Out[489]: 
float64          2
float32          1
object           1
int64            1
bool             1
datetime64[s]    1
Name: count, dtype: int64

# we have provided a minimum string column size
In [490]: store.root.df_mixed.table
Out[490]: 
/df_mixed/table (Table(8,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": Float64Col(shape=(2,), dflt=0.0, pos=1),
  "values_block_1": Float32Col(shape=(1,), dflt=0.0, pos=2),
  "values_block_2": StringCol(itemsize=50, shape=(1,), dflt=b'', pos=3),
  "values_block_3": Int64Col(shape=(1,), dflt=0, pos=4),
  "values_block_4": BoolCol(shape=(1,), dflt=False, pos=5),
  "values_block_5": Int64Col(shape=(1,), dflt=0, pos=6)}
  byteorder := 'little'
  chunkshape := (689,)
  autoindex := True
  colindexes := {
    "index": Index(6, mediumshuffle, zlib(1)).is_csi=False}

存储 MultiIndex DataFrame#

将 MultiIndex DataFrames 存储为表格与存储/从同质索引 DataFrames 中选择非常相似。

In [491]: 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 [492]: df_mi = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])

In [493]: df_mi
Out[493]: 
                  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 [494]: store.append("df_mi", df_mi)

In [495]: store.select("df_mi")
Out[495]: 
                  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 [496]: store.select("df_mi", "foo=bar")
Out[496]: 
                A         B         C
foo bar                              
bar one  0.410395  0.618321  0.560398
    two  1.434027 -0.033270  0.343197

备注

index 关键字是保留的,不能用作级别名称。

查询#

查询表#

selectdelete 操作有一个可选的条件,可以指定以选择/删除数据的子集。这允许用户拥有一个非常大的磁盘表,并只检索数据的一部分。

查询是使用 Term 类在后台指定的,作为布尔表达式。

  • indexcolumnsDataFrames 支持的索引器。

  • 如果指定了 data_columns ,这些可以作为额外的索引器使用。

  • 在 MultiIndex 中的层级名称,如果没有提供,则使用默认名称 level_0, level_1, …。

有效的比较运算符是:

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

有效的布尔表达式通过以下方式组合:

  • | : 或

  • & : 和

  • () : 用于分组

这些规则类似于在 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 [497]: dfq = pd.DataFrame(
   .....:     np.random.randn(10, 4),
   .....:     columns=list("ABCD"),
   .....:     index=pd.date_range("20130101", periods=10),
   .....: )
   .....: 

In [498]: store.append("dfq", dfq, format="table", data_columns=True)

使用布尔表达式,并进行内联函数评估。

In [499]: store.select("dfq", "index>pd.Timestamp('20130104') & columns=['A', 'B']")
Out[499]: 
                   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 [500]: store.select("dfq", where="A>0 or C>0")
Out[500]: 
                   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 [501]: store.select("df", "columns=['A', 'B']")
Out[501]: 
                   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

startstop 参数可以指定以限制总的搜索空间。这些参数是以表中的总行数为单位的。

备注

select 如果查询表达式包含未知的变量引用,将会引发 ValueError 。通常这意味着你正在尝试选择一个**不是** data_column 的列。

select 如果查询表达式无效,将引发 SyntaxError

查询 timedelta64[ns]#

你可以使用 timedelta64[ns] 类型进行存储和查询。术语可以按以下格式指定:<float>(<unit>),其中 float 可以是带符号的(和小数的),unit 可以是 D,s,ms,us,ns 用于 timedelta。这里有一个例子:

In [502]: from datetime import timedelta

In [503]: dftd = pd.DataFrame(
   .....:     {
   .....:         "A": pd.Timestamp("20130101"),
   .....:         "B": [
   .....:             pd.Timestamp("20130101") + timedelta(days=i, seconds=10)
   .....:             for i in range(10)
   .....:         ],
   .....:     }
   .....: )
   .....: 

In [504]: dftd["C"] = dftd["A"] - dftd["B"]

In [505]: dftd
Out[505]: 
           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 [506]: store.append("dftd", dftd, data_columns=True)

In [507]: store.select("dftd", "C<'-3.5D'")
Out[507]: 
Empty DataFrame
Columns: [A, B, C]
Index: []

查询 MultiIndex#

通过使用级别的名称,可以从 MultiIndex 中进行选择。

In [508]: df_mi.index.names
Out[508]: FrozenList(['foo', 'bar'])

In [509]: store.select("df_mi", "foo=baz and bar=two")
Out[509]: 
                A         B         C
foo bar                              
baz two -1.646063 -0.695847 -0.429156

如果 MultiIndex 级别的名称是 None ,级别将通过 level_n 关键字自动提供,其中 n 是你想要从中选择的 MultiIndex 的级别。

In [510]: 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 [511]: df_mi_2 = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])

In [512]: df_mi_2
Out[512]: 
                  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 [513]: store.append("df_mi_2", df_mi_2)

# the levels are automatically included as data columns with keyword level_n
In [514]: store.select("df_mi_2", "level_0=foo and level_1=two")
Out[514]: 
                A         B         C
foo two  0.053768  1.872644 -1.469813

索引#

你可以在数据已经进入表中(在 append/put 操作之后)使用 create_table_index 创建/修改表的索引。创建表索引是**非常**推荐的。当你使用带有索引维度作为 whereselect 时,这将大大加快你的查询速度。

备注

索引会自动在可索引项和你指定的任何数据列上创建。可以通过传递 index=Falseappend 来关闭此行为。

# we have automagically already created an index (in the first section)
In [515]: i = store.root.df.table.cols.index.index

In [516]: i.optlevel, i.kind
Out[516]: (6, 'medium')

# change an index by passing new parameters
In [517]: store.create_table_index("df", optlevel=9, kind="full")

In [518]: i = store.root.df.table.cols.index.index

In [519]: i.optlevel, i.kind
Out[519]: (9, 'full')

在向存储中追加大量的数据时,通常在每次追加时关闭索引创建,然后在最后重新创建索引是很有用的。

In [520]: df_1 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [521]: df_2 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [522]: st = pd.HDFStore("appends.h5", mode="w")

In [523]: st.append("df", df_1, data_columns=["B"], index=False)

In [524]: st.append("df", df_2, data_columns=["B"], index=False)

In [525]: st.get_storer("df").table
Out[525]: 
/df/table (Table(20,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
  "B": Float64Col(shape=(), dflt=0.0, pos=2)}
  byteorder := 'little'
  chunkshape := (2730,)

然后在完成追加后创建索引。

In [526]: st.create_table_index("df", columns=["B"], optlevel=9, kind="full")

In [527]: st.get_storer("df").table
Out[527]: 
/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 [528]: st.close()

请参见 这里 以了解如何在现有存储上创建完全排序索引(CSI)。

通过数据列查询#

你可以指定(并索引)某些列,以便能够执行查询(除了 indexable 列,这些列你总是可以查询)。例如,假设你想执行这个常见操作,在磁盘上,并仅返回与该查询匹配的帧。你可以指定 data_columns = True 以强制所有列成为 data_columns

In [529]: df_dc = df.copy()

In [530]: df_dc["string"] = "foo"

In [531]: df_dc.loc[df_dc.index[4:6], "string"] = np.nan

In [532]: df_dc.loc[df_dc.index[7:9], "string"] = "bar"

In [533]: df_dc["string2"] = "cool"

In [534]: df_dc.loc[df_dc.index[1:3], ["B", "C"]] = 1.0

In [535]: df_dc
Out[535]: 
                   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 [536]: store.append("df_dc", df_dc, data_columns=["B", "C", "string", "string2"])

In [537]: store.select("df_dc", where="B > 0")
Out[537]: 
                   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 [538]: store.select("df_dc", "B > 0 & C > 0 & string == foo")
Out[538]: 
                   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 [539]: df_dc[(df_dc.B > 0) & (df_dc.C > 0) & (df_dc.string == "foo")]
Out[539]: 
                   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 [540]: store.root.df_dc.table
Out[540]: 
/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}

将许多列转换为 数据列 会导致一些性能下降,因此由用户来指定这些列。此外,在第一次追加/放置操作后,您不能更改数据列(也不能更改可索引列)(当然,您可以简单地读取数据并创建一个新表!)。

迭代器#

你可以传递 iterator=Truechunksize=number_in_a_chunkselectselect_as_multiple 以返回结果的迭代器。默认是每块返回 50,000 行。

In [541]: 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 [542]: dfeq = pd.DataFrame({"number": np.arange(1, 11)})

In [543]: dfeq
Out[543]: 
   number
0       1
1       2
2       3
3       4
4       5
5       6
6       7
7       8
8       9
9      10

In [544]: store.append("dfeq", dfeq, data_columns=["number"])

In [545]: def chunks(l, n):
   .....:     return [l[i: i + n] for i in range(0, len(l), n)]
   .....: 

In [546]: evens = [2, 4, 6, 8, 10]

In [547]: coordinates = store.select_as_coordinates("dfeq", "number=evens")

In [548]: 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 [549]: store.select_column("df_dc", "index")
Out[549]: 
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 [550]: store.select_column("df_dc", "string")
Out[550]: 
0    foo
1    foo
2    foo
3    foo
4    NaN
5    NaN
6    foo
7    bar
Name: string, dtype: object
选择坐标#

有时您希望获取查询的坐标(即索引位置)。这将返回结果位置的 Index 。这些坐标也可以传递给后续的 where 操作。

In [551]: df_coord = pd.DataFrame(
   .....:     np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
   .....: )
   .....: 

In [552]: store.append("df_coord", df_coord)

In [553]: c = store.select_as_coordinates("df_coord", "index > 20020101")

In [554]: c
Out[554]: 
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 [555]: store.select("df_coord", where=c)
Out[555]: 
                   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 。这个例子选择了月份为5的datetimeindex。

In [556]: df_mask = pd.DataFrame(
   .....:     np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
   .....: )
   .....: 

In [557]: store.append("df_mask", df_mask)

In [558]: c = store.select_column("df_mask", "index")

In [559]: where = c[pd.DatetimeIndex(c).month == 5].index

In [560]: store.select("df_mask", where=where)
Out[560]: 
                   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 [561]: store.get_storer("df_dc").nrows
Out[561]: 8

多表查询#

方法 append_to_multipleselect_as_multiple 可以一次从多个表中执行追加/选择操作。其思路是有一个表(称之为选择器表),你对其大部分/所有列进行索引,并执行查询。其他表是数据表,其索引与选择器表的索引匹配。然后,你可以在选择器表上执行非常快速的查询,同时返回大量数据。这种方法类似于拥有一个非常宽的表,但可以实现更高效的查询。

append_to_multiple 方法根据 d 将给定的单个 DataFrame 拆分为多个表,d 是一个字典,将表名映射到您希望在该表中的 ‘列’ 列表。如果在列表的位置使用 None,该表将包含给定 DataFrame 中未指定的剩余列。参数 selector 定义哪个表是选择器表(您可以从中进行查询)。参数 dropna 将从输入的 DataFrame 中删除行以确保表同步。这意味着如果写入的表中有一行的所有值都是 np.nan,该行将从所有表中删除。

如果 dropna 为 False,用户负责同步表格。记住,完全为 np.Nan 的行不会写入 HDFStore,所以如果你选择调用 dropna=False,一些表格可能会有更多的行,因此 select_as_multiple 可能无法工作或者可能会返回意外的结果。

In [562]: 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 [563]: df_mt["foo"] = "bar"

In [564]: df_mt.loc[df_mt.index[1], ("A", "B")] = np.nan

# you can also create the tables individually
In [565]: store.append_to_multiple(
   .....:     {"df1_mt": ["A", "B"], "df2_mt": None}, df_mt, selector="df1_mt"
   .....: )
   .....: 

In [566]: store
Out[566]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

# individual tables were created
In [567]: store.select("df1_mt")
Out[567]: 
                   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 [568]: store.select("df2_mt")
Out[568]: 
                   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 [569]: store.select_as_multiple(
   .....:     ["df1_mt", "df2_mt"],
   .....:     where=["A>0", "B>0"],
   .....:     selector="df1_mt",
   .....: )
   .....: 
Out[569]: 
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 上的删除操作将会相当快,因为一个块被移除,然后后面的数据被移动。另一方面,在 minor_axis 上的删除操作将会非常昂贵。在这种情况下,几乎可以肯定使用 where 选择除缺失数据外的所有数据来重写表会更快。

警告

请注意,HDF5 不会自动回收空间 在 h5 文件中。因此,反复删除(或移除节点)并再次添加,往往会增加文件大小

重新打包和清理 文件,请使用 ptrepack

注意事项和警告#

压缩#

PyTables 允许存储的数据被压缩。这适用于所有类型的存储,而不仅仅是表格。有两个参数用于控制压缩:complevelcomplib

  • complevel 指定是否以及如何压缩数据。complevel=0complevel=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)就固定了;只能完全相同的列可以被追加。

  • 请注意,时区(例如,zoneinfo.ZoneInfo('US/Eastern'))在不同的时区版本中不一定相等。因此,如果数据在使用某个时区库版本的情况下被本地化到HDFStore中的特定时区,并且该数据被另一个版本更新,由于这些时区不被视为相等,数据将被转换为UTC。要么使用相同版本的时区库,要么使用``tz_convert``并使用更新的时区定义。

警告

如果列名不能用作属性选择器,PyTables 将显示一个 NaturalNameWarning自然 标识符仅包含字母、数字和下划线,并且不能以数字开头。其他标识符不能在 where 子句中使用,通常是一个坏主意。

数据类型#

HDFStore 会将一个对象类型映射到 PyTables 的底层类型。这意味着以下类型已知可以工作:

类型

表示缺失值

浮点数 : float64, float32, float16

np.nan

整数 : int64, int32, int8, uint64, uint32, uint8

布尔值

datetime64[ns]

NaT

timedelta64[ns]

NaT

分类 : 见下文

对象 : 字符串

np.nan

unicode 列不受支持,并且 将会失败

分类数据#

你可以将包含 category dtypes 的数据写入 HDFStore。查询的工作方式与对象数组相同。然而,category dtyped 数据以更高效的方式存储。

In [570]: dfcat = pd.DataFrame(
   .....:     {"A": pd.Series(list("aabbcdba")).astype("category"), "B": np.random.randn(8)}
   .....: )
   .....: 

In [571]: dfcat
Out[571]: 
   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 [572]: dfcat.dtypes
Out[572]: 
A    category
B     float64
dtype: object

In [573]: cstore = pd.HDFStore("cats.h5", mode="w")

In [574]: cstore.append("dfcat", dfcat, format="table", data_columns=["A"])

In [575]: result = cstore.select("dfcat", where="A in ['b', 'c']")

In [576]: result
Out[576]: 
   A         B
2  b -0.551981
3  b  0.452407
4  c  0.409257
6  b -0.640843

In [577]: result.dtypes
Out[577]: 
A    category
B     float64
dtype: object

字符串列#

min_itemsize

HDFStore 的底层实现对字符串列使用了固定的列宽(itemsize)。字符串列的 itemsize 是根据传递给 HDFStore 的(该列)数据的最大长度计算的,在第一次追加时。后续的追加可能会引入一个**大于**列所能容纳的字符串,这时会引发一个异常(否则可能会导致这些列的静默截断,从而导致信息丢失)。未来我们可能会放宽这一限制,并允许用户指定的截断发生。

在第一次创建表时传递 min_itemsize 以先验地指定特定字符串列的最小长度。min_itemsize 可以是一个整数,或者是一个将列名映射到整数的字典。你可以传递 values 作为键,以允许所有 可索引项数据列 具有这个最小长度。

传递一个 min_itemsize 字典将导致所有传递的列自动创建为 data_columns

备注

如果你没有传递任何 data_columns ,那么 min_itemsize 将是任何传递的字符串长度的最大值。

In [578]: dfs = pd.DataFrame({"A": "foo", "B": "bar"}, index=list(range(5)))

In [579]: dfs
Out[579]: 
     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 [580]: store.append("dfs", dfs, min_itemsize=30)

In [581]: store.get_storer("dfs").table
Out[581]: 
/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 [582]: store.append("dfs2", dfs, min_itemsize={"A": 30})

In [583]: store.get_storer("dfs2").table
Out[583]: 
/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

字符串列将使用 nan_rep 字符串表示序列化一个 np.nan``(一个缺失值)。这默认为字符串值 ``nan。你可能会无意中将一个实际的 nan 值变成一个缺失值。

In [584]: dfss = pd.DataFrame({"A": ["foo", "bar", "nan"]})

In [585]: dfss
Out[585]: 
     A
0  foo
1  bar
2  nan

In [586]: store.append("dfss", dfss)

In [587]: store.select("dfss")
Out[587]: 
     A
0  foo
1  bar
2  NaN

# here you need to specify a different nan rep
In [588]: store.append("dfss2", dfss, nan_rep="_nan_")

In [589]: store.select("dfss2")
Out[589]: 
     A
0  foo
1  bar
2  nan

性能#

  • tables 格式与 fixed 存储相比,会带来书写性能的损失。好处是可以追加/删除和查询(潜在的大量数据)。与常规存储相比,写入时间通常更长。查询时间可以非常快,特别是在索引轴上。

  • 你可以传递 chunksize=<int>append,指定写入的块大小(默认是 50000)。这将显著降低你写入时的内存使用。

  • 你可以在第一次 append 时传递 expectedrows=<int> ,以设置 PyTables 将预期的总行数。这将优化读/写性能。

  • 可以向表中写入重复的行,但在选择时会被过滤掉(选择最后一个项目;因此表在主、次对上是唯一的)

  • 如果你尝试存储将由 PyTables (而不是存储为本地类型)序列化的类型,将会引发 PerformanceWarning。更多信息和一些解决方案请参见 这里

Feather#

Feather 为数据框提供二进制列式序列化。它的设计旨在使数据框的读写高效,并使数据在数据分析语言之间的共享变得容易。

Feather 旨在忠实地序列化和反序列化 DataFrame,支持所有 pandas dtypes,包括分类和带时区的日期时间等扩展 dtypes。

几个注意事项:

  • 该格式不会为 DataFrame 写入 IndexMultiIndex,如果提供了非默认的索引,则会引发错误。你可以使用 .reset_index() 来存储索引,或者使用 .reset_index(drop=True) 来忽略它。

  • 不支持重复的列名和非字符串列名

  • 对象类型列中的实际 Python 对象不受支持。尝试序列化这些对象时会引发有用的错误消息。

查看 完整文档

In [590]: import pytz

In [591]: 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=pytz.timezone("US/Eastern")),
   .....:         "i": pd.date_range("20130101", periods=3, freq="ns"),
   .....:     }
   .....: )
   .....: 

In [592]: df
Out[592]: 
   a  b  c    d      e  f          g                         h                             i
0  a  1  3  4.0   True  a 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1  b  2  4  5.0  False  b 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2  c  3  5  6.0   True  c 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002

In [593]: df.dtypes
Out[593]: 
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 [594]: df.to_feather("example.feather")

从 feather 文件中读取。

In [595]: result = pd.read_feather("example.feather")

In [596]: result
Out[596]: 
   a  b  c    d      e  f          g                         h                             i
0  a  1  3  4.0   True  a 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1  b  2  4  5.0  False  b 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2  c  3  5  6.0   True  c 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002

# we preserve dtypes
In [597]: result.dtypes
Out[597]: 
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 旨在忠实地序列化和反序列化 DataFrame ,支持所有 pandas 数据类型,包括扩展数据类型,如带时区的日期时间。

几点注意事项。

  • 不支持重复的列名和非字符串的列名。

  • pyarrow 引擎总是将索引写入输出,但 fastparquet 只写入非默认索引。这个额外的列可能会给那些没有预料到它的非 pandas 使用者带来问题。你可以通过 index 参数强制包含或省略索引,无论底层引擎是什么。

  • 索引级别名称,如果指定,必须是字符串。

  • pyarrow 引擎中,非字符串类型的分类数据类型可以序列化为 parquet,但反序列化时会变为它们的原始数据类型。

  • pyarrow 引擎保留了带有字符串类型的分类 dtypes 的 ordered 标志。fastparquet 不保留 ordered 标志。

  • 不支持的类型包括 Interval 和实际的 Python 对象类型。这些在尝试序列化时会引发一个有帮助的错误消息。Period 类型在 pyarrow >= 0.16.0 时得到支持。

  • pyarrow 引擎保留了扩展数据类型,例如可空整数和字符串数据类型(需要 pyarrow >= 0.16.0,并且需要扩展类型实现所需协议,参见 扩展类型文档)。

你可以指定一个 engine 来指导序列化。这可以是 pyarrowfastparquetauto 之一。如果未指定引擎,则检查 pd.options.io.parquet.engine 选项;如果这也是 auto,则尝试 pyarrow,并回退到 fastparquet

请参阅 pyarrowfastparquet 的文档。

备注

这些引擎非常相似,应该读取/写入几乎相同的 parquet 格式文件。pyarrow>=8.0.0 支持 timedelta 数据,fastparquet>=0.1.4 支持带时区的日期时间。这些库的区别在于它们有不同的底层依赖(fastparquet 使用 numba,而 pyarrow 使用一个 c 库)。

In [598]: 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 [599]: df
Out[599]: 
   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 [600]: df.dtypes
Out[600]: 
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 [601]: df.to_parquet("example_pa.parquet", engine="pyarrow")

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

从 parquet 文件读取。

In [603]: result = pd.read_parquet("example_fp.parquet", engine="fastparquet")

In [604]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow")

In [605]: result.dtypes
Out[605]: 
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 使用的默认数据类型。

In [606]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow", dtype_backend="pyarrow")

In [607]: result.dtypes
Out[607]: 
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 [608]: result = pd.read_parquet(
   .....:     "example_fp.parquet",
   .....:     engine="fastparquet",
   .....:     columns=["a", "b"],
   .....: )
   .....: 

In [609]: result = pd.read_parquet(
   .....:     "example_pa.parquet",
   .....:     engine="pyarrow",
   .....:     columns=["a", "b"],
   .....: )
   .....: 

In [610]: result.dtypes
Out[610]: 
a    object
b     int64
dtype: object

处理索引#

DataFrame 序列化为 parquet 可能会在输出文件中包含隐式索引作为一个或多个列。因此,这段代码:

In [611]: df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})

In [612]: df.to_parquet("test.parquet", engine="pyarrow")

如果你使用 pyarrow 进行序列化,会创建一个包含 列的 parquet 文件:a, b, 和 __index_level_0__。如果你使用 fastparquet,索引 可能写入也可能不写入 文件。

这个意外的多余列会导致一些数据库(如 Amazon Redshift)拒绝该文件,因为目标表中不存在该列。

如果你想在写入时省略一个数据框的索引,传递 index=Falseto_parquet():

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

这将创建一个仅包含两个预期列 ab 的 parquet 文件。如果你的 DataFrame 有一个自定义索引,当你将此文件加载到 DataFrame 中时,你将无法取回它。

传递 index=True总是 写入索引,即使这不是底层引擎的默认行为。

分区 Parquet 文件#

Parquet 支持基于一个或多个列的值对数据进行分区。

In [614]: df = pd.DataFrame({"a": [0, 0, 1, 1], "b": [0, 1, 0, 1]})

In [615]: 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 库。

警告

  • 强烈建议使用 conda 安装 pyarrow,因为 pyarrow 存在一些问题。

  • to_orc() 需要 pyarrow>=7.0.0。

  • read_orc()to_orc() 在 Windows 上尚不支持,您可以在 安装可选依赖项 找到有效的环境。

  • 有关支持的数据类型,请参阅 Arrow 中支持的 ORC 功能

  • 当前,在将数据框转换为ORC文件时,datetime列中的时区不会保留。

In [616]: 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 [617]: df
Out[617]: 
   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 [618]: df.dtypes
Out[618]: 
a            object
b             int64
c           float64
d              bool
e    datetime64[ns]
dtype: object

写入一个 orc 文件。

In [619]: df.to_orc("example_pa.orc", engine="pyarrow")

从orc文件中读取。

In [620]: result = pd.read_orc("example_pa.orc")

In [621]: result.dtypes
Out[621]: 
a            object
b             int64
c           float64
d              bool
e    datetime64[ns]
dtype: object

只读取一个orc文件的某些列。

In [622]: result = pd.read_orc(
   .....:     "example_pa.orc",
   .....:     columns=["a", "b"],
   .....: )
   .....: 

In [623]: result.dtypes
Out[623]: 
a    object
b     int64
dtype: object

SQL 查询#

pandas.io.sql 模块提供了一系列查询包装器,既便于数据检索,又减少了对于特定数据库API的依赖。

在可用的情况下,用户可能首先希望选择 Apache Arrow ADBC 驱动程序。这些驱动程序应提供最佳性能、空值处理和类型检测。

Added in version 2.2.0: 增加了对 ADBC 驱动的原生支持

有关ADBC驱动程序及其开发状态的完整列表,请参阅 ADBC驱动程序实现状态 文档。

在没有ADBC驱动程序或可能缺少功能的情况下,用户应选择安装SQLAlchemy及其数据库驱动程序库。此类驱动程序的示例包括用于PostgreSQL的 psycopg2 或用于MySQL的 pymysql 。对于 SQLite ,这是Python标准库中默认包含的。您可以在 SQLAlchemy文档 中找到每种SQL方言支持的驱动程序概览。

如果 SQLAlchemy 未安装,可以使用 sqlite3.Connection 代替 SQLAlchemy 引擎、连接或 URI 字符串。

另请参阅一些高级策略的 食谱示例

关键功能是:

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 [624]: from sqlalchemy import create_engine

# Create your engine.
In [625]: 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

42

2012-10-19

Y

-12.4

63

2012-10-20

Z

5.73

In [626]: import datetime

In [627]: c = ["id", "Date", "Col_1", "Col_2", "Col_3"]

In [628]: 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 [629]: data = pd.DataFrame(d, columns=c)

In [630]: data
Out[630]: 
   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 [631]: data.to_sql("data", con=engine)
Out[631]: 3

对于某些数据库,由于超出数据包大小限制,写入大型 DataFrame 可能会导致错误。这可以通过在调用 to_sql 时设置 chunksize 参数来避免。例如,以下代码每次以 1000 行的批次将 data 写入数据库:

In [632]: data.to_sql("data_chunked", con=engine, chunksize=1000)
Out[632]: 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

双精度

REAL

对象

string

TEXT

TEXT

bool

bool_

布尔值

datetime64[ns]

时间戳(微秒)

TIMESTAMP

datetime64[ns,tz]

timestamp(us,tz)

TIMESTAMPTZ

date32

日期

month_day_nano_interval

INTERVAL

binary

BINARY

BLOB

decimal128

DECIMAL [1]

列表

ARRAY [1]

struct

复合类型

[1]

脚注

如果你有兴趣在整个 DataFrame 的生命周期中尽可能地保留数据库类型,建议用户利用 read_sql()dtype_backend="pyarrow" 参数

# for roundtripping
with pg_dbapi.connect(uri) as conn:
    df2 = pd.read_sql("pandas_table", conn, dtype_backend="pyarrow")

这将防止您的数据被转换为传统的 pandas/NumPy 类型系统,该系统通常以无法回转的方式转换 SQL 类型。

如果ADBC驱动不可用,to_sql() 将尝试根据数据的dtype将其映射到适当的SQL数据类型。当您有dtype为``object``的列时,pandas将尝试推断数据类型。

您始终可以通过使用 dtype 参数指定所需列的 SQL 类型来覆盖默认类型。此参数需要一个字典,将列名映射到 SQLAlchemy 类型(或 sqlite3 回退模式的字符串)。例如,指定对字符串列使用 sqlalchemy 的 String 类型而不是默认的 Text 类型:

In [633]: from sqlalchemy.types import String

In [634]: data.to_sql("data_dtype", con=engine, dtype={"Col_1": String})
Out[634]: 3

备注

由于不同数据库对 timedelta 的支持有限,类型为 timedelta64 的列将以纳秒为单位的整数值写入数据库,并会发出警告。唯一的例外是使用 ADBC PostgreSQL 驱动程序时,timedelta 将作为 INTERVAL 写入数据库。

备注

category 数据类型的列将被转换为密集表示,就像使用 np.asarray(categorical) 得到的那样(例如,对于字符串类别,这将生成一个字符串数组)。因此,读取数据库表时 不会 生成一个类别。

日期时间数据类型#

使用ADBC或SQLAlchemy,to_sql() 能够写入时区朴素或时区感知的日期时间数据。然而,存储在数据库中的结果数据最终取决于所使用的数据库系统对日期时间数据支持的数据类型。

下表列出了一些常见数据库支持的日期时间数据类型。其他数据库方言可能具有不同的日期时间数据类型。

数据库

SQL 日期时间类型

时区支持

SQLite

TEXT

MySQL

TIMESTAMPDATETIME

PostgreSQL

TIMESTAMPTIMESTAMP WITH TIME ZONE

是的

当将时区感知的数据写入不支持时区的数据库时,数据将被写为相对于时区的本地时间的时区天真时间戳。

read_sql_table() 也能够读取时区感知或无时区的日期时间数据。当读取 TIMESTAMP WITH TIME ZONE 类型时,pandas 会将数据转换为 UTC。

插入方法#

参数 method 控制使用的 SQL 插入子句。可能的值有:

  • None: 使用标准的 SQL INSERT 子句(每行一个)。

  • 'multi': 在单个 INSERT 子句中传递多个值。它使用了一种 特殊 的 SQL 语法,不是所有后端都支持。这通常为 PrestoRedshift 等分析数据库提供更好的性能,但如果表包含许多列,对于传统 SQL 后端性能会更差。更多信息请查看 SQLAlchemy 文档

  • 带有签名 (pd_table, conn, keys, data_iter) 的可调用对象:这可以用于基于特定后端方言特性实现更高效的插入方法。

使用 PostgreSQL COPY 子句 的可调用示例:

# 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 [635]: pd.read_sql_table("data", engine)
Out[635]: 
   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 驱动程序将直接将数据库类型映射回箭头类型。对于其他驱动程序,请注意 pandas 通过查询输出推断列 dtypes,而不是通过查找物理数据库模式中的数据类型。例如,假设 userid 是表中的一个整数列。那么,直观地说,select userid ... 将返回整数值序列,而 select cast(userid as text) ... 将返回对象值(str)序列。因此,如果查询输出为空,则所有结果列将作为对象值返回(因为它们是最通用的)。如果您预见到您的查询有时会产生空结果,您可能希望之后显式类型转换以确保 dtype 完整性。

你也可以将列名指定为 DataFrame 索引,并指定要读取的列的子集。

In [636]: pd.read_sql_table("data", engine, index_col="id")
Out[636]: 
    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 [637]: pd.read_sql_table("data", engine, columns=["Col_1", "Col_2"])
Out[637]: 
  Col_1  Col_2
0     X  27.50
1     Y -12.50
2     Z   5.73

并且你可以明确地强制将列解析为日期:

In [638]: pd.read_sql_table("data", engine, parse_dates=["Date"])
Out[638]: 
   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() 检查表是否存在

Schema 支持#

通过 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 [639]: pd.read_sql_query("SELECT * FROM data", engine)
Out[639]: 
   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 [640]: pd.read_sql_query("SELECT id, Col_1, Col_2 FROM data WHERE id = 42;", engine)
Out[640]: 
   id Col_1  Col_2
0  42     Y  -12.5

read_sql_query() 函数支持 chunksize 参数。指定这个参数将返回一个通过查询结果块的迭代器:

In [641]: df = pd.DataFrame(np.random.randn(20, 3), columns=list("abc"))

In [642]: df.to_sql(name="data_chunks", con=engine, index=False)
Out[642]: 20
In [643]: for chunk in pd.read_sql_query("SELECT * FROM data_chunks", engine, chunksize=5):
   .....:     print(chunk)
   .....: 
          a         b         c
0 -0.395347 -0.822726 -0.363777
1  1.676124 -0.908102 -1.391346
2 -1.094269  0.278380  1.205899
3  1.503443  0.932171 -0.709459
4 -0.645944 -1.351389  0.132023
          a         b         c
0  0.210427  0.192202  0.661949
1  1.690629 -1.046044  0.618697
2 -0.013863  1.314289  1.951611
3 -1.485026  0.304662  1.194757
4 -0.446717  0.528496 -0.657575
          a         b         c
0 -0.876654  0.336252  0.172668
1  0.337684 -0.411202 -0.828394
2 -0.244413  1.094948  0.087183
3  1.125934 -1.480095  1.205944
4 -0.451849  0.452214 -2.208192
          a         b         c
0 -2.061019  0.044184 -0.017118
1  1.248959 -0.675595 -1.908296
2 -0.125934  1.491974  0.648726
3  0.391214  0.438609  1.634248
4  1.208707 -1.535740  1.620399

引擎连接示例#

要连接到 SQLAlchemy,您可以使用 create_engine() 函数从数据库 URI 创建一个引擎对象。您只需要为每个要连接的数据库创建一次引擎。

from sqlalchemy import create_engine

engine = create_engine("postgresql://scott:tiger@localhost:5432/mydatabase")

engine = create_engine("mysql+mysqldb://scott:tiger@localhost/foo")

engine = create_engine("oracle://scott:tiger@127.0.0.1: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 [644]: import sqlalchemy as sa

In [645]: pd.read_sql(
   .....:     sa.text("SELECT * FROM data where Col_1=:col1"), engine, params={"col1": "X"}
   .....: )
   .....: 
Out[645]: 
   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 表达式来表达条件。

In [646]: metadata = sa.MetaData()

In [647]: 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 [648]: pd.read_sql(sa.select(data_table).where(data_table.c.Col_3 is True), engine)
Out[648]: 
Empty DataFrame
Columns: [index, Date, Col_1, Col_2, Col_3]
Index: []

你可以将 SQLAlchemy 表达式与传递给 read_sql() 的参数结合使用 sqlalchemy.bindparam()

In [649]: import datetime as dt

In [650]: expr = sa.select(data_table).where(data_table.c.Date > sa.bindparam("date"))

In [651]: pd.read_sql(expr, engine, params={"date": dt.datetime(2010, 10, 18)})
Out[651]: 
   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 回退#

不使用 SQLAlchemy 的情况下支持使用 sqlite。此模式需要一个遵循 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 读取/写入的功能。

完整的文档可以在这里找到 here

Stata 格式#

写入stata格式#

方法 DataFrame.to_stata() 将把一个 DataFrame 写入一个 .dta 文件。该文件的格式版本总是 115(Stata 12)。

In [652]: df = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [653]: df.to_stata("stata.dta")

Stata 数据文件支持的数据类型有限;只有长度不超过244个字符的字符串、int8int16int32float32float64 可以存储在 .dta 文件中。此外,Stata 保留某些值来表示缺失数据。导出超出特定数据类型在Stata中允许范围的非缺失值将重新调整变量的大小。例如,int8 值在Stata中限制在-127到100之间,因此值超过100的变量将触发转换为``int16``。浮点数据类型中的``nan`` 值存储为基本缺失数据类型(在*Stata*中为``.’’)。

备注

无法导出整数数据类型的缺失数据值。

Stata 写入器优雅地处理包括 int64booluint8uint16uint32 在内的其他数据类型,通过转换为可以表示数据的最小支持类型。例如,类型为 uint8 的数据如果所有值都小于 100(Stata 中非缺失 int8 数据的上限),则会被转换为 int8,或者,如果值超出此范围,则变量被转换为 int16

警告

int64 转换为 float64 可能会导致精度丢失,如果 int64 值大于 2**53。

警告

StataWriterDataFrame.to_stata() 仅支持包含最多 244 个字符的固定宽度字符串,这是由版本 115 dta 文件格式施加的限制。尝试写入字符串长度超过 244 个字符的 Stata dta 文件会引发 ValueError

从 Stata 格式读取#

顶级函数 read_stata 将读取一个 dta 文件并返回一个 DataFrame 或一个 pandas.api.typing.StataReader,可以用来增量读取文件。

In [654]: pd.read_stata("stata.dta")
Out[654]: 
   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 会生成一个 pandas.api.typing.StataReader 实例,该实例可以用来一次从文件中读取 chunksize 行。StataReader 对象可以用作迭代器。

In [655]: 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 [656]: with pd.read_stata("stata.dta", iterator=True) as reader:
   .....:     chunk1 = reader.read(5)
   .....:     chunk2 = reader.read(5)
   .....: 

目前,index 被作为一列检索。

参数 convert_categoricals 指示是否应读取值标签并使用它们创建一个 Categorical 变量。值标签也可以通过函数 value_labels 获取,这需要在之前调用 read()

参数 convert_missing 指示在 Stata 中是否应保留缺失值表示。如果为 False``(默认),缺失值表示为 ``np.nan。如果为 True,缺失值使用 StataMissingValue 对象表示,并且包含缺失值的列将具有 object 数据类型。

备注

read_stata()StataReader 支持 .dta 格式 113-115 (Stata 10-12), 117 (Stata 13), 和 118 (Stata 14)。

备注

设置 preserve_dtypes=False 将向上转换为标准的 pandas 数据类型:所有整数类型的 int64 和浮点数据的 float64。默认情况下,导入时会保留 Stata 数据类型。

备注

所有 StataReader 对象,无论是通过 read_stata() 创建的(当使用 iterator=Truechunksize 时)还是手动实例化的,都必须作为上下文管理器使用(例如 with 语句)。虽然 close() 方法可用,但其使用不受支持。它不是公共 API 的一部分,并且将在未来版本中无警告地移除。

分类数据#

Categorical 数据可以导出为带有值标签的 Stata 数据文件。导出的数据包括基础类别代码作为整数数据值和类别作为值标签。Stata 没有明确的 Categorical 等价物,并且在导出时会丢失关于变量是否有序的信息。

警告

Stata 仅支持字符串值标签,因此在导出数据时会对类别调用 str。导出具有非字符串类别的 Categorical 变量会产生警告,如果类别的 str 表示不唯一,则可能导致信息丢失。

标记数据同样可以从 Stata 数据文件中作为 Categorical 变量导入,使用关键字参数 convert_categoricals``(默认为 ``True)。关键字参数 order_categoricals``(默认为 ``True)决定导入的 Categorical 变量是否有序。

备注

在导入分类数据时,由于 Categorical 变量始终使用 -1n-1 之间的整数数据类型,其中 n 是类别数,因此 Stata 数据文件中变量的值不会被保留。如果需要原始的 Stata 数据文件中的值,可以通过设置 convert_categoricals=False 来导入这些原始数据(但不包括变量标签)。由于原始的 Stata 数据值与导入的分类变量的类别代码之间存在简单的映射关系,因此可以匹配原始值:缺失值被分配代码 -1,最小的原始值被分配 0,第二小的被分配 1,依此类推,直到最大的原始值被分配代码 n-1

备注

Stata 支持部分标记的系列。这些系列对某些数据值有值标签,但不是所有数据值都有。导入部分标记的系列将生成一个 Categorical ,对于有标签的值使用字符串类别,对于没有标签的值使用数字类别。

SAS 格式#

顶级函数 read_sas() 可以读取(但不能写入)SAS XPORT (.xpt) 和 SAS7BDAT (.sas7bdat) 格式文件。

SAS 文件只包含两种值类型:ASCII 文本和浮点值(通常是 8 字节,但有时会被截断)。对于 xport 文件,没有自动类型转换为整数、日期或分类变量。对于 SAS7BDAT 文件,格式代码可能允许日期变量自动转换为日期。默认情况下,整个文件被读取并以 DataFrame 形式返回。

指定一个 chunksize 或使用 iterator=True 来获取读取器对象(XportReaderSAS7BDATReader)以增量读取文件。读取器对象还包含一些属性,这些属性包含有关文件及其变量的附加信息。

读取一个 SAS7BDAT 文件:

df = pd.read_sas("sas_data.sas7bdat")

获取一个迭代器并一次读取一个 XPORT 文件的 100,000 行:

def do_something(chunk):
    pass


with pd.read_sas("sas_xport.xpt", chunk=100000) as rdr:
    for chunk in rdr:
        do_something(chunk)

xport 文件格式的规范 可以从 SAS 网站获取。

SAS7BDAT 格式的官方文档不可用。

SPSS 格式#

顶级函数 read_spss() 可以读取(但不能写入)SPSS SAV (.sav) 和 ZSAV (.zsav) 格式文件。

SPSS 文件包含列名。默认情况下,整个文件被读取,分类列被转换为 pd.Categorical,并返回一个包含所有列的 DataFrame

指定 usecols 参数以获取列的子集。指定 convert_categoricals=False 以避免将分类列转换为 pd.Categorical

读取一个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.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")

在写作时,速度方面的前三个功能是 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)

在读取时,速度方面前三的功能是 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.compress, test.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