Pandas 中文参考指南

IO tools (text, CSV, HDF5, …)

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

格式 类型

数据说明

读取器

写入器

文本

CSV

文本

固定宽度文本文件

文本

文本

文本

文本

XML

文本

本地剪贴板

binary

@ {s0}

@ {s1}

@ {s2}

binary

@ {s3}

@ {s4}

binary

@ {s5}

@ {s6}

@ {s7}

binary

@ {s8}

@ {s9}

@ {s10}

binary

@ {s11}

@ {s12}

@ {s13}

binary

@ {s14}

@ {s15}

@ {s16}

binary

@ {s17}

@ {s18}

binary

SAS

binary

binary

SQL

SQL

SQL

Here 是这些 IO 方法的部分非正式性能比较。

对于使用 StringIO 类的示例,请确保使用 from io import StringIO 将其导入到 Python 3。

CSV & text files

用于读取文本文件(又称平面文件)的重要函数是 read_csv()。有关一些高级策略,请参见 cookbook

Parsing options

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

  1. *filepath_or_buffervarious*路径为文件( strpathlib.Pathpy:py._path.local.LocalPath)、网址(包括 http、ftp 和 S3 位置)或任何带有 read() 方法的对象(例如打开的文件或 StringIO)。

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

  3. *delimiterstr,默认 None*sep 的替代参数名称。

  4. *delim_whitespaceboolean,默认 False*指定空格(例如 ' ''\t')是否用作分隔符。相当于设置 sep='\s+'。如果此选项设置为 True,则不应为 delimiter 参数传入任何内容。

  5. *headerint 或整型列表,默认 'infer'*用作列名称和数据开头的行号。默认行为是推断列名称:如果没有传递名称,则行为与 header=0 相同,并且从文件的第 1 行推断列名称,如果明确传递列名称,则行为与 header=None 相同。明确传递 header=0 以便替换现有名称。标头可以是整数列表,指定列上的 MultiIndex 行位置,例如 [0,1,3]。将跳过未指定的中间行(例如,此示例中的 2 被跳过)。请注意,此参数会忽略注释行和空行(如果 skip_blank_lines=True),因此 header=0 表示数据的第 1 行,而不是文件的第一行。

  6. *namesarray-like,默认 None*要使用的列名称列表。如果文件中不包含标头行,则应明确传递 header=None。此列表中不允许出现重复项。

  7. *index_colint、str、int/str 序列或 False,可选,默认 None*用作 DataFrame 的行标签的列,作为字符串名称或列索引给出。如果给出了 int/str 序列,则使用 MultiIndex。

index_col=False 可用于强制 Pandas 不将第一列用作索引,例如当您有一个格式错误的文件,每一行结尾都有分隔符。None 的默认值指示 Pandas 猜测。如果列头行中的字段数等于正文数据文件中的字段数,则使用默认索引。如果更大,则前几列用作索引,以便正文中剩余的字段数等于头中的字段数。标头之后的第一个行用于确定列数,该列数将进入索引。如果后续行包含的列少于第一行,则用 NaN 填充这些行。这可以通过 usecols 避免。这可确保原样获取列,并忽略尾部数据。

  1. *usecolslist-like 或可调用,默认 None*返回列的子集。如果类似列表,则所有元素都必须是位置(即文档列中的整数索引)或对应于列名称的字符串,这些名称由用户在 names 中提供或从文档头行推断。如果给出了 names,则不考虑文档标题行。例如,一个有效的类似列表 usecols 参数为 [0, 1, 2]['foo', 'bar', 'baz']。不考虑元素顺序,因此 usecols=[0, 1][1, 0] 相同。要通过保留元素顺序来从 data 实例化 DataFrame,请按 ['foo', 'bar'] 顺序为 pd.read_csv(data, usecols=['foo', 'bar'])[['foo', 'bar']] 中的列使用,或按 ['bar', 'foo'] 顺序使用 pd.read_csv(data, usecols=['foo', 'bar'])[['bar', 'foo']]。如果可调用,则可调用函数将针对列名称求值,返回可调用函数求值为 True 的名称:

In [1]: import pandas as pd

In [2]: from io import StringIO

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

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

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

使用此参数在使用 c 引擎时会大大缩短解析时间并降低内存使用量。Python 引擎先加载数据,然后再决定放弃哪些列。*dtype类型名称或列 → 类型的 dict,默认 None*数据或列的数据类型。例如,{'a': np.float64, 'b': np.int32, 'c': 'Int64'} 使用 strobject 与合适的 na_values 设置一起使用以保留和不解释 dtype。如果指定了转换器,它们将代替 dtype 转换应用。1.5.0 版新增:添加了对 defaultdict 的支持。指定一个 defaultdict 作为输入,其中默认值决定了未明确列出的列的数据类型。

  1. *dtype_backend{“numpy_nullable”、“pyarrow”},默认为 NumPy 支持的数据框*要使用的 dtype_backend,例如 DataFrame 是否应具有 NumPy 数组,当设置“numpy_nullable”时对具有空实现的所有数据类型都使用可空数据类型,如果设置“pyarrow”,则对所有数据类型都使用 pyarrow。dtype_backends 仍处于实验阶段。2.0 版新增。

  2. *engine{'c''python''pyarrow'}*要使用的解析引擎。C 和 pyarrow 引擎更快,而 python 引擎目前的功能更完备。目前,只有 pyarrow 引擎支持多线程。1.4.0 版新增:“pyarrow”引擎已添加为实验引擎,并且某些功能不受支持,或可能无法与此引擎一起正常工作。

  3. *convertersdict,默认 None*用于转换某些列中值的函数的词典。键可以是整数或列标签。

  4. *true_valueslist,默认 None*认为 True 的值。

  5. *false_valueslist,默认 None*认为 False 的值。

  6. *skipinitialspaceboolean,默认 False*跳过分隔符后的空格。

  7. *skiprowslist-like 或整数,默认 None*要跳过的行号(从 0 开始索引)或文件开头要跳过的行数(int)。如果可调用,则可调用函数将针对行索引求值,如果应该跳过行,则返回 True,否则返回 False:

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

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

In [8]: pd.read_csv(StringIO(data), skiprows=lambda x: x % 2 != 0)
Out[8]:
  col1 col2  col3
0    a    b     2
  1. *skipfooterint,默认 0*要跳过的文件底部的行数(引擎='c' 时不支持)。

  2. *nrowsint,默认 None*要读取的文件行数。对于读取大文件的部分内容非常有用。

  3. *low_memoryboolean,默认 True*以块状方式内部处理文件,从而在解析时降低内存使用,但可能会导致混合类型推断。要确保没有混合类型,请设置 False 或使用 dtype 参数指定类型。请注意,整个文件无论如何都会被读入一个 DataFrame,可以使用 chunksizeiterator 参数以块状方式返回数据。(仅对 C 解析器有效)

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

  5. *na_valuess 标量、str、类列表或 dict,默认 None*识别为 NA/NaN 的附加字符串。如果传递 dict,则是特定于每列的 NA 值。有关默认情况下解释为 NaN 的值列表,请参阅下面的 na values const

  6. *keep_default_naboolean,默认 True*在解析数据时是否包括默认 NaN 值。根据是否传递了 na_values,行为如下:

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

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

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

  10. 如果 keep_default_naFalse 且未指定 na_values,则不会将任何字符串解析为 NaN。请注意,如果将 na_filter 传递为 False,则将忽略 keep_default_nana_values 参数。

  11. *na_filterboolean,默认 True*检测缺失值标记(空字符串和 na_values 的值)。在不含任何 NA 的数据中,传递 na_filter=False 可以提高读取大文件时的性能。

  12. *verboseboolean,默认 False*指示放置在非数字列中的 NA 值的数量。

  13. *skip_blank_linesboolean,默认 True*如果 True,请跳过空行,而不是解释为 NaN 值。

  14. parse_datesboolean 或 int 或名称列表或列表列表或 dict,默认 False

  15. 如果 True → 尝试解析索引。

  16. 如果 [1, 2, 3] → 尝试将列 1、2、3 分别解析为单独的日期列。

  17. 如果 [[1, 3]] → 将列 1 和 3 组合并解析为单个日期列。

  18. 如果 {'foo': [1, 3]} → 将列 1、3 解析为日期并调用结果“foo”。

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

  1. *infer_datetime_formatboolean,默认 False*如果 True 并为一列启用了 parse_dates,则尝试推断日期时间格式以加快处理速度。从 2.0.0 版本开始弃用:此参数的严格版本现在是默认版本,传递它没有效果。

  2. *keep_date_colboolean,默认 False*如果 True 且 parse_dates 指定组合多个列,则保留原始列。

  3. *date_parserfunction, 默认 None*用于将一系列字符串列转换 datetime 实例数组的函数。默认使用 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()

  4. *date_formatstr 或列 → 格式字典,默认 None*如果与 parse_dates 一起使用,将根据此格式解析日期。对于更复杂的情况,请读入为 object,然后按需应用 to_datetime()。2.0.0 版本新增。

  5. *dayfirstboolean,默认 False*DD/MM 格式日期,国际和欧洲格式。

  6. *cache_datesboolean,默认 True*如果为 True,则使用唯一转换日期的缓存来应用 datetime 转换。在解析重复日期字符串时,尤其是带有时区偏移的字符串时,可能会极大地提高速度。

  7. *iteratorboolean,默认 False*返回用于迭代或使用 get_chunk() 获取块的 TextFileReader 对象。

  8. *chunksizeint,默认 None*返回用于迭代的 TextFileReader 对象。请参阅下方的 iterating and chunking

  9. *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.BZ2File, 或 zstandard.ZstdDecompressor。比如,可以传递以下配置以更快速地进行压缩并创建可再现的 gzip 存档:compression={'method': 'gzip', 'compresslevel': 1, 'mtime': 1}。1.2.0 版本中更改:以前版本会将“gzip”的字典条目转发到 gzip.open

  10. * thousandsstr, default None* Thousands separator.

  11. *decimalstr,默认 '.'*作为小数点识别的字符。例如,针对欧洲数据使用 ','

  12. *float_precisionstring,默认 None*指定 C 引擎应为浮点值使用哪个转换器。对于普通转换器,选项为 None;对于高精度转换器,选项为 high;对于往返转换器,选项为 round_trip

  13. *lineterminatorstr(长度 1),默认 None*将文件拆分成行的字符。仅适用于 C 解析器。

  14. *quotecharstr(长度 1)*表示引用项开始和结束时使用的字符。引用项可以包含分隔符,它将被忽略。

  15. *quotingint 或 csv.QUOTE*_ 实例,默认 0*根据 csv.QUOTE*_ 常量控制字段引用行为。使用 QUOTE_MINIMAL (0)、QUOTE_ALL (1)、QUOTE_NONNUMERIC (2) 或 QUOTE_NONE (3) 之一。

  16. *doublequoteboolean,默认 True*当指定 quotecharquoting 不为 QUOTE_NONE 时,表示是否将字段中连续两个 quotechar 元素算作单个 quotechar 元素。

  17. *escapecharstr(长度 1),默认 None*当引用为 QUOTE_NONE 时,用于转义分隔符的单字符字符串。

  18. *commentstr,默认 None*指示不应该解析该行的其余部分。如果在行首找到,则整个行将被忽略。此参数必须是单个字符。与空行(只要 skip_blank_lines=True)类似,完全注释的行会被参数 header 忽略,但不会被 skiprows 忽略。例如,如果 comment='#',使用 header=0 解析“#empty\na,b,c\n1,2,3”,则“a,b,c”会被当作标头处理。

  19. *encodingstr,默认 None*用于读取/写入时的 UTF 编码(例如 'utf-8')。 List of Python standard encodings

  20. *dialectstr 或 csv.Dialect 实例,默认 None*如果提供,此参数将覆盖以下参数的值(默认或非默认):delimiterdoublequoteescapecharskipinitialspacequotecharquoting。如果必须覆盖值,将会发出 ParserWarning。有关更多详细信息,请参阅 csv.Dialect 文档。

  21. *on_bad_lines(‘error’, ‘warn’, ‘skip’),默认 ‘error’*指定在遇到错误行(具有过多字段的行)时要执行的操作。允许的值有:

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

  23. 'warn',当遇到错误行时输出警告并跳过该行。

  24. 'skip',当遇到错误行时直接跳过,而不会发出错误或警告。

