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

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

格式类型

数据描述

读取器

编写器

文本

CSV

read_csv

to_csv

文本

固定宽度文本文件

read_fwf

文本

JSON

read_json

to_json

文本

HTML

read_html

to_html

文本

LaTeX

Styler.to_latex

文本

XML

read_xml

to_xml

文本

本地剪贴板

read_clipboard

to_clipboard

二进制

MS Excel

read_excel

to_excel

二进制

OpenDocument

read_excel

二进制

HDF5 格式

read_hdf

to_hdf

二进制

Feather 格式

read_feather

to_feather

二进制

Parquet 格式

read_parquet

to_parquet

二进制

ORC 格式

read_orc

to_orc

二进制

Stata

read_stata

to_stata

二进制

SAS

read_sas

二进制

SPSS

read_spss

二进制

Python Pickle 格式

read_pickle

to_pickle

SQL

SQL

read_sql

to_sql

SQL

Google BigQuery

read_gbq

to_gbq

这里 是对其中一些 I/O 方法的非正式性能比较。

注意

对于使用 StringIO 类的示例,请确保使用 from io import StringIO 在 Python 3 中导入它。

CSV & 文本文件#

用于读取文本文件(又名平面文件)的主力函数是 read_csv()。有关一些高级策略,请参见 食谱

解析选项#

read_csv() 接受以下常用参数

基本#

filepath_or_buffer各种

可以是文件路径(strpathlib.Pathpy:py._path.local.LocalPath)、URL(包括 http、ftp 和 S3 位置)或任何具有 read() 方法的对象(如打开的文件或 StringIO)。

sepstr,默认值为 ','(用于 read_csv()),\t(用于 read_table()

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

delimiterstr,默认值为 None

sep 的备用参数名称。

delim_whitespaceboolean,默认值为 False

指定是否使用空格(例如 ' ''\t')作为分隔符。等效于设置 sep='\s+'。如果将此选项设置为 True,则不应为 delimiter 参数传递任何内容。

列和索引位置以及名称#

headerint 或 int 列表,默认值为 'infer'

用作列名的行号,以及数据的开始位置。默认行为是推断列名:如果未传递任何名称,则行为与 header=0 相同,并且从文件的首行推断列名;如果显式传递列名,则行为与 header=None 相同。显式传递 header=0 以便能够替换现有名称。

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

names类数组,默认值 None

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

index_colint、str、int/str 序列或 False,可选,默认值 None

用作 DataFrame 行标签的列(s),以字符串名称或列索引给出。如果给定 int/str 序列,则使用多级索引。

注意

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 并保留元素顺序,请使用 pd.read_csv(data, usecols=['foo', 'bar'])[['foo', 'bar']] 来获取 ['foo', 'bar'] 顺序的列,或者使用 pd.read_csv(data, usecols=['foo', 'bar'])[['bar', 'foo']] 来获取 ['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 设置来保留并不会解释数据类型。如果指定了转换器,它们将应用于数据类型转换。

版本 1.5.0 中新增: 添加了对 defaultdict 的支持。指定一个 defaultdict 作为输入,其中默认值决定了未明确列出的列的数据类型。

dtype_backend{“numpy_nullable”, “pyarrow”}, 默认使用 NumPy 支持的 DataFrame

要使用的 dtype_backend,例如,DataFrame 应该使用 NumPy 数组,当设置 “numpy_nullable” 时,所有具有可空实现的数据类型都使用可空数据类型,如果设置 “pyarrow”,则所有数据类型都使用 pyarrow。

dtype_backends 仍处于实验阶段。

版本 2.0 中新增。

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

要使用的解析器引擎。C 和 pyarrow 引擎更快,而 python 引擎目前功能更完善。目前只有 pyarrow 引擎支持多线程。

版本 1.4.0 中新增: “pyarrow” 引擎作为实验性引擎添加,此引擎不支持某些功能,或者这些功能可能无法正常工作。

convertersdict, 默认 None

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

true_valueslist, 默认 None

视为 True 的值。

false_valueslist, 默认 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整数,默认值为 0

要跳过的文件末尾的行数(在 engine=’c’ 时不支持)。

nrows整数,默认值为 None

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

low_memory布尔值,默认值为 True

在内部以块的形式处理文件,从而在解析时降低内存使用量,但可能会导致混合类型推断。为了确保没有混合类型,请设置 False,或使用 dtype 参数指定类型。请注意,无论如何,整个文件都将读取到单个 DataFrame 中,使用 chunksizeiterator 参数以块的形式返回数据。(仅在 C 解析器中有效)

memory_map布尔值,默认值为 False

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

NA 和缺失数据处理#

na_values标量、字符串、列表或字典,默认值为 None

要识别为 NA/NaN 的其他字符串。如果传递字典,则为每列指定 NA 值。有关默认情况下解释为 NaN 的值的列表,请参见下面的 na 值常量

keep_default_na布尔值,默认值为 True

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

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

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

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

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

请注意,如果将 na_filter 传递为 False,则将忽略 keep_default_nana_values 参数。

na_filter布尔值,默认值为 True

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

verbose布尔值,默认值为 False

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

skip_blank_lines布尔值,默认值为 True

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

日期时间处理#

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

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

  • 如果为 [[1, 3]] -> 合并第 1 和第 3 列,并解析为单个日期列。

  • 如果为 {'foo': [1, 3]} -> 将第 1、3 列解析为日期,并将结果命名为 'foo'。

注意

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

infer_datetime_format布尔值,默认值为 False

如果为 True 且为某列启用了 parse_dates,则尝试推断日期时间格式以加快处理速度。

自版本 2.0.0 起弃用: 此参数的严格版本现在是默认值,传递它没有效果。

keep_date_col布尔值,默认值为 False

如果为 True 且 parse_dates 指定了合并多个列,则保留原始列。

date_parser函数,默认值为 None

用于将字符串列序列转换为日期时间实例数组的函数。默认情况下使用 dateutil.parser.parser 进行转换。pandas 将尝试以三种不同的方式调用 date_parser,如果出现异常,则继续执行下一种方式:1) 将一个或多个数组(由 parse_dates 定义)作为参数传递;2) 将 parse_dates 定义的列中的字符串值(按行)连接到一个数组中并传递;3) 使用一个或多个字符串(对应于 parse_dates 定义的列)作为参数,为每一行调用一次 date_parser。

从版本 2.0.0 开始弃用: 使用 date_format 代替,或者以 object 格式读取,然后根据需要应用 to_datetime()

date_formatstr 或 column -> format 的字典,默认值为 None

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

版本 2.0.0 中的新增功能。

dayfirst布尔值,默认值为 False

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

cache_dates布尔值,默认值为 True

如果为 True,则使用唯一转换日期的缓存来应用日期时间转换。在解析重复的日期字符串(尤其是带有时区偏移的字符串)时,可能会显著提高速度。

迭代#

iterator布尔值,默认值为 False

返回 TextFileReader 对象以进行迭代或使用 get_chunk() 获取块。

chunksizeint,默认值为 None

返回 TextFileReader 对象以进行迭代。请参阅下面的 迭代和分块

引用、压缩和文件格式#

compression{'infer', 'gzip', 'bz2', 'zip', 'xz', 'zstd', None, dict},默认值为 'infer'

用于对磁盘上的数据进行实时解压缩。如果为 'infer',则如果 filepath_or_buffer 是以 ‘.gz’,‘.bz2’,‘.zip’,‘.xz’,‘.zst’ 结尾的路径,则分别使用 gzip、bz2、zip、xz 或 zstandard,否则不进行解压缩。如果使用 'zip',则 ZIP 文件必须只包含一个要读取的数据文件。设置为 None 表示不进行解压缩。也可以是一个字典,其键 'method' 设置为 {'zip', 'gzip', 'bz2', 'zstd'} 中的一个,其他键值对将转发到 zipfile.ZipFile, gzip.GzipFile, bz2.BZ2Filezstandard.ZstdDecompressor。例如,以下内容可以用于更快的压缩和创建可重复的 gzip 存档:compression={'method': 'gzip', 'compresslevel': 1, 'mtime': 1}

版本 1.2.0 中的变更: 以前的版本将 'gzip' 的字典条目转发到 gzip.open

thousandsstr,默认 None

千位分隔符。

decimalstr,默认 '.'

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

float_precisionstring,默认 None

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

lineterminatorstr (长度为 1),默认 None

用于将文件拆分为行的字符。仅在使用 C 解析器时有效。

quotecharstr (长度为 1)

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

quotingint 或 csv.QUOTE_* 实例,默认值为 0

根据 csv.QUOTE_* 常量控制字段引号行为。使用以下之一:QUOTE_MINIMAL (0)、QUOTE_ALL (1)、QUOTE_NONNUMERIC (2) 或 QUOTE_NONE (3)。

doublequote布尔值,默认值为 True

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

escapecharstr (长度为 1),默认值为 None

当引号为 QUOTE_NONE 时,用于转义分隔符的单字符字符串。

commentstr,默认值为 None

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

encodingstr,默认值为 None

在读写时使用的 UTF 编码(例如 'utf-8')。Python 标准编码列表.

dialectstr 或 csv.Dialect 实例,默认值为 None

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

错误处理#

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

指定遇到错误行(字段过多的一行)时该怎么做。允许的值为

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

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

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

版本 1.3.0 中新增。

指定列数据类型#

您可以为整个 DataFrame 或单个列指定数据类型

In [9]: import numpy as np

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

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

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

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

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

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

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

幸运的是,pandas 提供了多种方法来确保您的列仅包含一个 dtype。如果您不熟悉这些概念,可以查看 此处 了解有关 dtypes 的更多信息,以及 此处 了解有关 pandas 中 object 转换的更多信息。

例如,您可以使用 read_csv()converters 参数

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

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

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

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

或者,您可以在读取数据后使用 to_numeric() 函数强制转换 dtypes,

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

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

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

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

这将把所有有效的解析转换为浮点数,并将无效解析保留为NaN

最终,如何处理包含混合数据类型的列取决于您的特定需求。在上面的情况下,如果您想将数据异常转换为NaN,那么to_numeric()可能是您最好的选择。但是,如果您希望所有数据都被强制转换,无论类型如何,那么使用read_csv()converters参数肯定值得尝试。

注意

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

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 数据类型,以及其他块的str 数据类型,这是由于读取的数据的混合数据类型造成的。重要的是要注意,整个列将被标记为dtypeobject,它用于包含混合数据类型的列。

设置dtype_backend="numpy_nullable" 将导致每列都具有可空数据类型。

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

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

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

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

指定分类数据类型#

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

The 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

The usecols 参数也可以用来指定最终结果中不使用的列

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

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

注释和空行#

忽略行注释和空行#

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

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

In [68]: print(data)

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

4,5,6

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

如果 skip_blank_lines=False,则 read_csv 不会忽略空白行

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

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

警告

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

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

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

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

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

如果同时指定了 headerskiprows,则 header 将相对于 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 数据#

对于编码的 Unicode 数据,应使用 encoding 参数,这将导致字节字符串在结果中被解码为 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[ns]', name='date', freq=None)

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

您可以将列列表指定给 parse_dates,生成的日期列将被添加到输出的前面(以便不影响现有的列顺序),新列名将是组成列名的串联。

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

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

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

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

默认情况下,解析器会删除组成日期列,但您可以通过 keep_date_col 关键字选择保留它们。

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

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

[6 rows x 7 columns]

请注意,如果您希望将多个列合并成一个日期列,则必须使用嵌套列表。换句话说,parse_dates=[1, 2] 表示第二列和第三列应该分别解析为单独的日期列,而 parse_dates=[[1, 2]] 表示这两列应该解析为一个单独的列。

您也可以使用字典来指定自定义名称列。

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

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

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

重要的是要记住,如果多个文本列要解析成一个日期列,那么一个新列将被添加到数据的前面。 index_col 的指定是基于这组新列而不是原始数据列。

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

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

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

注意

如果列或索引包含不可解析的日期,则整个列或索引将作为对象数据类型返回,不会改变。对于非标准日期时间解析,请在 pd.read_csv 之后使用 to_datetime()

注意

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

自版本 2.2.0 起已弃用: 在 read_csv 中组合日期列已弃用。请改用 pd.to_datetime 对相关结果列进行操作。

日期解析函数#

最后,解析器允许您指定自定义的 date_format。从性能角度来看,您应该按照以下顺序尝试这些解析日期的方法

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

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

解析包含混合时区的 CSV 文件#

pandas 无法原生表示包含混合时区的列或索引。如果您的 CSV 文件包含包含混合时区的列,即使使用 parse_dates,默认结果也将是包含字符串的 object 数据类型列。要将混合时区值解析为日期时间列,请读入为 object 数据类型,然后使用 to_datetime() 并设置 utc=True

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

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

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

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

推断日期时间格式#

以下是一些可以推断的日期时间字符串示例(均代表 2011 年 12 月 30 日 00:00:00)

  • “20111230”

  • “2011/12/30”

  • “20111230 00:00:00”

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

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

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

请注意,格式推断对 dayfirst 很敏感。使用 dayfirst=True,它将推断 “01/12/2011” 为 12 月 1 日。使用 dayfirst=False(默认值),它将推断 “01/12/2011” 为 1 月 12 日。

如果您尝试解析日期字符串列,pandas 将尝试从第一个非 NaN 元素推断格式,然后使用该格式解析该列的其余部分。如果 pandas 无法推断格式(例如,如果您的第一个字符串是 '01 December US/Pacific 2000'),则会发出警告,并且每个行将由 dateutil.parser.parse 单独解析。解析日期的最安全方法是显式设置 format=

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

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

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

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

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

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

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

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

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

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

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

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

国际日期格式#

虽然美国日期格式通常为 MM/DD/YYYY,但许多国际格式使用 DD/MM/YYYY。为了方便起见,提供了一个 dayfirst 关键字

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

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

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

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

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

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

版本 1.2.0 中的新增功能。

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

In [139]: import io

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

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

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

指定浮点数转换方法#

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

In [143]: val = "0.3066101993807095471566981359501369297504425048828125"

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

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

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

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

千位分隔符#

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

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

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

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

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

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

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

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

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

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

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

NA 值#