1.3.0 版中的新增功能。

Specifying column data types

可以指示整个_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_。如果你不熟悉这些概念,可以查看 here以了解有关dtypes的更多信息,并查看 here以了解有关pandas中的_object_转换的更多信息。

例如,你可以使用 convertersread_csv() 参数:

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

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

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

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

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

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

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

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

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

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

最终,你如何处理读取包含混合数据类型的列取决于你的具体需求。在上述情况下,如果你想 NaN 数据异常,那么 to_numeric() 可能就是你的最佳选择。但是,如果你希望所有数据都强制转换,无论类型如何,那么值得尝试使用 convertersread_csv() 参数。

在某些情况下,读取包含混合数据类型的列的异常数据会导致数据集不一致。如果你依靠 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

Specifying categorical dtype

Categorical 列可以通过指定 dtype='category'dtype=CategoricalDtype(categories, ordered) 直接解析。

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

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

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

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

可以使用字典规范将各个列解析为 Categorical

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

指定 dtype='category' 会导致一个无序的 Categorical,其 categories 是在数据中观察到的唯一值。要更好地控制类别和顺序,请提前创建一个 CategoricalDtype,并将其作为该列的 dtype 传递。

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

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

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

使用 dtype=CategoricalDtype 时, dtype.categories 以外的“意外”值被视为缺失值。

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

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

这符合 Categorical.set_categories() 的行为。

使用 dtype='category' 时,产生的类别始终会被解析为字符串(对象数据类型)。如果类别是数字,则可以使用 to_numeric() 函数进行转换,或者根据需要使用其他转换器,例如 to_datetime()

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

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

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

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

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

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

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

Naming and using columns

文件可能具有或者不具有标题行。pandas 假设第一行应作为列名:

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

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

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

通过将 names 参数与 header 结合起来指定,您可以指定要使用的其他名称以及是否要舍弃表头行(如果有):

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

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

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

如果表头在除第一行之外的其他行中,则将行号传递给 header。这将跳过前面的行:

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

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

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

Duplicate names parsing

如果文件或表头包含重复的名称,默认情况下,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 参数允许您使用列名称、位置编号或可调用对象选择文件中的任何列子集:

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

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

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

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

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

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

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

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

Comments and empty lines

如果指定了 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

Dealing with Unicode data

应该为编码的 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,在未指定编码的情况下根本无法正确解析。 Full list of Python standard encodings

Index columns and trailing delimiters

如果一个文件比列名称的数量多一列数据,则第一列将用作 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

Date Handling

为更好地促进对日期时间数据的处理, 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]] 表示这两列应解析为一列。

您还可以使用 dict 指定自定义名称列:

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. 如果您为不同列设置不同格式,或想向 to_datetime 传递任何其他选项(例如 utc),则应将数据读入作为 object dtype,然后使用 to_datetime

pandas 本机不能表示具有混合时区的列或索引。如果您的 CSV 文件包含具有混合时区的列,则默认结果将是带有字符串的对象 dtype 列,即使使用了 parse_dates。为将混合时区值解析为一个日期时间列,请读入为 object dtype 然后用 utc=True 调用 to_datetime()

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

  1. “20111230”

  2. “2011/12/30”

  3. “20111230 00:00:00”

  4. “12/30/2011 00:00:00”

  5. “30/Dec/2011 00:00:00”

  6. “30/December/2011 00:00:00”

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

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

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

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

如果您在同一列中混合了 datetime 格式,则可以传递 format='mixed'

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

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

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

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

或者,如果您的 datetime 格式都是 ISO8601(可能不是相同格式):

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

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

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

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

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

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

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

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

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

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

在版本 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")

Specifying method for floating-point conversion

可以指定参数 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

Thousand separators

对于用千位分隔符写的大数字,您可以将 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 values

要控制解析为缺失值(由 NaN 表示)的值,请在 na_values 中指定字符串。如果您指定字符串列表,那么其中的所有值都将被视为缺失值。如果您指定一个数字(float,如 5.0integer,如 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

Infinity

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

Boolean values

通用值 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

Handling “bad” lines

某些文件可能包含字段数过少或过多的格式错误的行。字段数过少的行将在尾部字段中填充 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

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 对象一样。

Quoting and Escape Characters

嵌入字段中的引号(和其他转义字符)可以用多种方式处理。一种方法是使用反斜杠;要正确解析此数据,你应传递 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

Files with fixed width columns

read_csv() 读取分隔数据时, read_fwf() 函数处理具有已知固定列宽的数据文件。read_fwf 的函数参数在很大程度上与 read_csv 相同,但有两个额外的参数,并且对 delimiter 参数有不同的用法:

  1. colspecs:一个列表对(元组),以半开区间(即 [from, to[ )给出了每一行的固定宽字段的范围。字符串值“infer”可用于指示解析器尝试从数据的头 100 行检测列规范。如果没有指定,则默认行为是推断。

  2. widths:一个字段宽列表,如果区间是连续的,则可以使用它来代替“colspecs”。

  3. 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,我们只需要将列 specification 提供给 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.<column number>。或者,您只需提供连续列的列宽:

# 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

Indexes

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

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)

假设数据由两列索引:

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)
   .....:

index_colread_csv 的参数可以采用列号列表,将多个列转换为返回对象索引的 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

通过为 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 都会丢失。

Automatically “sniffing” the delimiter

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

Reading multiple files to create a single DataFrame

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

Iterating through files chunk by chunk

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

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

Specifying the parser engine

Pandas 目前支持三种引擎,C 引擎、python 引擎和实验性的 pyarrow 引擎(要求 pyarrow 包)。一般来说,pyarrow 引擎在较大的工作负载上速度最快,并且在大多数其他工作负载上速度与 C 引擎相当。在大多数工作负载上,python 引擎往往比 pyarrow 引擎和 C 引擎慢。然而,pyarrow 引擎比 C 引擎不够健壮,与 Python 引擎相比,缺少一些特性。

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

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

  1. sep 除了单个字符以外(例如正则表达式的分隔符)

  2. skipfooter

  3. sep=None with delim_whitespace=False

除非明确使用 engine='python' 选择 python 引擎,否则指定以上任何选项都会生成 ParserWarning

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

  1. float_precision

  2. chunksize

  3. comment

  4. nrows

  5. thousands

  6. memory_map

  7. dialect

  8. on_bad_lines

  9. delim_whitespace

  10. quoting

  11. lineterminator

  12. converters

  13. decimal

  14. iterator

  15. dayfirst

  16. infer_datetime_format

  17. verbose

  18. skipinitialspace

  19. low_memory

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

Reading/writing remote files

您可以将 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 关键字参数,来发送一个自定义标头,连同 HTTP(s) 请求,如下所示:

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

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

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

在处理远程存储系统时,您可能需要在特殊位置额外的配置环境变量或配置文件。例如,要访问 S3 存储桶中的数据,您需要按照 S3Fs documentation 中列出的几种方法之一定义凭据。对于其他几个存储后端也是如此,您应该访问 fsimpl1 中的链接,用于已构建到 fsspecfsimpl2 中的实现,而 fsspec 的主要发行版中没有这些链接。

您还可以将参数直接传递给后端驱动程序。由于 fsspec 没有使用 AWS_S3_HOST 环境变量,我们可以直接定义包含 endpoint_url 的词典,并将对象传递到存储选项参数中:

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

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

如果您没有 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”部分,而不适用于缓存实现。请注意,这仅在会话期间缓存到一个临时目录,但您也可以指定一个永久存储。

Writing out data

SeriesDataFrame 对象具有一个实例方法 to_csv,它允许将对象的内容存储为逗号分隔的值文件。该函数采用多个参数。只有第一个参数是必需的。

  1. path_or_buf:要写入的文件的字符串路径或文件对象。如果是文件对象,则必须用 newline='' 打开。

  2. sep:输出文件的域分隔符(默认 “,”)

  3. na_rep:缺失值的字符串表示形式(默认 '')

  4. float_format:浮点数的格式字符串

  5. columns:要写入的列(默认 None)

  6. header:是否要写入列名称(默认 True)

  7. index:是否要写入行(索引)名称(默认 True)

  8. index_label:如果需要,则是索引列的列标签。如果为 None(默认值),并且 headerindex 为 True,则使用索引名称。(如果 DataFrame 使用 MultiIndex,则应给出序列。)

  9. mode:Python 写入模式,默认 'w'

  10. encoding:如果内容不是 ASCII,则用字符串表示要使用的编码,适用于 3 之前的 Python 版本

  11. lineterminator:表示行尾的字符串序列(默认 os.linesep

  12. quoting:如 csv 模块中设置引用规则(默认 csv.QUOTE_MINIMAL)。请注意,如果您已设置了 float_format,则浮点数会转换为字符串,csv.QUOTE_NONNUMERIC 会将它们视为非数字

  13. quotechar:用于引用字段的字符(默认 '”')

  14. doublequote:在字段中控制 quotechar 的引用(默认 True)

  15. escapechar:在适当的时候用于转义 sepquotechar 的字符(默认 None)

  16. chunksize:一次要写入的行数

  17. date_format:日期时间对象的格式字符串

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

  1. buf 默认 None,例如 StringIO 对象

  2. columns 默认情况下为 None,要写入数据列

  3. col_space 默认情况下为 None,每列的最小宽度。

  4. na_rep 默认情况下为 NaN,表示 NA 值

  5. formatters 默认情况下为 None,是一个字典(按列),其中的每个函数都接受一个参数并返回一个格式化的字符串

  6. float_format 默认情况下为 None,一个函数,它接受单个(浮点)参数并返回一个格式化的字符串;要应用于 DataFrame 中的浮点数。

  7. sparsify 默认情况下为 True,对于带层次索引的 DataFrame,设置为 False 以便打印每一行的每个 MultiIndex 键。

  8. index_names 默认情况下为 True,将打印索引的名称

  9. index 默认情况下为 True,将打印索引(即行标签)

  10. header 默认情况下为 True,将打印列标签

  11. justify 默认情况下为 left,将左对齐或右对齐打印列头

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

JSON

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

Writing JSON

SeriesDataFrame 可以转换为有效的 JSON 字符串。使用 to_json 和可选参数:

  1. path_or_buf:用于写入输出的路径名或缓冲区。它可以是 None,在这种情况下将返回一个 JSON 字符串。

  2. orient :

  3. Series:

  4. default is index

  5. 允许的值有 {split, records, index}

  6. DataFrame:

  7. default is columns

  8. 允许的值有 {split, records, index, columns, values, table}

  9. JSON 字符串的格式

split

类似于 {index → [index], columns → [columns], data → [values]} 的字典

records

如同列表 [{column → value}, … , {column → value}]

index

如同字典 {index → {column → value}}

columns

如同字典 {column → {index → value}}

values

仅是值数组

table

遵守 JSON Table Schema

  1. date_format : 字符串,日期转换类型,对于时间戳为“时间纪元”,对于 ISO8601 为“ISO”。

  2. double_precision : 在对浮点数进行编码时要使用的十进制位数,默认为 10。

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

  4. date_unit : 编码到的时间单位,控制时间戳和 ISO8601 精度。分别为秒、毫秒、微秒和纳秒的“s”、“ms”、“us”或“ns”。默认为“ms”。

  5. default_handler : 如果对象无法以其他方式转换为适合 JSON 的格式,则调用此处理程序。接收单一参数,即要转换的对象,并返回可序列化的对象。

  6. lines : 如果是 records orient,则将每条记录作为 json 写入每行。

  7. mode : 字符串,写入路径时的写入模式。对于写入,‘w’;对于追加,‘a’。默认为‘w’

请注意,NaN’s,NaT’s 和 None 将被转换为 nulldatetime 对象将根据 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 Table Schema,允许保留元数据,包括但不限于 dtypes 和索引名称。

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

以 ISO 日期格式写入:

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

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

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

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

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

以 ISO 日期格式写入,带微秒:

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

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

纪元时间戳,以秒为单位:

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

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

写入文件,并带有日期索引和日期列:

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

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

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

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

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

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

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

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

  1. 如果 dtype 不受支持(例如 np.complex_),则 default_handler(如果提供)将针对每个值调用,否则将引发异常。

  2. 如果不支持对象,则将尝试以下操作:

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

  4. 如果提供 default_handler,则调用 default_handler

  5. 通过遍历其内容将对象转换为 dict。但是,这通常会失败并显示 OverflowError 或给出意外的结果。

通常,对于不受支持的对象或 dtypes,最佳方法是提供 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)"}}'

Reading JSON

将 JSON 字符串解读为 pandas 对象时,可能需要一些参数。如果未提供 typ 或为 None,解析器将尝试解析 DataFrame。要明确强制 Series 解析,请传递 typ=series

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

  2. typ:要恢复的对象类型(序列或框架),默认为“frame”

  3. orient :

  4. * Series :*

  5. default is index

  6. 允许的值有 {split, records, index}

  7. * DataFrame*

  8. default is columns

  9. 允许的值有 {split, records, index, columns, values, table}

  10. JSON 字符串的格式

split

类似于 {index → [index], columns → [columns], data → [values]} 的字典

records

如同列表 [{column → value}, … , {column → value}]

index

如同字典 {index → {column → value}}

columns

如同字典 {column → {index → value}}

values

仅是值数组

table

遵守 JSON Table Schema

  1. dtype:如果为 True,则推断数据类型;如果为列到数据类型的字典,则使用它们;如果为 False,则根本不推断数据类型,默认为 True,仅适用于数据。

  2. convert_axes:布尔值,尝试将轴转换为合适的数据类型,默认为 True

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

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

  5. precise_float:布尔值,默认为 False。将其设置为启用在将字符串解码为双精度值时使用更高精度 (strtod) 函数。默认值 (False) 是使用快速但不太精确的内置功能。

  6. date_unit:字符串,如果转换日期,则用于检测时间戳单位。默认值为 None。默认情况下,将检测到时间戳精度,如果不需要此操作,则传递“s”、“ms”、“us”或“ns”之一以分别强制时间戳精度为秒、毫秒、微秒或纳秒。

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

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

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

  10. engine:{s27}(内置 JSON 解析器)或 "pyarrow"(派发到 pyarrow 的 pyarrow.json.read_json)。仅当 lines=True 时才可以使用 "pyarrow"

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

如果在编码为 JSON 时使用非默认 orient,请务必在此处传递相同的选项,以便解码产生合理的结果,请参阅 Orient Options 以获取概述。

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

如果 convert_dates=True 且数据和/或列标签显示“类似日期”,则大型整数值可以转换为日期。确切的阈值取决于指定的 date_unit。“类似日期”意味着列标签符合以下标准之一:

  1. it ends with '_at'

  2. it ends with '_time'

  3. it begins with 'timestamp'

  4. it is 'modified'

  5. it is 'date'

警告

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

  1. 索引可以以与序列化不同的顺序重建,即,返回的顺序不能保证与序列化之前相同

  2. 一个将 float 数据列转换为 integer,如果可以安全执行,例如,一列 1.

  3. bool 类型列会在重建时转换为 integer

因此,在某些情况下,你可能需要通过 dtype 关键字参数指定特定 dtypes。

从 JSON 字符串中读取:

In [262]: from io import StringIO

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

从文件中读取:

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

不转换任何数据(但仍可以转换坐标轴和日期):

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

指定转换的 dtypes:

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

保留字符串索引:

In [267]: from io import StringIO

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

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

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

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

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

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

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

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

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

以纳秒为单位编写的日期需要使用纳秒为单位进行读回:

In [277]: from io import StringIO

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

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

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

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

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

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

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

通过设置 dtype_backend 参数,你可以控制用于结果 DataFrame 的默认 dtypes。

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

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

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

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

Normalization

pandas 提供了一个实用函数来获取一个字典或字典列表,并将此半结构化数据归一化为一个平面表。

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

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

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

max_level 参数提供了对要在哪个级别结束归一化的更多控制。当 max_level=1 时,以下代码段会归一化到所提供字典的第一个嵌套级别。

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

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

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

还可以通过指定 engine="pyarrow" 使用 pyarrow reader 读取行分隔的 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

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 orient 序列化的数据。索引被包含在内,并且任何日期时间都采用表模式规范所需的形式 ISO 8601。

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

pandas 类型

表模式类型

int64

integer

float64

number

bool

boolean

datetime64[ns]

datetime

timedelta64[ns]

duration

categorical

any

object

str

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

  1. schema 对象包含 pandas_version 字段。它包含模式的 pandas 方言版本,且会随着每次修订进行递增。

  2. 序列化时,所有日期均转换为 UTC。即使是时区无关的值(该值将被视为具有偏移量 0 的 UTC)。

In [307]: from pandas.io.json import build_table_schema

In [308]: s = pd.Series(pd.date_range("2016", periods=4))

In [309]: build_table_schema(s)
Out[309]:
{'fields': [{'name': 'index', 'type': 'integer'},
  {'name': 'values', 'type': 'datetime'}],
 'primaryKey': ['index'],
 'pandas_version': '1.4.0'}
  1. 带时区的日期时间(在序列化之前)包括一个附加字段 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'}
  1. 时段会在序列化之前转换为时间戳,因此和转换为 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'}
  1. 分类变量使用 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'}
  1. 如果索引是唯一的,则包括一个 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'}
  1. 使用多索引时 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'}
  1. 默认命名大致遵循以下规则:

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

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

  4. 对于 Index(而非 MultiIndex),使用 index.name,如果为 None,则使用 index 作为后备。

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

read_json 也接受 orient='table' 作为参数。这么做可以保持元数据,例如 dtypes 和索引名称,作为可往返的方式。

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

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

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

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

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

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

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

请注意,index 这个文字字符串作为 Index 的名称是不可往返的,在 MultiIndex 内以 'level'_ 开头的任何名称亦不可往返。这些在 DataFrame.to_json() 中默认用于指示缺失值,后续的读取无法区分意图。

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

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

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

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

使用 orient='table' 及用户定义的 ExtensionArray 时,生成的模式会在相应 fields 元素中包含一个额外的 extDtype 密钥。这个额外的密钥不是标准的,但确实为扩展类型(例如 read_json(df.to_json(orient="table"), orient="table"))启用 JSON 回转。

extDtype 密钥携带扩展的名称,如果您已正确注册 ExtensionDtype,pandas 将使用该名称查找注册表,并将序列化的数据重新转换为您的自定义 dtype。

HTML

Reading HTML content

警告

我们强烈建议您阅读以下 HTML Table Parsing gotchas,了解围绕 BeautifulSoup4/html5lib/lxml 解析器的相关问题。

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

即使 HTML 内容中只包含单个表格,read_html 也会返回一个 listDataFrame 对象。

读取没有选项的 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]]