要控制哪些值被解析为缺失值(由 NaN 表示),请在 na_values 中指定一个字符串。如果指定一个字符串列表,则列表中的所有值都被视为缺失值。如果指定一个数字(一个 float,例如 5.0 或一个 integer,例如 5),则相应的等效值也将隐含缺失值(在本例中,实际上 [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

布尔值#

常见的 TrueFalseTRUEFALSE 都被识别为布尔值。有时你可能想识别其他值作为布尔值。为此,请使用 true_valuesfalse_values 选项,如下所示

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

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

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

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

处理“错误”行#

某些文件可能包含格式错误的行,字段过少或过多。字段过少的行将在尾随字段中填充 NA 值。默认情况下,字段过多的行将引发错误。

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

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

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

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

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

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

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

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

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

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

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

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

您可以选择跳过错误行。

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

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

1.4.0 版新增功能。

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

In [164]: external_list = []

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

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

注意

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

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

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

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

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

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

您还可以使用 usecols 参数来消除某些行中出现但在其他行中未出现的冗余列数据。

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

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

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

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

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

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

File ~/work/pandas/pandas/pandas/io/parsers/base_parser.py:979, in ParserBase._validate_usecols_names(self, usecols, names)
    977 missing = [c for c in usecols if c not in names]
    978 if len(missing) > 0:
--> 979     raise ValueError(
    980         f"Usecols do not match columns, columns expected but not found: "
    981         f"{missing}"
    982     )
    984 return usecols

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

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

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

方言#

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

假设您的数据包含未封闭的引号。

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

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

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

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

In [175]: import csv

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

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

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

所有方言选项都可以通过关键字参数单独指定。

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

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

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

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

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

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

解析器尽一切努力“做正确的事”并且不脆弱。类型推断是一件大事。如果可以将列强制转换为整数数据类型而不会改变内容,解析器将执行此操作。任何非数字列都将作为对象数据类型出现,与其他 pandas 对象一样。

引号和转义字符#

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

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

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

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

固定宽度列的文件#

虽然 read_csv() 读取分隔符数据,但 read_fwf() 函数适用于具有已知固定列宽的数据文件。 read_fwf 函数的参数与 read_csv 基本上相同,但有两个额外的参数,并且 delimiter 参数的用法不同。

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

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

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

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

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

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

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

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

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

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

请注意,当指定 header=None 参数时,解析器会自动选择列名 X.<列号>。或者,您也可以只为连续列提供列宽度。

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

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

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

解析器会处理列周围的额外空格,因此在文件中列之间有额外的分隔符是可以的。

默认情况下,read_fwf 会尝试通过使用文件的头 100 行来推断文件的 colspecs。它只能在列对齐且通过提供的 delimiter(默认分隔符是空格)正确分隔的情况下执行此操作。

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

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

read_fwf 支持 dtype 参数,用于指定要解析的列的类型,使其不同于推断的类型。

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

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

索引#

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

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

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

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

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

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

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

请注意,日期没有自动解析。在这种情况下,您需要像以前一样操作

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

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

读取具有 MultiIndex 的索引#

假设您的数据由两列索引

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

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

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

read_csvindex_col 参数可以接受列号列表,将多列转换为返回对象的索引的 MultiIndex

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

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

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

读取具有 MultiIndex 的列#

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

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

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

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

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

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


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

read_csv 也能解释更常见的格式的多列索引。

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

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

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

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

注意

如果未指定 index_col(例如,您没有索引,或者使用 df.to_csv(..., index=False) 写入它),那么列索引上的任何 names丢失

自动“嗅探”分隔符#

read_csv 能够推断分隔符(不一定用逗号分隔)文件,因为 pandas 使用了 csv 模块的 csv.Sniffer 类。为此,您必须指定 sep=None

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

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

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

读取多个文件以创建单个 DataFrame#

最好使用 concat() 来组合多个文件。有关示例,请参见 cookbook

逐块迭代文件#

假设您希望懒惰地迭代一个(可能非常大的)文件,而不是将整个文件读入内存,例如以下内容

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

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

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

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

通过为 read_csv 指定 chunksize,返回值将是类型为 TextFileReader 的可迭代对象

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

版本 1.2 中的变更: read_csv/json/sas 在迭代文件时返回上下文管理器。

指定 iterator=True 也会返回 TextFileReader 对象

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

指定解析器引擎#

Pandas 目前支持三种引擎:C 引擎、python 引擎和实验性的 pyarrow 引擎(需要 pyarrow 包)。通常,pyarrow 引擎在较大的工作负载上速度最快,并且在大多数其他工作负载上的速度与 C 引擎相当。python 引擎在大多数工作负载上的速度往往比 pyarrow 和 C 引擎慢。但是,pyarrow 引擎比 C 引擎的鲁棒性差得多,C 引擎与 Python 引擎相比缺少一些功能。

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

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

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

  • skipfooter

  • sep=Nonedelim_whitespace=False

除非使用 engine='python' 显式选择 python 引擎,否则指定上述任何选项都会产生 ParserWarning

pyarrow 引擎不支持的选项(不在上述列表中)包括

  • float_precision

  • 分块大小

  • 注释

  • 行数

  • 千位分隔符

  • 内存映射

  • 方言

  • 错误行处理

  • 以空格分隔

  • 引用

  • 行终止符

  • 转换器

  • 小数点

  • 迭代器

  • 日期优先

  • 推断日期时间格式

  • 详细模式

  • 跳过初始空格

  • 低内存模式

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

读取/写入远程文件#

您可以将 URL 传递给 pandas 的许多 IO 函数以读取或写入远程文件 - 以下示例显示了读取 CSV 文件

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

版本 1.3.0 中新增。

可以通过将包含头键值映射的字典传递给 storage_options 关键字参数来发送自定义头,如下所示

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

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

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

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

您也可以将参数直接传递给后端驱动程序。由于 fsspec 不使用 AWS_S3_HOST 环境变量,我们可以直接定义一个包含 endpoint_url 的字典,并将该对象传递给 storage option 参数

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

更多示例配置和文档可以在 S3Fs 文档 中找到。

如果您没有 S3 凭据,您仍然可以通过指定匿名连接来访问公共数据,例如

版本 1.2.0 中的新增功能。

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

fsspec 还允许使用复杂的 URL,用于访问压缩存档中的数据、本地缓存文件等等。要本地缓存上面的示例,您需要修改调用为

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

在这里,我们指定“anon”参数用于实现的“s3”部分,而不是用于缓存实现。请注意,这只会将缓存到会话期间的临时目录,但您也可以指定永久存储。

写入数据#

写入 CSV 格式#

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: 日期时间对象的格式字符串

写入格式化字符串#

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

  • buf 默认值为 None,例如 StringIO 对象

  • columns 默认值为 None,要写入的列

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

  • na_rep 默认值为 NaN,NA 值的表示形式

  • formatters 默认值为 None,一个字典(按列),每个字典都包含一个函数,该函数接受一个参数并返回一个格式化的字符串

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

  • sparsify 默认值为 True,设置为 False 用于具有层次索引的 DataFrame,以便在每行打印每个多级索引键。

  • 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

    类似于 {索引 -> [索引], 列 -> [列], 数据 -> [值]} 的字典

    记录

    类似于 [{列 -> 值}, … , {列 -> 值}] 的列表

    索引

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

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

    仅值数组

    表格

    遵循 JSON 表格模式

  • 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 [230]: dfj = pd.DataFrame(np.random.randn(5, 2), columns=list("AB"))

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

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

方向选项#

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

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

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

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

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

列方向DataFrame 的默认值)将数据序列化为嵌套的 JSON 对象,其中列标签充当主索引

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

# Not available for Series

索引方向Series 的默认值)类似于列方向,但索引标签现在是主要的

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

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

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

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

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

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

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

# Not available for Series

拆分方向将数据序列化为包含值、索引和列的单独条目的 JSON 对象。对于 Series,还会包含名称

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

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

表格方向将数据序列化为 JSON 表格模式,允许保留元数据,包括但不限于数据类型和索引名称。

注意

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

日期处理#

以 ISO 日期格式写入

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

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

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

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

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

以 ISO 日期格式写入,包含微秒

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

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

纪元时间戳,以秒为单位

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

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

写入文件,包含日期索引和日期列

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

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

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

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

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

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

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

回退行为#

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

  • 如果数据类型不受支持(例如 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 [261]: pd.DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json(default_handler=str)
Out[261]: '{"0":{"0":"(1+0j)","1":"(2+0j)","2":"(1+2j)"}}'

读取 JSON#

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

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

  • typ : 要恢复的对象类型(series 或 frame),默认值为 'frame'

  • orient :

    Series
    • 默认值为 index

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

    DataFrame
    • 默认值为 columns

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

    JSON 字符串的格式

    split

    类似于 {索引 -> [索引], 列 -> [列], 数据 -> [值]} 的字典

    记录

    类似于 [{列 -> 值}, … , {列 -> 值}] 的列表

    索引

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

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

    仅值数组

    表格

    遵循 JSON 表格模式

  • 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,请确保在此处传递相同的选项,以便解码产生合理的结果,有关概述,请参阅 方向选项

数据转换#

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

注意

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

  • 它以 '_at' 结尾

  • 它以 '_time' 结尾。

  • 它以 'timestamp' 开头。

  • 它是 'modified'

  • 它是 'date'

警告

读取 JSON 数据时,自动强制转换为数据类型有一些怪癖。

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

  • 如果可以安全地完成,则 float 数据的列将被转换为 integer,例如 1. 的列。

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

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

从 JSON 字符串读取

In [262]: from io import StringIO

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

从文件读取

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

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

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

指定数据类型进行转换

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

保留字符串索引

In [267]: from io import StringIO

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

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

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

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

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

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

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

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

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

以纳秒写入的日期需要以纳秒读回

In [277]: from io import StringIO

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

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

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

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

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

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

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

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

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

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

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

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

规范化#

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

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

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

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

max_level 参数提供了对要结束规范化的级别的更多控制。使用 max_level=1,以下代码段将对提供的字典的第 1 级嵌套进行规范化。

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

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

行分隔 JSON#

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

对于行分隔 JSON 文件,pandas 还可以返回一个迭代器,该迭代器一次读取 chunksize 行。这对于大型文件或从流中读取很有用。

In [295]: from io import StringIO

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

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

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

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

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

也可以使用 pyarrow 读取器通过指定 engine="pyarrow" 来读取行分隔 JSON。

In [301]: from io import BytesIO

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

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

版本 2.0.0 中的新增功能。

表模式#

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

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

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

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

schema 字段包含 fields 键,该键本身包含列名到类型对的列表,包括 IndexMultiIndex(有关类型的列表,请参见下文)。如果(多级)索引是唯一的,则 schema 字段还包含 primaryKey 字段。

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

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

pandas 类型

Table Schema 类型

int64

integer

float64

number

bool

boolean

datetime64[ns]

datetime

timedelta64[ns]

duration

categorical

any

object

str

关于生成的表格模式的一些说明

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

  • 所有日期在序列化时都转换为 UTC。即使是时区无感知的值,也被视为 UTC,偏移量为 0。

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

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

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

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

    In [316]: s_dupe = pd.Series([1, 2], index=[1, 1])
    
    In [317]: build_table_schema(s_dupe)
    Out[317]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
      {'name': 'values', 'type': 'integer'}],
     'pandas_version': '1.4.0'}
    
  • primaryKey 的行为在多级索引中相同,但在这种情况下,primaryKey 是一个数组。

    In [318]: s_multi = pd.Series(1, index=pd.MultiIndex.from_product([("a", "b"), (0, 1)]))
    
    In [319]: build_table_schema(s_multi)
    Out[319]: 
    {'fields': [{'name': 'level_0', 'type': 'string'},
      {'name': 'level_1', 'type': 'integer'},
      {'name': 'values', 'type': 'integer'}],
     'primaryKey': FrozenList(['level_0', 'level_1']),
     'pandas_version': '1.4.0'}
    
  • 默认命名大致遵循以下规则

    • 对于序列,使用 object.name。如果为空,则名称为 values

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

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

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

read_json 也接受 orient='table' 作为参数。这允许以可循环的方式保留元数据,例如数据类型和索引名称。

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

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

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

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

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

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

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

请注意,作为 Index 名称的文字字符串“index”不可循环,任何以 'level_' 开头的名称也不可循环 MultiIndex 中。这些在 DataFrame.to_json() 中默认使用,以指示缺失值,后续读取无法区分意图。

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

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

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

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

当使用 orient='table' 以及用户自定义的 ExtensionArray 时,生成的模式将在相应的 fields 元素中包含一个额外的 extDtype 键。此额外键不是标准的,但它确实允许扩展类型进行 JSON 往返(例如 read_json(df.to_json(orient="table"), orient="table"))。

如果已正确注册 ExtensionDtype,则 extDtype 键将包含扩展的名称,pandas 将使用该名称在注册表中执行查找,并将序列化数据重新转换为自定义数据类型。

HTML#

读取 HTML 内容#

警告

我们 **强烈建议** 您阅读下面的 HTML 表格解析注意事项,了解有关 BeautifulSoup4/html5lib/lxml 解析器的相关问题。

顶层 read_html() 函数可以接受 HTML 字符串/文件/URL,并将其解析为 pandas DataFrames 的列表。让我们看几个例子。

注意

read_html 返回一个 listDataFrame 对象,即使 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 时,将标题与 HTTP 请求一起传递

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

注意

我们看到,我们传递的标题反映在 HTTP 请求中。

读取以上 URL 的文件内容,并将其作为字符串传递给 read_html

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

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

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

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

如果您愿意,甚至可以传入 StringIO 的实例

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

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

注意

以下示例由于包含大量网络访问函数,导致文档构建速度过慢,因此未由 IPython 评估器运行。如果您发现错误或无法运行的示例,请随时在 pandas GitHub 问题页面 上报告。

读取 URL 并匹配包含特定文本的表格

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