上述网址的数据每周一更改,因此上述结果数据可能略有不同。

读取网址,同时传递 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 请求中。

从上面网址中读取文件的内容,并将其作为字符串传递给 read_html

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

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

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

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

如果您希望,甚至可以传入 StringIO 的实例:

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

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

由于拥有如此多的网络访问功能会减慢文档的构建速度,因此以下示例不会由 IPython 求值器运行。如果您发现错误或不运行的示例,请不要犹豫,在 pandas GitHub issues page 上进行报告。

读取网址并匹配包含特定文本的表:

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

指定标题行(默认情况下,<th><td> 元素位于 <thead> 内用于形成列索引,如果 <thead> 中包含多行,则会创建一个 MultiIndex);如果指定,则标题行将从数据中减去解析的标题元素(<th> 元素)。

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

指定索引列:

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

指定要跳过的行数:

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

使用列表指定要跳过的行数(range 也适用):

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

指定 HTML 属性:

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

指定应转换为 NaN 的值:

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

指定是否保留默认 NaN 值集:

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

为列指定转换器。这对于具有前导零的数字文本数据很有用。默认情况下,数字列会转换为数字类型,前导零会丢失。为了避免这种情况,我们可以将这些列转换为字符串。

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

使用上述组合:

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

读取熊猫 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 版中的新增功能。

Writing to HTML files

DataFrame 对象有一个实例方法 to_html,用于以 HTML 表格的形式呈现 DataFrame 的内容。函数参数与上述方法 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.python.org/", "https://pandas.pydata.org"],
   .....:     }
   .....: )
   .....:

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

In [360]: print(html)
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>name</th>
      <th>url</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>Python</td>
      <td><a href="https://www.python.org/" target="_blank">https://www.python.org/</a></td>
    </tr>
    <tr>
      <th>1</th>
      <td>pandas</td>
      <td><a href="https://pandas.pydata.org" target="_blank">https://pandas.pydata.org</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 Table Parsing Gotchas

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

有关 lxml 的问题

  1. Benefits

  2. lxml is very fast.

  3. lxml 需要 Cython 才能正确安装。

  4. Drawbacks

  5. 除非给定 strictly valid markup,否则 lxml 不担保解析结果。

  6. 鉴于上述情况,我们选择允许你(用户)使用 lxml 后端,但如果 lxml 解析失败,此后端将使用 html5lib

  7. 因此,强烈建议你安装 BeautifulSoup4html5lib,以便即使 lxml 失败,你仍然会得到有效的结果(前提是其他一切有效)。

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

  1. 由于 BeautifulSoup4 实际上只是围绕解析后端的一个包装器,所以上述问题也存在于此。

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

  1. Benefits

  2. html5liblxml 要灵活得多,因此它能够更明智地处理真实生活中的标记,而不是像 lxml 一样仅仅删除元素而不通知你。

  3. html5lib 会自动根据无效标记生成有效的 HTML5 标记。这对于解析 HTML 表格极为重要,因为它保证了一份有效的文件。但是,这并不意味着它就是“正确的”,因为修复标记的过程没有单一的定义。

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

  5. Drawbacks

  6. 使用 html5lib 最大的缺点就是它像糖蜜一样慢。但是考虑这样一个事实,很多网络上的表格往往并不大,不足以使解析算法运行时间成为一个问题。更有可能的是瓶颈在于通过 Web 从 URL 读取原始文本的过程,也就是说,IO(输入输出)。对于非常大的表格,这可能不是真的。

LaTeX

1.3.0 版中的新增功能。

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

Writing to LaTeX files

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

查看 Styler.to_latex 文档,其中给出了条件样式的示例并说明了其关键字参数的操作。

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

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

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

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

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

XML

Reading 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

StringIOBytesIO 实例中的“books.xml”内容读入并传递给 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 和 URI https://example.com 的命名空间。为了解析 doc:row 节点,必须使用 namespaces

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

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

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

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

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

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

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

但是,如果 XPath 不引用节点名称,例如 default,/*,则不需要 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”地铁线路的这个有点嵌套的结构,其中 station 和 rides 元素各自在其部分中封装数据。通过以下 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’s iterparseetree’s iterparse 解析如此大的文件,这些是内存高效的方法,用于遍历 XML 树并提取特定元素和属性。无需在内存中保存整个树。

1.5.0 版中的新增功能。

要使用此功能,你必须将物理 XML 文件路径传递到 read_xml 并使用 iterparse 参数。文件不应压缩也不应指向在线源,而是应存储在本地磁盘上。此外,iterparse 应为一个字典,其中键是文档中的重复节点(它们成为行),值是重复节点的后代(即子节点、孙节点)的任何元素或属性的列表。由于此方法中不使用 XPath,后代不必相互具有相同的关系。下面显示了读取 Wikipedia 非常大(12 GB+)的最新文章数据转储的示例。

In [1]: df = pd.read_xml(
...         "/path/to/downloaded/enwikisource-latest-pages-articles.xml",
...         iterparse = {"page": ["title", "ns", "id"]}
...     )
...     df
Out[2]:
                                                     title   ns        id
0                                       Gettysburg Address    0     21450
1                                                Main Page    0     42950
2                            Declaration by United Nations    0      8435
3             Constitution of the United States of America    0      8435
4                     Declaration of Independence (Israel)    0     17858
...                                                    ...  ...       ...
3578760               Page:Black cat 1897 07 v2 n10.pdf/17  104    219649
3578761               Page:Black cat 1897 07 v2 n10.pdf/43  104    219649
3578762               Page:Black cat 1897 07 v2 n10.pdf/44  104    219649
3578763      The History of Tom Jones, a Foundling/Book IX    0  12084291
3578764  Page:Shakespeare of Stratford (1926) Yale.djvu/91  104     21450

[3578765 rows x 3 columns]

Writing XML

1.3.0 版中的新增功能。

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

此方法不支持 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 Final Notes

  1. 所有 XML 文档都遵循 W3C specifications。如果标记文档格式不正确或不遵循 XML 语法规则,etreelxml 解析器都将无法解析该文档。请注意,除非遵循 XHTML 规范,否则 HTML 并不是一个 XM 文档。但是,其他流行的标记类型,包括 KML、XAML、RSS、MusicML、MathML 等都遵循 XML schemas

  2. 出于以上原因,如果你的应用程序在 pandas 运算之前构建 XML,请使用适当的 DOM 库(如 etreelxml)来构建必需的文档,而不是通过字符串连接或正则表达式调整。务必记住,XML 是一个带标记规则的特殊文本文件。

  3. 对于非常大的 XML 文件(数百 MB 到 GB),XPath 和 XSLT 可能成为占用内存很多的运算。请确保为读取和写入大型 XML 文件准备足够的可用 RAM(大约是文本大小的 5 倍)。

  4. 由于 XSLT 是一种编程语言,所以请谨慎使用,因为此类脚本可能会在你的环境中构成安全风险,并且可以运行大型或无限的递归运算。总是在小片段上测试脚本,然后再完全运行。

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

Excel files

read_excel() 方法可以使用 openpyxl Python 模块读取 Excel 2007+ (.xlsx) 文件。可以使用 xlrd 读取 Excel 2003 (.xls) 文件。可以使用 pyxlsb 读取二进制 Excel (.xlsb) 文件。可以使用 calamine 引擎读取所有格式。 to_excel() 实例方法用于将 DataFrame 保存到 Excel。通常,语义与使用 csv 数据类似。有关一些高级策略,请参阅 cookbook

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

  1. 如果 path_or_buffer 为 OpenDocument 格式(.odf、.ods、.odt),则将使用 odf

  2. 否则,如果 path_or_buffer 为 xls 格式,则将使用 xlrd

  3. 否则,如果 path_or_buffer 为 xlsb 格式,则将使用 pyxlsb

  4. 否则,将使用 openpyxl

Reading Excel files

在最基本的用例中,read_excel 获取 Excel 文件的路径和 sheet_name,指示要解析哪个工作表。

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

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

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

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

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

  5. 对于引擎 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 类可用于包装该文件,并且可传递到 read_excel 由于文件仅读入到内存一次,因此读取多个工作表将会带来性能优势。

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

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

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

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

ExcelFile 的主要用例是解析具有不同参数的多个工作表:

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

请注意,如果所有工作表都使用相同的解析参数,则可以将工作表名称列表直接传递给 read_excel,而不会损失性能。

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

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

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

import xlrd

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

第二个参数是 sheet_name,不要与 ExcelFile.sheet_names 混淆。

ExcelFile 的属性 sheet_names 提供对工作表列表的访问。

  1. 参数 sheet_name 允许指定要读取的工作表。

  2. sheet_name 的默认值为 0,表示读取第一个工作表

  3. 传递一个字符串以引用工作簿中特定工作表的名称。

  4. 传递一个整型数以引用工作表的索引。索引遵循 Python 约定,从 0 开始。

  5. 传递一个字符串或整型数的列表,以返回指定工作表的字典。

  6. 传递一个 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 以读取所有工作表来读取多个工作表。工作表可以通过工作表索引或工作表名称指定,分别使用整数或字符串。

read_excel 可以通过将列的列表传递给 index_col 和列 MultiIndex 来读取 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_excelmerged_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})

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

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


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

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

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

Writing Excel files

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

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

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

将以一种尝试模仿 REPL 输出的方式编写 DataFrameindex_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 在内部使用哪个函数非常重要。

  1. 对于引擎 openpyxl,pandas 使用 openpyxl.Workbook() 创建新工作表,并使用 openpyxl.load_workbook() 将数据附加到现有工作表。openpyxl 引擎写入 (.xlsx) 和 (.xlsm) 文件。

  2. 对于引擎 xlsxwriter,pandas 使用 xlsxwriter.Workbook() 写入 (.xlsx) 文件。

  3. 对于引擎 odf,pandas 使用 odf.opendocument.OpenDocumentSpreadsheet() 写入 (.ods) 文件。

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

Excel writer engines

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

  1. the engine keyword argument

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

默认情况下,pandas 对 .xlsx 使用 XlsxWriter,对 .xlsm 使用 openpyxl。如果您安装了多个引擎,可以通过 setting the config options io.excel.xlsx.writerio.excel.xls.writer 设置默认引擎。如果 Xlsxwriter 不可用的话,pandas 将对 .xlsx 文件使用 openpyxl 作为回退。

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

  1. openpyxl:需要 2.4 或更高版本

  2. 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")

Style and formatting

可以使用 DataFrameto_excel 方法中的以下参数修改由 pandas 创建的 Excel 工作表的观感。

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

  2. freeze_panes:表示最底行和最右列的两个整数组成的元组。这两个参数均以 1 为基,因此 (1, 1) 将冻结第一行和第一列(默认 None)。

通过使用引擎 Xlsxwriter,能够选择很多选项来控制通过 to_excel 方法创建的 Excel 电子表格的格式。此处可以查看 Xlsxwriter 文档中的优秀示例: https://xlsxwriter.readthedocs.io/working_with_pandas.html

OpenDocument Spreadsheets

用于 Excel files 的 io 方法还支持使用 odfpy 模块读取和写入 OpenDocument 电子表格。读取和写入 OpenDocument 电子表格的语义和功能与使用 engine='odf'Excel files 进行的操作匹配。需要安装可选依赖项“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")

Binary Excel (.xlsb) files

方法 read_excel() 还可以使用 pyxlsb 模块读取二进制 Excel 文件。读取二进制 Excel 文件的语义和功能与使用 engine='pyxlsb'Excel files 进行的操作基本匹配。pyxlsb 不识别文件中的日期时间类型,而会返回浮点数(如果需要识别日期时间类型,则可以使用 calamine)。

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

目前,pandas 只支持读取二进制 Excel 文件。未实现写入。

Calamine (Excel and ODS files)

方法 read_excel() 可以使用 python-calamine 模块读取 Excel 文件(.xlsx.xlsm.xls.xlsb)和 OpenDocument 电子表格(.ods)。在大多数情况下,此模块都可以绑定 Rust 库 calamine,并且其速度要快于其他引擎。需要安装可选依赖项“python-calamine”。

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

Clipboard

获取数据的一个便捷方法是使用 read_clipboard() 方法,它将剪贴板缓冲区的内容接收并传递给 read_csv 方法。例如,您可以将以下文本复制到剪贴板(在许多操作系统中,按 CTRL-C):

  A B C
x 1 4 p
y 2 5 q
z 3 6 r

然后通过调用以下命令,将数据直接导入到 DataFrame

>>> clipdf = pd.read_clipboard()
>>> clipdf
  A B C
x 1 4 p
y 2 5 q
z 3 6 r

方法 to_clipboard 可用于将 DataFrame 的内容写入剪贴板。然后,您可以将剪贴板中的内容粘贴到其他应用程序(在许多操作系统中,按 CTRL-V)。此处演示将 DataFrame 写入剪贴板并将其读回。

>>> df = pd.DataFrame(
...     {"A": [1, 2, 3], "B": [4, 5, 6], "C": ["p", "q", "r"]}, index=["x", "y", "z"]
... )

>>> df
  A B C
x 1 4 p
y 2 5 q
z 3 6 r
>>> df.to_clipboard()
>>> pd.read_clipboard()
  A B C
x 1 4 p
y 2 5 q
z 3 6 r

我们可以看到,我们已读回早前写入剪贴板的相同内容。

在 Linux 中,您可能需要安装 xclip 或 xsel(搭配 PyQt5、PyQt4 或 qtpy)才能使用这些方法。

Pickling

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

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

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

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

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

警告

加载从不可信来源接收的 pickle 数据可能不安全。

警告

read_pickle() 只向后兼容性较低的小次版本进行保证。

Compressed pickle files

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

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

压缩参数也可以是 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

从版本 1.0.0 中已删除 pandas 对 msgpack 的支持。建议改用 pickle

或者,您还可以将 Arrow IPC 序列化格式用于熊猫对象的在线传输。有关 pyarrow 的文档,请参阅 here

HDF5 (PyTables)

HDFStore 是一个类字典的对象,它使用出色的 PyTables 库,通过高性能 HDF5 格式来读写 pandas。请参阅 cookbook,了解一些高级策略

警告

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

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

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

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

可以将对象写入文件,就像将键值对添加到字典中一样:

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

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

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

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

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

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

在当前或更高版本的 Python 会话中,您可以检索存储的对象:

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

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

删除由键指定的对象:

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

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

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

In [466]: store.close()

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

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

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

Read/write API

HDFStore 支持一个使用 read_hdf 进行读写的一级 API,to_hdf 进行写入,类似于 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

Fixed format

上面的示例显示了使用 put 进行存储,它会以固定的数组格式(称为 fixed 格式)将 HDF5 写入 PyTables。此类存储在写入后不可追加(尽管您可以简单地将其删除并重写)。它们也不能查询;它们必须完整地检索。它们也不支持列名不唯一的 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

Table format

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

此格式也可设置一个选项 pd.set_option('io.hdf.default_format','table'),默认存储 put/append/to_hdftable 格式。

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

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

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

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

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

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

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

# the type of stored data
In [488]: store.root.df._v_attrs.pandas_type
Out[488]: 'frame_table'

您也可通过向 put 操作传递 format='table'format='t' 来创建 table

Hierarchical keys

存储区的密钥可以作为字符串指定。这些密钥可以采用分层路径名格式(例如 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

Storing types

支持存储混合数据类型。字符串以固定宽度存储,使用附加列的最大大小。随后尝试附加更长的字符串会引发 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 存储为表与从同质索引 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 关键字是保留的,不能用作级别名称。

Querying

selectdelete 操作具有可选条件,可指定该条件以仅选择/删除数据的一部分。这样可以使一个在磁盘上很大的表格只检索其中一部分数据。

查询使用 Term 类在后台指定,作为一个布尔表达式。

  1. indexcolumnsDataFrames 的支持索引器。

  2. 如果指定 data_columns,则可以将其用作附加索引器。

  3. MultiIndex 中的级别名称,有默认名称 level_0level_1,…,如果未提供。

有效的比较运算符为:

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

有效的布尔表达式与以下内容组合:

  1. | : or

  2. & : and

  3. ():用于分组

这些规则类似于 boolean 表达式在 pandas 中用于索引的方式。

  1. = 将自动展开为比较运算符 ==

  2. ~ 为 not 运算符,但仅能在极为有限的情况下使用

  3. 如果传递列表/元组的表达式,它们将通过 &amp; 组合

以下为有效表达式:

  1. 'index >= date'

  2. "columns = ['A', 'D']"

  3. "columns in ['A', 'D']"

  4. 'columns = A'

  5. 'columns == A'

  6. "~(columns = ['A', 'B'])"

  7. 'index &gt; df.index[3] &amp; string = "bar"'

  8. '(index &gt; df.index[3] &amp; index &#8656; df.index[6]) | string = "bar"'

  9. "ts >= Timestamp('2012-02-01')"

  10. "major_axis>=20130101"

indexers 位于子表达式的左侧:

columnsmajor_axists

子表达式的右侧(比较运算符之后)可以是:

  1. 将被计算的函数,例如 Timestamp('2012-02-01')

  2. strings, e.g. "bar"

  3. 类似日期的,例如 20130101"20130101"

  4. lists, e.g. "['A', 'B']"

  5. 本地命名空间中定义的变量,例如 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。通常这意味着您正在尝试选择不是 data_column 的列。

如果查询表达式无效,则 select 将引发 SyntaxError

您可以使用 timedelta64[ns] 类型存储和查询。可以使用以下格式指定术语:<float>(<unit>),其中浮点数可以带符号(且带有小数部分),并且对于 timedelta,单元可以是 D,s,ms,us,ns。这是一个示例:

In [518]: from datetime import timedelta

In [519]: dftd = pd.DataFrame(
   .....:     {
   .....:         "A": pd.Timestamp("20130101"),
   .....:         "B": [
   .....:             pd.Timestamp("20130101") + timedelta(days=i, seconds=10)
   .....:             for i in range(10)
   .....:         ],
   .....:     }
   .....: )
   .....:

In [520]: dftd["C"] = dftd["A"] - dftd["B"]

In [521]: dftd
Out[521]:
           A                   B                  C
0 2013-01-01 2013-01-01 00:00:10  -1 days +23:59:50
1 2013-01-01 2013-01-02 00:00:10  -2 days +23:59:50
2 2013-01-01 2013-01-03 00:00:10  -3 days +23:59:50
3 2013-01-01 2013-01-04 00:00:10  -4 days +23:59:50
4 2013-01-01 2013-01-05 00:00:10  -5 days +23:59:50
5 2013-01-01 2013-01-06 00:00:10  -6 days +23:59:50
6 2013-01-01 2013-01-07 00:00:10  -7 days +23:59:50
7 2013-01-01 2013-01-08 00:00:10  -8 days +23:59:50
8 2013-01-01 2013-01-09 00:00:10  -9 days +23:59:50
9 2013-01-01 2013-01-10 00:00:10 -10 days +23:59:50

In [522]: store.append("dftd", dftd, data_columns=True)

In [523]: store.select("dftd", "C<'-3.5D'")
Out[523]:
                              A                   B                  C
4 1970-01-01 00:00:01.356998400 2013-01-05 00:00:10  -5 days +23:59:50
5 1970-01-01 00:00:01.356998400 2013-01-06 00:00:10  -6 days +23:59:50
6 1970-01-01 00:00:01.356998400 2013-01-07 00:00:10  -7 days +23:59:50
7 1970-01-01 00:00:01.356998400 2013-01-08 00:00:10  -8 days +23:59:50
8 1970-01-01 00:00:01.356998400 2013-01-09 00:00:10  -9 days +23:59:50
9 1970-01-01 00:00:01.356998400 2013-01-10 00:00:10 -10 days +23:59:50

可以通过使用级别的名称从 MultiIndex 中进行选择。

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) 的信息,请参阅 here

您可以指定(并索引)您希望能够执行查询的某些列(除了您可以始终查询的 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_chunk 传递给 selectselect_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]

有时你的查询可能会涉及创建要选择的行列表。通常,此 mask 会是索引操作的结果 index。此示例选择 datetimeindex 中为 5 的月份。

In [572]: df_mask = pd.DataFrame(
   .....:     np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
   .....: )
   .....:

In [573]: store.append("df_mask", df_mask)

In [574]: c = store.select_column("df_mask", "index")

In [575]: where = c[pd.DatetimeIndex(c).month == 5].index

In [576]: store.select("df_mask", where=where)
Out[576]:
                   0         1
2000-05-01  1.479511  0.516433
2000-05-02 -0.334984 -1.493537
2000-05-03  0.900321  0.049695
2000-05-04  0.614266 -1.077151
2000-05-05  0.233881  0.493246
...              ...       ...
2002-05-27  0.294122  0.457407
2002-05-28 -1.102535  1.215650
2002-05-29 -0.432911  0.753606
2002-05-30 -1.105212  2.311877
2002-05-31  2.567296  2.610691

[93 rows x 2 columns]

如果要检查存储的对象,请通过 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: []

Delete from a table

你可以通过指定 where 有选择地从数据表中删除。在删除行时,重要的是要理解 PyTables 是通过擦除行然后移动以下数据来删除行。因此,根据数据的方向,删除有可能会是一个非常昂贵的操作。为了获得最佳性能,值得将你要删除的维度设为 indexables 中的第一个维度。

数据(在磁盘上)以 indexables 的形式进行排序。这是一个简单的用例。你存储具有面板类型的数据,major_axis 中的日期和 minor_axis 中的 ID。数据接着像这样交织在一起:

  1. * date_1*

  2. id_1

  3. id_2

  4. .

  5. id_n

  6. * date_2*

  7. id_1

  8. .

  9. id_n

显而易见的是,major_axis 上的删除操作将会相当快,因为它会移除一个数据块,然后移动以下数据。另一方面,minor_axis 上的删除操作将会非常昂贵。在这种情况下,几乎肯定使用 where(选择所有数据但丢失的数据除外)来重写数据表会更快。

警告

请注意,HDF5 不会自动在 h5 文件中回收空间。因此,重复删除(或移除节点)并再次添加,将会倾向于增加文件大小。

要重新打包和清理文件,请使用 ptrepack

Notes & caveats

PyTables 允许压缩存储的数据。这适用于所有种类的存储,不只是数据表。两个参数用于控制压缩:complevelcomplib

  1. complevel 指定是否压缩数据以及如何压缩数据。complevel=0complevel=None 禁用压缩,0&lt;complevel&lt;10 启用压缩。

  2. complib 指定要使用的压缩库。如果没有指定,则使用默认库 zlib。压缩库通常针对良好的压缩率或速度进行优化,结果将取决于数据类型。选择哪种压缩类型取决于你的具体需要和数据。受支持的压缩库列表:

  3. zlib:默认压缩库。在压缩方面是经典,能获得良好的压缩率,但速度稍慢。

  4. lzo:快速压缩和解压缩。

  5. bzip2: Good compression rates.

  6. blosc:快速压缩和解压缩。

  7. 支持替代 Blosc 压缩器:

  8. blosc:blosclzblosc 的默认压缩程序

  9. blosc:lz4: 一个紧凑、非常流行且快速的压缩程序。

  10. blosc:lz4hc: LZ4 的一个优化版本,牺牲速度换取更好的压缩比率。

  11. blosc:snappy: 许多地方使用的流行压缩程序。

  12. blosc:zlib: 经典;比前面的选项稍慢,但能获得更好的压缩比率。

  13. blosc:zstd: 一个极其均衡的编解码器;它在以上所有编解码器中提供最佳的压缩比率,并且以合理的速度进行。

  14. 如果将 complib 定义为除已列出库之外的其它库,则会引发 ValueError 异常。

如果在平台中缺少使用 complib 选项指定的库,则压缩将默认设置为 zlib,无需进一步提示。

为文件中的所有对象启用压缩:

store_compressed = pd.HDFStore(
    "store_compressed.h5", complevel=9, complib="blosc:blosclz"
)

或者在压缩未启用的存储中进行动态压缩(这仅适用于表):

store.append("df", df, complib="zlib", complevel=5)

在表写入后对表进行压缩时,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) 了解更多信息。

  1. 如果您使用锁在多个进程之间管理写访问权限,则可以在释放写锁之前使用 fsync() 。为方便起见,您可以使用 store.flush(fsync=True) 替您执行此操作。

  2. 一旦创建 table,列(DataFrame)将固定;只能附加完全相同的列

  3. 请注意,时区(例如,pytz.timezone('US/Eastern'))不一定在所有时区版本中都相等。因此,如果在 HDFStore 中使用某个时区库版本将数据定位到特定时区,并且使用另一个版本更新该数据,则这些时区不被视为相等,数据将转换为 UTC。可以使用相同版本的时区库,也可以将 tz_convert 与更新后的时区定义一起使用。

警告

如果列名称不能用作属性选择器,PyTables 会显示 NaturalNameWarning 。自然标识符仅包含字母、数字和下划线,且不能以数字开头。其他标识符不能用于 where 子句,而且通常不是一个好主意。

DataTypes

HDFStore 会将对象数据类型映射到 PyTables 底层数据类型。这意味着已知以下类型有效:

类型

表示丢失值

floating: float64, float32, float16

np.nan

integer : int64, int32, int8, uint64,uint32, uint8

boolean

datetime64[ns]

NaT

timedelta64[ns]

NaT

categorical : 参阅以下部分

object : strings

np.nan

unicode 列不受支持,并且将失败。

您可以将含有 category dtypes 的数据写入 HDFStore。与对象数组相同,查询能够发挥相同的作用。但是,category dtyped 数据将以更有效率的方式存储。

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

Performance

  1. fixed 存储相比,tables 格式会带来写入性能损失。其好处在于能够附加/删除和查询(可能非常大量的数据)。总体而言,与常规存储相比,写入时间更长。查询速度可能会很快,尤其是在索引轴线上。

  2. 您可以将 chunksize=&lt;int&gt; 作为 append 传递,以指定写入块大小(默认为 50000)。这将极大地降低写入时的内存使用量。

  3. 你可以将 expectedrows=&lt;int&gt; 传递给第一个 append,以设置 PyTables 期望的总行数。这将优化读/写性能。

  4. 重复行可以写入表中,但在选择中被过滤掉(选择最后一项;因此表在主、辅对中是唯一的)

  5. 如果您尝试存储将由 PyTables 腌制的类型(而不是存储为本地类型),则会引发 PerformanceWarning。请参阅 Here 获取更多信息和一些解决方案。

Feather

Feather 为数据框提供二进制列式序列化。它旨在提高数据框的读写效率,并便于在数据分析语言之间共享数据。

Feather 被设计用于忠实地序列化和反序列化 DataFrames,支持所有 pandas dtypes,包括扩展 dtypes,例如带时区的分类和 datetime。

几个注意事项:

  1. 该格式不会为 DataFrame 写入 IndexMultiIndex,并且如果提供了非默认值,则会引发错误。你可以 .reset_index() 存储索引或 .reset_index(drop=True) 忽略它。

  2. 不支持重复的列名和非字符串列名

  3. 不支持对象 dtype 列中的实际 Python 对象。这些将在尝试序列化时引发有用的错误消息。

请参阅 Full Documentation

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 dtypes,包括扩展 dtypes,例如带时区的 datetime。

几个注意事项。

  1. 不支持重复的列名和非字符串列名。

  2. pyarrow 引擎始终将索引写入输出,但是 fastparquet 只写入非默认索引。此额外的列可能会给不期望它的非 pandas 消费者带来问题。无论底层引擎如何,你都可以使用 index 参数强制包含或省略索引。

  3. 如果指定了索引级别名称,则必须为字符串。

  4. pyarrow 引擎中,非字符串类型的分类 dtypes 可以序列化为 parquet,但会反序列化为其基本 dtype。

  5. pyarrow 引擎将字符串类型中分类数据类型的 ordered 标记保留。fastparquet 不保留 ordered 标记。

  6. 不支持的类型包括 Interval 和实际 Python 对象类型。在尝试序列化时会引发一条有用的错误消息。Period 类型得到 pyarrow >= 0.16.0 的支持。

  7. pyarrow 引擎保留扩展数据类型,如可空整数和字符串数据类型(需要 pyarrow >= 0.16.0 且需要扩展类型来实现所需协议,参见 extension types documentation)。

可以指定 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 的默认 dtypes。

In [621]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow", dtype_backend="pyarrow")

In [622]: result.dtypes
Out[622]:
a                                      string[pyarrow]
b                                       int64[pyarrow]
c                                       uint8[pyarrow]
d                                      double[pyarrow]
e                                        bool[pyarrow]
f                               timestamp[ns][pyarrow]
g                timestamp[ns, tz=US/Eastern][pyarrow]
h    dictionary<values=string, indices=int32, order...
i    dictionary<values=string, indices=int32, order...
dtype: object

请注意,不支持将其用于 fastparquet

仅读取 parquet 文件的特定列。

In [623]: result = pd.read_parquet(
   .....:     "example_fp.parquet",
   .....:     engine="fastparquet",
   .....:     columns=["a", "b"],
   .....: )
   .....:

In [624]: result = pd.read_parquet(
   .....:     "example_pa.parquet",
   .....:     engine="pyarrow",
   .....:     columns=["a", "b"],
   .....: )
   .....:

In [625]: result.dtypes
Out[625]:
a    object
b     int64
dtype: object

Handling indexes

DataFrame 序列化为 parquet 可能将隐式索引作为输出文件中的一个或多个列。因此,这段代码:

In [626]: df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})

In [627]: df.to_parquet("test.parquet", engine="pyarrow")

如果你使用 pyarrow 进行序列化,则创建一个具有三列的 parquet 文件:abindex_level_0。如果你正在使用 fastparquet,则索引 may or may not 将被写入文件。

此意外的多余列会导致某些数据库(如 Amazon Redshift)拒绝该文件,因为目标表中不存在该列。

如果要在写入时忽略数据框的索引,请将 index=False 传递给 to_parquet()

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

这将创建一个只有两个预期列的 parquet 文件:ab。如果 DataFrame 具有自定义索引,则在你将此文件加载到 DataFrame 中时,你不会找回它。

传递 index=True 将始终写入索引,即使那不是底层引擎的默认行为。

Partitioning Parquet files

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 Format 是数据帧的二进制列式序列化。它旨在使数据帧的读取变得高效。pandas 提供了 ORC 格式的读取器和写入器, read_orc()to_orc()。这需要 pyarrow 库。

警告

  1. 强烈建议使用 conda 安装 pyarrow,因为 pyarrow 发生了一些问题。

  2. to_orc() requires pyarrow>=7.0.0.

  3. read_orc()to_orc() 尚不支持 Windows,你可以在 install optional dependencies 中找到有效环境。

  4. 对于支持的数据类型,请参阅 supported ORC features in Arrow

  5. 目前,当 DataFrame 转换为 ORC 文件时,日期时间列中的时区不会保留。

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 queries

pandas.io.sql 模块提供了一组查询包装器,既便于检索数据,又减少对特定于数据库的 API 的依赖。

在可用的情况下,用户可能首先希望选择 Apache Arrow ADBC 驱动程序。这些驱动程序应提供最佳性能、空值处理和类型检测。

版本 2.2.0 中的新功能:增加了对 ADBC 驱动程序的本机支持

有关 ADBC 驱动程序及其开发状态的完整列表,请参阅 ADBC Driver Implementation Status 文档。

如果 ADBC 驱动程序不可用或可能缺少功能,用户应选择在数据库驱动程序库旁边安装 SQLAlchemy。此类驱动程序的示例包括 psycopg2(用于 PostgreSQL)或 pymysql(用于 MySQL)。对于 SQLite,它默认包含在 Python 的标准库中。你可以在 SQLAlchemy docs 中找到针对每种 SQL 方言支持的驱动程序的概述。

如果未安装 SQLAlchemy,则可以使用 sqlite3.Connection 来替换 SQLAlchemy 引擎、连接或 URI 字符串。

另请参阅一些 cookbook examples 以了解一些高级策略。

主要功能是:

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 documentation

In [639]: from sqlalchemy import create_engine

# Create your engine.
In [640]: engine = create_engine("sqlite:///:memory:")

如果您要管理自己的连接,则可以传递其中一个。以下示例使用 Python 上下文管理器打开到数据库的连接,该管理器会在块完成之后自动关闭连接。请参阅 SQLAlchemy docs,了解有关如何处理数据库连接的说明。

with engine.connect() as conn, conn.begin():
    data = pd.read_sql_table("data", conn)

警告

当您打开到数据库的连接时,还要负责关闭它。使连接保持打开状态的副作用可能包括锁定数据库或其他破坏性行为。

Writing DataFrames

假设以下数据位于 DataFrame data 中,我们可以使用 to_sql() 将其插入数据库中。

id

日期

Col_1

Col_2

Col_3

26

2012-10-18

X

25.7

42

2012-10-19

Y

-12.4

63

2012-10-20

Z

5.73

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

在某些数据库中,由于数据包大小限制被超过,写入大型数据框可能会导致错误。可以在调用时设置 chunksize 参数来避免这种情况。例如,以下操作会将 data 一次分批写入 1000 行到数据库:

In [647]: data.to_sql("data_chunked", con=engine, chunksize=1000)
Out[647]: 3

确保在不同的 SQL 数据库中对数据类型进行一致管理具有挑战性。并非每个 SQL 数据库都提供相同的类型,即使提供了,特定类型的实现方式也可能存在一些微妙差别,影响了类型保存的方式。

为了最大程度地保留数据库类型,建议用户使用 ADBC 驱动程序(如果可用)。Apache Arrow 类型系统提供了更广泛的类型阵列,与数据库类型比历史悠久的 pandas/NumPy 类型系统更匹配。为了说明这一点,请注意以下在不同数据库和 pandas 后端中可用的类型(非详尽列表):

numpy/pandas

arrow

postgres

sqlite

int16/Int16

int16

SMALLINT

INTEGER

int32/Int32

int32

INTEGER

INTEGER

int64/Int64

int64

BIGINT

INTEGER

float32

float32

REAL

REAL

float64

float64

DOUBLE PRECISION

REAL

object

string

TEXT

TEXT

bool

bool_

BOOLEAN

datetime64[ns]

timestamp(us)

TIMESTAMP

datetime64[ns,tz]

timestamp(us,tz)

TIMESTAMPTZ

date32

DATE

month_day_nano_interval

INTERVAL

binary

BINARY

BLOB

decimal128

DECIMAL [1]

LIST

ARRAY [1]

STRUCT

  1. * COMPOSITE TYPE* [1] Footnotes

在本文撰写时尚未实施,但理论上可行。

如果您有兴趣在 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 写入数据库

dtype 为 category 的列将转换为密集表示,就像 np.asarray(categorical) 中获得的一样(例如,对于字符串类别,这会提供一个字符串数组)。因此,重新读取数据库表不会生成分类。

Datetime data types

使用 ADBC 或 SQLAlchemy, to_sql() 能够写入时区不敏感或时区敏感的日期时间数据。但是,最终存储在数据库中的结果数据取决于所使用数据库系统日期时间数据的受支持数据类型。

下表列出了某些常见数据库的日期时间数据的受支持数据类型。其他数据库方言的日期时间数据可能有不同的数据类型。

数据库

SQL Datetime 类型

时区支持

SQLite

TEXT

MySQL

TIMESTAMPDATETIME

PostgreSQL

TIMESTAMPTIMESTAMP WITH TIME ZONE

向不支持时区的数据库写入时区感知数据时,数据将以朴素时区的格式写入,其本地时间受时区影响。

read_sql_table() 还能读取带有时区或朴素的 datetime 数据。读取 TIMESTAMP WITH TIME ZONE 类型时,pandas 会将数据转换为 UTC。

参数 method 控制使用的 SQL 插入子句。可能的值有:

  1. None:使用标准 SQL INSERT 子句(每行一个)。

  2. 'multi':将多个值传递到单个 INSERT 子句中。这使用了特殊 SQL 语法,并非所有后端都支持。对于 Presto 和 Redshift 等分析数据库,这通常可带来更好的性能,但如果表包含很多列,则传统 SQL 后端的性能会更差。要了解更多信息,请查看 SQLAlchemy documentation

  3. 可调用的带签名 (pd_table, conn, keys, data_iter):这可以用于基于特定后端方言功能实现高性能插入方法。

使用 PostgreSQL COPY clause 的可调用的示例:

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

Reading tables

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()_来检查表是否存在

Schema support

通过 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")

Querying

你可以在 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

Engine connection examples

要连接到 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 documentation示例

Advanced SQLAlchemy queries

你可以使用 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 表达式来表达条件

In [661]: metadata = sa.MetaData()

In [662]: data_table = sa.Table(
   .....:     "data",
   .....:     metadata,
   .....:     sa.Column("index", sa.Integer),
   .....:     sa.Column("Date", sa.DateTime),
   .....:     sa.Column("Col_1", sa.String),
   .....:     sa.Column("Col_2", sa.Float),
   .....:     sa.Column("Col_3", sa.Boolean),
   .....: )
   .....:

In [663]: pd.read_sql(sa.select(data_table).where(data_table.c.Col_3 is True), engine)
Out[663]:
Empty DataFrame
Columns: [index, Date, Col_1, Col_2, Col_3]
Index: []

你可以使用_sqlalchemy.bindparam()将 SQLAlchemy 表达式与传递给 _read_sql()的参数相结合

In [664]: import datetime as dt

In [665]: expr = sa.select(data_table).where(data_table.c.Date > sa.bindparam("date"))

In [666]: pd.read_sql(expr, engine, params={"date": dt.datetime(2010, 10, 18)})
Out[666]:
   index       Date Col_1  Col_2  Col_3
0      1 2010-10-19     Y -12.50  False
1      2 2010-10-20     Z   5.73   True

Sqlite fallback

可以在不使用 SQLAlchemy 的情况下使用 sqlite。此模式需要一个遵循 Python DB-API的 Python 数据库适配器。

你可以像这样创建连接:

import sqlite3

con = sqlite3.connect(":memory:")

然后发出以下查询:

data.to_sql("data", con)
pd.read_sql_query("SELECT * FROM data", con)

Google BigQuery

_pandas-gbq_软件包提供从 Google BigQuery 读/写功能。

pandas 与这个外部软件包相集成。如果安装了_pandas-gbq_,你可以使用 pandas 方法_pd.read_gbq_和_DataFrame.to_gbq_,它们将从_pandas-gbq_中调用相应函数。

可以在 here中找到完整文档。

Stata format

Writing to stata format

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 允许范围的非缺失值将重新键入变量至下一较大的大小。例如,在 Stata 中,int8 值被限制在 -127 和 100 之间,因此值高于 100 的变量将触发转换至 int16。浮点数据类型中的 nan 值被存储为基本缺失数据类型(在 Stata 中为 .)。

针对整数数据类型,无法导出缺失数据值。

Stata 写入程序通过转换为可以表示数据的最小支持类型来优雅地处理其他数据类型,包括 int64booluint8uint16uint32。例如,如果所有值小于 100(Stata 中非缺失 int8 数据的上限),则类型为 uint8 的数据将转换为 int8;或者,如果值超出此范围,则该变量将转换为 int16

警告

如果 int64 值大于 2^53,从 int64float64 的转换可能会导致精度损失。

警告

StataWriterDataFrame.to_stata() 仅支持包含最多 244 个字符的固定宽度字符串,这是版本 115 dta 文件格式施加的限制。尝试写入字符串长度超过 244 个字符的 Stata dta 文件会引发 ValueError

Reading from Stata format

顶级函数 read_stata 会读取 dta 文件并返回 DataFramepandas.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 并指定 chunksize 以多次调用 read()

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 表示不唯一,则可能导致信息丢失。

采用类似的方式,可以按带有 Categorical 变量的格式使用关键字参数 convert_categoricals(默认情况下为 True)从 Stata 数据文件导入带标签的数据。关键字参数 order_categoricals(默认情况下为 True)确定导入的 Categorical 变量是否有序。

在导入类别数据时,Stata 数据文件中变量的值不予保留,因为 Categorical 变量始终使用 -1n-1 之间的整数数据类型,其中 n 是类别数。如果需要 Stata 数据文件中原始的值,可以通过设置 convert_categoricals=False 来导入这些值,该值将导入原始数据(但不包括变量标签)。原始值可以与导入的类别数据相匹配,因为在 Stata 原始数据值和导入的类别变量的类别代码之间存在简单的映射:缺失值分配代码 -1,最小的原始值分配 0,第二小的分配 1,依此类推,直到最大的原始值分配代码 n-1

Stata 支持部分标记系列。这些系列对某些数据值具有值标签,但并非所有数据值都有。导入部分标记系列将产生一个 Categorical,其中标记的值具有字符串类别,而未标记的值具有数字类别。

SAS formats

顶级函数 read_sas() 可以读取(但不能写入)SAS XPORT(.xpt)和 SAS7BDAT(.sas7bdat)格式文件。

SAS 文件仅包含两种值类型:ASCII 文本和浮点值(通常为 8 个字节,但有时会被截断)。对于 xport 文件,不会自动将类型转换为整数、日期或类别。对于 SAS7BDAT 文件,格式代码可能允许自动将日期变量转换为日期。默认情况下,将读取整个文件并以 DataFrame 的形式返回。

指定一个 chunksize 或使用 iterator=True 以通过增量方式读取文件获取阅读器对象 (XportReaderSAS7BDATReader)。阅读器对象还具有包含有关文件及其变量的其他信息的属性。

读取 SAS7BDAT 文件:

df = pd.read_sas("sas_data.sas7bdat")

获取一个迭代器并一次读取一个 XPORT 文件 100,000 行:

def do_something(chunk):
    pass


with pd.read_sas("sas_xport.xpt", chunk=100000) as rdr:
    for chunk in rdr:
        do_something(chunk)

xport 文件格式的 specification 可通过 SAS 网站获得。

没有 SAS7BDAT 格式的官方文档。

SPSS formats

顶级函数 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 文件格式的信息可 here 获得。

Other file formats

pandas 本身仅支持与一组有限的文件格式进行 IO,而这些格式可以清晰地映射到其表格数据模型。对于从 pandas 中读取和写入其他文件格式,我们推荐使用来自更广泛社区的以下软件包。

netCDF

xarray 提供受 pandas DataFrame 启发的数据结构,以便处理多维数据集,重点关注 netCDF 文件格式以及轻松转换为 pandas 的格式和从 pandas 格式转换。

Performance considerations

这是针对不同 IO 方法的非正式对比,使用的是 pandas 0.24.2。计时取决于机器,应当忽略微小的差异。

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