指定标题行(默认情况下,位于 <thead> 中的 <th><td> 元素用于形成列索引,如果 <thead> 中包含多行,则会创建多级索引);如果指定,则标题行取自数据减去解析的标题元素(<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 [337]: html_table = """
   .....: <table>
   .....:   <tr>
   .....:     <th>GitHub</th>
   .....:   </tr>
   .....:   <tr>
   .....:     <td><a href="https://github.com/pandas-dev/pandas">pandas</a></td>
   .....:   </tr>
   .....: </table>
   .....: """
   .....: 

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

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

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

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

版本 1.5.0 中的新增功能。

写入 HTML 文件#

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

注意

为了简洁起见,这里没有显示 DataFrame.to_html 的所有可能选项。有关所有选项,请参阅 DataFrame.to_html()

注意

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

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

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

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

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

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

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

columns 参数将限制显示的列

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

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

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

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

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

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

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

bold_rows 默认情况下会使行标签变为粗体,但您可以关闭此功能

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

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

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

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

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

render_links 参数提供了为包含 URL 的单元格添加超链接的能力。

In [358]: url_df = pd.DataFrame(
   .....:     {
   .....:         "name": ["Python", "pandas"],
   .....:         "url": ["https://www.pythonlang.cn/", "https://pandas.ac.cn"],
   .....:     }
   .....: )
   .....: 

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

In [360]: print(html)
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>name</th>
      <th>url</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>Python</td>
      <td><a href="https://www.pythonlang.cn/" target="_blank">https://www.pythonlang.cn/</a></td>
    </tr>
    <tr>
      <th>1</th>
      <td>pandas</td>
      <td><a href="https://pandas.ac.cn" target="_blank">https://pandas.ac.cn</a></td>
    </tr>
  </tbody>
</table>

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

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

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

已转义

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

In [364]: print(html)
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>a</th>
      <th>b</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>&amp;</td>
      <td>2.396780</td>
    </tr>
    <tr>
      <th>1</th>
      <td>&lt;</td>
      <td>0.014871</td>
    </tr>
    <tr>
      <th>2</th>
      <td>&gt;</td>
      <td>3.357427</td>
    </tr>
  </tbody>
</table>

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

未转义

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

In [367]: print(html)
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>a</th>
      <th>b</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>&</td>
      <td>2.396780</td>
    </tr>
    <tr>
      <th>1</th>
      <td><</td>
      <td>0.014871</td>
    </tr>
    <tr>
      <th>2</th>
      <td>></td>
      <td>3.357427</td>
    </tr>
  </tbody>
</table>

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

注意

某些浏览器可能不会显示前两个 HTML 表格渲染之间的差异。

HTML 表格解析注意事项#

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

关于 lxml 的问题

  • 优点

    • lxml 速度非常快。

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

  • 缺点

    • lxml 对其解析结果做任何保证,除非 它被提供 严格有效的标记

    • 鉴于以上情况,我们选择允许您(用户)使用 lxml 后端,但如果 lxml 无法解析,该后端将使用 html5lib

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

关于 BeautifulSoup4 使用 lxml 作为后端的问题

  • 由于 BeautifulSoup4 本质上只是解析后端的包装器,因此上述问题也适用于此。

关于 BeautifulSoup4 使用 html5lib 作为后端的问题

  • 优点

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

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

    • html5lib 是纯 Python 的,除了它自己的安装之外,不需要额外的构建步骤。

  • 缺点

    • 使用 html5lib 的最大缺点是它慢如蜗牛。但是,请考虑这样一个事实,即网络上的许多表格并不大,以至于解析算法运行时间无关紧要。更可能的是,瓶颈将出现在从 URL 通过网络读取原始文本的过程中,即 IO(输入-输出)。对于非常大的表格,这可能不成立。

LaTeX#

版本 1.3.0 中新增。

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

写入 LaTeX 文件#

注意

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

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

对于简单的应用,以下模式就足够了。

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

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

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

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

XML#

读取 XML#

版本 1.3.0 中新增。

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

注意

由于没有标准的 XML 结构,设计类型可以以多种方式变化,read_xml 最适合处理扁平、浅层的版本。如果 XML 文档嵌套很深,请使用 stylesheet 功能将 XML 转换为更扁平的版本。

让我们看几个例子。

读取 XML 字符串

In [372]: from io import StringIO

In [373]: xml = """<?xml version="1.0" encoding="UTF-8"?>
   .....: <bookstore>
   .....:   <book category="cooking">
   .....:     <title lang="en">Everyday Italian</title>
   .....:     <author>Giada De Laurentiis</author>
   .....:     <year>2005</year>
   .....:     <price>30.00</price>
   .....:   </book>
   .....:   <book category="children">
   .....:     <title lang="en">Harry Potter</title>
   .....:     <author>J K. Rowling</author>
   .....:     <year>2005</year>
   .....:     <price>29.99</price>
   .....:   </book>
   .....:   <book category="web">
   .....:     <title lang="en">Learning XML</title>
   .....:     <author>Erik T. Ray</author>
   .....:     <year>2003</year>
   .....:     <price>39.95</price>
   .....:   </book>
   .....: </bookstore>"""
   .....: 

In [374]: df = pd.read_xml(StringIO(xml))

In [375]: df
Out[375]: 
   category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95

读取没有选项的 URL

In [376]: df = pd.read_xml("https://www.w3schools.com/xml/books.xml")

In [377]: df
Out[377]: 
   category              title                  author  year  price      cover
0   cooking   Everyday Italian     Giada De Laurentiis  2005  30.00       None
1  children       Harry Potter            J K. Rowling  2005  29.99       None
2       web  XQuery Kick Start  Vaidyanathan Nagarajan  2003  49.99       None
3       web       Learning XML             Erik T. Ray  2003  39.95  paperback

读取 “books.xml” 文件的内容,并将其作为字符串传递给 read_xml

In [378]: file_path = "books.xml"

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

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

In [381]: df
Out[381]: 
   category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95

读取 “books.xml” 的内容,作为 StringIOBytesIO 的实例,并将其传递给 read_xml

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

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

In [384]: df
Out[384]: 
   category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95
In [385]: with open(file_path, "rb") as f:
   .....:     bio = BytesIO(f.read())
   .....: 

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

In [387]: df
Out[387]: 
   category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95

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

In [388]: df = pd.read_xml(
   .....:     "s3://pmc-oa-opendata/oa_comm/xml/all/PMC1236943.xml",
   .....:     xpath=".//journal-meta",
   .....: )
   .....: 

In [389]: df
Out[389]: 
              journal-id              journal-title       issn  publisher
0  Cardiovasc Ultrasound  Cardiovascular Ultrasound  1476-7120        NaN

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

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

In [391]: df
Out[391]: 
   category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99

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

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

In [393]: df
Out[393]: 
              title               author  year  price
0  Everyday Italian  Giada De Laurentiis  2005  30.00
1      Harry Potter         J K. Rowling  2005  29.99
2      Learning XML          Erik T. Ray  2003  39.95
In [394]: df = pd.read_xml(file_path, attrs_only=True)

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

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

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

In [396]: xml = """<?xml version='1.0' encoding='utf-8'?>
   .....: <doc:data xmlns:doc="https://example.com">
   .....:   <doc:row>
   .....:     <doc:shape>square</doc:shape>
   .....:     <doc:degrees>360</doc:degrees>
   .....:     <doc:sides>4.0</doc:sides>
   .....:   </doc:row>
   .....:   <doc:row>
   .....:     <doc:shape>circle</doc:shape>
   .....:     <doc:degrees>360</doc:degrees>
   .....:     <doc:sides/>
   .....:   </doc:row>
   .....:   <doc:row>
   .....:     <doc:shape>triangle</doc:shape>
   .....:     <doc:degrees>180</doc:degrees>
   .....:     <doc:sides>3.0</doc:sides>
   .....:   </doc:row>
   .....: </doc:data>"""
   .....: 

In [397]: df = pd.read_xml(StringIO(xml),
   .....:                  xpath="//doc:row",
   .....:                  namespaces={"doc": "https://example.com"})
   .....: 

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

类似地,XML 文档可以具有没有前缀的默认命名空间。如果未分配临时前缀,则不会返回任何节点,并会引发 ValueError。但将任何临时名称分配给正确的 URI 允许按节点解析。

In [399]: xml = """<?xml version='1.0' encoding='utf-8'?>
   .....: <data xmlns="https://example.com">
   .....:  <row>
   .....:    <shape>square</shape>
   .....:    <degrees>360</degrees>
   .....:    <sides>4.0</sides>
   .....:  </row>
   .....:  <row>
   .....:    <shape>circle</shape>
   .....:    <degrees>360</degrees>
   .....:    <sides/>
   .....:  </row>
   .....:  <row>
   .....:    <shape>triangle</shape>
   .....:    <degrees>180</degrees>
   .....:    <sides>3.0</sides>
   .....:  </row>
   .....: </data>"""
   .....: 

In [400]: df = pd.read_xml(StringIO(xml),
   .....:                  xpath="//pandas:row",
   .....:                  namespaces={"pandas": "https://example.com"})
   .....: 

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

但是,如果 XPath 不引用节点名称,例如默认值,/*,则不需要 namespaces

注意

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

In [402]: xml = """
   .....: <data>
   .....:   <row>
   .....:     <shape sides="4">square</shape>
   .....:     <degrees>360</degrees>
   .....:   </row>
   .....:   <row>
   .....:     <shape sides="0">circle</shape>
   .....:     <degrees>360</degrees>
   .....:   </row>
   .....:   <row>
   .....:     <shape sides="3">triangle</shape>
   .....:     <degrees>180</degrees>
   .....:   </row>
   .....: </data>"""
   .....: 

In [403]: df = pd.read_xml(StringIO(xml), xpath="./row")

In [404]: df
Out[404]: 
      shape  degrees
0    square      360
1    circle      360
2  triangle      180

显示 shape 元素上的属性 sides 未按预期解析,因为此属性位于 row 元素的子级上,而不是 row 元素本身。换句话说,sides 属性是 row 元素的孙子级后代。但是,xpath 目标是 row 元素,它只涵盖其子级和属性。

使用 lxml 作为解析器,您可以使用 XSLT 脚本扁平化嵌套的 XML 文档,该脚本也可以是字符串/文件/URL 类型。作为背景,XSLT 是一种特殊用途的语言,它以特殊的 XML 文件编写,可以使用 XSLT 处理器将原始 XML 文档转换为其他 XML、HTML,甚至文本(CSV、JSON 等)。

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

In [405]: xml = """<?xml version='1.0' encoding='utf-8'?>
   .....:  <response>
   .....:   <row>
   .....:     <station id="40850" name="Library"/>
   .....:     <month>2020-09-01T00:00:00</month>
   .....:     <rides>
   .....:       <avg_weekday_rides>864.2</avg_weekday_rides>
   .....:       <avg_saturday_rides>534</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
   .....:     </rides>
   .....:   </row>
   .....:   <row>
   .....:     <station id="41700" name="Washington/Wabash"/>
   .....:     <month>2020-09-01T00:00:00</month>
   .....:     <rides>
   .....:       <avg_weekday_rides>2707.4</avg_weekday_rides>
   .....:       <avg_saturday_rides>1909.8</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
   .....:     </rides>
   .....:   </row>
   .....:   <row>
   .....:     <station id="40380" name="Clark/Lake"/>
   .....:     <month>2020-09-01T00:00:00</month>
   .....:     <rides>
   .....:       <avg_weekday_rides>2949.6</avg_weekday_rides>
   .....:       <avg_saturday_rides>1657</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
   .....:     </rides>
   .....:   </row>
   .....:  </response>"""
   .....: 

In [406]: xsl = """<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   .....:    <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
   .....:    <xsl:strip-space elements="*"/>
   .....:    <xsl:template match="/response">
   .....:       <xsl:copy>
   .....:         <xsl:apply-templates select="row"/>
   .....:       </xsl:copy>
   .....:    </xsl:template>
   .....:    <xsl:template match="row">
   .....:       <xsl:copy>
   .....:         <station_id><xsl:value-of select="station/@id"/></station_id>
   .....:         <station_name><xsl:value-of select="station/@name"/></station_name>
   .....:         <xsl:copy-of select="month|rides/*"/>
   .....:       </xsl:copy>
   .....:    </xsl:template>
   .....:  </xsl:stylesheet>"""
   .....: 

In [407]: output = """<?xml version='1.0' encoding='utf-8'?>
   .....:  <response>
   .....:    <row>
   .....:       <station_id>40850</station_id>
   .....:       <station_name>Library</station_name>
   .....:       <month>2020-09-01T00:00:00</month>
   .....:       <avg_weekday_rides>864.2</avg_weekday_rides>
   .....:       <avg_saturday_rides>534</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
   .....:    </row>
   .....:    <row>
   .....:       <station_id>41700</station_id>
   .....:       <station_name>Washington/Wabash</station_name>
   .....:       <month>2020-09-01T00:00:00</month>
   .....:       <avg_weekday_rides>2707.4</avg_weekday_rides>
   .....:       <avg_saturday_rides>1909.8</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
   .....:    </row>
   .....:    <row>
   .....:       <station_id>40380</station_id>
   .....:       <station_name>Clark/Lake</station_name>
   .....:       <month>2020-09-01T00:00:00</month>
   .....:       <avg_weekday_rides>2949.6</avg_weekday_rides>
   .....:       <avg_saturday_rides>1657</avg_saturday_rides>
   .....:       <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
   .....:    </row>
   .....:  </response>"""
   .....: 

In [408]: df = pd.read_xml(StringIO(xml), stylesheet=xsl)

In [409]: df
Out[409]: 
   station_id       station_name  ... avg_saturday_rides  avg_sunday_holiday_rides
0       40850            Library  ...              534.0                     417.2
1       41700  Washington/Wabash  ...             1909.8                    1438.6
2       40380         Clark/Lake  ...             1657.0                    1453.8

[3 rows x 6 columns]

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

版本 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#

版本 1.3.0 中新增。

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

注意

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

让我们看几个例子。

写入没有选项的 XML

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

In [411]: print(geom_df.to_xml())
<?xml version='1.0' encoding='utf-8'?>
<data>
  <row>
    <index>0</index>
    <shape>square</shape>
    <degrees>360</degrees>
    <sides>4.0</sides>
  </row>
  <row>
    <index>1</index>
    <shape>circle</shape>
    <degrees>360</degrees>
    <sides/>
  </row>
  <row>
    <index>2</index>
    <shape>triangle</shape>
    <degrees>180</degrees>
    <sides>3.0</sides>
  </row>
</data>

写入具有新根和行名称的 XML

In [412]: print(geom_df.to_xml(root_name="geometry", row_name="objects"))
<?xml version='1.0' encoding='utf-8'?>
<geometry>
  <objects>
    <index>0</index>
    <shape>square</shape>
    <degrees>360</degrees>
    <sides>4.0</sides>
  </objects>
  <objects>
    <index>1</index>
    <shape>circle</shape>
    <degrees>360</degrees>
    <sides/>
  </objects>
  <objects>
    <index>2</index>
    <shape>triangle</shape>
    <degrees>180</degrees>
    <sides>3.0</sides>
  </objects>
</geometry>

写入以属性为中心的 XML

In [413]: print(geom_df.to_xml(attr_cols=geom_df.columns.tolist()))
<?xml version='1.0' encoding='utf-8'?>
<data>
  <row index="0" shape="square" degrees="360" sides="4.0"/>
  <row index="1" shape="circle" degrees="360"/>
  <row index="2" shape="triangle" degrees="180" sides="3.0"/>
</data>

写入元素和属性的混合

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

任何具有分层列的 DataFrames 将被扁平化为 XML 元素名称,级别以下划线分隔

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

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

In [417]: pvt_df
Out[417]: 
         degrees         sides        
type       other polygon other polygon
shape                                 
circle     360.0     NaN   0.0     NaN
square       NaN   360.0   NaN     4.0
triangle     NaN   180.0   NaN     3.0

In [418]: print(pvt_df.to_xml())
<?xml version='1.0' encoding='utf-8'?>
<data>
  <row>
    <shape>circle</shape>
    <degrees_other>360.0</degrees_other>
    <degrees_polygon/>
    <sides_other>0.0</sides_other>
    <sides_polygon/>
  </row>
  <row>
    <shape>square</shape>
    <degrees_other/>
    <degrees_polygon>360.0</degrees_polygon>
    <sides_other/>
    <sides_polygon>4.0</sides_polygon>
  </row>
  <row>
    <shape>triangle</shape>
    <degrees_other/>
    <degrees_polygon>180.0</degrees_polygon>
    <sides_other/>
    <sides_polygon>3.0</sides_polygon>
  </row>
</data>

写入具有默认命名空间的 XML

In [419]: print(geom_df.to_xml(namespaces={"": "https://example.com"}))
<?xml version='1.0' encoding='utf-8'?>
<data xmlns="https://example.com">
  <row>
    <index>0</index>
    <shape>square</shape>
    <degrees>360</degrees>
    <sides>4.0</sides>
  </row>
  <row>
    <index>1</index>
    <shape>circle</shape>
    <degrees>360</degrees>
    <sides/>
  </row>
  <row>
    <index>2</index>
    <shape>triangle</shape>
    <degrees>180</degrees>
    <sides>3.0</sides>
  </row>
</data>

写入具有命名空间前缀的 XML

In [420]: print(
   .....:     geom_df.to_xml(namespaces={"doc": "https://example.com"},
   .....:                    prefix="doc")
   .....: )
   .....: 
<?xml version='1.0' encoding='utf-8'?>
<doc:data xmlns:doc="https://example.com">
  <doc:row>
    <doc:index>0</doc:index>
    <doc:shape>square</doc:shape>
    <doc:degrees>360</doc:degrees>
    <doc:sides>4.0</doc:sides>
  </doc:row>
  <doc:row>
    <doc:index>1</doc:index>
    <doc:shape>circle</doc:shape>
    <doc:degrees>360</doc:degrees>
    <doc:sides/>
  </doc:row>
  <doc:row>
    <doc:index>2</doc:index>
    <doc:shape>triangle</doc:shape>
    <doc:degrees>180</doc:degrees>
    <doc:sides>3.0</doc:sides>
  </doc:row>
</doc:data>

写入没有声明或漂亮打印的 XML

In [421]: print(
   .....:     geom_df.to_xml(xml_declaration=False,
   .....:                    pretty_print=False)
   .....: )
   .....: 
<data><row><index>0</index><shape>square</shape><degrees>360</degrees><sides>4.0</sides></row><row><index>1</index><shape>circle</shape><degrees>360</degrees><sides/></row><row><index>2</index><shape>triangle</shape><degrees>180</degrees><sides>3.0</sides></row></data>

编写一个 XML 文件并使用样式表进行转换

In [422]: xsl = """<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   .....:    <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
   .....:    <xsl:strip-space elements="*"/>
   .....:    <xsl:template match="/data">
   .....:      <geometry>
   .....:        <xsl:apply-templates select="row"/>
   .....:      </geometry>
   .....:    </xsl:template>
   .....:    <xsl:template match="row">
   .....:      <object index="{index}">
   .....:        <xsl:if test="shape!='circle'">
   .....:            <xsl:attribute name="type">polygon</xsl:attribute>
   .....:        </xsl:if>
   .....:        <xsl:copy-of select="shape"/>
   .....:        <property>
   .....:            <xsl:copy-of select="degrees|sides"/>
   .....:        </property>
   .....:      </object>
   .....:    </xsl:template>
   .....:  </xsl:stylesheet>"""
   .....: 

In [423]: print(geom_df.to_xml(stylesheet=xsl))
<?xml version="1.0"?>
<geometry>
  <object index="0" type="polygon">
    <shape>square</shape>
    <property>
      <degrees>360</degrees>
      <sides>4.0</sides>
    </property>
  </object>
  <object index="1">
    <shape>circle</shape>
    <property>
      <degrees>360</degrees>
      <sides/>
    </property>
  </object>
  <object index="2" type="polygon">
    <shape>triangle</shape>
    <property>
      <degrees>180</degrees>
      <sides>3.0</sides>
    </property>
  </object>
</geometry>

XML 总结#

  • 所有 XML 文档都遵循 W3C 规范etreelxml 解析器将无法解析任何格式不正确或不符合 XML 语法规则的标记文档。请注意,HTML 不是 XML 文档,除非它遵循 XHTML 规范。但是,其他流行的标记类型,包括 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() 方法可以使用 openpyxl Python 模块读取 Excel 2007+ (.xlsx) 文件。可以使用 xlrd 读取 Excel 2003 (.xls) 文件。可以使用 pyxlsb 读取二进制 Excel (.xlsb) 文件。所有格式都可以使用 calamine 引擎读取。The to_excel() 实例方法用于将 DataFrame 保存到 Excel。通常,语义与使用 csv 数据类似。有关一些高级策略,请参见 cookbook

注意

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

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

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

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

  • 否则,将使用 openpyxl

读取 Excel 文件#

在最基本的使用情况下,read_excel 接受 Excel 文件的路径,以及指示要解析的表的 sheet_name

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

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

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

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

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

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

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

ExcelFile#

为了方便处理同一个文件中的多个工作表,可以使用 ExcelFile 类来包装文件,并将其传递给 read_excel。读取多个工作表时,文件只会被加载到内存一次,因此会提高性能。

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

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

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

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

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

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

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

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

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

ExcelFile 也可以使用 xlrd.book.Book 对象作为参数调用。这允许用户控制如何读取 Excel 文件。例如,可以通过调用 xlrd.open_workbook() 并设置 on_demand=True 来按需加载工作表。

import xlrd

xlrd_book = xlrd.open_workbook("path_to_file.xls", on_demand=True)
with pd.ExcelFile(xlrd_book) as xls:
    df1 = pd.read_excel(xls, "Sheet1")
    df2 = pd.read_excel(xls, "Sheet2")

指定工作表#

注意

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

index_col 中指定的列中的缺失值将被前向填充,以允许与 to_excel 进行往返,以实现 merged_cells=True。为了避免前向填充缺失值,请在读取数据后使用 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})

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

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 支持使用 ExcelWriter 将 Excel 文件写入类似缓冲区的对象,例如 StringIOBytesIO

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 是可选的,但建议使用。设置引擎决定生成的 workbooks 版本。设置 engine='xlrd' 将生成 Excel 2003 格式的 workbook (xls)。使用 'openpyxl''xlsxwriter' 将生成 Excel 2007 格式的 workbook (xlsx)。如果省略,将生成 Excel 2007 格式的 workbook。

Excel 写入引擎#

pandas 通过两种方法选择 Excel 写入器

  1. engine 关键字参数

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

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

要指定要使用的写入器,可以将引擎关键字参数传递给 to_excelExcelWriter。内置引擎是

  • openpyxl: 需要 2.4 或更高版本

  • xlsxwriter

# By setting the 'engine' in the DataFrame 'to_excel()' methods.
df.to_excel("path_to_file.xlsx", sheet_name="Sheet1", engine="xlsxwriter")

# By setting the 'engine' in the ExcelWriter constructor.
writer = pd.ExcelWriter("path_to_file.xlsx", engine="xlsxwriter")

# Or via pandas configuration.
from pandas import options  # noqa: E402

options.io.excel.xlsx.writer = "xlsxwriter"

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

样式和格式#

可以使用以下参数在 DataFrameto_excel 方法上修改从 pandas 创建的 Excel 工作表的样式和外观。

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

  • freeze_panes : 一个包含两个整数的元组,表示要冻结的最下面一行和最右边的列。这两个参数都是从 1 开始的,所以 (1, 1) 将冻结第一行和第一列(默认值为 None)。

使用 Xlsxwriter 引擎提供了许多选项来控制使用 to_excel 方法创建的 Excel 工作表的格式。在 Xlsxwriter 文档中可以找到很好的示例:https://xlsxwriter.readthedocs.io/working_with_pandas.html

OpenDocument 电子表格#

用于 Excel 文件 的 io 方法也支持使用 odfpy 模块读取和写入 OpenDocument 电子表格。用于读取和写入 OpenDocument 电子表格的语义和功能与使用 engine='odf'Excel 文件 所能做的事情相匹配。需要安装可选的依赖项“odfpy”。

可以使用 read_excel() 方法读取 OpenDocument 电子表格

# Returns a DataFrame
pd.read_excel("path_to_file.ods", engine="odf")

类似地,可以使用 to_excel() 方法写入 OpenDocument 电子表格

# Writes DataFrame to a .ods file
df.to_excel("path_to_file.ods", engine="odf")

二进制 Excel (.xlsb) 文件#

The read_excel() method can also read binary Excel files using the pyxlsb module. The semantics and features for reading binary Excel files mostly match what can be done for Excel files using engine='pyxlsb'. pyxlsb does not recognize datetime types in files and will return floats instead (you can use calamine if you need recognize datetime types).

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

注意

Currently pandas only supports reading binary Excel files. Writing is not implemented.

Calamine (Excel and ODS files)#

The read_excel() method can read Excel file (.xlsx, .xlsm, .xls, .xlsb) and OpenDocument spreadsheets (.ods) using the python-calamine module. This module is a binding for Rust library calamine and is faster than other engines in most cases. The optional dependency ‘python-calamine’ needs to be installed.

# 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

The to_clipboard method can be used to write the contents of a DataFrame to the clipboard. Following which you can paste the clipboard contents into other applications (CTRL-V on many operating systems). Here we illustrate writing a DataFrame into clipboard and reading it back.

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

我们可以看到我们得到了相同的内容,这是我们之前写入剪贴板的内容。

注意

You may need to install xclip or xsel (with PyQt5, PyQt4 or qtpy) on Linux to use these methods.

腌制#

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

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

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

pandas 命名空间中的 read_pickle 函数可用于从文件中加载任何腌制的 pandas 对象(或任何其他腌制的对象)。

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

警告

加载来自不受信任来源的腌制数据可能不安全。

参见:https://docs.pythonlang.cn/3/library/pickle.html

警告

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

压缩的 pickle 文件#

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

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

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

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

In [440]: df
Out[440]: 
            A    B                   C
0   -0.317441  foo 2013-01-01 00:00:00
1   -1.236269  foo 2013-01-01 00:00:01
2    0.896171  foo 2013-01-01 00:00:02
3   -0.487602  foo 2013-01-01 00:00:03
4   -0.082240  foo 2013-01-01 00:00:04
..        ...  ...                 ...
995 -0.171092  foo 2013-01-01 00:16:35
996  1.786173  foo 2013-01-01 00:16:36
997 -0.575189  foo 2013-01-01 00:16:37
998  0.820750  foo 2013-01-01 00:16:38
999 -1.256530  foo 2013-01-01 00:16:39

[1000 rows x 3 columns]

使用显式压缩类型

In [441]: df.to_pickle("data.pkl.compress", compression="gzip")

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

In [443]: rt
Out[443]: 
            A    B                   C
0   -0.317441  foo 2013-01-01 00:00:00
1   -1.236269  foo 2013-01-01 00:00:01
2    0.896171  foo 2013-01-01 00:00:02
3   -0.487602  foo 2013-01-01 00:00:03
4   -0.082240  foo 2013-01-01 00:00:04
..        ...  ...                 ...
995 -0.171092  foo 2013-01-01 00:16:35
996  1.786173  foo 2013-01-01 00:16:36
997 -0.575189  foo 2013-01-01 00:16:37
998  0.820750  foo 2013-01-01 00:16:38
999 -1.256530  foo 2013-01-01 00:16:39

[1000 rows x 3 columns]

从扩展名推断压缩类型

In [444]: df.to_pickle("data.pkl.xz", compression="infer")

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

In [446]: rt
Out[446]: 
            A    B                   C
0   -0.317441  foo 2013-01-01 00:00:00
1   -1.236269  foo 2013-01-01 00:00:01
2    0.896171  foo 2013-01-01 00:00:02
3   -0.487602  foo 2013-01-01 00:00:03
4   -0.082240  foo 2013-01-01 00:00:04
..        ...  ...                 ...
995 -0.171092  foo 2013-01-01 00:16:35
996  1.786173  foo 2013-01-01 00:16:36
997 -0.575189  foo 2013-01-01 00:16:37
998  0.820750  foo 2013-01-01 00:16:38
999 -1.256530  foo 2013-01-01 00:16:39

[1000 rows x 3 columns]

默认值为“infer”

In [447]: df.to_pickle("data.pkl.gz")

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

In [449]: rt
Out[449]: 
            A    B                   C
0   -0.317441  foo 2013-01-01 00:00:00
1   -1.236269  foo 2013-01-01 00:00:01
2    0.896171  foo 2013-01-01 00:00:02
3   -0.487602  foo 2013-01-01 00:00:03
4   -0.082240  foo 2013-01-01 00:00:04
..        ...  ...                 ...
995 -0.171092  foo 2013-01-01 00:16:35
996  1.786173  foo 2013-01-01 00:16:36
997 -0.575189  foo 2013-01-01 00:16:37
998  0.820750  foo 2013-01-01 00:16:38
999 -1.256530  foo 2013-01-01 00:16:39

[1000 rows x 3 columns]

In [450]: df["A"].to_pickle("s1.pkl.bz2")

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

In [452]: rt
Out[452]: 
0     -0.317441
1     -1.236269
2      0.896171
3     -0.487602
4     -0.082240
         ...   
995   -0.171092
996    1.786173
997   -0.575189
998    0.820750
999   -1.256530
Name: A, Length: 1000, dtype: float64

将选项传递给压缩协议以加快压缩速度

In [453]: df.to_pickle("data.pkl.gz", compression={"method": "gzip", "compresslevel": 1})

msgpack#

pandas 对 msgpack 的支持已在 1.0.0 版本中移除。建议使用 pickle 代替。

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

HDF5 (PyTables)#

HDFStore 是一个类似字典的对象,它使用出色的 PyTables 库,使用高性能 HDF5 格式读取和写入 pandas。有关一些高级策略,请参见 cookbook

警告

pandas 使用 PyTables 读取和写入 HDF5 文件,这允许使用 pickle 对 object-dtype 数据进行序列化。加载来自不受信任来源的腌制数据可能不安全。

参见:https://docs.pythonlang.cn/3/library/pickle.html 了解更多信息。

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

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

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

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

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

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

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

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

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

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

# store.get('df') is an equivalent method
In [462]: store["df"]
Out[462]: 
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

# dotted (attribute) access provides get as well
In [463]: store.df
Out[463]: 
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

删除由键指定的对象

# store.remove('df') is an equivalent method
In [464]: del store["df"]

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

关闭存储并使用上下文管理器

In [466]: store.close()

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

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

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

读写 API#

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

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

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

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

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

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

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

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

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

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

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

固定格式#

上面的示例展示了使用 put 进行存储,它将 HDF5 以固定数组格式写入 PyTables,称为 fixed 格式。这些类型的存储一旦写入就 **不能** 追加(尽管您可以简单地删除它们并重新写入)。它们也不能 **查询**;必须完整地检索它们。它们也不支持具有非唯一列名的 DataFrame。 fixed 格式存储提供非常快的写入速度,并且比 table 存储略快。使用 putto_hdfformat='fixed'format='f' 时,默认情况下会指定此格式。

警告

如果您尝试使用 where 检索,fixed 格式将引发 TypeError

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

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

File ~/work/pandas/pandas/pandas/io/pytables.py:452, in read_hdf(path_or_buf, key, mode, errors, where, start, stop, columns, iterator, chunksize, **kwargs)
    447                 raise ValueError(
    448                     "key must be provided when HDF5 "
    449                     "file contains multiple datasets."
    450                 )
    451         key = candidate_only_group._v_pathname
--> 452     return store.select(
    453         key,
    454         where=where,
    455         start=start,
    456         stop=stop,
    457         columns=columns,
    458         iterator=iterator,
    459         chunksize=chunksize,
    460         auto_close=auto_close,
    461     )
    462 except (ValueError, TypeError, LookupError):
    463     if not isinstance(path_or_buf, HDFStore):
    464         # if there is an error, close the store if we opened it.

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

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

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

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

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

TypeError: cannot pass a where specification when reading from a Fixed format store. this store must be selected in its entirety

表格格式#

HDFStore 支持磁盘上的另一种 PyTables 格式,即 table 格式。从概念上讲,table 的形状与 DataFrame 非常相似,具有行和列。可以将 table 追加到相同或其他会话中。此外,还支持删除和查询类型操作。此格式由 format='table'format='t' 指定为 appendputto_hdf

此格式也可以设置为选项 pd.set_option('io.hdf.default_format','table'),以使 put/append/to_hdf 默认情况下以 table 格式存储。

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

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

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

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

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

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

# select the entire object
In [487]: store.select("df")
Out[487]: 
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

# the type of stored data
In [488]: store.root.df._v_attrs.pandas_type
Out[488]: 'frame_table'

注意

您还可以通过将 format='table'format='t' 传递给 put 操作来创建 table

层次结构键#

存储的键可以指定为字符串。这些可以采用层次结构路径名格式(例如 foo/bar/bah),这将生成子存储的层次结构(或 PyTables 中的 Groups)。键可以指定为不带前导“/”,并且 **始终** 为绝对路径(例如,“foo” 指的是“/foo”)。删除操作可以删除子存储中及其 **下方** 的所有内容,因此请务必 **小心**。

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

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

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

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

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

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

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

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

In [496]: for (path, subgroups, subkeys) in store.walk():
   .....:     for subgroup in subgroups:
   .....:         print("GROUP: {}/{}".format(path, subgroup))
   .....:     for subkey in subkeys:
   .....:         key = "/".join([path, subkey])
   .....:         print("KEY: {}".format(key))
   .....:         print(store.get(key))
   .....: 
GROUP: /foo
KEY: /df
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517
GROUP: /foo/bar
KEY: /foo/bar/bah
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

警告

层次结构键不能像上面描述的那样以点号(属性)访问的方式检索,这些键存储在根节点下。

In [497]: store.foo.bar.bah
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[497], line 1
----> 1 store.foo.bar.bah

File ~/work/pandas/pandas/pandas/io/pytables.py:613, in HDFStore.__getattr__(self, name)
    611 """allow attribute access to get stores"""
    612 try:
--> 613     return self.get(name)
    614 except (KeyError, ClosedFileError):
    615     pass

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

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

File ~/work/pandas/pandas/pandas/io/pytables.py:1752, in HDFStore._create_storer(self, group, format, value, encoding, errors)
   1750         tt = "generic_table"
   1751     else:
-> 1752         raise TypeError(
   1753             "cannot create a storer if the object is not existing "
   1754             "nor a value are passed"
   1755         )
   1756 else:
   1757     if isinstance(value, Series):

TypeError: cannot create a storer if the object is not existing nor a value are passed
# you can directly access the actual PyTables node but using the root node
In [498]: store.root.foo.bar.bah
Out[498]: 
/foo/bar/bah (Group) ''
  children := ['axis0' (Array), 'axis1' (Array), 'block0_items' (Array), 'block0_values' (Array)]

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

In [499]: store["foo/bar/bah"]
Out[499]: 
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

存储类型#

在表中存储混合类型#

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

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

In [500]: df_mixed = pd.DataFrame(
   .....:     {
   .....:         "A": np.random.randn(8),
   .....:         "B": np.random.randn(8),
   .....:         "C": np.array(np.random.randn(8), dtype="float32"),
   .....:         "string": "string",
   .....:         "int": 1,
   .....:         "bool": True,
   .....:         "datetime64": pd.Timestamp("20010102"),
   .....:     },
   .....:     index=list(range(8)),
   .....: )
   .....: 

In [501]: df_mixed.loc[df_mixed.index[3:5], ["A", "B", "string", "datetime64"]] = np.nan

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

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

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

[8 rows x 7 columns]

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

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

存储 MultiIndex DataFrames#

将 MultiIndex DataFrames 存储为表与存储/选择同类索引 DataFrames 非常相似。

In [507]: index = pd.MultiIndex(
   .....:    levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
   .....:    codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
   .....:    names=["foo", "bar"],
   .....: )
   .....: 

In [508]: df_mi = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])

In [509]: df_mi
Out[509]: 
                  A         B         C
foo bar                                
foo one   -1.303456 -0.642994 -0.649456
    two    1.012694  0.414147  1.950460
    three  1.094544 -0.802899 -0.583343
bar one    0.410395  0.618321  0.560398
    two    1.434027 -0.033270  0.343197
baz two   -1.646063 -0.695847 -0.429156
    three -0.244688 -1.428229 -0.138691
qux one    1.866184 -1.446617  0.036660
    two   -1.660522  0.929553 -1.298649
    three  3.565769  0.682402  1.041927

In [510]: store.append("df_mi", df_mi)

In [511]: store.select("df_mi")
Out[511]: 
                  A         B         C
foo bar                                
foo one   -1.303456 -0.642994 -0.649456
    two    1.012694  0.414147  1.950460
    three  1.094544 -0.802899 -0.583343
bar one    0.410395  0.618321  0.560398
    two    1.434027 -0.033270  0.343197
baz two   -1.646063 -0.695847 -0.429156
    three -0.244688 -1.428229 -0.138691
qux one    1.866184 -1.446617  0.036660
    two   -1.660522  0.929553 -1.298649
    three  3.565769  0.682402  1.041927

# the levels are automatically included as data columns
In [512]: store.select("df_mi", "foo=bar")
Out[512]: 
                A         B         C
foo bar                              
bar one  0.410395  0.618321  0.560398
    two  1.434027 -0.033270  0.343197

注意

index 关键字是保留的,不能用作级别名称。

查询#

查询表#

selectdelete 操作有一个可选的条件,可以指定它来选择/删除数据的子集。这允许您拥有一个非常大的磁盘表,并且只检索一部分数据。

查询使用 Term 类在后台指定为布尔表达式。

  • indexcolumnsDataFrames 支持的索引器。

  • 如果指定了 data_columns,则可以将其用作附加索引器。

  • 多级索引中的级别名称,默认名称为 level_0level_1 等,如果未提供。

有效的比较运算符为

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

有效的布尔表达式与以下内容结合使用

  • | : 或

  • & : 且

  • () : 用于分组

这些规则类似于 pandas 中用于索引的布尔表达式。

注意

  • = 将自动扩展为比较运算符 ==

  • ~ 是非运算符,但只能在非常有限的情况下使用

  • 如果传递表达式列表/元组,它们将通过 & 结合。

以下是有效的表达式

  • 'index >= date'

  • "columns = ['A', 'D']"

  • "columns in ['A', 'D']"

  • 'columns = A'

  • 'columns == A'

  • "~(columns = ['A', 'B'])"

  • 'index > df.index[3] & string = "bar"'

  • '(index > df.index[3] & index <= df.index[6]) | string = "bar"'

  • "ts >= Timestamp('2012-02-01')"

  • "major_axis>=20130101"

子表达式左侧是 indexers

columns, major_axis, ts

子表达式右侧(比较运算符之后)可以是

  • 将要评估的函数,例如 Timestamp('2012-02-01')

  • 字符串,例如 "bar"

  • 日期类型,例如 20130101"20130101"

  • 列表,例如 "['A', 'B']"

  • 在本地命名空间中定义的变量,例如 date

注意

不建议通过将字符串插入查询表达式来将字符串传递给查询。只需将感兴趣的字符串分配给一个变量,并在表达式中使用该变量。例如,执行以下操作

string = "HolyMoly'"
store.select("df", "index == string")

而不是执行以下操作

string = "HolyMoly'"
store.select('df', f'index == {string}')

后者将**无法**工作,并将引发 SyntaxError。请注意,string 变量中有一个单引号后跟一个双引号。

如果您**必须**进行插值,请使用 '%r' 格式说明符

store.select("df", "index == %r" % string)

它将引用 string

以下是一些示例

In [513]: dfq = pd.DataFrame(
   .....:     np.random.randn(10, 4),
   .....:     columns=list("ABCD"),
   .....:     index=pd.date_range("20130101", periods=10),
   .....: )
   .....: 

In [514]: store.append("dfq", dfq, format="table", data_columns=True)

使用布尔表达式,以及内联函数评估。

In [515]: store.select("dfq", "index>pd.Timestamp('20130104') & columns=['A', 'B']")
Out[515]: 
                   A         B
2013-01-05 -0.830545 -0.457071
2013-01-06  0.431186  1.049421
2013-01-07  0.617509 -0.811230
2013-01-08  0.947422 -0.671233
2013-01-09 -0.183798 -1.211230
2013-01-10  0.361428  0.887304

使用内联列引用。

In [516]: store.select("dfq", where="A>0 or C>0")
Out[516]: 
                   A         B         C         D
2013-01-02  0.658179  0.362814 -0.917897  0.010165
2013-01-03  0.905122  1.848731 -1.184241  0.932053
2013-01-05 -0.830545 -0.457071  1.565581  1.148032
2013-01-06  0.431186  1.049421  0.383309  0.595013
2013-01-07  0.617509 -0.811230 -2.088563 -1.393500
2013-01-08  0.947422 -0.671233 -0.847097 -1.187785
2013-01-10  0.361428  0.887304  0.266457 -0.399641

可以使用 columns 关键字选择要返回的列列表,这等效于传递 'columns=list_of_columns_to_filter'

In [517]: store.select("df", "columns=['A', 'B']")
Out[517]: 
                   A         B
2000-01-01  0.858644 -0.851236
2000-01-02 -0.080372 -1.268121
2000-01-03  0.816983  1.965656
2000-01-04  0.712795 -0.062433
2000-01-05 -0.298721 -1.988045
2000-01-06  1.103675  1.382242
2000-01-07 -0.729161 -0.142928
2000-01-08 -1.005977  0.465222

startstop 参数可以指定以限制总搜索空间。这些参数指的是表格中的总行数。

注意

select 如果查询表达式包含未知变量引用,则会引发 ValueError。通常这意味着您试图选择一个 **不是** 数据列的列。

select 如果查询表达式无效,则会引发 SyntaxError

查询 timedelta64[ns]#

您可以使用 timedelta64[ns] 类型进行存储和查询。术语可以以以下格式指定:<float>(<unit>),其中 float 可以是带符号的(和分数),而 unit 可以是 D,s,ms,us,ns,用于 timedelta。以下是一个示例

In [518]: from datetime import timedelta

In [519]: dftd = pd.DataFrame(
   .....:     {
   .....:         "A": pd.Timestamp("20130101"),
   .....:         "B": [
   .....:             pd.Timestamp("20130101") + timedelta(days=i, seconds=10)
   .....:             for i in range(10)
   .....:         ],
   .....:     }
   .....: )
   .....: 

In [520]: dftd["C"] = dftd["A"] - dftd["B"]

In [521]: dftd
Out[521]: 
           A                   B                  C
0 2013-01-01 2013-01-01 00:00:10  -1 days +23:59:50
1 2013-01-01 2013-01-02 00:00:10  -2 days +23:59:50
2 2013-01-01 2013-01-03 00:00:10  -3 days +23:59:50
3 2013-01-01 2013-01-04 00:00:10  -4 days +23:59:50
4 2013-01-01 2013-01-05 00:00:10  -5 days +23:59:50
5 2013-01-01 2013-01-06 00:00:10  -6 days +23:59:50
6 2013-01-01 2013-01-07 00:00:10  -7 days +23:59:50
7 2013-01-01 2013-01-08 00:00:10  -8 days +23:59:50
8 2013-01-01 2013-01-09 00:00:10  -9 days +23:59:50
9 2013-01-01 2013-01-10 00:00:10 -10 days +23:59:50

In [522]: store.append("dftd", dftd, data_columns=True)

In [523]: store.select("dftd", "C<'-3.5D'")
Out[523]: 
                              A                   B                  C
4 1970-01-01 00:00:01.356998400 2013-01-05 00:00:10  -5 days +23:59:50
5 1970-01-01 00:00:01.356998400 2013-01-06 00:00:10  -6 days +23:59:50
6 1970-01-01 00:00:01.356998400 2013-01-07 00:00:10  -7 days +23:59:50
7 1970-01-01 00:00:01.356998400 2013-01-08 00:00:10  -8 days +23:59:50
8 1970-01-01 00:00:01.356998400 2013-01-09 00:00:10  -9 days +23:59:50
9 1970-01-01 00:00:01.356998400 2013-01-10 00:00:10 -10 days +23:59:50

查询 MultiIndex#

可以通过使用级别的名称从 MultiIndex 中进行选择。

In [524]: df_mi.index.names
Out[524]: FrozenList(['foo', 'bar'])

In [525]: store.select("df_mi", "foo=baz and bar=two")
Out[525]: 
                A         B         C
foo bar                              
baz two -1.646063 -0.695847 -0.429156

如果 MultiIndex 级别的名称为 None,则这些级别会通过 level_n 关键字自动提供,其中 n 是您要从中选择的 MultiIndex 的级别。

In [526]: index = pd.MultiIndex(
   .....:     levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
   .....:     codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
   .....: )
   .....: 

In [527]: df_mi_2 = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])

In [528]: df_mi_2
Out[528]: 
                  A         B         C
foo one   -0.219582  1.186860 -1.437189
    two    0.053768  1.872644 -1.469813
    three -0.564201  0.876341  0.407749
bar one   -0.232583  0.179812  0.922152
    two   -1.820952 -0.641360  2.133239
baz two   -0.941248 -0.136307 -1.271305
    three -0.099774 -0.061438 -0.845172
qux one    0.465793  0.756995 -0.541690
    two   -0.802241  0.877657 -2.553831
    three  0.094899 -2.319519  0.293601

In [529]: store.append("df_mi_2", df_mi_2)

# the levels are automatically included as data columns with keyword level_n
In [530]: store.select("df_mi_2", "level_0=foo and level_1=two")
Out[530]: 
                A         B         C
foo two  0.053768  1.872644 -1.469813

索引#

您可以在数据已存在于表中(在 append/put 操作之后)之后,使用 create_table_index 为表创建/修改索引。强烈建议创建表索引。当您使用 select 并将索引维度作为 where 时,这将极大地提高您的查询速度。

注意

索引会自动在可索引列和您指定的任何数据列上创建。可以通过将 index=False 传递给 append 来关闭此行为。

# we have automagically already created an index (in the first section)
In [531]: i = store.root.df.table.cols.index.index

In [532]: i.optlevel, i.kind
Out[532]: (6, 'medium')

# change an index by passing new parameters
In [533]: store.create_table_index("df", optlevel=9, kind="full")

In [534]: i = store.root.df.table.cols.index.index

In [535]: i.optlevel, i.kind
Out[535]: (9, 'full')

通常,当将大量数据追加到存储区时,关闭每次追加的索引创建,然后在最后重新创建索引非常有用。

In [536]: df_1 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [537]: df_2 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [538]: st = pd.HDFStore("appends.h5", mode="w")

In [539]: st.append("df", df_1, data_columns=["B"], index=False)

In [540]: st.append("df", df_2, data_columns=["B"], index=False)

In [541]: st.get_storer("df").table
Out[541]: 
/df/table (Table(20,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
  "B": Float64Col(shape=(), dflt=0.0, pos=2)}
  byteorder := 'little'
  chunkshape := (2730,)

然后在完成追加后创建索引。

In [542]: st.create_table_index("df", columns=["B"], optlevel=9, kind="full")

In [543]: st.get_storer("df").table
Out[543]: 
/df/table (Table(20,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
  "B": Float64Col(shape=(), dflt=0.0, pos=2)}
  byteorder := 'little'
  chunkshape := (2730,)
  autoindex := True
  colindexes := {
    "B": Index(9, fullshuffle, zlib(1)).is_csi=True}

In [544]: st.close()

有关如何在现有存储区上创建完全排序索引 (CSI) 的方法,请参阅 此处

通过数据列查询#

您可以指定(并索引)某些列,以便您能够执行查询(除了 indexable 列,您始终可以查询这些列)。例如,假设您想在磁盘上执行此常见操作,并仅返回与该查询匹配的帧。您可以指定 data_columns = True 强制所有列成为 data_columns

In [545]: df_dc = df.copy()

In [546]: df_dc["string"] = "foo"

In [547]: df_dc.loc[df_dc.index[4:6], "string"] = np.nan

In [548]: df_dc.loc[df_dc.index[7:9], "string"] = "bar"

In [549]: df_dc["string2"] = "cool"

In [550]: df_dc.loc[df_dc.index[1:3], ["B", "C"]] = 1.0

In [551]: df_dc
Out[551]: 
                   A         B         C string string2
2000-01-01  0.858644 -0.851236  1.058006    foo    cool
2000-01-02 -0.080372  1.000000  1.000000    foo    cool
2000-01-03  0.816983  1.000000  1.000000    foo    cool
2000-01-04  0.712795 -0.062433  0.736755    foo    cool
2000-01-05 -0.298721 -1.988045  1.475308    NaN    cool
2000-01-06  1.103675  1.382242 -0.650762    NaN    cool
2000-01-07 -0.729161 -0.142928 -1.063038    foo    cool
2000-01-08 -1.005977  0.465222 -0.094517    bar    cool

# on-disk operations
In [552]: store.append("df_dc", df_dc, data_columns=["B", "C", "string", "string2"])

In [553]: store.select("df_dc", where="B > 0")
Out[553]: 
                   A         B         C string string2
2000-01-02 -0.080372  1.000000  1.000000    foo    cool
2000-01-03  0.816983  1.000000  1.000000    foo    cool
2000-01-06  1.103675  1.382242 -0.650762    NaN    cool
2000-01-08 -1.005977  0.465222 -0.094517    bar    cool

# getting creative
In [554]: store.select("df_dc", "B > 0 & C > 0 & string == foo")
Out[554]: 
                   A    B    C string string2
2000-01-02 -0.080372  1.0  1.0    foo    cool
2000-01-03  0.816983  1.0  1.0    foo    cool

# this is in-memory version of this type of selection
In [555]: df_dc[(df_dc.B > 0) & (df_dc.C > 0) & (df_dc.string == "foo")]
Out[555]: 
                   A    B    C string string2
2000-01-02 -0.080372  1.0  1.0    foo    cool
2000-01-03  0.816983  1.0  1.0    foo    cool

# we have automagically created this index and the B/C/string/string2
# columns are stored separately as ``PyTables`` columns
In [556]: store.root.df_dc.table
Out[556]: 
/df_dc/table (Table(8,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
  "B": Float64Col(shape=(), dflt=0.0, pos=2),
  "C": Float64Col(shape=(), dflt=0.0, pos=3),
  "string": StringCol(itemsize=3, shape=(), dflt=b'', pos=4),
  "string2": StringCol(itemsize=4, shape=(), dflt=b'', pos=5)}
  byteorder := 'little'
  chunkshape := (1680,)
  autoindex := True
  colindexes := {
    "index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
    "B": Index(6, mediumshuffle, zlib(1)).is_csi=False,
    "C": Index(6, mediumshuffle, zlib(1)).is_csi=False,
    "string": Index(6, mediumshuffle, zlib(1)).is_csi=False,
    "string2": Index(6, mediumshuffle, zlib(1)).is_csi=False}

将大量列转换为 data columns 会导致一些性能下降,因此由用户来指定这些列。此外,您无法在第一次追加/放置操作后更改数据列(或可索引列)(当然,您可以简单地读取数据并创建一个新表!)。

迭代器#

您可以传递 iterator=Truechunksize=number_in_a_chunkselectselect_as_multiple 以返回结果的迭代器。默认情况下,每块返回 50,000 行。

In [557]: for df in store.select("df", chunksize=3):
   .....:     print(df)
   .....: 
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
                   A         B         C
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
                   A         B         C
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

注意

您也可以将迭代器与 read_hdf 一起使用,它将在迭代完成后自动打开并关闭存储。

for df in pd.read_hdf("store.h5", "df", chunksize=3):
    print(df)

请注意,chunksize 关键字适用于 **源** 行。因此,如果您正在执行查询,那么 chunksize 将细分表中的总行数和应用的查询,返回一个可能大小不一的块的迭代器。

以下是如何生成查询并使用它来创建大小相等的返回块的示例。

In [558]: dfeq = pd.DataFrame({"number": np.arange(1, 11)})

In [559]: dfeq
Out[559]: 
   number
0       1
1       2
2       3
3       4
4       5
5       6
6       7
7       8
8       9
9      10

In [560]: store.append("dfeq", dfeq, data_columns=["number"])

In [561]: def chunks(l, n):
   .....:     return [l[i: i + n] for i in range(0, len(l), n)]
   .....: 

In [562]: evens = [2, 4, 6, 8, 10]

In [563]: coordinates = store.select_as_coordinates("dfeq", "number=evens")

In [564]: for c in chunks(coordinates, 2):
   .....:     print(store.select("dfeq", where=c))
   .....: 
   number
1       2
3       4
   number
5       6
7       8
   number
9      10

高级查询#

选择单个列#

要检索单个可索引或数据列,请使用 select_column 方法。例如,这将使您能够非常快速地获取索引。这些返回结果的 Series,按行号索引。目前,这些方法不接受 where 选择器。

In [565]: store.select_column("df_dc", "index")
Out[565]: 
0   2000-01-01
1   2000-01-02
2   2000-01-03
3   2000-01-04
4   2000-01-05
5   2000-01-06
6   2000-01-07
7   2000-01-08
Name: index, dtype: datetime64[ns]

In [566]: store.select_column("df_dc", "string")
Out[566]: 
0    foo
1    foo
2    foo
3    foo
4    NaN
5    NaN
6    foo
7    bar
Name: string, dtype: object
选择坐标#

有时您需要获取查询的坐标(也称为索引位置)。这将返回结果位置的 Index。这些坐标也可以传递给后续的 where 操作。

In [567]: df_coord = pd.DataFrame(
   .....:     np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
   .....: )
   .....: 

In [568]: store.append("df_coord", df_coord)

In [569]: c = store.select_as_coordinates("df_coord", "index > 20020101")

In [570]: c
Out[570]: 
Index([732, 733, 734, 735, 736, 737, 738, 739, 740, 741,
       ...
       990, 991, 992, 993, 994, 995, 996, 997, 998, 999],
      dtype='int64', length=268)

In [571]: store.select("df_coord", where=c)
Out[571]: 
                   0         1
2002-01-02  0.007717  1.168386
2002-01-03  0.759328 -0.638934
2002-01-04 -1.154018 -0.324071
2002-01-05 -0.804551 -1.280593
2002-01-06 -0.047208  1.260503
...              ...       ...
2002-09-22 -1.139583  0.344316
2002-09-23 -0.760643 -1.306704
2002-09-24  0.059018  1.775482
2002-09-25  1.242255 -0.055457
2002-09-26  0.410317  2.194489

[268 rows x 2 columns]
使用 where 掩码进行选择#

有时您的查询可能涉及创建要选择的行的列表。通常,此 mask 将是索引操作产生的 index。此示例选择日期时间索引中月份为 5 的月份。

In [572]: df_mask = pd.DataFrame(
   .....:     np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
   .....: )
   .....: 

In [573]: store.append("df_mask", df_mask)

In [574]: c = store.select_column("df_mask", "index")

In [575]: where = c[pd.DatetimeIndex(c).month == 5].index

In [576]: store.select("df_mask", where=where)
Out[576]: 
                   0         1
2000-05-01  1.479511  0.516433
2000-05-02 -0.334984 -1.493537
2000-05-03  0.900321  0.049695
2000-05-04  0.614266 -1.077151
2000-05-05  0.233881  0.493246
...              ...       ...
2002-05-27  0.294122  0.457407
2002-05-28 -1.102535  1.215650
2002-05-29 -0.432911  0.753606
2002-05-30 -1.105212  2.311877
2002-05-31  2.567296  2.610691

[93 rows x 2 columns]
Storer 对象#

如果您想检查存储的对象,请通过 get_storer 检索。您可以以编程方式使用它来获取对象中的行数。

In [577]: store.get_storer("df_dc").nrows
Out[577]: 8

多表查询#

方法 append_to_multipleselect_as_multiple 可以一次性对多个表进行追加/选择操作。其理念是拥有一个表(称为选择器表),您可以在其中索引大多数/所有列,并执行查询。其他表是数据表,其索引与选择器表的索引匹配。然后,您可以在选择器表上执行非常快速的查询,但可以获取大量数据。此方法类似于拥有一个非常宽的表,但可以实现更有效的查询。

方法 append_to_multiple 会根据 d 将给定的单个 DataFrame 拆分为多个表,d 是一个字典,它将表名映射到您想要在该表中使用的“列”列表。如果使用 None 代替列表,则该表将包含给定 DataFrame 中未指定的剩余列。参数 selector 定义哪个表是选择器表(您可以从中进行查询)。参数 dropna 将从输入 DataFrame 中删除行,以确保表同步。这意味着如果要写入的其中一个表的行完全为 np.nan,则该行将从所有表中删除。

如果 dropna 为 False,用户负责同步表。请记住,完全为 np.Nan 的行不会写入 HDFStore,因此如果您选择调用 dropna=False,某些表可能比其他表具有更多行,因此 select_as_multiple 可能无法正常工作或可能返回意外结果。

In [578]: df_mt = pd.DataFrame(
   .....:     np.random.randn(8, 6),
   .....:     index=pd.date_range("1/1/2000", periods=8),
   .....:     columns=["A", "B", "C", "D", "E", "F"],
   .....: )
   .....: 

In [579]: df_mt["foo"] = "bar"

In [580]: df_mt.loc[df_mt.index[1], ("A", "B")] = np.nan

# you can also create the tables individually
In [581]: store.append_to_multiple(
   .....:     {"df1_mt": ["A", "B"], "df2_mt": None}, df_mt, selector="df1_mt"
   .....: )
   .....: 

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

# individual tables were created
In [583]: store.select("df1_mt")
Out[583]: 
                   A         B
2000-01-01  0.162291 -0.430489
2000-01-02       NaN       NaN
2000-01-03  0.429207 -1.099274
2000-01-04  1.869081 -1.466039
2000-01-05  0.092130 -1.726280
2000-01-06  0.266901 -0.036854
2000-01-07 -0.517871 -0.990317
2000-01-08 -0.231342  0.557402

In [584]: store.select("df2_mt")
Out[584]: 
                   C         D         E         F  foo
2000-01-01 -2.502042  0.668149  0.460708  1.834518  bar
2000-01-02  0.130441 -0.608465  0.439872  0.506364  bar
2000-01-03 -1.069546  1.236277  0.116634 -1.772519  bar
2000-01-04  0.137462  0.313939  0.748471 -0.943009  bar
2000-01-05  0.836517  2.049798  0.562167  0.189952  bar
2000-01-06  1.112750 -0.151596  1.503311  0.939470  bar
2000-01-07 -0.294348  0.335844 -0.794159  1.495614  bar
2000-01-08  0.860312 -0.538674 -0.541986 -1.759606  bar

# as a multiple
In [585]: store.select_as_multiple(
   .....:     ["df1_mt", "df2_mt"],
   .....:     where=["A>0", "B>0"],
   .....:     selector="df1_mt",
   .....: )
   .....: 
Out[585]: 
Empty DataFrame
Columns: [A, B, C, D, E, F, foo]
Index: []

从表中删除#

您可以通过指定 where 选择性地从表中删除数据。在删除行时,重要的是要了解 PyTables 通过擦除行,然后移动后续数据来删除行。因此,删除操作可能会非常昂贵,具体取决于数据的排列方式。为了获得最佳性能,值得将要删除的维度设置为 indexables 的第一个维度。

数据按 indexables 的顺序(在磁盘上)排列。以下是一个简单的用例。您存储面板类型数据,日期在 major_axis 中,ID 在 minor_axis 中。然后,数据像这样交织在一起

  • date_1
    • id_1

    • id_2

    • .

    • id_n

  • date_2
    • id_1

    • .

    • id_n

很明显,对 major_axis 进行删除操作会很快,因为只需要移除一个块,然后将后面的数据移动。另一方面,对 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)就被固定了;只能追加完全相同的列。

  • 请注意,时区(例如,pytz.timezone('US/Eastern'))在不同时区版本之间并不一定相等。因此,如果数据在 HDFStore 中使用一个版本的时区库被本地化为特定时区,并且该数据使用另一个版本更新,则数据将被转换为 UTC,因为这些时区不被认为是相等的。请使用相同的时区库版本或使用 tz_convert 以及更新的时区定义。

警告

PyTables 如果列名不能用作属性选择器,将显示 NaturalNameWarning自然标识符仅包含字母、数字和下划线,并且不能以数字开头。其他标识符不能在 where 子句中使用,并且通常是一个不好的主意。

数据类型#

HDFStore 将把对象数据类型映射到 PyTables 的底层数据类型。这意味着以下类型已知可以工作

类型

表示缺失值

浮点数:float64, float32, float16

np.nan

整数:int64, int32, int8, uint64,uint32, uint8

boolean

datetime64[ns]

NaT

timedelta64[ns]

NaT

分类数据:请参见下面的部分

对象:字符串

np.nan

unicode 列不受支持,并且**将失败**。

分类数据#

您可以将包含category 数据类型的数据写入HDFStore。查询方式与对象数组相同。但是,category 数据类型的数据以更有效的方式存储。

In [586]: dfcat = pd.DataFrame(
   .....:     {"A": pd.Series(list("aabbcdba")).astype("category"), "B": np.random.randn(8)}
   .....: )
   .....: 

In [587]: dfcat
Out[587]: 
   A         B
0  a -1.520478
1  a -1.069391
2  b -0.551981
3  b  0.452407
4  c  0.409257
5  d  0.301911
6  b -0.640843
7  a -2.253022

In [588]: dfcat.dtypes
Out[588]: 
A    category
B     float64
dtype: object

In [589]: cstore = pd.HDFStore("cats.h5", mode="w")

In [590]: cstore.append("dfcat", dfcat, format="table", data_columns=["A"])

In [591]: result = cstore.select("dfcat", where="A in ['b', 'c']")

In [592]: result
Out[592]: 
   A         B
2  b -0.551981
3  b  0.452407
4  c  0.409257
6  b -0.640843

In [593]: result.dtypes
Out[593]: 
A    category
B     float64
dtype: object

字符串列#

min_itemsize

HDFStore 的底层实现对字符串列使用固定列宽(itemsize)。字符串列的 itemsize 计算为传递给HDFStore 的数据(对于该列)长度的最大值,**在第一次追加时**。后续追加可能会为列引入**大于**列所能容纳的字符串,将引发异常(否则您可能会对这些列进行静默截断,从而导致信息丢失)。将来,我们可能会放宽此限制,允许用户指定截断。

在第一次创建表时传递min_itemsize 以先验指定特定字符串列的最小长度。min_itemsize 可以是整数,也可以是将列名映射到整数的字典。您可以传递values 作为键,以允许所有索引数据列具有此 min_itemsize。

传递min_itemsize 字典将导致所有传递的列自动创建为数据列

注意

如果您没有传递任何data_columns,那么min_itemsize 将是传递的任何字符串长度的最大值。

In [594]: dfs = pd.DataFrame({"A": "foo", "B": "bar"}, index=list(range(5)))

In [595]: dfs
Out[595]: 
     A    B
0  foo  bar
1  foo  bar
2  foo  bar
3  foo  bar
4  foo  bar

# A and B have a size of 30
In [596]: store.append("dfs", dfs, min_itemsize=30)

In [597]: store.get_storer("dfs").table
Out[597]: 
/dfs/table (Table(5,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": StringCol(itemsize=30, shape=(2,), dflt=b'', pos=1)}
  byteorder := 'little'
  chunkshape := (963,)
  autoindex := True
  colindexes := {
    "index": Index(6, mediumshuffle, zlib(1)).is_csi=False}

# A is created as a data_column with a size of 30
# B is size is calculated
In [598]: store.append("dfs2", dfs, min_itemsize={"A": 30})

In [599]: store.get_storer("dfs2").table
Out[599]: 
/dfs2/table (Table(5,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": StringCol(itemsize=3, shape=(1,), dflt=b'', pos=1),
  "A": StringCol(itemsize=30, shape=(), dflt=b'', pos=2)}
  byteorder := 'little'
  chunkshape := (1598,)
  autoindex := True
  colindexes := {
    "index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
    "A": Index(6, mediumshuffle, zlib(1)).is_csi=False}

nan_rep

字符串列将使用nan_rep 字符串表示来序列化np.nan(缺失值)。默认值为字符串值nan。您可能会无意中将实际的nan 值转换为缺失值。

In [600]: dfss = pd.DataFrame({"A": ["foo", "bar", "nan"]})

In [601]: dfss
Out[601]: 
     A
0  foo
1  bar
2  nan

In [602]: store.append("dfss", dfss)

In [603]: store.select("dfss")
Out[603]: 
     A
0  foo
1  bar
2  NaN

# here you need to specify a different nan rep
In [604]: store.append("dfss2", dfss, nan_rep="_nan_")

In [605]: store.select("dfss2")
Out[605]: 
     A
0  foo
1  bar
2  nan

性能#

  • tables 格式与 fixed 存储相比,写入性能会受到影响。其优势是可以追加/删除和查询(可能非常大量的数据)。写入时间通常比普通存储更长。查询时间可能非常快,尤其是在索引轴上。

  • 您可以将 chunksize=<int> 传递给 append,指定写入块大小(默认值为 50000)。这将显着降低写入时的内存使用量。

  • 您可以将 expectedrows=<int> 传递给第一个 append,以设置 PyTables 预期的总行数。这将优化读写性能。

  • 可以将重复行写入表格,但在选择中会过滤掉(选择最后一个项目;因此表格在主、次对上是唯一的)

  • 如果您尝试存储将被 PyTables 腌制(而不是存储为内在类型)的类型,则会引发 PerformanceWarning。有关更多信息和一些解决方案,请参见 此处

Feather#

Feather 为数据帧提供二进制列式序列化。它旨在使读取和写入数据帧更高效,并使跨数据分析语言共享数据变得容易。

Feather 旨在忠实地序列化和反序列化 DataFrame,支持所有 pandas 数据类型,包括扩展数据类型,例如分类和带时区的日期时间。

一些注意事项

  • 该格式不会为 DataFrame 写入 IndexMultiIndex,如果提供了非默认索引,则会引发错误。您可以使用 .reset_index() 存储索引,或使用 .reset_index(drop=True) 忽略它。

  • 不支持重复的列名和非字符串列名。

  • 不支持 object 类型列中的实际 Python 对象。尝试序列化时,这些对象会引发有帮助的错误消息。

查看 完整文档

In [606]: df = pd.DataFrame(
   .....:     {
   .....:         "a": list("abc"),
   .....:         "b": list(range(1, 4)),
   .....:         "c": np.arange(3, 6).astype("u1"),
   .....:         "d": np.arange(4.0, 7.0, dtype="float64"),
   .....:         "e": [True, False, True],
   .....:         "f": pd.Categorical(list("abc")),
   .....:         "g": pd.date_range("20130101", periods=3),
   .....:         "h": pd.date_range("20130101", periods=3, tz="US/Eastern"),
   .....:         "i": pd.date_range("20130101", periods=3, freq="ns"),
   .....:     }
   .....: )
   .....: 

In [607]: df
Out[607]: 
   a  b  c  ...          g                         h                             i
0  a  1  3  ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1  b  2  4  ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2  c  3  5  ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002

[3 rows x 9 columns]

In [608]: df.dtypes
Out[608]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                      category
g                datetime64[ns]
h    datetime64[ns, US/Eastern]
i                datetime64[ns]
dtype: object

写入 feather 文件。

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

从 feather 文件读取。

In [610]: result = pd.read_feather("example.feather")

In [611]: result
Out[611]: 
   a  b  c  ...          g                         h                             i
0  a  1  3  ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1  b  2  4  ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2  c  3  5  ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002

[3 rows x 9 columns]

# we preserve dtypes
In [612]: result.dtypes
Out[612]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                      category
g                datetime64[ns]
h    datetime64[ns, US/Eastern]
i                datetime64[ns]
dtype: object

Parquet#

Apache Parquet 为数据帧提供了一种分区的二进制列式序列化方法。它旨在使数据帧的读写更高效,并简化跨数据分析语言共享数据。Parquet 可以使用各种压缩技术尽可能缩小文件大小,同时保持良好的读取性能。

Parquet 旨在忠实地序列化和反序列化 DataFrame,支持所有 pandas 数据类型,包括扩展数据类型,例如带时区的 datetime。

一些注意事项。

  • 不支持重复的列名和非字符串列名。

  • pyarrow 引擎始终将索引写入输出,但 fastparquet 仅写入非默认索引。此额外的列可能会给不期望它的非 pandas 消费者带来问题。您可以使用 index 参数强制包含或省略索引,而不管底层引擎如何。

  • 如果指定了索引级别名称,则必须为字符串。

  • pyarrow 引擎中,非字符串类型的分类数据类型可以序列化为 parquet,但会反序列化为其原始数据类型。

  • pyarrow 引擎保留了字符串类型分类数据类型的 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 [613]: df = pd.DataFrame(
   .....:     {
   .....:         "a": list("abc"),
   .....:         "b": list(range(1, 4)),
   .....:         "c": np.arange(3, 6).astype("u1"),
   .....:         "d": np.arange(4.0, 7.0, dtype="float64"),
   .....:         "e": [True, False, True],
   .....:         "f": pd.date_range("20130101", periods=3),
   .....:         "g": pd.date_range("20130101", periods=3, tz="US/Eastern"),
   .....:         "h": pd.Categorical(list("abc")),
   .....:         "i": pd.Categorical(list("abc"), ordered=True),
   .....:     }
   .....: )
   .....: 

In [614]: df
Out[614]: 
   a  b  c    d      e          f                         g  h  i
0  a  1  3  4.0   True 2013-01-01 2013-01-01 00:00:00-05:00  a  a
1  b  2  4  5.0  False 2013-01-02 2013-01-02 00:00:00-05:00  b  b
2  c  3  5  6.0   True 2013-01-03 2013-01-03 00:00:00-05:00  c  c

In [615]: df.dtypes
Out[615]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                datetime64[ns]
g    datetime64[ns, US/Eastern]
h                      category
i                      category
dtype: object

写入 parquet 文件。

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

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

从 parquet 文件读取。

In [618]: result = pd.read_parquet("example_fp.parquet", engine="fastparquet")

In [619]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow")

In [620]: result.dtypes
Out[620]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                datetime64[ns]
g    datetime64[ns, US/Eastern]
h                      category
i                      category
dtype: object

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

In [621]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow", dtype_backend="pyarrow")

In [622]: result.dtypes
Out[622]: 
a                                      string[pyarrow]
b                                       int64[pyarrow]
c                                       uint8[pyarrow]
d                                      double[pyarrow]
e                                        bool[pyarrow]
f                               timestamp[ns][pyarrow]
g                timestamp[ns, tz=US/Eastern][pyarrow]
h    dictionary<values=string, indices=int32, order...
i    dictionary<values=string, indices=int32, order...
dtype: object

注意

请注意,这在 fastparquet 中不受支持。

仅读取 parquet 文件的某些列。

In [623]: result = pd.read_parquet(
   .....:     "example_fp.parquet",
   .....:     engine="fastparquet",
   .....:     columns=["a", "b"],
   .....: )
   .....: 

In [624]: result = pd.read_parquet(
   .....:     "example_pa.parquet",
   .....:     engine="pyarrow",
   .....:     columns=["a", "b"],
   .....: )
   .....: 

In [625]: result.dtypes
Out[625]: 
a    object
b     int64
dtype: object

处理索引#

DataFrame 序列化为 parquet 可能包含隐式索引作为输出文件中的一个或多个列。因此,这段代码

In [626]: df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})

In [627]: df.to_parquet("test.parquet", engine="pyarrow")

如果使用 pyarrow 进行序列化,则会创建一个包含 *三个* 列的 parquet 文件:ab__index_level_0__。如果您使用的是 fastparquet,则索引 可能写入或不写入 文件。

此意外的额外列会导致某些数据库(如 Amazon Redshift)拒绝该文件,因为目标表中不存在该列。

如果要省略写入时的数据帧索引,请将 index=False 传递给 to_parquet()

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

这将创建一个仅包含两个预期列的 parquet 文件,即 ab。如果您的 DataFrame 具有自定义索引,则在将此文件加载到 DataFrame 中时,您将无法获得该索引。

传递 index=True 将 *始终* 写入索引,即使这不是底层引擎的默认行为。

对 Parquet 文件进行分区#

Parquet 支持根据一个或多个列的值对数据进行分区。

In [629]: df = pd.DataFrame({"a": [0, 0, 1, 1], "b": [0, 1, 0, 1]})

In [630]: df.to_parquet(path="test", engine="pyarrow", partition_cols=["a"], compression=None)

path 指定将保存数据的父目录。 partition_cols 是用于对数据集进行分区的列名。列按给定的顺序进行分区。分区拆分由分区列中的唯一值确定。上面的示例创建了一个可能如下所示的分区数据集

test
├── a=0
│   ├── 0bac803e32dc42ae83fddfd029cbdebc.parquet
│   └──  ...
└── a=1
    ├── e6ab24a4f45147b49b54a662f0c412a3.parquet
    └── ...

ORC#

parquet 格式类似,ORC 格式 是数据帧的二进制列式序列化。它旨在提高读取数据帧的效率。pandas 提供了 ORC 格式的读取器和写入器,即 read_orc()to_orc()。这需要 pyarrow 库。

警告

  • 强烈建议使用 conda 安装 pyarrow,因为 pyarrow 存在一些问题。

  • to_orc() 需要 pyarrow>=7.0.0。

  • read_orc()to_orc() 目前在 Windows 上尚不支持,您可以在 安装可选依赖项 中找到有效的环境。

  • 有关支持的数据类型,请参考 Arrow 中支持的 ORC 功能

  • 目前,当将数据框转换为 ORC 文件时,不会保留 datetime 列中的时区。

In [631]: df = pd.DataFrame(
   .....:     {
   .....:         "a": list("abc"),
   .....:         "b": list(range(1, 4)),
   .....:         "c": np.arange(4.0, 7.0, dtype="float64"),
   .....:         "d": [True, False, True],
   .....:         "e": pd.date_range("20130101", periods=3),
   .....:     }
   .....: )
   .....: 

In [632]: df
Out[632]: 
   a  b    c      d          e
0  a  1  4.0   True 2013-01-01
1  b  2  5.0  False 2013-01-02
2  c  3  6.0   True 2013-01-03

In [633]: df.dtypes
Out[633]: 
a            object
b             int64
c           float64
d              bool
e    datetime64[ns]
dtype: object

写入 orc 文件。

In [634]: df.to_orc("example_pa.orc", engine="pyarrow")

从 orc 文件读取。

In [635]: result = pd.read_orc("example_pa.orc")

In [636]: result.dtypes
Out[636]: 
a            object
b             int64
c           float64
d              bool
e    datetime64[ns]
dtype: object

仅读取 orc 文件的某些列。

In [637]: result = pd.read_orc(
   .....:     "example_pa.orc",
   .....:     columns=["a", "b"],
   .....: )
   .....: 

In [638]: result.dtypes
Out[638]: 
a    object
b     int64
dtype: object

SQL 查询#

pandas.io.sql 模块提供了一组查询包装器,既便于数据检索,又减少了对特定于数据库的 API 的依赖。

在可用情况下,用户可能首先希望选择 Apache Arrow ADBC 驱动程序。这些驱动程序应提供最佳的性能、空值处理和类型检测。

版本 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 [639]: from sqlalchemy import create_engine

# Create your engine.
In [640]: engine = create_engine("sqlite:///:memory:")

如果您想管理自己的连接,您可以传递其中一个连接。以下示例使用 Python 上下文管理器打开与数据库的连接,该管理器在代码块完成后自动关闭连接。有关如何处理数据库连接的说明,请参阅 SQLAlchemy 文档

with engine.connect() as conn, conn.begin():
    data = pd.read_sql_table("data", conn)

警告

当您打开与数据库的连接时,您也负责关闭它。保持连接打开的副作用可能包括锁定数据库或其他破坏性行为。

写入 DataFrame#

假设以下数据位于 DataFrame data 中,我们可以使用 to_sql() 将其插入数据库。

id

日期

Col_1

Col_2

Col_3

26

2012-10-18

X

25.7

True

42

2012-10-19

Y

-12.4

False

63

2012-10-20

Z

5.73

True

In [641]: import datetime

In [642]: c = ["id", "Date", "Col_1", "Col_2", "Col_3"]

In [643]: d = [
   .....:     (26, datetime.datetime(2010, 10, 18), "X", 27.5, True),
   .....:     (42, datetime.datetime(2010, 10, 19), "Y", -12.5, False),
   .....:     (63, datetime.datetime(2010, 10, 20), "Z", 5.73, True),
   .....: ]
   .....: 

In [644]: data = pd.DataFrame(d, columns=c)

In [645]: data
Out[645]: 
   id       Date Col_1  Col_2  Col_3
0  26 2010-10-18     X  27.50   True
1  42 2010-10-19     Y -12.50  False
2  63 2010-10-20     Z   5.73   True

In [646]: data.to_sql("data", con=engine)
Out[646]: 3

对于某些数据库,写入大型 DataFrame 可能会导致错误,因为超过了数据包大小限制。可以通过在调用 to_sql 时设置 chunksize 参数来避免这种情况。例如,以下代码将 data 以每次 1000 行的批次写入数据库

In [647]: data.to_sql("data_chunked", con=engine, chunksize=1000)
Out[647]: 3

SQL 数据类型#

确保跨 SQL 数据库一致的数据类型管理具有挑战性。并非每个 SQL 数据库都提供相同类型,即使它们提供相同类型,给定类型的实现也可能在细微方面有所不同,从而对类型如何保留产生微妙的影响。

为了最大程度地保留数据库类型,建议用户在可用时使用 ADBC 驱动程序。Arrow 类型系统提供比历史 pandas/NumPy 类型系统更广泛的类型,这些类型更接近数据库类型。为了说明这一点,请注意此(非详尽的)不同数据库和 pandas 后端中可用类型的列表

numpy/pandas

arrow

postgres

sqlite

int16/Int16

int16

SMALLINT

INTEGER

int32/Int32

int32

INTEGER

INTEGER

int64/Int64

int64

BIGINT

INTEGER

float32

float32

REAL

REAL

float64

float64

DOUBLE PRECISION

REAL

object

string

TEXT

TEXT

bool

bool_

BOOLEAN

datetime64[ns]

timestamp(us)

TIMESTAMP

datetime64[ns,tz]

timestamp(us,tz)

TIMESTAMPTZ

date32

DATE

month_day_nano_interval

INTERVAL

二进制

BINARY

BLOB

decimal128

DECIMAL [1]

list

ARRAY [1]

struct

COMPOSITE TYPE

[1]

脚注

如果您有兴趣在 DataFrame 的整个生命周期中尽可能保留数据库类型,建议用户利用 dtype_backend="pyarrow" read_sql() 的参数。

# 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 [648]: from sqlalchemy.types import String

In [649]: data.to_sql("data_dtype", con=engine, dtype={"Col_1": String})
Out[649]: 3

注意

由于不同数据库版本对 timedelta 的支持有限,类型为 timedelta64 的列将以纳秒为单位的整数值写入数据库,并会发出警告。唯一的例外是使用 ADBC PostgreSQL 驱动程序时,在这种情况下,timedelta 将作为 INTERVAL 写入数据库。

注意

类型为 category 的列将转换为密集表示,就像使用 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 [650]: pd.read_sql_table("data", engine)
Out[650]: 
   index  id       Date Col_1  Col_2  Col_3
0      0  26 2010-10-18     X  27.50   True
1      1  42 2010-10-19     Y -12.50  False
2      2  63 2010-10-20     Z   5.73   True

注意

ADBC 驱动程序将直接将数据库类型映射回箭头类型。对于其他驱动程序,请注意,pandas 从查询输出推断列数据类型,而不是通过查找物理数据库模式中的数据类型来推断。例如,假设 userid 是表中的整数列。然后,直观地,select userid ... 将返回整数值系列,而 select cast(userid as text) ... 将返回对象值 (str) 系列。因此,如果查询输出为空,则所有结果列都将作为对象值 (因为它们是最通用的) 返回。如果您预见到您的查询有时会生成空结果,则您可能希望在之后显式类型转换以确保数据类型完整性。

您也可以将列名指定为 DataFrame 索引,并指定要读取的列子集。

In [651]: pd.read_sql_table("data", engine, index_col="id")
Out[651]: 
    index       Date Col_1  Col_2  Col_3
id                                      
26      0 2010-10-18     X  27.50   True
42      1 2010-10-19     Y -12.50  False
63      2 2010-10-20     Z   5.73   True

In [652]: pd.read_sql_table("data", engine, columns=["Col_1", "Col_2"])
Out[652]: 
  Col_1  Col_2
0     X  27.50
1     Y -12.50
2     Z   5.73

您还可以显式地强制将列解析为日期。

In [653]: pd.read_sql_table("data", engine, parse_dates=["Date"])
Out[653]: 
   index  id       Date Col_1  Col_2  Col_3
0      0  26 2010-10-18     X  27.50   True
1      1  42 2010-10-19     Y -12.50  False
2      2  63 2010-10-20     Z   5.73   True

如果需要,您可以显式地指定格式字符串,或传递给 pandas.to_datetime() 的参数字典。

pd.read_sql_table("data", engine, parse_dates={"Date": "%Y-%m-%d"})
pd.read_sql_table(
    "data",
    engine,
    parse_dates={"Date": {"format": "%Y-%m-%d %H:%M:%S"}},
)

您可以使用 has_table() 检查表是否存在。

模式支持#

通过 read_sql_table()to_sql() 函数中的 schema 关键字支持从不同模式读取和写入。但是请注意,这取决于数据库类型(sqlite 没有模式)。例如

df.to_sql(name="table", con=engine, schema="other_schema")
pd.read_sql_table("table", engine, schema="other_schema")

查询#

您可以在 read_sql_query() 函数中使用原始 SQL 进行查询。在这种情况下,您必须使用适合您的数据库的 SQL 变体。使用 SQLAlchemy 时,您还可以传递 SQLAlchemy 表达式语言构造,这些构造与数据库无关。

In [654]: pd.read_sql_query("SELECT * FROM data", engine)
Out[654]: 
   index  id                        Date Col_1  Col_2  Col_3
0      0  26  2010-10-18 00:00:00.000000     X  27.50      1
1      1  42  2010-10-19 00:00:00.000000     Y -12.50      0
2      2  63  2010-10-20 00:00:00.000000     Z   5.73      1

当然,您可以指定更“复杂”的查询。

In [655]: pd.read_sql_query("SELECT id, Col_1, Col_2 FROM data WHERE id = 42;", engine)
Out[655]: 
   id Col_1  Col_2
0  42     Y  -12.5

read_sql_query() 函数支持 chunksize 参数。指定此参数将返回查询结果块的迭代器。

In [656]: df = pd.DataFrame(np.random.randn(20, 3), columns=list("abc"))

In [657]: df.to_sql(name="data_chunks", con=engine, index=False)
Out[657]: 20
In [658]: for chunk in pd.read_sql_query("SELECT * FROM data_chunks", engine, chunksize=5):
   .....:     print(chunk)
   .....: 
          a         b         c
0 -0.395347 -0.822726 -0.363777
1  1.676124 -0.908102 -1.391346
2 -1.094269  0.278380  1.205899
3  1.503443  0.932171 -0.709459
4 -0.645944 -1.351389  0.132023
          a         b         c
0  0.210427  0.192202  0.661949
1  1.690629 -1.046044  0.618697
2 -0.013863  1.314289  1.951611
3 -1.485026  0.304662  1.194757
4 -0.446717  0.528496 -0.657575
          a         b         c
0 -0.876654  0.336252  0.172668
1  0.337684 -0.411202 -0.828394
2 -0.244413  1.094948  0.087183
3  1.125934 -1.480095  1.205944
4 -0.451849  0.452214 -2.208192
          a         b         c
0 -2.061019  0.044184 -0.017118
1  1.248959 -0.675595 -1.908296
2 -0.125934  1.491974  0.648726
3  0.391214  0.438609  1.634248
4  1.208707 -1.535740  1.620399

引擎连接示例#

要连接到 SQLAlchemy,您使用 create_engine() 函数从数据库 URI 创建引擎对象。您只需要为要连接的每个数据库创建一次引擎。

from sqlalchemy import create_engine

engine = create_engine("postgresql://scott:tiger@localhost:5432/mydatabase")

engine = create_engine("mysql+mysqldb://scott:tiger@localhost/foo")

engine = create_engine("oracle://scott:[email protected]:1521/sidname")

engine = create_engine("mssql+pyodbc://mydsn")

# sqlite://<nohostname>/<path>
# where <path> is relative:
engine = create_engine("sqlite:///foo.db")

# or absolute, starting with a slash:
engine = create_engine("sqlite:////absolute/path/to/foo.db")

有关更多信息,请参阅 SQLAlchemy 文档 中的示例。

高级 SQLAlchemy 查询#

您可以使用 SQLAlchemy 结构来描述您的查询。

使用 sqlalchemy.text() 以与后端无关的方式指定查询参数

In [659]: import sqlalchemy as sa

In [660]: pd.read_sql(
   .....:     sa.text("SELECT * FROM data where Col_1=:col1"), engine, params={"col1": "X"}
   .....: )
   .....: 
Out[660]: 
   index  id                        Date Col_1  Col_2  Col_3
0      0  26  2010-10-18 00:00:00.000000     X   27.5      1

如果您有数据库的 SQLAlchemy 描述,您可以使用 SQLAlchemy 表达式来表达 where 条件

In [661]: metadata = sa.MetaData()

In [662]: data_table = sa.Table(
   .....:     "data",
   .....:     metadata,
   .....:     sa.Column("index", sa.Integer),
   .....:     sa.Column("Date", sa.DateTime),
   .....:     sa.Column("Col_1", sa.String),
   .....:     sa.Column("Col_2", sa.Float),
   .....:     sa.Column("Col_3", sa.Boolean),
   .....: )
   .....: 

In [663]: pd.read_sql(sa.select(data_table).where(data_table.c.Col_3 is True), engine)
Out[663]: 
Empty DataFrame
Columns: [index, Date, Col_1, Col_2, Col_3]
Index: []

您可以将 SQLAlchemy 表达式与传递给 read_sql() 的参数结合使用 sqlalchemy.bindparam()

In [664]: import datetime as dt

In [665]: expr = sa.select(data_table).where(data_table.c.Date > sa.bindparam("date"))

In [666]: pd.read_sql(expr, engine, params={"date": dt.datetime(2010, 10, 18)})
Out[666]: 
   index       Date Col_1  Col_2  Col_3
0      1 2010-10-19     Y -12.50  False
1      2 2010-10-20     Z   5.73   True

Sqlite 回退#

在不使用 SQLAlchemy 的情况下支持使用 sqlite。此模式需要一个 Python 数据库适配器,它符合 Python DB-API

您可以像这样创建连接

import sqlite3

con = sqlite3.connect(":memory:")

然后发出以下查询

data.to_sql("data", con)
pd.read_sql_query("SELECT * FROM data", con)

Google BigQuery#

pandas-gbq 包提供了从 Google BigQuery 读取/写入的功能。

pandas 与这个外部包集成。如果安装了 pandas-gbq,您可以使用 pandas 方法 pd.read_gbqDataFrame.to_gbq,它们将调用 pandas-gbq 中的相应函数。

完整的文档可以在 这里 找到。

Stata 格式#

写入 stata 格式#

方法 DataFrame.to_stata() 将把 DataFrame 写入 .dta 文件。此文件的格式版本始终为 115(Stata 12)。

In [667]: df = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [668]: df.to_stata("stata.dta")

Stata 数据文件对数据类型的支持有限;只能存储长度不超过 244 个字符的字符串、int8int16int32float32float64 类型的数据,这些数据可以存储在 .dta 文件中。此外,Stata 会保留某些值来表示缺失数据。如果将非缺失值导出到 Stata 中,而该值超出了特定数据类型允许的范围,则会将变量重新类型化为下一个更大的尺寸。例如,int8 值在 Stata 中的取值范围限制在 -127 到 100 之间,因此,如果变量的值超过 100,则会触发转换为 int16。浮点数据类型中的 nan 值将存储为基本缺失数据类型(在 Stata 中为 .)。

注意

无法导出整数数据类型的缺失数据值。

Stata 写入器可以很好地处理其他数据类型,包括 int64booluint8uint16uint32,方法是将其转换为能够表示数据的最小支持类型。例如,如果数据类型为 uint8,并且所有值都小于 100(Stata 中非缺失 int8 数据的上限),则该数据将转换为 int8;如果值超出此范围,则该变量将转换为 int16

警告

int64 转换为 float64 可能会导致精度损失,如果 int64 值大于 2**53。

警告

StataWriterDataFrame.to_stata() 仅支持包含最多 244 个字符的固定宽度字符串,这是由版本 115 dta 文件格式所施加的限制。尝试写入包含超过 244 个字符的字符串的 Stata dta 文件会导致 ValueError 错误。

从 Stata 格式读取#

顶级函数 read_stata 将读取 dta 文件并返回一个 DataFrame 或一个 pandas.api.typing.StataReader,该对象可用于增量读取文件。

In [669]: pd.read_stata("stata.dta")
Out[669]: 
   index         A         B
0      0 -0.165614  0.490482
1      1 -0.637829  0.067091
2      2 -0.242577  1.348038
3      3  0.647699 -0.644937
4      4  0.625771  0.918376
5      5  0.401781 -1.488919
6      6 -0.981845 -0.046882
7      7 -0.306796  0.877025
8      8 -0.336606  0.624747
9      9 -1.582600  0.806340

指定 chunksize 将生成一个 pandas.api.typing.StataReader 实例,可用于一次读取文件中的 chunksize 行。 StataReader 对象可以用作迭代器。

In [670]: with pd.read_stata("stata.dta", chunksize=3) as reader:
   .....:     for df in reader:
   .....:         print(df.shape)
   .....: 
(3, 3)
(3, 3)
(3, 3)
(1, 3)

为了更精细的控制,请使用 iterator=True 并为每次调用 read() 指定 chunksize

In [671]: with pd.read_stata("stata.dta", iterator=True) as reader:
   .....:     chunk1 = reader.read(5)
   .....:     chunk2 = reader.read(5)
   .....: 

目前, index 被检索为一列。

参数 convert_categoricals 指示是否应该读取值标签并使用它们从值标签创建 Categorical 变量。值标签也可以通过函数 value_labels 检索,该函数需要在使用之前调用 read()

参数 convert_missing 指示是否应该保留 Stata 中的缺失值表示。如果为 False(默认值),则缺失值表示为 np.nan。如果为 True,则缺失值使用 StataMissingValue 对象表示,包含缺失值的列将具有 object 数据类型。

注意

read_stata()StataReader 支持 .dta 格式 113-115(Stata 10-12)、117(Stata 13)和 118(Stata 14)。

注意

设置 preserve_dtypes=False 将向上转换为标准的 pandas 数据类型:所有整数类型为 int64,浮点数据为 float64。默认情况下,导入时会保留 Stata 数据类型。

注意

所有 StataReader 对象,无论是由 read_stata()(当使用 iterator=Truechunksize)创建,还是手动实例化,都必须用作上下文管理器(例如 with 语句)。虽然 close() 方法可用,但其使用不受支持。它不是公共 API 的一部分,将在未来版本中不经警告地删除。

分类数据#

Categorical 数据可以导出到 Stata 数据文件作为值标记数据。导出数据包含作为整数数据值的底层类别代码和作为值标签的类别。Stata 没有与 Categorical 等效的显式等效项,并且关于变量是否排序的信息在导出时会丢失。

警告

Stata 仅支持字符串值标签,因此在导出数据时会对类别调用 str。导出具有非字符串类别的 Categorical 变量会产生警告,如果类别的 str 表示形式不唯一,可能会导致信息丢失。

使用关键字参数 convert_categoricals(默认值为 True)可以从 Stata 数据文件将标记数据类似地导入为 Categorical 变量。关键字参数 order_categoricals(默认值为 True)决定导入的 Categorical 变量是否排序。

注意

导入分类数据时,Stata 数据文件中的变量值不会保留,因为 Categorical 变量始终使用介于 -1n-1 之间的整数数据类型,其中 n 是类别数。如果需要 Stata 数据文件中的原始值,可以通过设置 convert_categoricals=False 导入这些值,这将导入原始数据(但不会导入变量标签)。原始值可以与导入的分类数据匹配,因为导入的 Categorical 变量的原始 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")

获取迭代器并以 100,000 行为单位读取 XPORT 文件

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.core.frame.DataFrame'>
RangeIndex: 1000000 entries, 0 to 999999
Data columns (total 2 columns):
A    1000000 non-null float64
B    1000000 non-null int64
dtypes: float64(1), int64(1)
memory usage: 15.3 MB

以下测试函数将用于比较几种 IO 方法的性能

import numpy as np

import os

sz = 1000000
df = pd.DataFrame({"A": np.random.randn(sz), "B": [1] * sz})

sz = 1000000
np.random.seed(42)
df = pd.DataFrame({"A": np.random.randn(sz), "B": [1] * sz})


def test_sql_write(df):
    if os.path.exists("test.sql"):
        os.remove("test.sql")
    sql_db = sqlite3.connect("test.sql")
    df.to_sql(name="test_table", con=sql_db)
    sql_db.close()


def test_sql_read():
    sql_db = sqlite3.connect("test.sql")
    pd.read_sql_query("select * from test_table", sql_db)
    sql_db.close()


def test_hdf_fixed_write(df):
    df.to_hdf("test_fixed.hdf", key="test", mode="w")


def test_hdf_fixed_read():
    pd.read_hdf("test_fixed.hdf", "test")


def test_hdf_fixed_write_compress(df):
    df.to_hdf("test_fixed_compress.hdf", key="test", mode="w", complib="blosc")


def test_hdf_fixed_read_compress():
    pd.read_hdf("test_fixed_compress.hdf", "test")


def test_hdf_table_write(df):
    df.to_hdf("test_table.hdf", key="test", mode="w", format="table")


def test_hdf_table_read():
    pd.read_hdf("test_table.hdf", "test")


def test_hdf_table_write_compress(df):
    df.to_hdf(
        "test_table_compress.hdf", key="test", mode="w", complib="blosc", format="table"
    )


def test_hdf_table_read_compress():
    pd.read_hdf("test_table_compress.hdf", "test")


def test_csv_write(df):
    df.to_csv("test.csv", mode="w")


def test_csv_read():
    pd.read_csv("test.csv", index_col=0)


def test_feather_write(df):
    df.to_feather("test.feather")


def test_feather_read():
    pd.read_feather("test.feather")


def test_pickle_write(df):
    df.to_pickle("test.pkl")


def test_pickle_read():
    pd.read_pickle("test.pkl")


def test_pickle_write_compress(df):
    df.to_pickle("test.pkl.compress", compression="xz")


def test_pickle_read_compress():
    pd.read_pickle("test.pkl.compress", compression="xz")


def test_parquet_write(df):
    df.to_parquet("test.parquet")


def test_parquet_read():
    pd.read_parquet("test.parquet")

在写入方面,速度最快的三个函数是 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.compresstest.parquettest.feather 在磁盘上占用的空间最小(以字节为单位)。

29519500 Oct 10 06:45 test.csv
16000248 Oct 10 06:45 test.feather
8281983  Oct 10 06:49 test.parquet
16000857 Oct 10 06:47 test.pkl
7552144  Oct 10 06:48 test.pkl.compress
34816000 Oct 10 06:42 test.sql
24009288 Oct 10 06:43 test_fixed.hdf
24009288 Oct 10 06:43 test_fixed_compress.hdf
24458940 Oct 10 06:44 test_table.hdf
24458940 Oct 10 06:44 test_table_compress.hdf