Pandas-2-2-中文文档-五-

Pandas 2.2 中文文档(五)

原文:pandas.pydata.org/docs/

IO 工具(文本,CSV,HDF5,…)

原文:pandas.pydata.org/docs/user_guide/io.html

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

格式类型 数据描述 读取器 写入器
文本 CSV read_csv to_csv
文本 定宽文本文件 read_fwf
文本 JSON read_json to_json
文本 HTML read_html to_html
文本 LaTeX Styler.to_latex
文本 XML read_xml to_xml
文本 本地剪贴板 read_clipboard to_clipboard
二进制 MS Excel read_excel to_excel
二进制 OpenDocument read_excel
二进制 HDF5 格式 read_hdf to_hdf
二进制 Feather 格式 read_feather to_feather
二进制 Parquet 格式 read_parquet to_parquet
二进制 ORC 格式 read_orc to_orc
二进制 Stata read_stata to_stata
二进制 SAS read_sas
二进制 SPSS read_spss
二进制 Python Pickle 格式 read_pickle to_pickle
SQL SQL read_sql to_sql
SQL Google BigQuery read_gbq to_gbq

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

注意

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

CSV & 文本文件

用于读取文本文件(也称为平面文件)的主要函数是 read_csv()。查看食谱以获取一些高级策略。

解析选项

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

基本

filepath_or_buffervarious

要么是文件的路径(strpathlib.Path,或 py:py._path.local.LocalPath),URL(包括 http、ftp 和 S3 地址),或具有 read() 方法的任何对象(例如打开的文件或 StringIO)。

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

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

delimiterstr,默认为 None

sep 的替代参数名称。

delim_whitespaceboolean,默认为 False

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

列和索引位置及名称

headerint 或整数列表,默认为 'infer'

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

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

namesarray-like,默认为 None

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

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

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

注意

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

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

在标题之后的第一行用于确定要放入索引的列数。如果后续行的列数少于第一行,则用NaN填充。

可以通过usecols来避免这种情况。这确保了列按原样获取,而尾随数据被忽略。

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

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

元素顺序被忽略,因此usecols=[0, 1][1, 0]相同。要从具有保留元素顺序的data实例化数据帧,请使用pd.read_csv(data, usecols=['foo', 'bar'])[['foo', 'bar']]['foo', 'bar']顺序或pd.read_csv(data, usecols=['foo', 'bar'])[['bar', 'foo']]['bar', 'foo']顺序。

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

In [1]: import pandas as pd

In [2]: from io import StringIO

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

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

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

使用此参数可在使用 c 引擎时获得更快的解析时间和更低的内存使用率。Python 引擎在决定要删除哪些列之前首先加载数据。

通用解析配置

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

数据或列的数据类型。例如{'a': np.float64, 'b': np.int32, 'c': 'Int64'} 使用strobject与适当的na_values设置一起使用以保留并不解释数据类型。如果指定了转换器,则将应用转换器,而不是数据类型转换。

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

dtype_backend{“numpy_nullable”,“pyarrow”},默认为 NumPy 支持的数据帧

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

dtype_backends 仍处于实验阶段。

2.0 版本中新增。

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

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

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

转换器字典,默认为None

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

true_values 列表,默认为None

要视为True的值。

false_values 列表,默认为None

要视为False的值。

skipinitialspace 布尔值,默认为False

在分隔符后跳过空格。

skiprows 类似列表或整数,默认为None

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

如果可调用,则将针对行索引评估可调用函数,如果应跳过该行则返回 True,否则返回 False:

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

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

In [8]: pd.read_csv(StringIO(data), skiprows=lambda x: x % 2 != 0)
Out[8]: 
 col1 col2  col3
0    a    b     2 

skipfooterint,默认为0

要跳过文件底部的行数(与 engine=’c’ 不兼容)。

nrows 整数,默认为None

要读取的文件行数。用于读取大文件的片段。

low_memory 布尔值,默认为True

在块中内部处理文件,导致解析时使用更少的内存,但可能混合类型推断。为确保没有混合类型,要么设置为False,要么使用dtype参数指定类型。请注意,无论如何整个文件都会读入单个DataFrame,使用chunksizeiterator参数以返回分块数据。 (仅适用于 C 解析器)

memory_map 布尔值,默认为 False

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

NA 和缺失数据处理

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

附加字符串识别为 NA/NaN。如果传递了字典,则为每列指定特定的 NA 值。请参见下面的 na values const 以获取默认情况下解释为 NaN 的值列表。

keep_default_na 布尔值,默认为True

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

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

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

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

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

请注意,如果传递na_filterFalse,则keep_default_nana_values参数将被忽略。

na_filter 布尔值,默认为True

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

verbose 布尔值,默认为False

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

skip_blank_lines 布尔值,默认为True

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

日期时间处理

parse_dates 布尔值或整数列表或名称列表或列表列表或字典,默认为False

  • 如果为True -> 尝试解析索引。

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

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

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

注意

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

infer_datetime_format 布尔值,默认为False

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

自 2.0.0 版本起弃用:此参数的严格版本现在是默认值,传递它不会产生任何效果。

keep_date_col 布尔值,默认为False

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

date_parser 函数,默认为None

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

自 2.0.0 版本起弃用:改用date_format,或者读取为object,然后根据需要应用to_datetime()

date_format 字符串或列->格式字典,默认为None

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

2.0.0 版本中的新功能。

dayfirst 布尔值,默认为False

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

cache_dates 布尔值,默认为 True

如果为 True,则使用唯一的转换日期缓存来应用日期时间转换。在解析重复日期字符串时可能会产生显著的加速,特别是带有时区偏移的日期字符串。

迭代

迭代器布尔值,默认为False

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

块大小整数,默认为None

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

引用、压缩和文件格式

压缩{'infer', 'gzip', 'bz2', 'zip', 'xz', 'zstd', None, dict},默认为'infer'

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

从版本 1.2.0 更改:以前的版本将‘gzip’的字典条目转发到gzip.open

千位分隔符字符串,默认为None

千位分隔符。

十进制字符串,默认为'.'

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

浮点精度字符串,默认为 None

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

行终止符字符串(长度为 1),默认为None

用于将文件分成行的字符。仅与 C 解析器有效。

引用字符字符串(长度为 1)

用于表示引用项的起始和结束的字符。引用项可以包括分隔符,它将被忽略。

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

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

双引号布尔值,默认为True

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

转义字符字符串(长度为 1),默认为None

在引用方式为QUOTE_NONE时用于转义分隔符的单字符字符串。

注释字符串,默认为None

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

encodingstr,默认为None

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

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

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

错误处理

on_bad_lines(‘error’、‘warn’、‘skip’),默认为‘error’

指定在遇到坏行(字段过多的行)时要执行的操作。允许的值为:

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

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

  • ‘skip’,遇到坏行时跳过而不引发或警告。

1.3.0 版中的新功能。

指定列数据类型

您可以指示整个DataFrame或单独的列的数据类型:

In [9]: import numpy as np

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

注意

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

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

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

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

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

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

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

将导致mixed_df包含某些列块的int dtype,以及由于读取的数据中混合 dtype 而导致其他列块的str。重要的是要注意,整体列将被标记为objectdtype,用于具有混合 dtype 的列。

设置dtype_backend="numpy_nullable"将导致每列具有可空 dtype。

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

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

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

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

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

```py
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',生成的类别将始终被解析为字符串(对象 dtype)。如果类别是数字的,可以使用to_numeric()函数进行转换,或者根据需要使用另一个转换器,如to_datetime()

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

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

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

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

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

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

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

命名和使用列

处理列名

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

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

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

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

通过在header中与names参数结合使用,可以指示要使用的其他名称以及是否丢弃标题行(如果有):

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

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

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

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

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

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

注意

默认行为是推断列名:如果没有传递列名,则行为与header=0相同,并且列名是从文件的第一行非空行推断出来的,如果显式传递了列名,则行为与header=None相同。 ### 重复名称解析

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

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

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

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

过滤列(usecols

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

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

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

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

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

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

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

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

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

注释和空行

忽略行注释和空行

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

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

In [68]: print(data)

a,b,c

# commented line
1,2,3

4,5,6

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

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

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

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

警告

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

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

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

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

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

如果同时指定了headerskiprowsheader将相对于skiprows的末尾。例如:

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

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

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

有时文件中可能包含注释或元数据:

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

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

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

默认情况下,解析器会将注释包含在输出中:

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

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

我们可以使用comment关键字来抑制注释:

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

In [85]: df
Out[85]: 
 ID    level category
0  Patient1   123000       x 
1  Patient2    23000       y 
2  Patient3  1234018       z 
```  ### 处理 Unicode 数据

应该使用`encoding`参数来处理编码的 Unicode 数据,这将导致字节字符串在结果中被解码为 Unicode:

```py
In [86]: from io import BytesIO

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

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

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

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

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

一些将所有字符编码为多字节的格式,如 UTF-16,如果不指定编码,则根本无法正确解析。Python 标准编码的完整列表。 ### 索引列和尾随分隔符

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

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

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

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

通常情况下,您可以使用index_col选项来实现这种行为。

在某些异常情况下,文件在每个数据行末尾都有分隔符,这会使解析器混淆。要显式禁用索引列推断并丢弃最后一列,请传入index_col=False

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

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

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

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

如果正在使用usecols选项解析数据的子集,则index_col规范是基于该子集而不是原始数据的。

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

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

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

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

#### 指定日期列

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

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

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

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

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

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

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

您可以将列列表的列表指定为 parse_dates,生成的日期列将被添加到输出中(以不影响现有列顺序),新列名将是组件列名的连接:

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

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

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

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

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

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

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

[6 rows x 7 columns] 

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

您还可以使用字典指定自定义列名:

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

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

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

重要的是要记住,如果要将多个文本列解析为单个日期列,则会在数据前添加一个新列。index_col 规范是基于这组新列而不是原始数据列的:

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

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

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

请注意

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

请注意

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

自版本 2.2.0 起已弃用:在 read_csv 中合并日期列已弃用。请改为在相关结果列上使用 pd.to_datetime

日期解析函数

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

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

  2. 如果不同列有不同格式,或者想要向 to_datetime 传递任何额外选项(如 utc),则应以 object 类型读取数据,然后使用 to_datetime

解析具有混合时区的 CSV

pandas 无法原生表示具有混合时区的列或索引。如果您的 CSV 文件包含具有混合时区的列,则默认结果将是一个对象类型的列,其中包含字符串,即使使用 parse_dates 也是如此。要将混合时区值解析为日期时间列,请以 object 类型读取,然后调用 to_datetime() 并设置 utc=True

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

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

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

In [123]: df["a"]
Out[123]: 
0   1999-12-31 19:00:00+00:00
1   1999-12-31 18:00:00+00:00
Name: a, dtype: datetime64[ns, UTC] 
```  #### 推断日期时间格式

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

+   “20111230”

+   “2011/12/30”

+   “20111230 00:00:00”

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

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

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

请注意,格式推断对 `dayfirst` 敏感。当 `dayfirst=True` 时,它会猜测“01/12/2011”是 12 月 1 日。当 `dayfirst=False`(默认)时,它会猜测“01/12/2011”是 1 月 12 日。

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

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

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

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

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

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

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

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

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

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

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

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

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

国际日期格式

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

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

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

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

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

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

将 CSV 写入二进制文件对象

版本 1.2.0 中的新功能。

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

In [139]: import io

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

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

In [142]: data.to_csv(buffer, encoding="utf-8", compression="gzip") 
```  ### 指定浮点数转换方法

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

```py
In [143]: val = "0.3066101993807095471566981359501369297504425048828125"

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

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

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

In [147]: abs(
 .....:    pd.read_csv(StringIO(data), engine="c", float_precision="round_trip")["c"][0]
 .....:    - float(val)
 .....: )
 .....: 
Out[147]: 0.0 
```  ### 千位分隔符

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

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

```py
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') 
```  ### 缺失值

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

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

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

让我们考虑一些例子:

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

在上面的示例中,55.0 将被识别为 NaN,除了默认值。一个字符串首先被解释为数值 5,然后作为 NaN

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

在上面的示例中,只有空字段将被识别为NaN

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

在上面的示例中,NA0 作为字符串都被识别为 NaN

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

默认值,除了字符串"Nope",也被识别为NaN。### 无穷大

inf 类似的值将被解析为np.inf(正无穷大),而 -inf 将被解析为-np.inf(负无穷大)。这些将忽略值的大小写,意思是Inf也将被解析为np.inf。### 布尔值

常见的值TrueFalseTRUEFALSE都被识别为布尔值。偶尔你可能希望识别其他值为布尔值。要做到这一点,使用true_valuesfalse_values选项如下:

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

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

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

In [159]: pd.read_csv(StringIO(data), true_values=["Yes"], false_values=["No"])
Out[159]: 
 a      b  c
0  1   True  2
1  3  False  4 
```### 处理“坏”行

一些文件可能有格式错误的行,字段太少或太多。字段太少的行将在尾部字段中填充 NA 值。字段太多的行将默认引发错误:

```py
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 mappingengine
  1899 except Exception:
  1900     if self.handles is not None:

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

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

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

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

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

`dialect`关键字提供了更大的灵活性,用于指定文件格式。默认情况下,它使用 Excel 方言,但你可以指定方言名称或[`csv.Dialect`](https://docs.python.org/3/library/csv.html#csv.Dialect "(在 Python v3.12)")实例。

假设你有一些未封闭引号的数据:

```py
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 对象一样以对象 dtype 传递。### 引用和转义字符

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

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

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

In [186]: pd.read_csv(StringIO(data), escapechar="\\")
Out[186]: 
 a  b
0  hello, "Bob", nice to see you  5 
```### 固定宽度列的文件

当 `read_csv()` 读取分隔数据时,`read_fwf()` 函数与具有已知和固定列宽的数据文件一起工作。`read_fwf` 的函数参数与 `read_csv` 大致相同,但有两个额外参数,并且 `delimiter` 参数的用法不同:

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

+   `widths`:一个字段宽度列表,可以代替 ‘colspecs’ 使用,如果间隔是连续的。

+   `delimiter`:固定宽度文件中要考虑为填充字符的字符。如果字段的填充字符不是空格(例如,‘~’),可以使用它来指定填充字符。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

索引

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

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

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

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

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

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

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

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

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

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

使用 MultiIndex 读取索引

假设您的数据由两列索引:

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

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

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

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

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

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

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

使用 MultiIndex 读取列

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

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

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

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

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

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

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

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

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

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

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

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

注意

如果未指定index_col(例如您没有索引,或者使用df.to_csv(..., index=False)写入它),则列索引上的任何names都将丢失。### 自动“嗅探”分隔符

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

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

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

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

最好使用`concat()`来合并多个文件。请参阅 cookbook 以获取示例。### 通过文件逐块迭代

假设您希望懒惰地迭代(而不是将整个文件读入内存),比如以下内容:

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

指定解析器引擎

Pandas 目前支持三种引擎,即 C 引擎、Python 引擎和实验性的 pyarrow 引擎(需要pyarrow包)。一般来说,对于较大的工作负载,pyarrow 引擎速度最快,在大多数其他工作负载上与 C 引擎速度相当。Python 引擎在大多数工作负载上往往比 pyarrow 和 C 引擎慢。但是,pyarrow 引擎比 C 引擎要脆弱得多,与 Python 引擎相比,缺少一些功能。

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

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

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

  • skipfooter

  • sep=Nonedelim_whitespace=False

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

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

  • float_precision

  • chunksize

  • comment

  • nrows

  • thousands

  • memory_map

  • dialect

  • on_bad_lines

  • delim_whitespace

  • quoting

  • lineterminator

  • converters

  • decimal

  • iterator

  • dayfirst

  • infer_datetime_format

  • verbose

  • skipinitialspace

  • low_memory

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

读取/写入远程文件

您可以传递 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 文档中列出的几种方式之一中定义凭据。对于几个存储后端也是如此,你应该按照fsimpl1中内置到fsspec中的实现和fsimpl2中未包含在主fsspec分发中的实现的链接进行操作。

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

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

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

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

版本 1.2.0 中新增。

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

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

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

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

写出数据

写入 CSV 格式

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

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

  • sep:输出文件的字段分隔符(默认为“,”)

  • na_rep:缺失值的字符串表示(默认为‘’)

  • float_format:浮点数的格式字符串

  • columns:要写入的列(默认为 None)

  • header:是否写出列名(默认为 True)

  • index:是否写入行(索引)名称(默认为 True)

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

  • mode : Python 写入模式,默认为 ‘w’

  • encoding: 表示要使用的编码的字符串,如果内容为非 ASCII,则用于 Python 版本 3 之前

  • lineterminator: 表示行结束的字符序列(默认为 os.linesep

  • quoting: 设置引用规则,如 csv 模块(默认为 csv.QUOTE_MINIMAL)。请注意,如果设置了 float_format,则浮点数将被转换为字符串,csv.QUOTE_NONNUMERIC 将将其视为非数值

  • quotechar: 用于引用字段的字符(默认为 ‘”’)

  • doublequote: 控制字段中 quotechar 的引用(默认为 True)

  • escapechar: 用于适当时转义 sepquotechar 的字符(默认为 None)

  • chunksize: 每次写入的行数

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

写入格式化字符串

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

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

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

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

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

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

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

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

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

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

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

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

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

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

写入 JSON

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

  • path_or_buf : 要写入输出的路径名或缓冲区。如果为 None,则返回一个 JSON 字符串。

  • orient :

    Series:

    • 默认为 index

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

    DataFrame:

    • 默认为 columns

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

    JSON 字符串的格式

    split 类似字典
    records 类似列表 [{column -> value}, … , {column -> value}]
    index 类似字典 {index -> {column -> value}}
    columns 类似于{column -> {index -> value}}的字典
    values 仅值数组
    table 符合 JSON Table Schema的表格
  • date_format:日期转换类型,‘epoch’表示时间戳,‘iso’表示 ISO8601。

  • double_precision:编码浮点值时要使用的小数位数,默认为 10。

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

  • date_unit:要编码的时间单位,控制时间戳和 ISO8601 精度。其中之一为's'、'ms'、'us'或'ns',分别表示秒、毫秒、微秒和纳秒。默认为'ms'。

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

  • lines:如果是records方向,则将每个记录写成一行 json。

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

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

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

Epoch 时间戳,以秒为单位:

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

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

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

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

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

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

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

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

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

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

回退行为

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

  • 如果 dtype 不受支持(例如np.complex_),则会调用default_handler(如果提供)处理每个值,否则会引发异常。

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

    • 检查对象是否定义了toDict方法并调用它。toDict方法应返回一个将被 JSON 序列化的dict
    • 如果提供了一个,则调用default_handler
    • 通过遍历其内容将对象转换为dict。但是这通常会失败并出现OverflowError或给出意外结果。

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

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

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

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

将 JSON 字符串读取到 pandas 对象可以使用多个参数。如果未提供或提供的是`None`,则解析器将尝试解析`DataFrame`。要明确强制解析`Series`,请传递`typ=series`。

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

+   `typ`:要恢复的对象类型(series 或 frame),默认为‘frame’。

+   `orient`:

    Series:

    +   默认为`index`

    +   允许的值为{`split`,`records`,`index`}。

    DataFrame

    +   默认为`columns`

    +   允许的值为{`split`,`records`,`index`,`columns`,`values`,`table`}。

    JSON 字符串的格式

    | `split` | 类似字典 {index -> [index],columns -> [columns],data -> [values]} |
    | --- | --- |
    | `records` | 类似列表 [{column -> value},…,{column -> value}] |
    | `index` | 类似字典 {index -> {column -> value}} |
    | `columns` | 类似字典 {column -> {index -> value}} |
    | `values` | 仅值数组 |
    | `table` | 符合 JSON [Table Schema](https://specs.frictionlessdata.io/table-schema/) 的规范 |

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

+   `convert_axes`:布尔值,尝试将轴转换为正确的数据类型,默认为`True`。

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

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

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

+   `date_unit`:字符串,用于检测日期转换的时间戳单位。默认为 None。默认情况下,将检测时间戳精度,如果不希望这样,则传递‘s’,‘ms’,‘us’或‘ns’中的一个来强制时间戳精度为秒,毫秒,微秒或纳秒。

+   `lines`:每行读取一个 json 对象。

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

+   `chunksize`:与`lines=True`组合使用时,每次迭代读取`chunksize`行的`pandas.api.typing.JsonReader`。

+   `engine`: 要么是 `"ujson"`,内置的 JSON 解析器,要么是 `"pyarrow"`,它会分派到 pyarrow 的 `pyarrow.json.read_json`。当 `lines=True` 时,仅可用 `"pyarrow"`。

如果 JSON 不可解析,解析器将引发 `ValueError/TypeError/AssertionError` 中的一个。

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

#### 数据转换

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

注意

如果 `convert_dates=True` 并且数据和/或列标签看起来像是日期,则大整数值可能会转换为日期。确切的阈值取决于指定的 `date_unit`。‘看起来像日期’ 意味着列标签符合以下标准之一:

+   它以 `'_at'` 结尾

+   它以 `'_time'` 结尾

+   它以 `'timestamp'` 开始

+   它是 `'modified'`

+   它是 `'date'`

警告

在读取 JSON 数据时,自动强制转换为 dtypes 会有一些怪异之处:

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

+   如果列是 `float` 数据,则如果安全的话会转换为 `integer`,例如列为 `1.`。

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

因此,有时您可能希望通过 `dtype` 关键字参数指定特定的 dtypes。

从 JSON 字符串中读取:

```py
In [262]: from io import StringIO

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

从文件中读取:

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

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

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

指定转换的 dtypes:

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

保留字符串索引:

In [267]: from io import StringIO

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

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

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

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

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

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

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

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

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

以纳秒写入的日期需要以纳秒读取:

In [277]: from io import StringIO

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

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

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

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

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

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

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

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

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

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

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

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

pandas 提供了一个实用函数,可以接受字典或字典列表,并将这种半结构化数据 *规范化* 为一个平面表。

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

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

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

max_level 参数提供了更多控制规范化结束的级别。使用 max_level=1 将规范化到所提供字典的第一个嵌套级别。

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

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

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

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

```py
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 读取行分隔的 json。

In [301]: from io import BytesIO

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

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

新版本 2.0.0 中的新增功能。 ### 表模式

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

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

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

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

schema字段包含fields键,它本身包含列名到类型对的列表,包括IndexMultiIndex(请参阅下面的类型列表)。如果(多)索引是唯一的,则schema字段还包含一个primaryKey字段。

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

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

pandas 类型 Table Schema 类型
int64 integer
float64 number
bool boolean
datetime64[ns] datetime
timedelta64[ns] duration
categorical any
object str

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

  • schema对象包含一个pandas_version字段。这包含 pandas 模式的版本,并将随每个修订版递增。

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

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

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

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

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

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

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

    • 对于系列,使用object.name。如果没有,则名称为values
    • 对于DataFrames,使用列名的字符串版本。
    • 对于Index(而不是MultiIndex),使用index.name,如果为 None,则使用index
    • 对于MultiIndex,使用mi.names。如果任何级别没有名称,则使用level_<i>

read_json还接受orient='table'作为参数。这样可以以往返的方式保留元数据,如数据类型和索引名称。

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

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

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

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

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

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

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

请注意,作为 Index 的名称的文字字符串 'index' 不具有往返性,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 往返。

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

HTML

读取 HTML 内容

警告

我们强烈建议您阅读下面关于 BeautifulSoup4/html5lib/lxml 解析器的 HTML 表格解析陷阱。

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

注意

read_html 返回一个 DataFrame 对象的 list,即使在 HTML 内容中只包含一个表格。

无选项读取 URL:

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

 [563 rows x 7 columns]] 

注意

上述 URL 的数据每个星期一都会更改,因此上面生成的数据可能会略有不同。

在 HTTP 请求中传递标题时读取 URL:

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

注意

我们可以看到上面我们传递的标题反映在 HTTP 请求中。

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

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

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

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

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

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

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

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

注意

由于具有如此多的网络访问功能会减慢文档构建速度,因此 IPython 评估器未运行以下示例。如果您发现错误或无法运行的示例,请毫不犹豫地在 pandas GitHub 问题页面 上报告。

读取一个包含特定文本的表格的 URL:

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

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

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

指定一个索引列:

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

指定要跳过的行数:

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

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

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

指定 HTML 属性:

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

指定应转换为 NaN 的值:

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

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

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

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

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

使用上述某种组合:

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

读取 pandas to_html 输出(会损失浮点数精度):

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

如果 lxml 后端在提供唯一解析器的情况下解析失败,则会引发错误。如果您只有一个解析器,可以只提供一个字符串,但是,如果函数期望一个字符串序列,那么传递一个包含一个字符串的列表被认为是一种良好的做法。您可以使用:

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

或者您可以不带列表传递 flavor='lxml'

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

但是,如果你已经安装了 bs4 和 html5lib,并且传递了 None['lxml', 'bs4'],那么解析很可能会成功。请注意,一旦解析成功,函数将立即返回

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

可以使用 extract_links="all" 从单元格中提取链接和文本。

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

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

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

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

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

版本 1.5.0 中的新功能。 ### 写入 HTML 文件

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

注意

由于篇幅限制,这里没有显示 DataFrame.to_html 的所有可能选项。有关完整选项集,请参阅 DataFrame.to_html()

注意

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

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

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

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

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

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

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

columns 参数将限制显示的列:

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

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

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

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

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

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

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

bold_rows 默认会使行标签加粗,但你可以关闭这个选项:

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

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

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

classes 参数提供了为生成的 HTML 表格添加 CSS 类的功能。请注意,这些类会 追加 到现有的 'dataframe' 类中。

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

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

In [358]: url_df = pd.DataFrame(
 .....:    {
 .....:        "name": ["Python", "pandas"],
 .....:        "url": ["https://www.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 表格解析的陷阱

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

lxml 有关的问题

  • 优点

    • lxml 非常快速。
    • lxml 需要正确安装 Cython 才能安装。
  • 缺点

    • lxml 在没有提供 严格有效的标记 的情况下, 对其解析结果做出任何保证。
    • 综上所述,我们选择允许您,用户,使用lxml后端,但是如果lxml无法解析,则将使用html5lib
    • 因此,强烈建议您安装BeautifulSoup4html5lib,这样即使lxml失败,您仍将获得有效结果(假设其他一切有效)。

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

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

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

  • 优点

    • html5liblxml宽容得多,因此以更理智的方式处理现实中的标记,而不仅仅是,例如,删除一个元素而不通知您。
    • html5lib 会自动从无效标记生成有效的 HTML5 标记。这对于解析 HTML 表格非常重要,因为它保证了一个有效的文档。但是,这并不意味着它是“正确的”,因为修复标记的过程没有一个单一的定义。
    • html5lib是纯 Python 的,除了自己的安装之外,不需要额外的构建步骤。
  • 缺点

    • 使用html5lib的最大缺点是速度极慢。但是请考虑到许多网页上的表格都不足以使解析算法运行时间成为问题。更可能的是瓶颈将出现在通过网络从 URL 读取原始文本的过程中,即 IO(输入输出)。对于非常大的表格,这可能不成立。## LaTeX

在版本 1.3.0 中新增。

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

编写到 LaTeX 文件

注意

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

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

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

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

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

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

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

XML

读取 XML

版本 1.3.0 中的新功能。

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

注意

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

让我们看几个例子。

读取 XML 字符串:

In [372]: from io import StringIO

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

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

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

读取无选项的 URL:

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

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

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

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

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

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

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

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

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

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

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

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

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

甚至可以从 AWS S3 存储桶读取 XML,例如 NIH NCBI PMC Article Datasets,提供生物医学和生命科学期刊:

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,您可以访问扩展了 Python 的 ElementTree API 的功能齐全的 XML 库。其中一个强大的工具是能够使用更具表达力的 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 >
 .....:  <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 >
 .....: <row>
 .....:   <shape>square</shape>
 .....:   <degrees>360</degrees>
 .....:   <sides>4.0</sides>
 .....: </row>
 .....: <row>
 .....:   <shape>circle</shape>
 .....:   <degrees>360</degrees>
 .....:   <sides/>
 .....: </row>
 .....: <row>
 .....:   <shape>triangle</shape>
 .....:   <degrees>180</degrees>
 .....:   <sides>3.0</sides>
 .....: </row>
 .....: </data>"""
 .....: 

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

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

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

注意

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

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

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

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

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

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

例如,考虑芝加哥“L”列车的稍微嵌套的结构,其中 stationrides 元素将数据封装在各自的部分中。使用下面的 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" >
 .....:   <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
 .....:   <xsl:strip-space elements="*"/>
 .....:   <xsl:template match="/response">
 .....:      <xsl:copy>
 .....:        <xsl:apply-templates select="row"/>
 .....:      </xsl:copy>
 .....:   </xsl:template>
 .....:   <xsl:template match="row">
 .....:      <xsl:copy>
 .....:        <station_id><xsl:value-of select="station/@id"/></station_id>
 .....:        <station_name><xsl:value-of select="station/@name"/></station_name>
 .....:        <xsl:copy-of select="month|rides/*"/>
 .....:      </xsl:copy>
 .....:   </xsl:template>
 .....: </xsl:stylesheet>"""
 .....: 

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

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

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

[3 rows x 6 columns] 

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

新功能,版本 1.5.0。

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

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

[3578765 rows x 3 columns] 
```  ### 编写 XML

新功能,版本 1.3.0。

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

注意

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

让我们看几个示例。

编写没有选项的 XML:

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

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

编写具有新根和行名称的 XML:

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

编写基于属性的 XML:

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

编写混合元素和属性:

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

任何具有分层列的 DataFrame 将被展平为以下划线分隔的 XML 元素名称:

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

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

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

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

使用默认命名空间编写 XML:

In [419]: print(geom_df.to_xml(namespaces={"": "https://example.com"}))
<?xml version='1.0' encoding='utf-8'?>
<data >
 <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 >
 <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" >
 .....:   <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
 .....:   <xsl:strip-space elements="*"/>
 .....:   <xsl:template match="/data">
 .....:     <geometry>
 .....:       <xsl:apply-templates select="row"/>
 .....:     </geometry>
 .....:   </xsl:template>
 .....:   <xsl:template match="row">
 .....:     <object index="{index}">
 .....:       <xsl:if test="shape!='circle'">
 .....:           <xsl:attribute name="type">polygon</xsl:attribute>
 .....:       </xsl:if>
 .....:       <xsl:copy-of select="shape"/>
 .....:       <property>
 .....:           <xsl:copy-of select="degrees|sides"/>
 .....:       </property>
 .....:     </object>
 .....:   </xsl:template>
 .....: </xsl:stylesheet>"""
 .....: 

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

XML 最终注意事项

  • 所有 XML 文档遵循W3C 规范etreelxml解析器将无法解析任何不符合规范或遵循 XML 语法规则的标记文档。请注意,除非遵循 XHTML 规范,否则 HTML 不是 XML 文档。然而,其他流行的标记类型,包括 KML、XAML、RSS、MusicML、MathML 都符合XML 模式

  • 出于上述原因,如果您的应用在 pandas 操作之前构建 XML,请使用适当的 DOM 库(如etreelxml)构建必要的文档,而不是通过字符串连接或正则表达式调��。请始终记住,XML 是一个带有标记规则的特殊文本文件。

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

  • 因为 XSLT 是一种编程语言,请谨慎使用,因为这样的脚本可能在您的环境中构成安全风险,并且可能运行大型或无限递归操作。始终在小片段上测试脚本,然后再进行完整运行。

  • etree解析器支持read_xmlto_xml的所有功能,除了复杂的 XPath 和任何 XSLT。尽管功能有限,etree仍然是一个可靠且功能强大的解析器和树构建器。对于较大的文件,其性能可能略逊于lxml,但在小到中等大小的文件上相对不易察觉。

Excel 文件

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

注意

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

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

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

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

  • 否则将使用openpyxl

读取 Excel 文件

在最基本的用例中,read_excel接受 Excel 文件的路径,以及指示要解析哪个工作表的sheet_name

在使用engine_kwargs参数时,pandas 将这些参数传递给引擎。因此,重要的是要知道 pandas 内部使用的函数。

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

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

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

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

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

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

ExcelFile

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

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

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

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

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

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

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

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

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

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

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

import xlrd

xlrd_book = xlrd.open_workbook("path_to_file.xls", on_demand=True)
with pd.ExcelFile(xlrd_book) as xls:
    df1 = pd.read_excel(xls, "Sheet1")
    df2 = pd.read_excel(xls, "Sheet2") 
```  #### 指定工作表

注意

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

注意

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

+   参数`sheet_name`允许指定要读取的工作表。

+   参数`sheet_name`的默认值为 0,表示读取第一个工作表

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

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

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

+   传递`None`返回所有可用工作表的字典。

```py
# Returns a DataFrame
pd.read_excel("path_to_file.xls", "Sheet1", index_col=None, na_values=["NA"]) 

使用工作表索引:

# Returns a DataFrame
pd.read_excel("path_to_file.xls", 0, index_col=None, na_values=["NA"]) 

使用所有默认值:

# Returns a DataFrame
pd.read_excel("path_to_file.xls") 

使用 None 获取所有工作表:

# Returns a dictionary of DataFrames
pd.read_excel("path_to_file.xls", sheet_name=None) 

使用列表获取多个工作表:

# Returns the 1st and 4th sheet, as a dictionary of DataFrames.
pd.read_excel("path_to_file.xls", sheet_name=["Sheet1", 3]) 

read_excel可以通过将sheet_name设置为工作表名称列表、工作表位置列表或None来读取多个工作表。可以通过工作表索引或工作表名称指定工作表,分别使用整数或字符串。 #### 读取MultiIndex

read_excel可以通过将列列表传递给index_col和将行列表传递给header来读取MultiIndex索引。如果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}) 

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

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

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

Dtype 规范

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

pd.read_excel("path_to_file.xls", dtype={"MyInts": "int64", "MyText": str}) 
```  ### 写入 Excel 文件

#### 将 Excel 文件写入磁盘

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

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

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

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

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

为了将单独的DataFrame写入单个 Excel 文件的不同工作表中,可以传递一个ExcelWriter

with pd.ExcelWriter("path_to_file.xlsx") as writer:
    df1.to_excel(writer, sheet_name="Sheet1")
    df2.to_excel(writer, sheet_name="Sheet2") 

当使用engine_kwargs参数时,pandas 将这些参数传递给引擎。因此,重要的是要知道 pandas 内部使用的是哪个函数。

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

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

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

将 Excel 文件写入内存

pandas 支持将 Excel 文件写入类似缓冲区的对象,如StringIOBytesIO,使用ExcelWriter

from io import BytesIO

bio = BytesIO()

# By setting the 'engine' in the ExcelWriter constructor.
writer = pd.ExcelWriter(bio, engine="xlsxwriter")
df.to_excel(writer, sheet_name="Sheet1")

# Save the workbook
writer.save()

# Seek to the beginning and read to copy the workbook to a variable in memory
bio.seek(0)
workbook = bio.read() 

注意

engine是可选的但建议设置。设置引擎确定生成的工作簿版本。设置engine='xlrd'将生成一个 Excel 2003 格式的工作簿(xls)。使用'openpyxl''xlsxwriter'将生成一个 Excel 2007 格式的工作簿(xlsx)。如果省略,将生成一个 Excel 2007 格式的工作簿。### Excel 写入器引擎

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

  1. engine关键字参数

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

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

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

  • openpyxl:需要 2.4 或更高版本

  • xlsxwriter

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

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

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

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

df.to_excel("path_to_file.xlsx", sheet_name="Sheet1") 
```### 样式和格式

可以使用`DataFrame`的`to_excel`方法上的以下参数修改从 pandas 创建的 Excel 工作表的外观和感觉。

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

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

使用 [Xlsxwriter](https://xlsxwriter.readthedocs.io) 引擎提供了许多控制使用 `to_excel` 方法创建的 Excel 工作表格式的选项。在 [Xlsxwriter](https://xlsxwriter.readthedocs.io) 文档中可以找到出色的示例:[`xlsxwriter.readthedocs.io/working_with_pandas.html`](https://xlsxwriter.readthedocs.io/working_with_pandas.html)  ## OpenDocument 电子表格

Excel 文件 的 io 方法还支持使用 [odfpy](https://pypi.org/project/odfpy/) 模块读取和写入 OpenDocument 电子表格。读取和写入 OpenDocument 电子表格���语义和功能与使用 `engine='odf'` 可以为 Excel 文件 做的事情相匹配。需要安装可选依赖‘odfpy’。

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

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

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

# Writes DataFrame to a .ods file
df.to_excel("path_to_file.ods", engine="odf") 
```  ## 二进制 Excel(.xlsb)文件

`read_excel()` 方法还可以使用 `pyxlsb` 模块读取二进制 Excel 文件。读取二进制 Excel 文件的语义和功能大部分与使用 `engine='pyxlsb'` 可以为 Excel 文件 做的事情相匹配。`pyxlsb` 不识别文件中的日期时间类型,而会返回浮点数(如果需要识别日期时间类型,可以使用 calamine)。

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

注意

目前 pandas 只支持读取二进制 Excel 文件。写入尚未实现。 ## Calamine(Excel 和 ODS 文件)

read_excel() 方法可以使用 python-calamine 模块读取 Excel 文件(.xlsx, .xlsm, .xls, .xlsb)和 OpenDocument 电子表格(.ods)。该模块是 Rust 库 calamine 的绑定,大多数情况下比其他引擎更快。需要安装可选依赖python-calamine

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

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

```py
 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函数可用于从文件加载任何 pickled pandas 对象(或任何其他 pickled 对象):

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 

警告

从不受信任的来源接收 pickled 数据可能不安全。

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

警告

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

压缩的 pickle 文件

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

压缩类型可以是一个显式参数,也可以从文件扩展名中推断出来。如果是‘infer’,则在文件名以'.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

pandas 在 1.0.0 版本中移除了对`msgpack`的支持。建议改用 pickle。

或者,您也可以使用 Arrow IPC 序列化格式来传输 pandas 对象。有关 pyarrow 的文档,请参见[这里](https://arrow.apache.org/docs/python/ipc.html)。  ## HDF5(PyTables)

`HDFStore`是一个类似字典的对象,使用高性能 HDF5 格式读写 pandas,使用优秀的[PyTables](https://www.pytables.org/)库。查看 cookbook 了解一些高级策略

警告

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

更多信息请参见:[`docs.python.org/3/library/pickle.html`](https://docs.python.org/3/library/pickle.html)。

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

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

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

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

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

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

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

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

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

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

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

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

删除由键指定的对象:

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

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

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

In [466]: store.close()

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

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

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

读/写 API

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

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

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

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

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

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

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

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

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

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

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

固定格式

上面的示例显示了使用put进行存储,它将 HDF5 写入PyTables中的固定数组格式,称为fixed格式。这些类型的存储一旦写入就不可追加(尽管您可以简单地删除它们并重新写入)。它们也不可查询;必须完全检索它们。它们也不支持具有非唯一列名的数据框。使用putto_hdf时,默认情况下指定fixed格式,或通过format='fixed'format='f'指定。

警告

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

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

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

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

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

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

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

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

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

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

`HDFStore`支持另一种磁盘上的`PyTables`格式,即`table`格式。在概念上,`table`的形状非常类似于 DataFrame,具有行和列。`table`可以在相同或其他会话中追加。此外,支持删除和查询类型操作。通过`format='table'`或`format='t'`指定此格式以进行`append`或`put`或`to_hdf`。

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

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

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

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

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

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

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

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

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

注意

您还可以通过将format='table'format='t'传递给put操作来创建一个table。 ### 分层键

存储的键可以指定为字符串。这些可以采用分层路径名称格式(例如foo/bar/bah),这将生成子存储(或PyTables术语中的Groups)的层次结构。键可以指定为没有前导‘/’的,并且始终是绝对的(例如,‘foo’指的是‘/foo’)。删除操作可以删除子存储中的所有内容以及以下内容,因此要小心

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

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

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

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

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

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

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

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

In [496]: for (path, subgroups, subkeys) in store.walk():
 .....:    for subgroup in subgroups:
 .....:        print("GROUP: {}/{}".format(path, subgroup))
 .....:    for subkey in subkeys:
 .....:        key = "/".join([path, subkey])
 .....:        print("KEY: {}".format(key))
 .....:        print(store.get(key))
 .....: 
GROUP: /foo
KEY: /df
 A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517
GROUP: /foo/bar
KEY: /foo/bar/bah
 A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517 

警告

无法像上面描述的在根节点下存储的项目那样,以点(属性)访问的方式检索分层键。

In [497]: store.foo.bar.bah
---------------------------------------------------------------------------
TypeError  Traceback (most recent call last)
Cell In[497], line 1
----> 1 store.foo.bar.bah

File ~/work/pandas/pandas/pandas/io/pytables.py:613, in HDFStore.__getattr__(self, name)
  611  """allow attribute access to get stores"""
  612 try:
--> 613     return self.get(name)
  614 except (KeyError, ClosedFileError):
  615     pass

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

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

File ~/work/pandas/pandas/pandas/io/pytables.py:1752, in HDFStore._create_storer(self, group, format, value, encoding, errors)
  1750         tt = "generic_table"
  1751     else:
-> 1752         raise TypeError(
  1753             "cannot create a storer if the object is not existing "
  1754             "nor a value are passed"
  1755         )
  1756 else:
  1757     if isinstance(value, Series):

TypeError: cannot create a storer if the object is not existing nor a value are passed 
# you can directly access the actual PyTables node but using the root node
In [498]: store.root.foo.bar.bah
Out[498]: 
/foo/bar/bah (Group) ''
 children := ['axis0' (Array), 'axis1' (Array), 'block0_items' (Array), 'block0_values' (Array)] 

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

In [499]: store["foo/bar/bah"]
Out[499]: 
 A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517 
```  ### 存储类型

#### 在表中存储混合类型

支持存储混合 dtype 数据。字符串以使用附加列的最大大小存储为固定宽度。尝试追加更长字符串将引发`ValueError`。

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

```py
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} 

存储多级索引的 DataFrames

将多级索引的 DataFrames 存储为表与存储/选择同质索引的 DataFrames 非常相似。

In [507]: index = pd.MultiIndex(
 .....:   levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
 .....:   codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
 .....:   names=["foo", "bar"],
 .....: )
 .....: 

In [508]: df_mi = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])

In [509]: df_mi
Out[509]: 
 A         B         C
foo bar 
foo one   -1.303456 -0.642994 -0.649456
 two    1.012694  0.414147  1.950460
 three  1.094544 -0.802899 -0.583343
bar one    0.410395  0.618321  0.560398
 two    1.434027 -0.033270  0.343197
baz two   -1.646063 -0.695847 -0.429156
 three -0.244688 -1.428229 -0.138691
qux one    1.866184 -1.446617  0.036660
 two   -1.660522  0.929553 -1.298649
 three  3.565769  0.682402  1.041927

In [510]: store.append("df_mi", df_mi)

In [511]: store.select("df_mi")
Out[511]: 
 A         B         C
foo bar 
foo one   -1.303456 -0.642994 -0.649456
 two    1.012694  0.414147  1.950460
 three  1.094544 -0.802899 -0.583343
bar one    0.410395  0.618321  0.560398
 two    1.434027 -0.033270  0.343197
baz two   -1.646063 -0.695847 -0.429156
 three -0.244688 -1.428229 -0.138691
qux one    1.866184 -1.446617  0.036660
 two   -1.660522  0.929553 -1.298649
 three  3.565769  0.682402  1.041927

# the levels are automatically included as data columns
In [512]: store.select("df_mi", "foo=bar")
Out[512]: 
 A         B         C
foo bar 
bar one  0.410395  0.618321  0.560398
 two  1.434027 -0.033270  0.343197 

注意

index 关键字是保留的,不能用作级别名称。### 查询

查询表

selectdelete 操作有一个可选的条件,可以指定选择/删除数据的子集。这允许在磁盘上有一个非常大的表,并且只检索数据的一部分。

使用 Term 类在底层指定查询,作为布尔表达式。

  • indexcolumnsDataFrames 的支持索引器。

  • 如果指定了 data_columns,则可以将其用作额外的索引器。

  • 多级索引中的级别名称,默认名称为 level_0level_1,如果未提供。

有效的比较运算符有:

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

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

  • |:或

  • &:和

  • ():用于分组

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

注意

  • = 将自动扩展为比较运算符 ==

  • ~ 是非运算符,但只能在非��有限的情况下使用

  • 如果传递了表达式的列表/元组,它们将通过 & 组合。

以下是有效的表达式:

  • 'index >= date'

  • "columns = ['A', 'D']"

  • "columns in ['A', 'D']"

  • 'columns = A'

  • 'columns == A'

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

  • 'index > df.index[3] & string = "bar"'

  • '(index > df.index[3] & index <= df.index[6]) | string = "bar"'

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

  • "major_axis>=20130101"

indexers 在子表达式的左侧:

columnsmajor_axists

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

  • 将被评估的函数,例如Timestamp('2012-02-01')

  • 字符串,例如"bar"

  • 类似日期的格式,例如20130101,或"20130101"

  • 列表,例如"['A', 'B']"

  • 在本地命名空间中定义的变量,例如date

注意

不建议通过将字符串插入查询表达式来查询字符串。只需将感兴趣的字符串赋值给一个变量,并在表达式中使用该变量。例如,这样做

string = "HolyMoly'"
store.select("df", "index == string") 

而不是这样

string = "HolyMoly'"
store.select('df', f'index == {string}') 

后者将不起作用,并将引发 SyntaxError。请注意 string 变量中有一个单引号后跟一个双引号。

如果必须插值,请使用 '%r' 格式说明符

store.select("df", "index == %r" % string) 

将引用 string

以下是一些示例:

In [513]: dfq = pd.DataFrame(
 .....:    np.random.randn(10, 4),
 .....:    columns=list("ABCD"),
 .....:    index=pd.date_range("20130101", periods=10),
 .....: )
 .....: 

In [514]: store.append("dfq", dfq, format="table", data_columns=True) 

使用内联列引用。

In [515]: store.select("dfq", "index>pd.Timestamp('20130104') & columns=['A', 'B']")
Out[515]: 
 A         B
2013-01-05 -0.830545 -0.457071
2013-01-06  0.431186  1.049421
2013-01-07  0.617509 -0.811230
2013-01-08  0.947422 -0.671233
2013-01-09 -0.183798 -1.211230
2013-01-10  0.361428  0.887304 

使用内联列引用。

In [516]: store.select("dfq", where="A>0 or C>0")
Out[516]: 
 A         B         C         D
2013-01-02  0.658179  0.362814 -0.917897  0.010165
2013-01-03  0.905122  1.848731 -1.184241  0.932053
2013-01-05 -0.830545 -0.457071  1.565581  1.148032
2013-01-06  0.431186  1.049421  0.383309  0.595013
2013-01-07  0.617509 -0.811230 -2.088563 -1.393500
2013-01-08  0.947422 -0.671233 -0.847097 -1.187785
2013-01-10  0.361428  0.887304  0.266457 -0.399641 

columns 关键字可以用来选择要返回的列的列表,这相当于传递 'columns=list_of_columns_to_filter'

In [517]: store.select("df", "columns=['A', 'B']")
Out[517]: 
 A         B
2000-01-01  0.858644 -0.851236
2000-01-02 -0.080372 -1.268121
2000-01-03  0.816983  1.965656
2000-01-04  0.712795 -0.062433
2000-01-05 -0.298721 -1.988045
2000-01-06  1.103675  1.382242
2000-01-07 -0.729161 -0.142928
2000-01-08 -1.005977  0.465222 

可以指定 startstop 参数以限制总搜索空间。这些是以表中总行数为单位的。

注意

如果查询表达式具有未知变量引用,则 select 将引发 ValueError。通常,这意味着您正在尝试选择一个不是数据列的列。

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

查询 timedelta64[ns]

您可以使用 timedelta64[ns] 类型进行存储和查询。时间间隔可以以 <float>(<unit>) 的格式指定,其中浮点数可以是有符号的(也可以是分数),单位可以是 D,s,ms,us,ns 用于时间间隔。以下是一个示例:

In [518]: from datetime import timedelta

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

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

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

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

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

通过使用级别的名称可以实现从 `MultiIndex` 中选择。

```py
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 关键字自动使用 level_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 为表创建/修改索引。强烈建议创建表索引。当您使用具有索引维度作为 whereselect 时,这将大大加快查询速度。

注意

索引会自动创建在可索引和您指定的任何数据列上。通过向 append 传递 index=False 可以关闭此行为。

# we have automagically already created an index (in the first section)
In [531]: i = store.root.df.table.cols.index.index

In [532]: i.optlevel, i.kind
Out[532]: (6, 'medium')

# change an index by passing new parameters
In [533]: store.create_table_index("df", optlevel=9, kind="full")

In [534]: i = store.root.df.table.cols.index.index

In [535]: i.optlevel, i.kind
Out[535]: (9, 'full') 

在向存储附加大量数据时,通常很有用关闭每次附加的索引创建,然后在最后重新创建。

In [536]: df_1 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [537]: df_2 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [538]: st = pd.HDFStore("appends.h5", mode="w")

In [539]: st.append("df", df_1, data_columns=["B"], index=False)

In [540]: st.append("df", df_2, data_columns=["B"], index=False)

In [541]: st.get_storer("df").table
Out[541]: 
/df/table (Table(20,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
 "B": Float64Col(shape=(), dflt=0.0, pos=2)}
 byteorder := 'little'
 chunkshape := (2730,) 

然后在附加完成后创建索引。

In [542]: st.create_table_index("df", columns=["B"], optlevel=9, kind="full")

In [543]: st.get_storer("df").table
Out[543]: 
/df/table (Table(20,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
 "B": Float64Col(shape=(), dflt=0.0, pos=2)}
 byteorder := 'little'
 chunkshape := (2730,)
 autoindex := True
 colindexes := {
 "B": Index(9, fullshuffle, zlib(1)).is_csi=True}

In [544]: st.close() 

请参阅这里如何在现有存储上创建完全排序索引(CSI)。

通过数据列查询

您可以指定(并索引)您希望能够执行查询的特定列(除了可始终查询的 indexable 列之外)。例如,假设您想要执行此常见操作,在磁盘上,并仅返回与此查询匹配的框架。您可以指定 data_columns = True 来强制所有列都成为 data_columns

In [545]: df_dc = df.copy()

In [546]: df_dc["string"] = "foo"

In [547]: df_dc.loc[df_dc.index[4:6], "string"] = np.nan

In [548]: df_dc.loc[df_dc.index[7:9], "string"] = "bar"

In [549]: df_dc["string2"] = "cool"

In [550]: df_dc.loc[df_dc.index[1:3], ["B", "C"]] = 1.0

In [551]: df_dc
Out[551]: 
 A         B         C string string2
2000-01-01  0.858644 -0.851236  1.058006    foo    cool
2000-01-02 -0.080372  1.000000  1.000000    foo    cool
2000-01-03  0.816983  1.000000  1.000000    foo    cool
2000-01-04  0.712795 -0.062433  0.736755    foo    cool
2000-01-05 -0.298721 -1.988045  1.475308    NaN    cool
2000-01-06  1.103675  1.382242 -0.650762    NaN    cool
2000-01-07 -0.729161 -0.142928 -1.063038    foo    cool
2000-01-08 -1.005977  0.465222 -0.094517    bar    cool

# on-disk operations
In [552]: store.append("df_dc", df_dc, data_columns=["B", "C", "string", "string2"])

In [553]: store.select("df_dc", where="B > 0")
Out[553]: 
 A         B         C string string2
2000-01-02 -0.080372  1.000000  1.000000    foo    cool
2000-01-03  0.816983  1.000000  1.000000    foo    cool
2000-01-06  1.103675  1.382242 -0.650762    NaN    cool
2000-01-08 -1.005977  0.465222 -0.094517    bar    cool

# getting creative
In [554]: store.select("df_dc", "B > 0 & C > 0 & string == foo")
Out[554]: 
 A    B    C string string2
2000-01-02 -0.080372  1.0  1.0    foo    cool
2000-01-03  0.816983  1.0  1.0    foo    cool

# this is in-memory version of this type of selection
In [555]: df_dc[(df_dc.B > 0) & (df_dc.C > 0) & (df_dc.string == "foo")]
Out[555]: 
 A    B    C string string2
2000-01-02 -0.080372  1.0  1.0    foo    cool
2000-01-03  0.816983  1.0  1.0    foo    cool

# we have automagically created this index and the B/C/string/string2
# columns are stored separately as ``PyTables`` columns
In [556]: store.root.df_dc.table
Out[556]: 
/df_dc/table (Table(8,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
 "B": Float64Col(shape=(), dflt=0.0, pos=2),
 "C": Float64Col(shape=(), dflt=0.0, pos=3),
 "string": StringCol(itemsize=3, shape=(), dflt=b'', pos=4),
 "string2": StringCol(itemsize=4, shape=(), dflt=b'', pos=5)}
 byteorder := 'little'
 chunkshape := (1680,)
 autoindex := True
 colindexes := {
 "index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "B": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "C": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "string": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "string2": Index(6, mediumshuffle, zlib(1)).is_csi=False} 

将许多列转换为 data columns 会导致一些性能下降,因此用户需要指定这些列。此外,在第一次附加/放置操作之后,您不能更改数据列(也不能更改索引列)(当然,您可以简单地读取数据并创建新表!)。

迭代器

您可以将 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] 
```  ##### 使用 where 掩码进行选择

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

```py
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 拆分成多个表。如果在列表的位置使用None,那么该表将具有给定 DataFrame 的其余未指定的列。参数selector定义了哪个表是选择器表(你可以从中进行查询)。参数dropna将从输入的DataFrame中删除行,以确保表同步。这意味着如果要写入的表中的一行完全由np.nan组成,那么该行将从所有表中删除。

如果dropna为 False,用户需要负责同步表格。请记住,完全由np.Nan行组成的行不会被写入 HDFStore,因此如果选择调用dropna=False,某些表可能比其他表有更多的行,因此select_as_multiple可能无法工作,或者可能返回意外结果。

In [578]: df_mt = pd.DataFrame(
 .....:    np.random.randn(8, 6),
 .....:    index=pd.date_range("1/1/2000", periods=8),
 .....:    columns=["A", "B", "C", "D", "E", "F"],
 .....: )
 .....: 

In [579]: df_mt["foo"] = "bar"

In [580]: df_mt.loc[df_mt.index[1], ("A", "B")] = np.nan

# you can also create the tables individually
In [581]: store.append_to_multiple(
 .....:    {"df1_mt": ["A", "B"], "df2_mt": None}, df_mt, selector="df1_mt"
 .....: )
 .....: 

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

# individual tables were created
In [583]: store.select("df1_mt")
Out[583]: 
 A         B
2000-01-01  0.162291 -0.430489
2000-01-02       NaN       NaN
2000-01-03  0.429207 -1.099274
2000-01-04  1.869081 -1.466039
2000-01-05  0.092130 -1.726280
2000-01-06  0.266901 -0.036854
2000-01-07 -0.517871 -0.990317
2000-01-08 -0.231342  0.557402

In [584]: store.select("df2_mt")
Out[584]: 
 C         D         E         F  foo
2000-01-01 -2.502042  0.668149  0.460708  1.834518  bar
2000-01-02  0.130441 -0.608465  0.439872  0.506364  bar
2000-01-03 -1.069546  1.236277  0.116634 -1.772519  bar
2000-01-04  0.137462  0.313939  0.748471 -0.943009  bar
2000-01-05  0.836517  2.049798  0.562167  0.189952  bar
2000-01-06  1.112750 -0.151596  1.503311  0.939470  bar
2000-01-07 -0.294348  0.335844 -0.794159  1.495614  bar
2000-01-08  0.860312 -0.538674 -0.541986 -1.759606  bar

# as a multiple
In [585]: store.select_as_multiple(
 .....:    ["df1_mt", "df2_mt"],
 .....:    where=["A>0", "B>0"],
 .....:    selector="df1_mt",
 .....: )
 .....: 
Out[585]: 
Empty DataFrame
Columns: [A, B, C, D, E, F, foo]
Index: [] 

从表中删除

您可以通过指定where有选择性地从表中删除。在删除行时,重要的是要了解PyTables通过擦除行然后移动后续数据来删除行。因此,删除操作可能是一个非常昂贵的操作,具体取决于数据的方向。为了获得最佳性能,最好让您要删除的维度成为indexables的第一个维度。

数据按照indexables的顺序(在磁盘上)进行排序。这里有一个简单的用例。你存储面板类型的数据,日期在major_axis中,id 在minor_axis中。然后数据被交错存储如下:

  • date_1

    • id_1

    • id_2

    • .

    • id_n

  • date_2

    • id_1

    • .

    • id_n

应该清楚,对major_axis进行删除操作会相当快,因为一个块被移除,然后后续数据被移动。另一方面,对minor_axis进行删除操作将非常昂贵。在这种情况下,重新编写使用where选择除缺失数据外的所有数据的表几乎肯定会更快。

警告

请注意,HDF5 不会自动回收 h5 文件中的空间。因此,反复删除(或移除节点)然后再添加,会增加文件大小

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

注意事项 & 警告

压缩

PyTables允许对存储的数据进行压缩。这适用于���有类型的存储,不仅仅是表格。用于控制压缩的两个参数是complevelcomplib

  • complevel指定数据压缩的难度。complevel=0complevel=None禁用压缩,0<complevel<10启用压缩。

  • complib指定要使用的压缩库。如果未指定任何内容,则使用默认库zlib。压缩库通常会针对良好的压缩率或速度进行优化,结果将取决于数据类型。选择哪种类型的压缩取决于您的具体需求和数据。支持的压缩库列表:

    • zlib:默认的压缩库。在压缩方面经典,能够获得很高的压缩率,但速度有些慢。

    • lzo:快速的压缩和解压。

    • bzip2:压缩率很高。

    • blosc:快速的压缩和解压。

      支持替代的 blosc 压缩器:

      • blosc:blosclz 这是blosc的默认压缩器

      • blosc:lz4:紧凑、非常流行且快速的压缩器。

      • blosc:lz4hc:LZ4 的改进版本,在牺牲速度的情况下产生更好的压缩比。

      • blosc:snappy:在许多地方使用的流行压缩器。

      • blosc:zlib:经典;比前几个稍慢,但实现更好的压缩比。

      • blosc:zstd:一个极其平衡的编解码器;它在以上其他编解码器中提供最佳的���缩比,并且速度相当快。

    如果complib被定义为除列出的库之外的内容,则会引发ValueError异常。

注意

如果在您的平台上缺少complib选项指定的库,则压缩默认为zlib,无需进一步操作。

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

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

或在未启用压缩的存储中进行即时压缩(仅适用于表):

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

ptrepack

当表在写入后进行压缩时,PyTables提供更好的写入性能,而不是在一开始就打开压缩。您可以使用提供的PyTables实用程序ptrepack。此外,ptrepack可以在事后更改压缩级别。

ptrepack --chunkshape=auto --propindexes --complevel=9 --complib=blosc in.h5 out.h5 

此外,ptrepack in.h5 out.h5重新打包文件,以便您可以重用先前删除的空间。或者,可以简单地删除文件并重新写入,或者使用copy方法。 #### 注意事项

警告

HDFStore对于写入不是线程安全的。底层的PyTables仅支持并发读取(通过线程或进程)。如果您需要同时进行读取和写入,您需要在单个线程中的单个进程中串行化这些操作。否则,您的数据将被破坏。有关更多信息,请参见(GH 2397)。

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

  • 一旦创建了table,列(DataFrame)就是固定的;只能追加完全相同的列

  • 请注意时区(例如,pytz.timezone('US/Eastern'))在不同时区版本之间不一定相等。因此,如果使用一个版本的时区库将数据本地化到 HDFStore 中的特定时区,并且使用另一个版本更新数据,则数据将被转换为 UTC,因为这些时区不被视为相等。要么使用相同版本的时区库,要么使用带有更新时区定义的tz_convert

警告

如果列名不能用作属性选择器,则PyTables将显示NaturalNameWarning自然标识符仅包含字母、数字和下划线,并且不能以数字开头。其他标识符不能在where子句中使用,通常是一个坏主意。 ### 数据类型

HDFStore将对象 dtype 映射到PyTables底层 dtype。这意味着以下类型已知可用:

类型 表示缺失值
floating : float64, float32, float16 np.nan
integer : int64, int32, int8, uint64,uint32, uint8
布尔值
datetime64[ns] NaT
timedelta64[ns] NaT
分类:请参见下面的部分
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作为一个键传递,以允许所有可索引data_columns具有此最小长度。

传递min_itemsize字典将导致所有传递的列自动创建为data_columns

注意

如果没有传递任何data_columns,那么min_itemsize将是传递的任何字符串的长度的最大值

In [594]: dfs = pd.DataFrame({"A": "foo", "B": "bar"}, index=list(range(5)))

In [595]: dfs
Out[595]: 
 A    B
0  foo  bar
1  foo  bar
2  foo  bar
3  foo  bar
4  foo  bar

# A and B have a size of 30
In [596]: store.append("dfs", dfs, min_itemsize=30)

In [597]: store.get_storer("dfs").table
Out[597]: 
/dfs/table (Table(5,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": StringCol(itemsize=30, shape=(2,), dflt=b'', pos=1)}
 byteorder := 'little'
 chunkshape := (963,)
 autoindex := True
 colindexes := {
 "index": Index(6, mediumshuffle, zlib(1)).is_csi=False}

# A is created as a data_column with a size of 30
# B is size is calculated
In [598]: store.append("dfs2", dfs, min_itemsize={"A": 30})

In [599]: store.get_storer("dfs2").table
Out[599]: 
/dfs2/table (Table(5,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": StringCol(itemsize=3, shape=(1,), dflt=b'', pos=1),
 "A": StringCol(itemsize=30, shape=(), dflt=b'', pos=2)}
 byteorder := 'little'
 chunkshape := (1598,)
 autoindex := True
 colindexes := {
 "index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "A": Index(6, mediumshuffle, zlib(1)).is_csi=False} 

nan_rep

字符串列将使用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 

性能

  • fixed存储相比,tables格式会带来写入性能的损失。好处在于能够追加/删除和查询(可能是非常大量的数据)。与常规存储相比,写入时间通常更长。查询时间可能非常快,特别是在索引轴上。

  • 您可以通过在append中传递chunksize=<int>来指定写入的块大小(默认为 50000)。这将显著降低写入时的内存使用。

  • 您可以通过在第一次append中传递expectedrows=<int>来设置PyTables预期的总行数。这将优化读/写性能。

  • 可以将重复行写入表中,但在选择时会被过滤掉(选择最后的项目;因此表在主要、次要对上是唯一的)

  • 如果您尝试存储将由 PyTables 进行 pickle 处理的类型(而不是作为固有类型存储),将会引发PerformanceWarning。有关更多信息和一些解决方案,请参见这里。 ## Feather

Feather 为数据框提供了二进制列序列化。它旨在使数据框的读写高效,并使数据在数据分析语言之间的共享变得容易。

Feather 旨在忠实地序列化和反序列化 DataFrames,支持所有 pandas 的数据类型,包括分类和带有时区的日期时间等扩展数据类型。

几个注意事项:

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

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

  • 不支持对象数据类型列中的实际 Python 对象。在尝试序列化时,这些将引发一个有用的错误消息。

查看完整文档

In [606]: df = pd.DataFrame(
 .....:    {
 .....:        "a": list("abc"),
 .....:        "b": list(range(1, 4)),
 .....:        "c": np.arange(3, 6).astype("u1"),
 .....:        "d": np.arange(4.0, 7.0, dtype="float64"),
 .....:        "e": [True, False, True],
 .....:        "f": pd.Categorical(list("abc")),
 .....:        "g": pd.date_range("20130101", periods=3),
 .....:        "h": pd.date_range("20130101", periods=3, tz="US/Eastern"),
 .....:        "i": pd.date_range("20130101", periods=3, freq="ns"),
 .....:    }
 .....: )
 .....: 

In [607]: df
Out[607]: 
 a  b  c  ...          g                         h                             i
0  a  1  3  ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1  b  2  4  ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2  c  3  5  ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002

[3 rows x 9 columns]

In [608]: df.dtypes
Out[608]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                      category
g                datetime64[ns]
h    datetime64[ns, US/Eastern]
i                datetime64[ns]
dtype: object 

写入一个 feather 文件。

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

从一个 feather 文件中读取。

In [610]: result = pd.read_feather("example.feather")

In [611]: result
Out[611]: 
 a  b  c  ...          g                         h                             i
0  a  1  3  ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1  b  2  4  ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2  c  3  5  ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002

[3 rows x 9 columns]

# we preserve dtypes
In [612]: result.dtypes
Out[612]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                      category
g                datetime64[ns]
h    datetime64[ns, US/Eastern]
i                datetime64[ns]
dtype: object 
```  ## Parquet

[Apache Parquet](https://parquet.apache.org/) 为数据框提供了分区的二进制列序列化。它旨在使数据框的读写高效,并使数据在数据分析语言之间的共享变得容易。Parquet 可以使用各种压缩技术来尽可能地缩小文件大小,同时保持良好的读取性能。

Parquet 旨在忠实地序列化和反序列化 `DataFrame`,支持所有 pandas 的数据类型,包括带有时区的日期时间等扩展数据类型。

几个注意事项。

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

+   `pyarrow` 引擎始终将索引写入输出,但 `fastparquet` 仅写入非默认索引。这个额外的列可能会给那些不希望看到它的非 pandas 消费者带来问题。您可以使用 `index` 参数强制包含或省略索引,而不管底层引擎如何。

+   如果指定了索引级别名称,则必须是字符串。

+   在 `pyarrow` 引擎中,非字符串类型的分类数据类型可以序列化为 parquet,但会反序列化为其原始数据类型。

+   `pyarrow` 引擎保留了具有字符串类型的分类数据类型的 `ordered` 标志。`fastparquet` 不保留 `ordered` 标志。

+   不支持的类型包括 `Interval` 和实际的 Python 对象类型。在尝试序列化时,这些将引发一个有用的错误消息。`Period` 类型在 pyarrow >= 0.16.0 中受支持。

+   `pyarrow` 引擎保留扩展数据类型,如可空整数和字符串数据类型(需要 pyarrow >= 0.16.0,并要求扩展类型实现所需的协议,请参阅扩展类型文档)。

您可以指定一个`engine`来指导序列化。这可以是`pyarrow`、`fastparquet`或`auto`中的一个。如果未指定引擎,则会检查`pd.options.io.parquet.engine`选项;如果这也是`auto`,则尝试`pyarrow`,并回退到`fastparquet`。

参阅[pyarrow](https://arrow.apache.org/docs/python/)和[fastparquet](https://fastparquet.readthedocs.io/en/latest/)的文档。

注意

这些引擎非常相似,几乎可以读/写完全相同的 Parquet 格式文件。`pyarrow>=8.0.0`支持时间间隔数据,`fastparquet>=0.1.4`支持时区感知日期时间。这些库之间的区别在于具有不同的底层依赖关系(`fastparquet`使用`numba`,而`pyarrow`使用 C 库)。

```py
In [613]: df = pd.DataFrame(
 .....:    {
 .....:        "a": list("abc"),
 .....:        "b": list(range(1, 4)),
 .....:        "c": np.arange(3, 6).astype("u1"),
 .....:        "d": np.arange(4.0, 7.0, dtype="float64"),
 .....:        "e": [True, False, True],
 .....:        "f": pd.date_range("20130101", periods=3),
 .....:        "g": pd.date_range("20130101", periods=3, tz="US/Eastern"),
 .....:        "h": pd.Categorical(list("abc")),
 .....:        "i": pd.Categorical(list("abc"), ordered=True),
 .....:    }
 .....: )
 .....: 

In [614]: df
Out[614]: 
 a  b  c    d      e          f                         g  h  i
0  a  1  3  4.0   True 2013-01-01 2013-01-01 00:00:00-05:00  a  a
1  b  2  4  5.0  False 2013-01-02 2013-01-02 00:00:00-05:00  b  b
2  c  3  5  6.0   True 2013-01-03 2013-01-03 00:00:00-05:00  c  c

In [615]: df.dtypes
Out[615]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                datetime64[ns]
g    datetime64[ns, US/Eastern]
h                      category
i                      category
dtype: object 

写入 Parquet 文件。

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

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

从 Parquet 文件中读取。

In [618]: result = pd.read_parquet("example_fp.parquet", engine="fastparquet")

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

In [620]: result.dtypes
Out[620]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                datetime64[ns]
g    datetime64[ns, US/Eastern]
h                      category
i                      category
dtype: object 

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

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

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

注意

请注意,这对于fastparquet不受支持。

仅读取 Parquet 文件的特定列。

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

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

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

处理索引

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

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

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

如果您使用pyarrow进行序列化,将创建一个包含列的 Parquet 文件:ab__index_level_0__。如果您使用fastparquet,索引可能会或可能不会写入文件。

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

如果你想在写入时省略数据框的索引,请在to_parquet()中传递index=False

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

当你将这个文件加载到DataFrame中时,这将创建一个只包含两个预期列ab的 Parquet 文件。如果你的DataFrame有自定义索引,当你加载这个文件时将不会得到它。

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

对 Parquet 文件进行分区

Parquet 支持根据一个或多个列的值对数据进行分区。

In [629]: df = pd.DataFrame({"a": [0, 0, 1, 1], "b": [0, 1, 0, 1]})

In [630]: df.to_parquet(path="test", engine="pyarrow", partition_cols=["a"], compression=None) 

path指定将数据保存到的父目录。partition_cols是数据集将根据其进行分区的列名。列按给定顺序进行分区。分区拆分由分区列中的唯一值确定。上面的示例创建了一个可能如下所示的分区数据集:

test
├── a=0
│   ├── 0bac803e32dc42ae83fddfd029cbdebc.parquet
│   └──  ...
└── a=1
    ├── e6ab24a4f45147b49b54a662f0c412a3.parquet
    └── ... 
```  ## ORC

与 parquet 格式类似,[ORC 格式](https://orc.apache.org/)是用于数据框的二进制列序列化。它旨在使数据框的读取效率更高。pandas 为 ORC 格式提供了读取器和写入器,`read_orc()`和`to_orc()`。这需要[pyarrow](https://arrow.apache.org/docs/python/)库。

警告

+   建议使用 conda 安装 pyarrow,因为 pyarrow 存在一些问题。

+   `to_orc()`需要 pyarrow>=7.0.0。

+   `read_orc()`和`to_orc()`目前尚不支持 Windows,您可以在安装可选依赖项中找到有效的环境。

+   有关支持的数据类型,请参考[Arrow 中支持的 ORC 功能](https://arrow.apache.org/docs/cpp/orc.html#data-types)。

+   目前,将数据框转换为 ORC 文件时,日期时间列中的时区信息不会被保留。

```py
In [631]: df = pd.DataFrame(
 .....:    {
 .....:        "a": list("abc"),
 .....:        "b": list(range(1, 4)),
 .....:        "c": np.arange(4.0, 7.0, dtype="float64"),
 .....:        "d": [True, False, True],
 .....:        "e": pd.date_range("20130101", periods=3),
 .....:    }
 .....: )
 .....: 

In [632]: df
Out[632]: 
 a  b    c      d          e
0  a  1  4.0   True 2013-01-01
1  b  2  5.0  False 2013-01-02
2  c  3  6.0   True 2013-01-03

In [633]: df.dtypes
Out[633]: 
a            object
b             int64
c           float64
d              bool
e    datetime64[ns]
dtype: object 

写入 orc 文件。

In [634]: df.to_orc("example_pa.orc", engine="pyarrow") 

从 orc 文件中读取。

In [635]: result = pd.read_orc("example_pa.orc")

In [636]: result.dtypes
Out[636]: 
a            object
b             int64
c           float64
d              bool
e    datetime64[ns]
dtype: object 

仅读取 orc 文件的某些列。

In [637]: result = pd.read_orc(
 .....:    "example_pa.orc",
 .....:    columns=["a", "b"],
 .....: )
 .....: 

In [638]: result.dtypes
Out[638]: 
a    object
b     int64
dtype: object 
```  ## SQL 查询

`pandas.io.sql`模块提供了一组查询包装器,旨在促进数据检索并减少对特定于数据库的 API 的依赖。

如有可能,用户可能首选选择[Apache Arrow ADBC](https://arrow.apache.org/adbc/current/index.html)驱动程序。这些驱动程序应提供最佳性能、空值处理和类型检测。

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

有关 ADBC 驱动程序及其开发状态的完整列表,请参阅[ADBC 驱动程序实现状态](https://arrow.apache.org/adbc/current/driver/status.html)文档。

如果没有 ADBC 驱动程序或缺少功能,则用户应选择安装 SQLAlchemy 以及其数据库驱动程序库。这些驱动程序的示例是[psycopg2](https://www.psycopg.org/)用于 PostgreSQL 或[pymysql](https://github.com/PyMySQL/PyMySQL)用于 MySQL。对于[SQLite](https://docs.python.org/3/library/sqlite3.html),这在 Python 的标准库中默认包含。您可以在[SQLAlchemy 文档](https://docs.sqlalchemy.org/en/latest/dialects/index.html)中找到每个 SQL 方言支持的驱动程序的概述。

如果未安装 SQLAlchemy,可以使用[`sqlite3.Connection`](https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection "(在 Python v3.12 中)")代替 SQLAlchemy 引擎、连接或 URI 字符串。

还可以查看一些烹饪书示例以获取一些高级策略。

关键函数包括:

| `read_sql_table`(table_name, con[, schema, ...]) | 将 SQL 数据库表读取到数据框中。 |
| --- | --- |
| `read_sql_query`(sql, con[, index_col, ...]) | 将 SQL 查询读取到数据框中。 |
| `read_sql`(sql, con[, index_col, ...]) | 将 SQL 查询或数据库表读取到数据框中。 |
| `DataFrame.to_sql`(name, con, *[, schema, ...]) | 将存储在数据框中的记录写入 SQL 数据库。 |

注意

函数`read_sql()`是对`read_sql_table()`和`read_sql_query()`(以及向后兼容性)的便捷包装,并根据提供的输入(数据库表名或 SQL 查询)委托给特定函数。如果表名包含特殊字符,则不需要对表名加引号。

在以下示例中,我们使用 [SQlite](https://www.sqlite.org/index.html) SQL 数据库引擎。您可以使用一个临时 SQLite 数据库,其中数据存储在“内存”中。

要使用 ADBC 驱动程序进行连接,您需要使用包管理器安装`adbc_driver_sqlite`。安装后,您可以使用 ADBC 驱动程序提供的 DBAPI 接口连接到数据库。

```py
import adbc_driver_sqlite.dbapi as sqlite_dbapi

# Create the connection
with sqlite_dbapi.connect("sqlite:///:memory:") as conn:
     df = pd.read_sql_table("data", conn) 

要使用 SQLAlchemy 进行连接,您可以使用create_engine()函数从数据库 URI 创建一个引擎对象。您只需要为每个要连接的数据库创建一次引擎。有关create_engine()和 URI 格式化的更多信息,请参见下面的示例和 SQLAlchemy 文档

In [639]: from sqlalchemy import create_engine

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

如果您想管理自己的连接,可以传递其中一个。下面的示例使用 Python 上下文管理器打开与数据库的连接,在块完成后自动关闭连接。请参阅SQLAlchemy 文档了解数据库连接是如何处理的。

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

警告

当你打开与数据库的连接时,你也有责任关闭它。保持连接打开的副作用可能包括锁定数据库或其他破坏性行为。

写入数据框

假设以下数据存储在一个DataFrame data中,我们可以使用to_sql()将其插入到数据库中。

id 日期 列 _1 列 _2 列 _3
26 2012-10-18 X 25.7 True
42 2012-10-19 Y -12.4 False
63 2012-10-20 Z 5.73 True
In [641]: import datetime

In [642]: c = ["id", "Date", "Col_1", "Col_2", "Col_3"]

In [643]: d = [
 .....:    (26, datetime.datetime(2010, 10, 18), "X", 27.5, True),
 .....:    (42, datetime.datetime(2010, 10, 19), "Y", -12.5, False),
 .....:    (63, datetime.datetime(2010, 10, 20), "Z", 5.73, True),
 .....: ]
 .....: 

In [644]: data = pd.DataFrame(d, columns=c)

In [645]: data
Out[645]: 
 id       Date Col_1  Col_2  Col_3
0  26 2010-10-18     X  27.50   True
1  42 2010-10-19     Y -12.50  False
2  63 2010-10-20     Z   5.73   True

In [646]: data.to_sql("data", con=engine)
Out[646]: 3 

在某些数据库中,写入大型 DataFrame 可能会因超出数据包大小限制而导致错误。可以通过在调用 to_sql 时设置 chunksize 参数来避免这种情况。例如,以下代码将以每次 1000 行的批量方式将 data 写入数据库:

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

SQL 数据类型

确保跨 SQL 数据库的一致数据类型管理具有挑战性。并非每个 SQL 数据库都提供相同的类型,即使提供了,给定类型的实现方式也可能有微妙的差异,对类型的保留方式可能产生细微影响。

为了最大限度地保留数据库类型,建议用户在可用时使用 ADBC 驱动程序。Arrow 类型系统提供了更广泛的类型数组,与历史上的 pandas/NumPy 类型系统更接近匹配数据库类型。举例来说,注意一下不同数据库和 pandas 后端中可用的类型(非穷尽列表):

numpy/pandas arrow postgres sqlite
int16/Int16 int16 SMALLINT INTEGER
int32/Int32 int32 INTEGER INTEGER
int64/Int64 int64 BIGINT INTEGER
float32 float32 REAL REAL
float64 float64 DOUBLE PRECISION REAL
object string TEXT TEXT
bool bool_ BOOLEAN
datetime64[ns] timestamp(us) TIMESTAMP
datetime64[ns,tz] timestamp(us,tz) TIMESTAMPTZ
date32 DATE
month_day_nano_interval INTERVAL
binary BINARY BLOB
decimal128 DECIMAL [1]
list ARRAY [1]
struct

复合类型

[1]

脚注

如果您希望在 DataFrame 的整个生命周期中尽可能保留数据库类型,建议用户利用 read_sql()dtype_backend="pyarrow" 参数。

# for roundtripping
with pg_dbapi.connect(uri) as conn:
    df2 = pd.read_sql("pandas_table", conn, dtype_backend="pyarrow") 

这将防止您的数据被转换为传统的 pandas/NumPy 类型系统,后者经常以使 SQL 类型无法往返的方式进行转换。

如果没有可用的 ADBC 驱动程序,to_sql() 将尝试根据数据的 dtype 将数据映射到适当的 SQL 数据类型。当您有 dtype 为 object 的列时,pandas 将尝试推断数据类型。

您可以通过使用 dtype 参数指定任何列的所需 SQL 类型来始终覆盖默认类型。该参数需要一个将列名映射到 SQLAlchemy 类型(或字符串以用于 sqlite3 回退模式)的字典。例如,指定为字符串列使用 sqlalchemy 的 String 类型而不是默认的 Text 类型:

In [648]: from sqlalchemy.types import String

In [649]: data.to_sql("data_dtype", con=engine, dtype={"Col_1": String})
Out[649]: 3 

注意

由于不同数据库版本对 timedelta 的支持有限,类型为timedelta64的列将被写入为纳秒整数值到数据库中,并会引发警告。唯一的例外是在使用 ADBC PostgreSQL 驱动程序时,此时 timedelta 将被写入数据库作为INTERVAL

注意

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

日期时间数据类型

使用 ADBC 或 SQLAlchemy,to_sql() 能够写入时区无关或时区感知的日期时间数据。然而,最终存储在数据库中的数据取决于所使用的数据库系统支持的日期时间数据类型。

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

数据库 SQL 日期时间类型 时区支持
SQLite TEXT
MySQL TIMESTAMPDATETIME
PostgreSQL TIMESTAMPTIMESTAMP WITH TIME ZONE

当将带有时区信息的数据写入不支持时区的数据库时,数据将被写入为相对于时区的本地时间的时区无关时间戳。

read_sql_table() 也能够读取时区感知或时区无关的日期时间数据。当读取TIMESTAMP WITH TIME ZONE类型时,pandas 将数据转换为 UTC 时间。

插入方法

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

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

  • 'multi':在单个INSERT子句中传递多个值。它使用一种特殊的 SQL 语法,不是所有后端都支持。这通常对于像PrestoRedshift这样的分析数据库提供更好的性能,但如果表包含许多列,则传统 SQL 后端的性能会更差。有关更多信息,请查看 SQLAlchemy 的文档

  • 具有签名(pd_table, conn, keys, data_iter)的可调用函数:这可用于基于特定后端方言功能实现更高性能的插入方法。

使用 PostgreSQL 的示例可调用复制子句

# Alternative to_sql() *method* for DBs that support COPY FROM
import csv
from io import StringIO

def psql_insert_copy(table, conn, keys, data_iter):
  """
 Execute SQL statement inserting data

 Parameters
 ----------
 table : pandas.io.sql.SQLTable
 conn : sqlalchemy.engine.Engine or sqlalchemy.engine.Connection
 keys : list of str
 Column names
 data_iter : Iterable that iterates the values to be inserted
 """
    # gets a DBAPI connection that can provide a cursor
    dbapi_conn = conn.connection
    with dbapi_conn.cursor() as cur:
        s_buf = StringIO()
        writer = csv.writer(s_buf)
        writer.writerows(data_iter)
        s_buf.seek(0)

        columns = ', '.join(['"{}"'.format(k) for k in keys])
        if table.schema:
            table_name = '{}.{}'.format(table.schema, table.name)
        else:
            table_name = table.name

        sql = 'COPY {} ({}) FROM STDIN WITH CSV'.format(
            table_name, columns)
        cur.copy_expert(sql=sql, file=s_buf) 

读取表

read_sql_table() 将读取给定表名的数据库表,可选择性地读取一部分列。

注意

为了使用read_sql_table(),你必须安装 ADBC 驱动程序或 SQLAlchemy 可选依赖项。

In [650]: pd.read_sql_table("data", engine)
Out[650]: 
 index  id       Date Col_1  Col_2  Col_3
0      0  26 2010-10-18     X  27.50   True
1      1  42 2010-10-19     Y -12.50  False
2      2  63 2010-10-20     Z   5.73   True 

注意

ADBC 驱动程序将数据库类型直接映射回 arrow 类型。对于其他驱动程序,请注意 pandas 从查询输出中推断列 dtype,而不是通过查找物理数据库模式中的数据类型。例如,假设userid是表中的整数列。那么,直观地,select userid ...将返回整数值系列,而select cast(userid as text) ...将返回对象值(str)系列。因此,如果查询输出为空,则所有生成的列将作为对象值返回(因为它们是最一般的)。如果你预见到你的查询有时会生成��结果,你可能希望在之后明确进行类型转换以确保 dtype 的完整性。

你还可以指定列的名称作为DataFrame索引,并指定要读取的列的子集。

In [651]: pd.read_sql_table("data", engine, index_col="id")
Out[651]: 
 index       Date Col_1  Col_2  Col_3
id 
26      0 2010-10-18     X  27.50   True
42      1 2010-10-19     Y -12.50  False
63      2 2010-10-20     Z   5.73   True

In [652]: pd.read_sql_table("data", engine, columns=["Col_1", "Col_2"])
Out[652]: 
 Col_1  Col_2
0     X  27.50
1     Y -12.50
2     Z   5.73 

你还可以明确强制将列解析为日期:

In [653]: pd.read_sql_table("data", engine, parse_dates=["Date"])
Out[653]: 
 index  id       Date Col_1  Col_2  Col_3
0      0  26 2010-10-18     X  27.50   True
1      1  42 2010-10-19     Y -12.50  False
2      2  63 2010-10-20     Z   5.73   True 

如果需要,你可以明确指定格式字符串,或传递给pandas.to_datetime()的参数字典:

pd.read_sql_table("data", engine, parse_dates={"Date": "%Y-%m-%d"})
pd.read_sql_table(
    "data",
    engine,
    parse_dates={"Date": {"format": "%Y-%m-%d %H:%M:%S"}},
) 

使用has_table()可以检查表是否存在

模式支持

通过read_sql_table()to_sql()函数中的schema关键字支持从不同模式读取和写入。但请注意,这取决于数据库类型(sqlite 没有模式)。例如:

df.to_sql(name="table", con=engine, schema="other_schema")
pd.read_sql_table("table", engine, schema="other_schema") 

查询

你可以在read_sql_query()函数中使用原始 SQL 进行查询。在这种情况下,你必须使用适合你的数据库的 SQL 变体。当使用 SQLAlchemy 时,你还可以传递数据库无关的 SQLAlchemy 表达式语言构造。

In [654]: pd.read_sql_query("SELECT * FROM data", engine)
Out[654]: 
 index  id                        Date Col_1  Col_2  Col_3
0      0  26  2010-10-18 00:00:00.000000     X  27.50      1
1      1  42  2010-10-19 00:00:00.000000     Y -12.50      0
2      2  63  2010-10-20 00:00:00.000000     Z   5.73      1 

当然,你可以指定一个更“复杂”的查询。

In [655]: pd.read_sql_query("SELECT id, Col_1, Col_2 FROM data WHERE id = 42;", engine)
Out[655]: 
 id Col_1  Col_2
0  42     Y  -12.5 

read_sql_query()函数支持chunksize参数。指定这个参数将返回查询结果的迭代器:

In [656]: df = pd.DataFrame(np.random.randn(20, 3), columns=list("abc"))

In [657]: df.to_sql(name="data_chunks", con=engine, index=False)
Out[657]: 20 
In [658]: for chunk in pd.read_sql_query("SELECT * FROM data_chunks", engine, chunksize=5):
 .....:    print(chunk)
 .....: 
 a         b         c
0 -0.395347 -0.822726 -0.363777
1  1.676124 -0.908102 -1.391346
2 -1.094269  0.278380  1.205899
3  1.503443  0.932171 -0.709459
4 -0.645944 -1.351389  0.132023
 a         b         c
0  0.210427  0.192202  0.661949
1  1.690629 -1.046044  0.618697
2 -0.013863  1.314289  1.951611
3 -1.485026  0.304662  1.194757
4 -0.446717  0.528496 -0.657575
 a         b         c
0 -0.876654  0.336252  0.172668
1  0.337684 -0.411202 -0.828394
2 -0.244413  1.094948  0.087183
3  1.125934 -1.480095  1.205944
4 -0.451849  0.452214 -2.208192
 a         b         c
0 -2.061019  0.044184 -0.017118
1  1.248959 -0.675595 -1.908296
2 -0.125934  1.491974  0.648726
3  0.391214  0.438609  1.634248
4  1.208707 -1.535740  1.620399 

引擎连接示例

要使用 SQLAlchemy 连接,你可以使用create_engine()函数从数据库 URI 创建一个引擎对象。你只需要为每个要连接的数据库创建一次引擎。

from sqlalchemy import create_engine

engine = create_engine("postgresql://scott:tiger@localhost:5432/mydatabase")

engine = create_engine("mysql+mysqldb://scott:tiger@localhost/foo")

engine = create_engine("oracle://scott:[[email protected]](/cdn-cgi/l/email-protection):1521/sidname")

engine = create_engine("mssql+pyodbc://mydsn")

# sqlite://<nohostname>/<path>
# where <path> is relative:
engine = create_engine("sqlite:///foo.db")

# or absolute, starting with a slash:
engine = create_engine("sqlite:////absolute/path/to/foo.db") 

欲了解更多信息,请参阅 SQLAlchemy 文档中的示例文档

高级 SQLAlchemy 查询

你可以使用 SQLAlchemy 构造描述你的查询。

使用sqlalchemy.text()以与后端无关的方式指定查询参数

In [659]: import sqlalchemy as sa

In [660]: pd.read_sql(
 .....:    sa.text("SELECT * FROM data where Col_1=:col1"), engine, params={"col1": "X"}
 .....: )
 .....: 
Out[660]: 
 index  id                        Date Col_1  Col_2  Col_3
0      0  26  2010-10-18 00:00:00.000000     X   27.5      1 

如果您有数据库的 SQLAlchemy 描述,可以使用 SQLAlchemy 表达式表示 where 条件

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

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

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

您可以将 SQLAlchemy 表达式与传递给 read_sql() 的参数结合使用 sqlalchemy.bindparam()

In [664]: import datetime as dt

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

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

Sqlite 回退

支持使用 sqlite 而不使用 SQLAlchemy。此模式需要一个遵守 Python DB-API 的 Python 数据库适配器。

您可以这样创建连接:

import sqlite3

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

然后发出以下查询:

data.to_sql("data", con)
pd.read_sql_query("SELECT * FROM data", con) 
```  ## Google BigQuery

`pandas-gbq` 包提供了与 Google BigQuery 读写的功能。

pandas 与这个外部包集成。如果安装了 `pandas-gbq`,则可以使用 pandas 方法 `pd.read_gbq` 和 `DataFrame.to_gbq`,这将调用 `pandas-gbq` 中的相应函数。

完整文档可以在[这里](https://pandas-gbq.readthedocs.io/en/latest/)找到。  ## Stata 格式

### 写入到 Stata 格式

方法 `DataFrame.to_stata()` 将 DataFrame 写入 .dta 文件。此文件的格式版本始终为 115(Stata 12)。

```py
In [667]: df = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [668]: df.to_stata("stata.dta") 

Stata 数据文件具有有限的数据类型支持;只能在 .dta 文件中存储包含 244 个或更少字符的字符串,int8, int16, int32, float32float64。此外,Stata 保留某些值来表示缺失数据。导出特定数据类型的非缺失值超出 Stata 允许范围的值将重新定义变量为下一个更大的大小。例如,在 Stata 中,int8 值限制在 -127 和 100 之间,因此值大于 100 的变量将触发转换为 int16。浮点数据类型中的 nan 值存储为基本缺失数据类型(Stata 中的 .)。

注意

无法导出整数数据类型的缺失数据值。

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

警告

int64 转换为 float64 可能会导致精度损失,如果 int64 值大于 2**53。

警告

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

顶层函数 read_stata 将读取一个 dta 文件,并返回一个 DataFrame 或一个 pandas.api.typing.StataReader,可用于逐步读取文件。

In [669]: pd.read_stata("stata.dta")
Out[669]: 
 index         A         B
0      0 -0.165614  0.490482
1      1 -0.637829  0.067091
2      2 -0.242577  1.348038
3      3  0.647699 -0.644937
4      4  0.625771  0.918376
5      5  0.401781 -1.488919
6      6 -0.981845 -0.046882
7      7 -0.306796  0.877025
8      8 -0.336606  0.624747
9      9 -1.582600  0.806340 

指定chunksize会产生一个pandas.api.typing.StataReader实例,可以用来一次从文件中读取chunksize行。StataReader对象可以用作迭代器。

In [670]: with pd.read_stata("stata.dta", chunksize=3) as reader:
 .....:    for df in reader:
 .....:        print(df.shape)
 .....: 
(3, 3)
(3, 3)
(3, 3)
(1, 3) 

要获得更精细的控制,请使用iterator=True并在每次调用read()时指定chunksize

In [671]: with pd.read_stata("stata.dta", iterator=True) as reader:
 .....:    chunk1 = reader.read(5)
 .....:    chunk2 = reader.read(5)
 .....: 

目前index被检索为一列。

参数convert_categoricals指示是否应读取值标签并使用它们创建Categorical变量。值标签也可以通过函数value_labels检索,但在使用之前需要调用read()

参数convert_missing指示是否应保留 Stata 中的缺失值表示。如果为False(默认值),缺失值将表示为np.nan。如果为True,缺失值将使用StataMissingValue对象表示,并且包含缺失值的列将具有object数据类型。

注意

read_stata()StataReader支持.dta 格式 113-115(Stata 10-12)、117(Stata 13)和 118(Stata 14)。

注意

��置preserve_dtypes=False将升级为标准的 pandas 数据类型:所有整数类型为int64,浮点数据为float64。默认情况下,导入时保留 Stata 数据类型。

注意

所有StataReader对象,无论是由read_stata()(使用iterator=Truechunksize)创建还是手动实例化,都必须作为上下文管理器使用(例如with语句)。虽然close()方法可用,但不受支持。它不是公共 API 的一部分,并将在未来的某个时候被删除而没有警告。

分类数据

分类数据可以导出为Stata数据文件,作为带有值标签的数据。导出的数据包括底层类别代码作为整数数据值和类别作为值标签。在导出时,Stata没有明确的等价Categorical,并且关于变量是否有序的信息会丢失。

警告

Stata仅支持字符串值标签,因此在导出数据时会调用str。使用非字符串类别导出Categorical变量会产生警告,并且如果类别的str表示不唯一,则可能导致信息丢失。

类似地,可以使用关键字参数convert_categoricals(默认为True)从Stata数据文件中导入带有值标签的Categorical变量。关键字参数order_categoricals(默认为True)确定导入的Categorical变量是否有序。

注意

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

注意

Stata 支持部分标记的系列。这些系列对一些数据值有值标签,但并非所有数据值都有。导入部分标记的系列将产生一个具有字符串类别的Categorical,对于已标记的值和没有标记的值,将产生数值类别。 ## SAS 格式

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

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

指定chunksize或使用iterator=True以获取读取器对象(XportReaderSAS7BDATReader),以逐步读取文件。读取器对象还具有包含有关文件及其变量的其他信息的属性。

读取一个 SAS7BDAT 文件:

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

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

def do_something(chunk):
    pass

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

可从 SAS 网站获取 xport 文件格式的规范

没有关于 SAS7BDAT 格式的官方文档。 ## SPSS 格式

顶层函数read_spss()可以读取(但不能写入)SPSS SAV(.sav)和 ZSAV(.zsav)格式文件。

SPSS 文件包含列名。默认情况下,整个文件被读取,分类列被转换为pd.Categorical,并返回一个包含所有列的DataFrame

指定usecols参数以获取列的子集。指定convert_categoricals=False以避免将分类列转换为pd.Categorical

读取一个 SPSS 文件:

df = pd.read_spss("spss_data.sav") 

从 SPSS 文件中提取usecols中包含的列的子集,并避免将分类列转换为pd.Categorical

df = pd.read_spss(
    "spss_data.sav",
    usecols=["foo", "bar"],
    convert_categoricals=False,
) 

有关 SAV 和 ZSAV 文件格式的更多信息,请参阅此处。 ## 其他文件格式

pandas 本身仅支持与其表格数据模型清晰映射的有限一组文件格式的 IO。为了将其他文件格式读取和写入 pandas,我们建议使用来自更广泛社区的这些软件包。

netCDF

xarray提供了受到 pandas DataFrame启发的数据结构,用于处理多维数据集,重点放在 netCDF 文件格式上,并且易于与 pandas 之间进行转换。 ## 性能考虑

这是对各种 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 
```  ## CSV 和文本文件

用于读取文本文件(也称为平面文件)的主要函数是`read_csv()`。查看 cookbook 以获取一些高级策略。

### 解析选项

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

#### 基本

filepath_or_buffervarious

要么是文件路径([`str`](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")),[`pathlib.Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path "(在 Python v3.12 中)"),或`py:py._path.local.LocalPath`),URL(包括 http,ftp 和 S3 位置),或具有`read()`方法的任何对象(例如打开的文件或[`StringIO`](https://docs.python.org/3/library/io.html#io.StringIO "(在 Python v3.12 中)"))。

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

要使用的分隔符。如果 sep 为`None`,C 引擎无法自动检测分隔符,但 Python 解析引擎可以,这意味着将使用后者,并通过 Python 的内置 sniffer 工具[`csv.Sniffer`](https://docs.python.org/3/library/csv.html#csv.Sniffer "(in Python v3.12)")自动检测分隔符。此外,长于 1 个字符且不同于`'\s+'`的分隔符将被解释为正则表达式,并且还会强制使用 Python 解析引擎。请注意,正则表达式分隔符容易忽略带引号的数据。正则表达式示例:`'\\r\\t'`。

分隔符字符串,默认为`None`

sep 的替代参数名称。

delim_whitespace 布尔值,默认为 False

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

#### 列和索引位置及名称

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

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

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

names 数组样式,默认为`None`

要使用的列名列表。如果文件不包含表头行,则应明确传递`header=None`。不允许在此列表中存在重复项。

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

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

注意

`index_col=False`可用于强制 pandas*不*将第一列用作索引,例如当您有一个每行末尾都有分隔符的格式不正确的文件时。

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

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

可通过 `usecols` 避免这种情况。这确保列按原样采取,并且尾随数据被忽略。

usecols 类似列表或可调用函数,默认为 `None`。

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

元素顺序被忽略,因此 `usecols=[0, 1]` 与 `[1, 0]` 相同。要从保留元素顺序的 `data` 实例化 DataFrame,请使用 `pd.read_csv(data, usecols=['foo', 'bar'])[['foo', 'bar']]` 以 `['foo', 'bar']` 顺序的列或 `pd.read_csv(data, usecols=['foo', 'bar'])[['bar', 'foo']]` 以 `['bar', 'foo']` 顺序。

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

```py
In [1]: import pandas as pd

In [2]: from io import StringIO

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

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

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

使用此参数可在使用 c 引擎时获得更快的解析时间和更低的内存使用。Python 引擎在决定要删除哪些列之前会先加载数据。

通用解析配置

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

数据或列的数据类型。例如 {'a': np.float64, 'b': np.int32, 'c': 'Int64'} 使用 strobject 与适当的 na_values 设置一起使用以保留并不解释 dtype。如果指定了转换器,则将应用转换器而不是 dtype 转换。

版本 1.5.0 中新增功能:支持 defaultdict。指定一个 defaultdict 作为输入,其中默认值确定未明确列出的列的 dtype。

dtype_backend{“numpy_nullable”, “pyarrow”},默认为 NumPy 支持的 DataFrames。

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

dtype_backends 仍处于实验阶段。

版本 2.0 中新增。

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

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

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

转换器字典,默认为 None

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

true_values 列表,默认为 None

要视为 True 的值。

false_values 列表,默认为 None

要视为 False 的值。

skipinitialspace 布尔值,默认为 False

在分隔符后跳过空格。

skiprows 类型为列表或整数,默认为 None

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

如果可调用,可调用函数将针对行索引进行评估,如果应跳过该行则返回 True,否则返回 False:

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

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

In [8]: pd.read_csv(StringIO(data), skiprows=lambda x: x % 2 != 0)
Out[8]: 
 col1 col2  col3
0    a    b     2 

skipfooterint,默认为0

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

nrowsint,默认为None

要读取的文件行数。用于读取大文件的片段。

low_memoryboolean,默认为True

在块中内部处理文件,从而在解析时降低内存使用,但可能混合类型推断。为确保没有混合类型,要么设置为False,要么使用dtype参数指定类型。请注意,无论如何整个文件都会读入单个DataFrame,使用chunksizeiterator参数以块返回数据。(仅适用于 C 解析器)

memory_mapboolean,默认为 False

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

NA 和缺失数据处理

na_valuesscalar、str、类似列表或字典,默认为None

附加字符串识别为 NA/NaN。如果传递字典,则为每列指定特定的 NA 值。有关默认解释为 NaN 的值列表,请参见 na values const。

keep_default_naboolean,默认为True

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

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

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

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

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

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

na_filterboolean,默认为True

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

verboseboolean,默认为False

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

skip_blank_linesboolean,默认为True

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

日期时间处理

parse_datesboolean 或 int 或名称列表或列表或字典,默认为False

  • 如果为True -> 尝试解析索引。

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

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

  • 如果{'foo': [1, 3]} -> 将列 1、3 解析为日期并调用结果为‘foo’。

注意

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

infer_datetime_format 布尔值,默认为False

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

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

keep_date_col 布尔值,默认为False

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

date_parserfunction,默认为None

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

自版本 2.0.0 起已弃用:改用date_format,或按object读取,然后根据需要应用to_datetime()

date_formatstr 或列->格式的字典,默认为None

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

自版本 2.0.0 起新增。

dayfirst 布尔值,默认为False

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

cache_dates 布尔值,默认为 True

如果为True,则使用一个唯一的转换日期缓存来应用日期时间转换。在解析重复日期字符串时可能会产生显著的加速,特别是带有时区偏移的日期字符串。

迭代

iterator 布尔值,默认为False

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

chunksizeint,默认为None

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

引用、压缩和文件格式

压缩{'infer''gzip''bz2''zip''xz''zstd'Nonedict},默认为'infer'

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

从版本 1.2.0 中更改:以前的版本将‘gzip’的字典条目转发到gzip.open

thousandsstr,默认为None

千位分隔符。

decimalstr,默认为'.'

用作小数点的字符。例如,欧洲数据可以使用,

float_precisionstring,默认为 None

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

lineterminatorstr(长度为 1),默认为None

用于将文件分成行的字符。仅与 C 解析器有效。

quotecharstr(长度为 1)

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

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

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

doublequoteboolean,默认为True

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

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

用于在引用为QUOTE_NONE时转义分隔符的一个字符字符串。

commentstr,默认为None

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

encodingstr,默认为None

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

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

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

错误处理

on_bad_lines(‘error’,‘warn’,‘skip’),默认为‘error’

指定在遇到错误行(字段过多的行)时要执行的操作。允许的值为:

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

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

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

自版本 1.3.0 起新增。

指定列数据类型

您可以指定整个DataFrame或单独的列的数据类型:

In [9]: import numpy as np

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

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

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

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

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

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

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

幸运的是,pandas 提供了多种方法来确保您的列只包含一个dtype。如果您对这些概念不熟悉,您可以在这里了解有关 dtype 的更多信息,并在这里了解有关 pandas 中object转换的更多信息。

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

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

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

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

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

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

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

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

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

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

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

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

注意

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

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

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

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

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

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

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

将导致mixed_df包含某些列的int dtype,而由于读取的数据中存在混合 dtype,其他列包含str。重要的是要注意,整体列将标记为object的 dtype,用于包含混合 dtype 的列。

设置dtype_backend="numpy_nullable"将导致每一列都具有可空的 dtype。

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

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

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

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

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

```py
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是数据中观察到的唯一值。要对 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',生成的 categories 将始终被解析为字符串(object dtype)。如果 categories 是数字,可以使用to_numeric()函数进行转换,或者根据需要使用另一个转换器,如to_datetime()

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

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

In [46]: df.dtypes
Out[46]: 
col1    category
col2    category
col3    category
dtype: object

In [47]: df["col3"]
Out[47]: 
0    1
1    2
2    3
Name: col3, dtype: category
Categories (3, object): ['1', '2', '3']

In [48]: new_categories = pd.to_numeric(df["col3"].cat.categories)

In [49]: df["col3"] = df["col3"].cat.rename_categories(new_categories)

In [50]: df["col3"]
Out[50]: 
0    1
1    2
2    3
Name: col3, dtype: category
Categories (3, int64): [1, 2, 3] 

命名和使用列

处理列名

文件可能有或没有标题行。pandas 假定第一行应该用作列名:

In [51]: data = "a,b,c\n1,2,3\n4,5,6\n7,8,9"

In [52]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9

In [53]: pd.read_csv(StringIO(data))
Out[53]: 
 a  b  c
0  1  2  3
1  4  5  6
2  7  8  9 

通过在header中与names参数一起指定,可以指示要使用的其他名称以及是否丢弃标题行(如果有):

In [54]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9

In [55]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=0)
Out[55]: 
 foo  bar  baz
0    1    2    3
1    4    5    6
2    7    8    9

In [56]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=None)
Out[56]: 
 foo bar baz
0   a   b   c
1   1   2   3
2   4   5   6
3   7   8   9 

如果标题在第一行之外的行中,将行号传递给header。这将跳过前面的行:

In [57]: data = "skip this skip it\na,b,c\n1,2,3\n4,5,6\n7,8,9"

In [58]: pd.read_csv(StringIO(data), header=1)
Out[58]: 
 a  b  c
0  1  2  3
1  4  5  6
2  7  8  9 

注意

默认行为是推断列名:如果没有传递名称,则行为与header=0相同,并且列名从文件的第一行开始推断,如果显式传递列名,则行为与header=None相同。### 解析重复名称

如果文件或标题包含重复名称,pandas 默认会区分它们,以防止覆盖数据:

In [59]: data = "a,b,a\n0,1,2\n3,4,5"

In [60]: pd.read_csv(StringIO(data))
Out[60]: 
 a  b  a.1
0  0  1    2
1  3  4    5 

不再有重复数据,因为重复列‘X’,…,‘X’变为‘X’,‘X.1’,…,‘X.N’。

过滤列(usecols

usecols参数允许您选择文件中的任何列的子集,可以使用列名称、位置编号或可调用函数:

In [61]: data = "a,b,c,d\n1,2,3,foo\n4,5,6,bar\n7,8,9,baz"

In [62]: pd.read_csv(StringIO(data))
Out[62]: 
 a  b  c    d
0  1  2  3  foo
1  4  5  6  bar
2  7  8  9  baz

In [63]: pd.read_csv(StringIO(data), usecols=["b", "d"])
Out[63]: 
 b    d
0  2  foo
1  5  bar
2  8  baz

In [64]: pd.read_csv(StringIO(data), usecols=[0, 2, 3])
Out[64]: 
 a  c    d
0  1  3  foo
1  4  6  bar
2  7  9  baz

In [65]: pd.read_csv(StringIO(data), usecols=lambda x: x.upper() in ["A", "C"])
Out[65]: 
 a  c
0  1  3
1  4  6
2  7  9 

usecols参数也可用于指定在最终结果中不使用哪些列:

In [66]: pd.read_csv(StringIO(data), usecols=lambda x: x not in ["a", "c"])
Out[66]: 
 b    d
0  2  foo
1  5  bar
2  8  baz 

在这种情况下,可调用函数指定我们从输出中排除“a”和“c”列。

注释和空行

忽略行注释和空行

如果指定了comment参数,则完全注释的行将被忽略。默认情况下,完全空白行也将被忽略。

In [67]: data = "\na,b,c\n  \n# commented line\n1,2,3\n\n4,5,6"

In [68]: print(data)

a,b,c

# commented line
1,2,3

4,5,6

In [69]: pd.read_csv(StringIO(data), comment="#")
Out[69]: 
 a  b  c
0  1  2  3
1  4  5  6 

如果skip_blank_lines=False,则read_csv将不会忽略空白行:

In [70]: data = "a,b,c\n\n1,2,3\n\n\n4,5,6"

In [71]: pd.read_csv(StringIO(data), skip_blank_lines=False)
Out[71]: 
 a    b    c
0  NaN  NaN  NaN
1  1.0  2.0  3.0
2  NaN  NaN  NaN
3  NaN  NaN  NaN
4  4.0  5.0  6.0 

警告

忽略行的存在可能会导致涉及行号的歧义;参数header使用行号(忽略注释/空行),而skiprows使用行号(包括注释/空行):

In [72]: data = "#comment\na,b,c\nA,B,C\n1,2,3"

In [73]: pd.read_csv(StringIO(data), comment="#", header=1)
Out[73]: 
 A  B  C
0  1  2  3

In [74]: data = "A,B,C\n#comment\na,b,c\n1,2,3"

In [75]: pd.read_csv(StringIO(data), comment="#", skiprows=2)
Out[75]: 
 a  b  c
0  1  2  3 

如果同时指定了headerskiprowsheader将相对于skiprows的末尾。例如:

In [76]: data = (
 ....:    "# empty\n"
 ....:    "# second empty line\n"
 ....:    "# third emptyline\n"
 ....:    "X,Y,Z\n"
 ....:    "1,2,3\n"
 ....:    "A,B,C\n"
 ....:    "1,2.,4.\n"
 ....:    "5.,NaN,10.0\n"
 ....: )
 ....: 

In [77]: print(data)
# empty
# second empty line
# third emptyline
X,Y,Z
1,2,3
A,B,C
1,2.,4.
5.,NaN,10.0

In [78]: pd.read_csv(StringIO(data), comment="#", skiprows=4, header=1)
Out[78]: 
 A    B     C
0  1.0  2.0   4.0
1  5.0  NaN  10.0 
```  #### 注释

有时文件中可能包含注释或元数据:

```py
In [79]: data = (
 ....:    "ID,level,category\n"
 ....:    "Patient1,123000,x # really unpleasant\n"
 ....:    "Patient2,23000,y # wouldn't take his medicine\n"
 ....:    "Patient3,1234018,z # awesome"
 ....: )
 ....: 

In [80]: with open("tmp.csv", "w") as fh:
 ....:    fh.write(data)
 ....: 

In [81]: print(open("tmp.csv").read())
ID,level,category
Patient1,123000,x # really unpleasant
Patient2,23000,y # wouldn't take his medicine
Patient3,1234018,z # awesome 

默认情况下,解析器会在输出中包含注释:

In [82]: df = pd.read_csv("tmp.csv")

In [83]: df
Out[83]: 
 ID    level                        category
0  Patient1   123000           x # really unpleasant
1  Patient2    23000  y # wouldn't take his medicine
2  Patient3  1234018                     z # awesome 

我们可以使用comment关键字来抑制注释:

In [84]: df = pd.read_csv("tmp.csv", comment="#")

In [85]: df
Out[85]: 
 ID    level category
0  Patient1   123000       x 
1  Patient2    23000       y 
2  Patient3  1234018       z 
```  ### 处理 Unicode 数据

应该使用`encoding`参数来处理编码的 Unicode 数据,这将导致字节字符串在结果中被解码为 Unicode:

```py
In [86]: from io import BytesIO

In [87]: data = b"word,length\n" b"Tr\xc3\xa4umen,7\n" b"Gr\xc3\xbc\xc3\x9fe,5"

In [88]: data = data.decode("utf8").encode("latin-1")

In [89]: df = pd.read_csv(BytesIO(data), encoding="latin-1")

In [90]: df
Out[90]: 
 word  length
0  Träumen       7
1    Grüße       5

In [91]: df["word"][1]
Out[91]: 'Grüße' 

一些将所有字符编码为多字节的格式,如 UTF-16,如果不指定编码,将无法正确解析。 Python 标准编码的完整列表。 ### 索引列和尾随分隔符

如果文件的数据列数比列名多一个,第一列将被用作DataFrame的行名:

In [92]: data = "a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"

In [93]: pd.read_csv(StringIO(data))
Out[93]: 
 a    b     c
4   apple  bat   5.7
8  orange  cow  10.0 
In [94]: data = "index,a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"

In [95]: pd.read_csv(StringIO(data), index_col=0)
Out[95]: 
 a    b     c
index 
4       apple  bat   5.7
8      orange  cow  10.0 

通常,您可以使用index_col选项来实现此行为。

当文件在每个数据行末尾都有分隔符时,解析器会产生一些异常情况,导致解析混乱。为了显式禁用索引列推断并丢弃最后一列,传递index_col=False

In [96]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"

In [97]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,

In [98]: pd.read_csv(StringIO(data))
Out[98]: 
 a    b   c
4   apple  bat NaN
8  orange  cow NaN

In [99]: pd.read_csv(StringIO(data), index_col=False)
Out[99]: 
 a       b    c
0  4   apple  bat
1  8  orange  cow 

如果使用usecols选项解析数据的子集,则index_col规范基于该子集,而不是原始数据。

In [100]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"

In [101]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,

In [102]: pd.read_csv(StringIO(data), usecols=["b", "c"])
Out[102]: 
 b   c
4  bat NaN
8  cow NaN

In [103]: pd.read_csv(StringIO(data), usecols=["b", "c"], index_col=0)
Out[103]: 
 b   c
4  bat NaN
8  cow NaN 
```  ### 日期处理

#### 指定日期列

为了更好地处理日期时间数据,`read_csv()`使用关键字参数`parse_dates`和`date_format`允许用户指定各种列和日期/时间格式将输入文本数据转换为`datetime`对象。

最简单的情况是只需传入`parse_dates=True`:

```py
In [104]: with open("foo.csv", mode="w") as f:
 .....:    f.write("date,A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5")
 .....: 

# Use a column as an index, and parse it as dates.
In [105]: df = pd.read_csv("foo.csv", index_col=0, parse_dates=True)

In [106]: df
Out[106]: 
 A  B  C
date 
2009-01-01  a  1  2
2009-01-02  b  3  4
2009-01-03  c  4  5

# These are Python datetime objects
In [107]: df.index
Out[107]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', name='date', freq=None) 

通常情况下,我们可能希望将日期和时间数据分开存储,或将各种日期字段分开存储。 parse_dates关键字可用于指定要从中解析日期和/或时间的列的组合。

您可以指定一个列列表的列表给parse_dates,生成的日期列将被预置到输出中(以不影响现有列顺序)且新列名将是组件列名的连接:

In [108]: data = (
 .....:    "KORD,19990127, 19:00:00, 18:56:00, 0.8100\n"
 .....:    "KORD,19990127, 20:00:00, 19:56:00, 0.0100\n"
 .....:    "KORD,19990127, 21:00:00, 20:56:00, -0.5900\n"
 .....:    "KORD,19990127, 21:00:00, 21:18:00, -0.9900\n"
 .....:    "KORD,19990127, 22:00:00, 21:56:00, -0.5900\n"
 .....:    "KORD,19990127, 23:00:00, 22:56:00, -0.5900"
 .....: )
 .....: 

In [109]: with open("tmp.csv", "w") as fh:
 .....:    fh.write(data)
 .....: 

In [110]: df = pd.read_csv("tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]])

In [111]: df
Out[111]: 
 1_2                 1_3     0     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59 

默认情况下,解析器会删除组件日期列,但您可以通过keep_date_col关键字选择保留它们:

In [112]: df = pd.read_csv(
 .....:    "tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]], keep_date_col=True
 .....: )
 .....: 

In [113]: df
Out[113]: 
 1_2                 1_3     0  ...          2          3     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  ...   19:00:00   18:56:00  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  ...   20:00:00   19:56:00  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD  ...   21:00:00   20:56:00 -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD  ...   21:00:00   21:18:00 -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD  ...   22:00:00   21:56:00 -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD  ...   23:00:00   22:56:00 -0.59

[6 rows x 7 columns] 

请注意,如果您希望将多个列合并为单个日期列,则必须使用嵌套列表。换句话说,parse_dates=[1, 2]表示第二列和第三列应分别解析为单独的日期列,而parse_dates=[[1, 2]]表示两列应解析为单个列。

您还可以使用字典指定自定义名称列:

In [114]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}

In [115]: df = pd.read_csv("tmp.csv", header=None, parse_dates=date_spec)

In [116]: df
Out[116]: 
 nominal              actual     0     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59 

重要的是要记住,如果要将多个文本列解析为单个日期列,则会在数据前添加一个新列。index_col规范是基于这组新列而不是原始数据列:

In [117]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}

In [118]: df = pd.read_csv(
 .....:    "tmp.csv", header=None, parse_dates=date_spec, index_col=0
 .....: )  # index is the nominal column
 .....: 

In [119]: df
Out[119]: 
 actual     0     4
nominal 
1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59 

注意

如果列或索引包含无法解析的日期,则整个列或索引将不经更改地返回为对象数据类型。对于非标准日期时间解析,请在pd.read_csv后使用to_datetime()

注意

read_csv 对 iso8601 格式的日期时间字符串有一个快速路���,例如“2000-01-01T00:01:02+00:00”和类似变体。如果您可以安排数据以这种格式存储日期时间,加载时间将显着更快,已观察到约 20 倍的速度。

自版本 2.2.0 起已弃用:在 read_csv 中合并日期列已弃用。请改为在相关结果列上使用pd.to_datetime

日期解析函数

最后,解析器允许您指定自定义的date_format。从性能角度考虑,您应该按顺序尝试这些日期解析方法:

  1. 如果知道格式,请使用date_format,例如:date_format="%d/%m/%Y"date_format={column_name: "%d/%m/%Y"}

  2. 如果不同列有不同格式,或者想要将任何额外选项(如utc)传递给to_datetime,则应以object dtype 读取数据,然后使用to_datetime

解析具有混合时区的 CSV

pandas 无法本地表示具有混合时区的列或索引。如果您的 CSV 文件包含具有混合时区的列,则默认结果将是一个对象 dtype 列,其中包含字符串,即使使用parse_dates也是如此。要将混合时区值解析为日期时间列,请以object dtype 读取,然后调用to_datetime()并使用utc=True

In [120]: content = """\
 .....: a
 .....: 2000-01-01T00:00:00+05:00
 .....: 2000-01-01T00:00:00+06:00"""
 .....: 

In [121]: df = pd.read_csv(StringIO(content))

In [122]: df["a"] = pd.to_datetime(df["a"], utc=True)

In [123]: df["a"]
Out[123]: 
0   1999-12-31 19:00:00+00:00
1   1999-12-31 18:00:00+00:00
Name: a, dtype: datetime64[ns, UTC] 
```  #### 推断日期时间格式

这里有一些可以猜测的日期时间字符串示例(均表示 2011 年 12 月 30 日 00:00:00):

+   “20111230”

+   “2011/12/30”

+   “20111230 00:00:00”

+   “12/30/2011 00:00:00”

+   “30/Dec/2011 00:00:00”

+   “30/December/2011 00:00:00”

请注意,格式推断对`dayfirst`很敏感。使用`dayfirst=True`,它会猜测“01/12/2011”为 12 月 1 日。使用`dayfirst=False`(默认),它会猜测“01/12/2011”为 1 月 12 日。

如果尝试解析日期字符串列,pandas 将尝试从第一个非 NaN 元素猜测格式,然后使用该格式解析列的其余部分。如果 pandas 无法猜测格式(例如,如果您的第一个字符串是`'01 December US/Pacific 2000'`),则会发出警告,并且每行将通过`dateutil.parser.parse`单独解析。解析日期的最安全方式是显式设置`format=`。

```py
In [124]: df = pd.read_csv(
 .....:    "foo.csv",
 .....:    index_col=0,
 .....:    parse_dates=True,
 .....: )
 .....: 

In [125]: df
Out[125]: 
 A  B  C
date 
2009-01-01  a  1  2
2009-01-02  b  3  4
2009-01-03  c  4  5 

如果同一列中有混合的日期时间格式,可以传递format='mixed'

In [126]: data = StringIO("date\n12 Jan 2000\n2000-01-13\n")

In [127]: df = pd.read_csv(data)

In [128]: df['date'] = pd.to_datetime(df['date'], format='mixed')

In [129]: df
Out[129]: 
 date
0 2000-01-12
1 2000-01-13 

或者,如果您的日期时间格式都是 ISO8601(可能不是完全相同的格式):

In [130]: data = StringIO("date\n2020-01-01\n2020-01-01 03:00\n")

In [131]: df = pd.read_csv(data)

In [132]: df['date'] = pd.to_datetime(df['date'], format='ISO8601')

In [133]: df
Out[133]: 
 date
0 2020-01-01 00:00:00
1 2020-01-01 03:00:00 

国际日期格式

美国的日期格式通常是 MM/DD/YYYY,而许多国际格式则使用 DD/MM/YYYY。为了方便起见,提供了一个 dayfirst 关键字:

In [134]: data = "date,value,cat\n1/6/2000,5,a\n2/6/2000,10,b\n3/6/2000,15,c"

In [135]: print(data)
date,value,cat
1/6/2000,5,a
2/6/2000,10,b
3/6/2000,15,c

In [136]: with open("tmp.csv", "w") as fh:
 .....:    fh.write(data)
 .....: 

In [137]: pd.read_csv("tmp.csv", parse_dates=[0])
Out[137]: 
 date  value cat
0 2000-01-06      5   a
1 2000-02-06     10   b
2 2000-03-06     15   c

In [138]: pd.read_csv("tmp.csv", dayfirst=True, parse_dates=[0])
Out[138]: 
 date  value cat
0 2000-06-01      5   a
1 2000-06-02     10   b
2 2000-06-03     15   c 

将 CSV 写入二进制文件对象

新版本 1.2.0 中新增。

df.to_csv(..., mode="wb") 允许将 CSV 写入以二进制模式打开的文件对象。在大多数情况下,不需要指定 mode,因为 Pandas 将自动检测文件对象是以文本模式还是二进制模式打开的。

In [139]: import io

In [140]: data = pd.DataFrame([0, 1, 2])

In [141]: buffer = io.BytesIO()

In [142]: data.to_csv(buffer, encoding="utf-8", compression="gzip") 
```  ### 指定浮点转换方法

可以通过指定 `float_precision` 参数来使用特定的浮点数转换器在 C 引擎解析时。选项有普通转换器、高精度转换器和往返转换器(保证在写入文件后循环的值)。例如:

```py
In [143]: val = "0.3066101993807095471566981359501369297504425048828125"

In [144]: data = "a,b,c\n1,2,{0}".format(val)

In [145]: abs(
 .....:    pd.read_csv(
 .....:        StringIO(data),
 .....:        engine="c",
 .....:        float_precision=None,
 .....:    )["c"][0] - float(val)
 .....: )
 .....: 
Out[145]: 5.551115123125783e-17

In [146]: abs(
 .....:    pd.read_csv(
 .....:        StringIO(data),
 .....:        engine="c",
 .....:        float_precision="high",
 .....:    )["c"][0] - float(val)
 .....: )
 .....: 
Out[146]: 5.551115123125783e-17

In [147]: abs(
 .....:    pd.read_csv(StringIO(data), engine="c", float_precision="round_trip")["c"][0]
 .....:    - float(val)
 .....: )
 .....: 
Out[147]: 0.0 
```  ### 千位分隔符

对于以千位分隔符编写的大数字,您可以将 `thousands` 关键字设置为长度为 1 的字符串,以便正确解析整数:

默认情况下,带有千位分隔符的数字将被解析为字符串:

```py
In [148]: data = (
 .....:    "ID|level|category\n"
 .....:    "Patient1|123,000|x\n"
 .....:    "Patient2|23,000|y\n"
 .....:    "Patient3|1,234,018|z"
 .....: )
 .....: 

In [149]: with open("tmp.csv", "w") as fh:
 .....:    fh.write(data)
 .....: 

In [150]: df = pd.read_csv("tmp.csv", sep="|")

In [151]: df
Out[151]: 
 ID      level category
0  Patient1    123,000        x
1  Patient2     23,000        y
2  Patient3  1,234,018        z

In [152]: df.level.dtype
Out[152]: dtype('O') 

thousands 关键字允许正确解析整数:

In [153]: df = pd.read_csv("tmp.csv", sep="|", thousands=",")

In [154]: df
Out[154]: 
 ID    level category
0  Patient1   123000        x
1  Patient2    23000        y
2  Patient3  1234018        z

In [155]: df.level.dtype
Out[155]: dtype('int64') 
```  ### NA 值

要控制哪些值被解析为缺失值(用 `NaN` 表示),请在 `na_values` 中指定一个字符串。如果您指定了一个字符串列表,那么其中的所有值都将被视为缺失值。如果您指定了一个数字(一个 `float`,比如 `5.0` 或一个 `integer`,比如 `5`),则相应的等价值也将暗示一个缺失值(在这种情况下,实际上 `[5.0, 5]` 被认为是 `NaN`)。

要完全覆盖默认被识别为缺失的值,请指定 `keep_default_na=False`。

默认的 `NaN` 被识别的值为 `['-1.#IND', '1.#QNAN', '1.#IND', '-1.#QNAN', '#N/A N/A', '#N/A', 'N/A', 'n/a', 'NA', '<NA>', '#NA', 'NULL', 'null', 'NaN', '-NaN', 'nan', '-nan', 'None', '']`。

让我们考虑一些例子:

```py
pd.read_csv("path_to_file.csv", na_values=[5]) 

在上面的例子中,55.0 将被识别为 NaN,除了默认值。一个字符串首先被解释为数值 5,然后作为 NaN

pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=[""]) 

上面,只有一个空字段会被识别为 NaN

pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=["NA", "0"]) 

上面,NA0 都作为字符串是 NaN

pd.read_csv("path_to_file.csv", na_values=["Nope"]) 

默认值除了字符串 "Nope" 外,也被识别为 NaN。 ### 无穷大

类似 inf 的值将被解析为 np.inf(正无穷大),而 -inf 将被解析为 -np.inf(负无穷大)。这些将忽略值的大小写,意味着 Inf 也将被解析为 np.inf。 ### 布尔值

常见的值 TrueFalseTRUEFALSE 都被识别为布尔值。偶尔你可能想要识别其他值为布尔值。为此,请使用如下所示的 true_valuesfalse_values 选项:

In [156]: data = "a,b,c\n1,Yes,2\n3,No,4"

In [157]: print(data)
a,b,c
1,Yes,2
3,No,4

In [158]: pd.read_csv(StringIO(data))
Out[158]: 
 a    b  c
0  1  Yes  2
1  3   No  4

In [159]: pd.read_csv(StringIO(data), true_values=["Yes"], false_values=["No"])
Out[159]: 
 a      b  c
0  1   True  2
1  3  False  4 
```  ### 处理“坏”行

一些文件可能存在字段过少或过多的格式不正确的行。字段过少的行将在尾部字段中填充 NA 值。字段过多的行将默认引发错误:

```py
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 mappingengine
  1899 except Exception:
  1900     if self.handles is not None:

File ~/work/pandas/pandas/pandas/io/parsers/c_parser_wrapper.py:155, in CParserWrapper.__init__(self, src, **kwds)
  152     # error: Cannot determine type of 'names'
  153     if len(self.names) < len(usecols):  # type: ignore[has-type]
  154         # error: Cannot determine type of 'names'
--> 155         self._validate_usecols_names(
  156             usecols,
  157             self.names,  # type: ignore[has-type]
  158         )
  160 # error: Cannot determine type of 'names'
  161 self._validate_parse_dates_presence(self.names)  # type: ignore[has-type]

File ~/work/pandas/pandas/pandas/io/parsers/base_parser.py:979, in ParserBase._validate_usecols_names(self, usecols, names)
  977 missing = [c for c in usecols if c not in names]
  978 if len(missing) > 0:
--> 979     raise ValueError(
  980         f"Usecols do not match columns, columns expected but not found: "
  981         f"{missing}"
  982     )
  984 return usecols

ValueError: Usecols do not match columns, columns expected but not found: [0, 1, 2] 

如果您希望保留所有数据,包括字段过多的行,可以指定足够数量的names。这样可以确保字段不足的行填充为NaN

In [172]: pd.read_csv(StringIO(data), names=['a', 'b', 'c', 'd'])
Out[172]: 
 a                b   c   d
0    name             type NaN NaN
1  name a   a is of type a NaN NaN
2  name b  b is of type b" NaN NaN 
```  ### 方言

`dialect`关键字提供了更大的灵活性来指定文件格式。默认情况下使用 Excel 方言,但您可以指定方言名称或[`csv.Dialect`](https://docs.python.org/3/library/csv.html#csv.Dialect "(在 Python v3.12 中)")实例。

假设您的数据中有未封闭的引号:

```py
In [173]: data = "label1,label2,label3\n" 'index1,"a,c,e\n' "index2,b,d,f"

In [174]: print(data)
label1,label2,label3
index1,"a,c,e
index2,b,d,f 

默认情况下,read_csv使用 Excel 方言,并将双引号视为引号字符,这会导致在找到关闭双引号之前找到换行符时失败。

我们可以使用dialect来解决这个问题:

In [175]: import csv

In [176]: dia = csv.excel()

In [177]: dia.quoting = csv.QUOTE_NONE

In [178]: pd.read_csv(StringIO(data), dialect=dia)
Out[178]: 
 label1 label2 label3
index1     "a      c      e
index2      b      d      f 

所有方言选项都可以通过关键字参数单独指定:

In [179]: data = "a,b,c~1,2,3~4,5,6"

In [180]: pd.read_csv(StringIO(data), lineterminator="~")
Out[180]: 
 a  b  c
0  1  2  3
1  4  5  6 

另一个常见的方言选项是skipinitialspace,用于跳过分隔符后的任何空格:

In [181]: data = "a, b, c\n1, 2, 3\n4, 5, 6"

In [182]: print(data)
a, b, c
1, 2, 3
4, 5, 6

In [183]: pd.read_csv(StringIO(data), skipinitialspace=True)
Out[183]: 
 a  b  c
0  1  2  3
1  4  5  6 

解析器会尽力“做正确的事情”而不会变得脆弱。类型推断是一件很重要的事情。如果可以将列强制转换为整数 dtype 而不改变内容,则解析器将这样做。任何非数字列将像其他 pandas 对象一样以 object dtype 传递。 ### 引用和转义字符

嵌入字段中的引号(和其他转义字符)可以以多种方式处理。一种方法是使用反斜杠;为了正确解析这些数据,您应该传递escapechar选项:

In [184]: data = 'a,b\n"hello, \\"Bob\\", nice to see you",5'

In [185]: print(data)
a,b
"hello, \"Bob\", nice to see you",5

In [186]: pd.read_csv(StringIO(data), escapechar="\\")
Out[186]: 
 a  b
0  hello, "Bob", nice to see you  5 
```  ### 具有固定宽度列的文件

虽然`read_csv()`用于读取分隔数据,`read_fwf()`函数用于处理具有已知和固定列宽的数据文件。`read_fwf`的函数参数与`read_csv`基本相同,但有两个额外参数,并且`delimiter`参数的使用方式不同:

+   `colspecs`:一个对给出每行固定宽度字段的范围的一半开放区间(即,[from, to[)的列表(元组)。字符串值‘infer’ 可以用于指示解析器尝试从数据的前 100 行检测列规格。如果未指定,默认行为是推断。

+   `widths`:一个字段宽度的列表,可用于代替‘colspecs’,如果间隔是连续的。

+   `delimiter`:在固定宽度文件中视为填充字符的字符。如果字段的填充字符不是空格(例如,‘~’),则可以用它来指定字段的填充字符。

考虑一个典型的固定宽度数据文件:

```py
In [187]: data1 = (
 .....:    "id8141    360.242940   149.910199   11950.7\n"
 .....:    "id1594    444.953632   166.985655   11788.4\n"
 .....:    "id1849    364.136849   183.628767   11806.2\n"
 .....:    "id1230    413.836124   184.375703   11916.8\n"
 .....:    "id1948    502.953953   173.237159   12468.3"
 .....: )
 .....: 

In [188]: with open("bar.csv", "w") as f:
 .....:    f.write(data1)
 .....: 

为了将此文件解析为 DataFrame,我们只需提供列规格到 read_fwf 函数以及文件名:

# Column specifications are a list of half-intervals
In [189]: colspecs = [(0, 6), (8, 20), (21, 33), (34, 43)]

In [190]: df = pd.read_fwf("bar.csv", colspecs=colspecs, header=None, index_col=0)

In [191]: df
Out[191]: 
 1           2        3
0 
id8141  360.242940  149.910199  11950.7
id1594  444.953632  166.985655  11788.4
id1849  364.136849  183.628767  11806.2
id1230  413.836124  184.375703  11916.8
id1948  502.953953  173.237159  12468.3 

注意当指定了 header=None 参数时,解析器会自动选择列名 X.<列编号>。或者,您可以仅提供连续列的列宽:

# Widths are a list of integers
In [192]: widths = [6, 14, 13, 10]

In [193]: df = pd.read_fwf("bar.csv", widths=widths, header=None)

In [194]: df
Out[194]: 
 0           1           2        3
0  id8141  360.242940  149.910199  11950.7
1  id1594  444.953632  166.985655  11788.4
2  id1849  364.136849  183.628767  11806.2
3  id1230  413.836124  184.375703  11916.8
4  id1948  502.953953  173.237159  12468.3 

解析器会处理列周围的额外空格,因此文件中的列之间有额外的分隔是可以的。

默认情况下,read_fwf 将尝试通过使用文件的前 100 行推断文件的 colspecs。它只能在列对齐且由提供的 delimiter(默认分隔符是空格)正确分隔的情况下进行。

In [195]: df = pd.read_fwf("bar.csv", header=None, index_col=0)

In [196]: df
Out[196]: 
 1           2        3
0 
id8141  360.242940  149.910199  11950.7
id1594  444.953632  166.985655  11788.4
id1849  364.136849  183.628767  11806.2
id1230  413.836124  184.375703  11916.8
id1948  502.953953  173.237159  12468.3 

read_fwf 支持 dtype 参数,用于指定解析列的类型与推断类型不同。

In [197]: pd.read_fwf("bar.csv", header=None, index_col=0).dtypes
Out[197]: 
1    float64
2    float64
3    float64
dtype: object

In [198]: pd.read_fwf("bar.csv", header=None, dtype={2: "object"}).dtypes
Out[198]: 
0     object
1    float64
2     object
3    float64
dtype: object 

索引

带有“隐式”索引列的文件

考虑文件中标题项比数据列数少一个的情况:

In [199]: data = "A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5"

In [200]: print(data)
A,B,C
20090101,a,1,2
20090102,b,3,4
20090103,c,4,5

In [201]: with open("foo.csv", "w") as f:
 .....:    f.write(data)
 .....: 

在这种特殊情况下,read_csv 假设第一列将用作 DataFrame 的索引:

In [202]: pd.read_csv("foo.csv")
Out[202]: 
 A  B  C
20090101  a  1  2
20090102  b  3  4
20090103  c  4  5 

请注意,日期没有被自动解析。在这种情况下,您需要像以前一样操作:

In [203]: df = pd.read_csv("foo.csv", parse_dates=True)

In [204]: df.index
Out[204]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', freq=None) 

使用 MultiIndex 读取索引

假设您的数据由两列索引:

In [205]: data = 'year,indiv,zit,xit\n1977,"A",1.2,.6\n1977,"B",1.5,.5'

In [206]: print(data)
year,indiv,zit,xit
1977,"A",1.2,.6
1977,"B",1.5,.5

In [207]: with open("mindex_ex.csv", mode="w") as f:
 .....:    f.write(data)
 .....: 

read_csvindex_col 参数可以接受列号列表,将多个列转换为返回对象的索引的 MultiIndex

In [208]: df = pd.read_csv("mindex_ex.csv", index_col=[0, 1])

In [209]: df
Out[209]: 
 zit  xit
year indiv 
1977 A      1.2  0.6
 B      1.5  0.5

In [210]: df.loc[1977]
Out[210]: 
 zit  xit
indiv 
A      1.2  0.6
B      1.5  0.5 

使用 MultiIndex 读取列

通过为 header 参数指定行位置列表,您可以读取列的 MultiIndex。指定非连续行将跳过介于其间的行。

In [211]: mi_idx = pd.MultiIndex.from_arrays([[1, 2, 3, 4], list("abcd")], names=list("ab"))

In [212]: mi_col = pd.MultiIndex.from_arrays([[1, 2], list("ab")], names=list("cd"))

In [213]: df = pd.DataFrame(np.ones((4, 2)), index=mi_idx, columns=mi_col)

In [214]: df.to_csv("mi.csv")

In [215]: print(open("mi.csv").read())
c,,1,2
d,,a,b
a,b,,
1,a,1.0,1.0
2,b,1.0,1.0
3,c,1.0,1.0
4,d,1.0,1.0

In [216]: pd.read_csv("mi.csv", header=[0, 1, 2, 3], index_col=[0, 1])
Out[216]: 
c                    1                  2
d                    a                  b
a   Unnamed: 2_level_2 Unnamed: 3_level_2
1                  1.0                1.0
2 b                1.0                1.0
3 c                1.0                1.0
4 d                1.0                1.0 

read_csv 还能够解释更常见的多列索引格式。

In [217]: data = ",a,a,a,b,c,c\n,q,r,s,t,u,v\none,1,2,3,4,5,6\ntwo,7,8,9,10,11,12"

In [218]: print(data)
,a,a,a,b,c,c
,q,r,s,t,u,v
one,1,2,3,4,5,6
two,7,8,9,10,11,12

In [219]: with open("mi2.csv", "w") as fh:
 .....:    fh.write(data)
 .....: 

In [220]: pd.read_csv("mi2.csv", header=[0, 1], index_col=0)
Out[220]: 
 a         b   c 
 q  r  s   t   u   v
one  1  2  3   4   5   6
two  7  8  9  10  11  12 

注意

如果未指定 index_col(例如,您没有索引,或者用 df.to_csv(..., index=False) 写入了它,则列索引上的任何 names 将会 丢失。### 自动“嗅探”定界符

read_csv 能够推断分隔(不一定是逗号分隔)的文件,因为 pandas 使用 csv 模块的 csv.Sniffer 类。为此,您必须指定 sep=None

In [221]: df = pd.DataFrame(np.random.randn(10, 4))

In [222]: df.to_csv("tmp2.csv", sep=":", index=False)

In [223]: pd.read_csv("tmp2.csv", sep=None, engine="python")
Out[223]: 
 0         1         2         3
0  0.469112 -0.282863 -1.509059 -1.135632
1  1.212112 -0.173215  0.119209 -1.044236
2 -0.861849 -2.104569 -0.494929  1.071804
3  0.721555 -0.706771 -1.039575  0.271860
4 -0.424972  0.567020  0.276232 -1.087401
5 -0.673690  0.113648 -1.478427  0.524988
6  0.404705  0.577046 -1.715002 -1.039268
7 -0.370647 -1.157892 -1.344312  0.844885
8  1.075770 -0.109050  1.643563 -1.469388
9  0.357021 -0.674600 -1.776904 -0.968914 
```  ### 读取多个文件以创建单个 DataFrame

最好使用`concat()`来合并多个文件。查看 cookbook 以获取示例。  ### 逐块迭代文件

假设您希望惰性地迭代(可能非常大的)文件,而不是将整个文件读入内存,例如以下内容:

```py
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 

指定解析引擎

Pandas 目前支持三种引擎,C 引擎、Python 引擎和实验性的 pyarrow 引擎(需要pyarrow软件包)。一般来说,pyarrow 引擎在较大的工作负载上速度最快,在大多数其他工作负载上与 C 引擎的速度相当。Python 引擎在大多数工作负载上比 pyarrow 和 C 引擎慢。但是,与 C 引擎相比,pyarrow 引擎要不那么稳定,缺少一些与 Python 引擎相比的功能。

在可能的情况下,pandas 使用 C 解析器(指定为engine='c'),但如果指定了不受 C 支持的选项,则可能会退回到 Python。

目前,C 和 pyarrow 引擎不支持的选项包括:

  • sep 不是单个字符(例如正则表达式分隔符)

  • skipfooter

  • sep=Nonedelim_whitespace=False

指定上述任何选项将产生一个ParserWarning,除非显式选择engine='python'来选择 Python 引擎。

pyarrow 引擎不支持的选项,不在上面的列表中包括:

  • float_precision

  • chunksize

  • comment

  • nrows

  • thousands

  • memory_map

  • dialect

  • on_bad_lines

  • delim_whitespace

  • quoting

  • lineterminator

  • converters

  • decimal

  • iterator

  • dayfirst

  • infer_datetime_format

  • verbose

  • skipinitialspace

  • low_memory

使用engine='pyarrow'指定这些选项将引发ValueError

读取/写入远程文件

您可以传递一个 URL 给许多 pandas 的 IO 函数来读取或写入远程文件 - 以下示例显示了读取 CSV 文件:

df = pd.read_csv("https://download.bls.gov/pub/time.series/cu/cu.item", sep="\t") 

版本 1.3.0 中的新功能。

可以通过将头键值映射的字典传递给storage_options关键字参数来发送自定义标头,如下所示:

headers = {"User-Agent": "pandas"}
df = pd.read_csv(
    "https://download.bls.gov/pub/time.series/cu/cu.item",
    sep="\t",
    storage_options=headers
) 

所有不是本地文件或 HTTP(s) 的 URL 都由fsspec处理,如果安装了它,以及其各种文件系统实现(包括 Amazon S3、Google Cloud、SSH、FTP、webHDFS…)。其中一些实现将需要安装其他软件包,例如 S3 URL 需要s3fs库:

df = pd.read_json("s3://pandas-test/adatafile.json") 

当处理远程存储系统时,您可能需要在特殊位置的环境变量或配置文件中进行额外配置。例如,要访问您的 S3 存储桶中的数据,您需要在S3Fs documentation中列出的几种方式之一中定义凭据。对于几个存储后端,情况也是如此,您应该遵循fsspec内置的fsimpl1和未包含在主fsspec分发中的fsimpl2的链接。

您还可以直接将参数传递给后端驱动程序。由于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”部分,而不是缓存实现。请注意,这仅在会话期间缓存到临时目录,但您还可以指定一个永久存储。

写出数据

写入到 CSV 格式

SeriesDataFrame对象具有一个实例方法to_csv,允许将对象的内容存储为逗号分隔值文件。该函数接受多个参数。只需要第一个。

  • path_or_buf: 要写入的文件的字符串路径或文件对象。如果是文件对象,则必须使用newline=''打开。

  • sep: 输出文件的字段分隔符(默认为“,”)

  • na_rep: 缺失值的字符串表示(默认为‘’)

  • float_format: 浮点数的格式字符串

  • columns: 写入的列(默认为 None)

  • header: 是否写出列名(默认为 True)

  • index: 是否写入行(索引)名称(默认为 True)

  • index_label: 如果需要,用于索引列的列标签。如果为 None(默认值),并且headerindex为 True,则使用索引名称。(如果DataFrame使用 MultiIndex,则应给出一个序列)。

  • mode: Python 写入模式,默认为‘w’

  • encoding: 表示要使用的编码的字符串,如果内容为非 ASCII 字符,对于 Python 版本 3 之前

  • lineterminator: 表示行尾的字符序列(默认为os.linesep

  • quoting:设置引用规则,如 csv 模块中的设置(默认为 csv.QUOTE_MINIMAL)。注意,如果您设置了一个 float_format,那么浮点数将被转换为字符串,并且 csv.QUOTE_NONNUMERIC 将把它们视为非数值。

  • quotechar:用于引用字段的字符(默认为 ‘”’)。

  • doublequote:控制在字段中引用 quotechar(默认为 True)。

  • escapechar:用于在适当时候转义 sepquotechar 的字符(默认为 None)。

  • chunksize:一次写入的行数。

  • date_format:datetime 对象的格式字符串。

写入一个格式化字符串

DataFrame 对象有一个实例方法 to_string,它允许控制对象的字符串表示。所有参数都是可选的:

  • buf 默认为 None,例如一个 StringIO 对象。

  • columns 默认为 None,要写入的列。

  • col_space 默认为 None,每列的最小宽度。

  • na_rep 默认为 NaN,表示 NA 值。

  • formatters 默认为 None,一个字典(按列)的函数,每个函数接受一个参数并返回一个格式化的字符串。

  • float_format 默认为 None,一个函数,它接受一个单一(浮点)参数并返回一个格式化的字符串;应用于 DataFrame 中的浮点数。

  • sparsify 默认为 True,对于具有分层索引的 DataFrame,设置为 False 以在每一行打印每个 MultiIndex 键。

  • index_names 默认为 True,将打印索引的名称。

  • index 默认为 True,将打印索引(即,行标签)。

  • header 默认为 True,将打印列标签。

  • justify 默认为 left,将列标题左对齐或右对齐。

Series 对象也有一个 to_string 方法,但只有 bufna_repfloat_format 参数。还有一个 length 参数,如果设置为 True,还会输出 Series 的长度。

解析选项

read_csv() 接受以下常见参数:

基础

filepath_or_buffervarious

要么是文件路径(str, pathlib.Pathpy:py._path.local.LocalPath),URL(包括 http、ftp 和 S3 位置),或具有 read() 方法的任何对象(例如打开的文件或 StringIO)。

sepstr,默认为 read_csv()','\tread_table()

要使用的分隔符。如果 sep 为None,C 引擎无法自动检测分隔符,但 Python 解析引擎可以,这意味着将使用后者,并通过 Python 的内置嗅探工具csv.Sniffer自动检测分隔符。此外,长度大于 1 个字符且不同于'\s+'的分隔符将被解释为正则表达式,并且还会强制使用 Python 解析引擎。请注意,正则表达式分隔符容易忽略带引号的数据。正则表达式示例:'\\r\\t'

分隔符字符串,默认为None

sep 的替代参数名称。

delim_whitespace 布尔值,默认为 False

指定是否使用空格(例如' ''\t')作为分隔符。等效于设置sep='\s+'。如果将此选项设置为True,则不应为delimiter参数传递任何内容。

列和索引位置及名称

header 整数或整数列表,默认为'infer'

用作列名和数据起始位置的行号。默认行为是推断列名:如果没有传递名称,则行为与header=0相同,并且列名从文件的第一行推断出来,如果明确传递了列名,则行为与header=None相同。明确传递header=0以能够替换现有名称。

标题可以是指定列的 MultiIndex 的行位置的整数列表,例如[0,1,3]。未指定的中间行将被跳过(例如,在此示例中跳过 2)。请注意,如果skip_blank_lines=True,此参数会忽略注释行和空行,因此 header=0 表示数据的第一行而不是文件的第一行。

names 类似数组,默认为None

要使用的列名列表。如果文件不包含标题行,则应明确传递header=None。不允许在此列表中存在重复项。

index_col 整数、字符串、int/str 序列或 False,可选,默认为None

作为DataFrame行标签使用的列。可以作为字符串名称或列索引给出。如果给出 int / str 序列,则将使用 MultiIndex。

注意

index_col=False可用于强制 pandas将第一列用作索引,例如当您有一个在每行末尾带有分隔符的格式不正确的文件时。

默认值为None指示 pandas 进行猜测。如果列标题行中的字段数等于数据文件主体中的字段数,则使用默认索引。如果大于,则使用前几列作为索引,以使数据主体中的剩余字段数等于标题中的字段数。

标题后的第一行用于确定将进入索引的列数。如果后续行包含的列少于第一行,则用NaN填充。

这可以通过usecols来避免。这确保列按原样取出,并且忽略尾随数据。

usecols 类似列表或可调用,默认为None

返回列的子集。如果类似列表,则所有元素必须是位置(即整数索引到文档列)或字符串,对应于用户在names中提供的列名称或从文档标题行推断出的列名称。如果给出了names,则不考虑文档标题行。例如,一个有效的类似列表usecols参数将是[0, 1, 2]['foo', 'bar', 'baz']

元素顺序被忽略,因此usecols=[0, 1][1, 0]相同。要从具有元素顺序的data实例化 DataFrame,请使用pd.read_csv(data, usecols=['foo', 'bar'])[['foo', 'bar']]['foo', 'bar']顺序的列或pd.read_csv(data, usecols=['foo', 'bar'])[['bar', 'foo']]['bar', 'foo']顺序的列。

如果是可调用的,将对列名求值可调用函数,返回使可调用函数求值为 True 的名称:

In [1]: import pandas as pd

In [2]: from io import StringIO

In [3]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3"

In [4]: pd.read_csv(StringIO(data))
Out[4]: 
 col1 col2  col3
0    a    b     1
1    a    b     2
2    c    d     3

In [5]: pd.read_csv(StringIO(data), usecols=lambda x: x.upper() in ["COL1", "COL3"])
Out[5]: 
 col1  col3
0    a     1
1    a     2
2    c     3 

使用此参数会导致使用 c 引擎时解析时间更快,内存使用量更低。 Python 引擎首先加载数据,然后再决定要删除哪些列。

通用解析配置

dtype 类型名称或列->类型的字典,默认为None

数据或列的数据类型。例如{'a': np.float64, 'b': np.int32, 'c': 'Int64'}使用strobject以及合适的na_values设置来保留并不解释 dtype。如果指定了转换器,则将应用于 dtype 转换而不是。

1.5.0 版中的新功能:添加了对 defaultdict 的支持。在输入中指定 defaultdict,其中默认值确定未明确列出的列的 dtype。

dtype_backend{“numpy_nullable”, “pyarrow”},默认为 NumPy 后端的 DataFrame

要使用的 dtype_backend,例如 DataFrame 应该具有 NumPy 数组,当设置为“numpy_nullable”时,所有具有可空实现的 dtype 都使用可空 dtype,如果设置为“pyarrow”,则使用 pyarrow。

dtype_backends 仍然是实验性的。

2.0 版中的新功能。

engine{'c', 'python', 'pyarrow'}

要使用的解析引擎。 C 和 pyarrow 引擎速度更快,而 Python 引擎目前更完整。当前仅 pyarrow 引擎支持多线程。

1.4.0 版中的新功能:添加了“pyarrow”引擎作为实验性引擎,并且某些功能不受支持,或者可能在此引擎下无法正常工作。

转换器字典,默认为None

用于在某些列中转换值的函数字典。键可以是整数或列标签。

true_values 列表,默认为None

要考虑为True的值。

false_values 列表,默认为None

要考虑为False的值。

skipinitialspace 布尔值,默认为False

在分隔符后跳过空格。

skiprows 类似列表或整数,默认为None

要跳过的行号(从 0 开始)或文件开头要跳过的行数(整数)。

如果可调用,则将对行索引评估可调用函数,如果应跳过该行,则返回 True,否则返回 False:

In [6]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3"

In [7]: pd.read_csv(StringIO(data))
Out[7]: 
 col1 col2  col3
0    a    b     1
1    a    b     2
2    c    d     3

In [8]: pd.read_csv(StringIO(data), skiprows=lambda x: x % 2 != 0)
Out[8]: 
 col1 col2  col3
0    a    b     2 

skipfooter 整数,默认为 0

要跳过文件底部的行数(在引擎为 'c' 时不支持)。

nrows 整数,默认为 None

要读取的文件行数。用于读取大文件的片段。

low_memory 布尔值,默认为 True

在解析文件时以块的方式内部处理,从而在解析时降低内存使用,但可能导致混合类型推断。要确保没有混合类型,可以设置 False,或者使用 dtype 参数指定类型。请注意,无论如何,整个文件都会读入一个单独的 DataFrame,使用 chunksizeiterator 参数返回数据块。(仅与 C 解析器有效)

memory_map 布尔值,默认为 False

如果为 filepath_or_buffer 提供了文件路径,则直接将文件对象映射到内存中,并直接从那里访问数据。使用此选项可以提高性能,因为不再有 I/O 开销。

NA 和缺失数据处理

na_values 标量、字符串、类似列表或字典,默认为 None

附加字符串以识别为 NA/NaN。如果传入了字典,则为每列指定特定的 NA 值。请参阅下面的 na values const 列表,了解默认情况下解释为 NaN 的值。

keep_default_na 布尔值,默认为 True

解析数据时是否包括默认的 NaN 值。根据是否传入了 na_values,行为如下:

  • 如果 keep_default_naTrue,并且指定了 na_values,则将 na_values 追加到用于解析的默认 NaN 值中。

  • 如果 keep_default_naTrue,并且未指定 na_values,则仅使用默认的 NaN 值进行解析。

  • 如果 keep_default_naFalse,并且指定了 na_values,则仅使用指定的 NaN 值 na_values 进行解析。

  • 如果 keep_default_naFalse,并且未指定 na_values,则不会将任何字符串解析为 NaN。

注意,如果将 na_filter 传入为 False,则将忽略 keep_default_nana_values 参数。

na_filter 布尔值,默认为 True

检测缺失值标记(空字符串和 na_values 的值)。在没有任何 NA 的数据中,传入 na_filter=False 可以提高读取大文件的性能。

verbose 布尔值,默认为 False

在非数字列中指示放置的 NA 值的数量。

skip_blank_lines 布尔值,默认为 True

如果为 True,则跳过空行而不是解释为 NaN 值。

日期时间处理

parse_dates 布尔值或整数列表或名称列表或列表列表或字典,默认为 False

  • 如果为 True -> 尝试解析索引。

  • 如果 [1, 2, 3] -> 尝试将列 1、2、3 分别解析为单独的日期列。

  • 如果 [[1, 3]] -> 组合列 1 和 3 并解析为单个日期列。

  • 如果 {'foo': [1, 3]} -> 解析列 1、3 为日期,并将结果命名为 'foo'。

注意

iso8601 格式的日期存在快速路径。

infer_datetime_format 布尔值,默认为 False

如果为 True 并且启用了 parse_dates 列,则尝试推断日期时间格式以加快处理速度。

自 2.0.0 版本起已弃用:现在该参数的严格版本是默认的,传递它不会产生任何效果。

keep_date_col 布尔值,默认为 False

如果为 True 并且 parse_dates 指定了组合多列,则保留原始列。

date_parser 函数,默认为 None

用于将一系列字符串列转换为日期时间实例数组的函数。默认情况下,使用 dateutil.parser.parser 进行转换。pandas 将尝试以三种不同的方式调用 date_parser,如果发生异常,则会进入下一步:1) 将一个或多个数组(由 parse_dates 定义)作为参数传递;2) 将由 parse_dates 定义的列的字符串值(按行)连接起来成为单个数组,并将其传递;3) 对每行使用一个或多个字符串(与由 parse_dates 定义的列相对应)作为参数调用 date_parser。

自 2.0.0 版本起已弃用:改用 date_format,或将其读入为 object,然后根据需要应用 to_datetime()

date_format 字符串或列 -> 格式的字典,默认为 None

如果与 parse_dates 结合使用,则会根据此格式解析日期。对于更复杂的情况,请将其读入为 object,然后根据需要应用 to_datetime()

自 2.0.0 版本新增。

dayfirst 布尔值,默认为 False

DD/MM 格式的日期,国际和欧洲格式。

cache_dates 布尔值,默认为 True

如果为 True,则使用唯一的、转换后的日期缓存来应用日期时间转换。在解析重复的日期字符串时可能会产生显著的加速,特别是带有时区偏移的字符串。

迭代

iteratorboolean,默认为 False

返回用于迭代或使用 get_chunk() 获取块的 TextFileReader 对象。

chunksize 整数,默认为 None

返回用于迭代的 TextFileReader 对象。参见下文的 迭代和块处理。

引用、压缩和文件格式

compression{'infer', 'gzip', 'bz2', 'zip', 'xz', 'zstd', None, dict},默认为 'infer'

用于在磁盘上数据的即时解压缩。如果为‘infer’,则如果filepath_or_buffer是以‘.gz’、‘.bz2’、‘.zip’、‘.xz’、‘.zst’结尾的路径,则使用 gzip、bz2、zip、xz 或 zstandard,否则不进行解压缩。如果使用'zip',ZIP 文件必须只包含一个要读取的数据文件。设置为None表示不解压缩。也可以是一个字典,其中键为'method',设置为其中之一{'zip''gzip''bz2''zstd'},其他键值对将被转发到zipfile.ZipFilegzip.GzipFilebz2.BZ2Filezstandard.ZstdDecompressor。例如,可以传递以下内容以进行更快的压缩并创建可重现的 gzip 存档:compression={'method': 'gzip', 'compresslevel': 1, 'mtime': 1}

从版本 1.2.0 中更改:以前的版本将‘gzip’的字典条目转发给gzip.open

thousandsstr,默认为None

千位分隔符。

decimalstr,默认为'.'

用作小数点的字符。例如,对于欧洲数据,使用','

float_precisionstring,默认为 None

指定 C 引擎应使用哪个转换器处理浮点值。选项为None表示普通转换器,high表示高精度转换器,round_trip表示往返转换器。

lineterminatorstr(长度为 1),默认为None

用于将文件分成行的字符。仅与 C 解析器有效。

quotecharstr(长度为 1)

用于表示引用项的开始和结束的字符。引用项可以包括分隔符,它将被忽略。

quotingint 或csv.QUOTE_*实例,默认为0

根据csv.QUOTE_*常量控制字段引用行为。使用QUOTE_MINIMAL(0)、QUOTE_ALL(1)、QUOTE_NONNUMERIC(2)或QUOTE_NONE(3)中的一个。

doublequoteboolean,默认为True

当指定quotechar并且quoting不是QUOTE_NONE时,指示是否将字段内两个连续的quotechar元素解释为单个quotechar元素。

escapecharstr(长度为 1),默认为None

用于在引用为QUOTE_NONE时转义分隔符的单字符字符串。

commentstr,默认为None

表示剩余的行不应被解析。如果在行的开头找到,整行将被完全忽略。此参数必须是一个单个字符。与空行一样(只要skip_blank_lines=True),完全注释的行由参数header忽略,但不由skiprows忽略。例如,如果comment='#',使用header=0解析‘#empty\na,b,c\n1,2,3’将导致‘a,b,c’被视为标题。

encodingstr,默认为None

读取/写入 UTF 时要使用的编码(例如,'utf-8')。Python 标准编码列表

dialectstr 或csv.Dialect实例,默认为None

如果提供了此参数,将覆盖以下参数的值(默认或非默认):delimiterdoublequoteescapecharskipinitialspacequotecharquoting。如果需要覆盖值,将发出一个 ParserWarning。更多详情请参阅csv.Dialect文档。

错误处理

on_bad_lines(‘error’、‘warn’、‘skip’),默认为 ‘error’

指定在遇到坏行(字段过多的行)时要执行的操作。允许的值为:

  • ‘error’,遇到坏行时引发 ParserError。

  • ‘warn’,遇到坏行时打印警告并跳过该行。

  • ‘skip’,遇到坏行时跳过而不引发或警告。

1.3.0 版本中的新功能。

基本

filepath_or_buffervarious

可以是文件路径(一个strpathlib.Path,或 py:py._path.local.LocalPath),URL(包括 http、ftp 和 S3 地址),或任何具有 read() 方法的对象(例如打开的文件或 StringIO)。

sepstr,默认为 read_csv()','read_table()\t

要使用的分隔符。如果 sep 为 None,C 引擎无法自动检测分隔符,但 Python 解析引擎可以,这意味着将使用后者,并通过 Python 的内置嗅探工具 csv.Sniffer 自动检测分隔符。此外,长度大于 1 且不同于 '\s+' 的分隔符将被解释为正则表达式,并且还将强制使用 Python 解析引擎。请注意,正则表达式分隔符容易忽略带引号的数据。正则表达式示例:'\\r\\t'

delimiterstr,默认为 None

sep 的替代参数名称。

delim_whitespaceboolean,默认为 False

指定是否使用空格(例如 ' ''\t')作为分隔符。等同于设置 sep='\s+'。如果将此选项设置为 True,则不应为 delimiter 参数传递任何内容。

列和索引位置和名称

headerint 或 int 列表,默认为 'infer'

用作列名和数据起始位置的行号, 默认行为是推断列名:如果没有传递名称,则行为与 header=0 相同,并且列名从文件的第一行推断出来,如果显式传递了列名,则行为与 header=None 相同。显式传递 header=0 以替换现有名称。

标题可以是指定列的多个位置的整数列表,例如[0,1,3]。如果未指定的介入行,则会被跳过(例如,在此示例中跳过了 2)。请注意,此参数忽略了注释行和空行,如果skip_blank_lines=True,则 header=0 表示数据的第一行,而不是文件的第一行。

名称数组样式,默认为None

要使用的列名列表。如果文件不包含标题行,则应明确传递header=None。此列表中不允许重复项。

index_col 整数、字符串、int/str 序列或 False,可选,默认为None

用作DataFrame的行标签的列,可以是字符串名称或列索引。如果给定了 int / str 序列,则使用 MultiIndex。

注意

index_col=False可用于强制 pandas将第一列用作索引,例如当您有一个每行末尾都有定界符的格式不正确的文件时。

默认值为None,指示 pandas 猜测。如果列标题行中的字段数量等于数据文件主体中的字段数量,则使用默认索引。如果大于,则使用第一列作为索引,以使数据主体中的剩余字段数量等于标题中的字段数量。

标题之后的第一行用于确定要进入索引的列数。如果后续行的列少于第一行,则填充为NaN

可以通过usecols来避免这种情况。这确保列被保持原样,而尾随数据被忽略。

usecols 类似列表或可调用函数,默认为None

返回列的子集。如果类似列表,所有元素必须是位置的(即,整数索引到文档列)或与用户在names中提供的列名相对应的字符串。如果给定了names,则不考虑文档标题行。例如,一个有效的类似列表usecols参数将是[0, 1, 2]['foo', 'bar', 'baz']

元素顺序被忽略,因此usecols=[0, 1][1, 0]相同。要从以元素顺序保持的data实例化 DataFrame,请使用pd.read_csv(data, usecols=['foo', 'bar'])[['foo', 'bar']]['foo', 'bar']顺序的列或pd.read_csv(data, usecols=['foo', 'bar'])[['bar', 'foo']]['bar', 'foo']顺序的列。

如果可调用,则将针对列名评估可调用函数,返回可调用函数评估为 True 的名称:

In [1]: import pandas as pd

In [2]: from io import StringIO

In [3]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3"

In [4]: pd.read_csv(StringIO(data))
Out[4]: 
 col1 col2  col3
0    a    b     1
1    a    b     2
2    c    d     3

In [5]: pd.read_csv(StringIO(data), usecols=lambda x: x.upper() in ["COL1", "COL3"])
Out[5]: 
 col1  col3
0    a     1
1    a     2
2    c     3 

使用此参数可以在使用 c 引擎时实现更快的解析速度和更低的内存使用量。Python 引擎在决定要删除哪些列之前首先加载数据。

通用解析配置

dtype 类型名称或列->类型的字典,默认为None

数据或列的数据类型。例如{'a': np.float64, 'b': np.int32, 'c': 'Int64'} 使用strobject与适当的na_values设置一起使用以保留并不解释 dtype。如果指定了转换器,则将应用转换器而不是 dtype 转换。

新版本 1.5.0 中添加了对 defaultdict 的支持。指定一个 defaultdict 作为输入,其中默认值确定未明确列出的列的 dtype。

dtype_backend{“numpy_nullable”、“pyarrow”},默认为 NumPy 支持的 DataFrames

使用哪种 dtype 后端,例如 DataFrame 是否应具有 NumPy 数组,当设置“numpy_nullable”时,所有具有可空实现的 dtype 都将使用可空 dtype,如果设置“pyarrow”,则所有 dtype 都将使用 pyarrow。

dtype_backends 仍处于实验阶段。

新版本 2.0 中添加。

engine{'c''python''pyarrow'}

要使用的解析引擎。C 和 pyarrow 引擎更快,而 python 引擎目前更完整。目前仅 pyarrow 引擎支持多线程。

新版本 1.4.0 中添加了“pyarrow”引擎作为实验性引擎,并且某些功能不受支持,或者可能无法正确工作,使用此引擎。

转换器字典,默认为None

用于转换某些列中值的函数字典。键可以是整数或列标签。

true_values 列表,默认为None

要视为True的值。

false_values 列表,默认为None

要视为False的值。

skipinitialspace 布尔值,默认为False

在分隔符后跳过空格。

skiprows 类似列表或整数,默认为None

要跳过的行号(从 0 开始索引)或文件开头要跳过的行数(int)。

如果可调用,将对行索引评估可调用函数,如果应跳过该行则返回 True,否则返回 False:

In [6]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3"

In [7]: pd.read_csv(StringIO(data))
Out[7]: 
 col1 col2  col3
0    a    b     1
1    a    b     2
2    c    d     3

In [8]: pd.read_csv(StringIO(data), skiprows=lambda x: x % 2 != 0)
Out[8]: 
 col1 col2  col3
0    a    b     2 

skipfooter 整数,默认为0

要跳过文件底部的行数(与 engine=’c’不兼容)。

nrows 整数,默认为None

要读取的文件行数。用于读取大文件的片段。

low_memory 布尔值,默认为True

在内部以块的形式处理文件,从而在解析时使用更少的内存,但可能会混合类型推断。为了确保没有混合类型,要么设置为False,要么使用dtype参数指定类型。请注意,无论如何整个文件都会被读入单个DataFrame中,使用chunksizeiterator参数以块返回数据。(仅适用于 C 解析器)

memory_map 布尔值,默认为 False

如果为filepath_or_buffer提供了文件路径,则直接将文件对象映射到内存并直接从那里访问数据。使用此选项可以提高性能,因为不再有任何 I/O 开销。

NA 和缺失数据处理

na_valuesscalar、str、类似列表或字典,默认为None

附加字符串以识别为 NA/NaN。如果传递了字典,则特定于每列的 NA 值。请参阅下面的 na values const 以获取默认情况下解释为 NaN 的值列表。

keep_default_na 布尔值,默认为True

在解析数据时是否包含默认的 NaN 值。根据是否传入 na_values,行为如下:

  • 如果 keep_default_na 为True,并且指定了 na_values,则 na_values 将附加到用于解析的默认 NaN 值。

  • 如果 keep_default_na 为True,并且未指定 na_values,则仅使用默认 NaN 值进行解析。

  • 如果 keep_default_na 为False,并且指定了 na_values,则仅使用指定的 NaN 值 na_values 进行解析。

  • 如果 keep_default_na 为False,并且未指定 na_values,则不会将任何字符串解析为 NaN。

请注意,如果传递了 na_filter 为False,则 keep_default_na 和 na_values 参数将被忽略。

na_filter 布尔值,默认为True

检测缺失值标记(空字符串和 na_values 的值)。在没有任何 NA 的数据中,传递 na_filter=False 可以提高读取大型文件的性能。

verbose 布尔值,默认为False

指示放置在非数字列中的 NA 值的数量。

skip_blank_lines 布尔值,默认为True

如果为True,则跳过空行而不解释为 NaN 值。

日期时间处理

parse_dates 布尔值或整数或名称列表或列表或字典的列表,默认为False

  • 如果为True -> 尝试解析索引。

  • 如果为[1, 2, 3] -> 尝试将列 1、2、3 分别解析为单独的日期列。

  • 如果为[[1, 3]] -> 合并列 1 和 3 并解析为单个日期列。

  • 如果为{'foo': [1, 3]} -> 解析列 1、3 为日期并将结果命名为‘foo’。

注意

存在用于 iso8601 格式日期的快速路径。

infer_datetime_format 布尔值,默认为False

如果为True并且启用了 parse_dates 列,则尝试推断日期时间格式以加快处理速度。

自版本 2.0.0 起弃用:此参数的严格版本现在是默认值,传递它不会产生任何效果。

keep_date_col 布尔值,默认为False

如果为True并且 parse_dates 指定了组合多个列,则保留原始列。

date_parser 函数,默认为None

将一系列字符串列转换为日期时间实例数组的函数。默认使用dateutil.parser.parser进行转换。pandas 将尝试以三种不同的方式调用 date_parser,如果发生异常,则会尝试下一个:1)将一个或多个数组(由 parse_dates 定义)作为参数传递;2)将由 parse_dates 定义的列的字符串值(按行)连接成单个数组并传递;3)对每一行使用一个或多个字符串(对应于由 parse_dates 定义的列)作为参数调用 date_parser。

自版本 2.0.0 起弃用:改用date_format,或者读取为object,然后根据需要应用to_datetime()

date_format 字符串或列->格式字典,默认为None

如果与parse_dates一起使用,将根据此格式解析日期。对于更复杂的情况,请将其读取为object,然后根据需要应用to_datetime()

在 2.0.0 版本中新增。

dayfirstboolean,默认为False

DD/MM 格式的日期,国际和欧洲格式。

cache_datesboolean,默认为 True

如果为 True,则使用唯一的转换日期缓存应用日期时间转换。在解析重复日期字符串时可能会产生显著加速,特别是带有时区偏移的日期字符串。

迭代

iteratorboolean,默认为False

返回用于迭代或获取块的TextFileReader对象。

chunksizeint,默认为None

返回用于迭代的TextFileReader对象。请参见下面的迭代和分块。

引用、压缩和文件格式

compression{'infer''gzip''bz2''zip''xz''zstd'Nonedict},默认为'infer'

用于在磁盘上的数据进行即时解压缩。如果为'infer',则如果filepath_or_buffer是以'.gz'、'.bz2'、'.zip'、'.xz'、'.zst'结尾的路径,则使用 gzip、bz2、zip、xz 或 zstandard,否则不进行解压缩。如果使用'zip',ZIP 文件必须只包含一个要读取的数据文件。设置为None表示不进行解压缩。也可以是一个字典,其中键为'method',设置为其中之一{'zip'、'gzip'、'bz2'、'zstd'},其他键值对将转发给zipfile.ZipFilegzip.GzipFilebz2.BZ2Filezstandard.ZstdDecompressor。例如,可以传递以下内容以获得更快的压缩和创建可重现的 gzip 存档:compression={'method': 'gzip', 'compresslevel': 1, 'mtime': 1}

在 1.2.0 版本中更改:以前的版本将'gzip'的字典条目转发给gzip.open

thousandsstr,默认为None

千位分隔符。

decimalstr,默认为'.'

用作十进制点的字符。例如,对于欧洲数据,请使用','

float_precisionstring,默认为 None

指定 C 引擎应使用哪个转换器来处理浮点值。选项为None表示普通转换器,high表示高精度转换器,round_trip表示往返转换器。

lineterminatorstr(长度为 1),默认为None

用于将文件分成行的字符。仅与 C 解析器有效。

quotecharstr(长度为 1)

用于表示引号项的开始和结束的字符。引号项可以包括分隔符,它将被忽略。

quotingint 或csv.QUOTE_*实例,默认为0

控制字段引用行为的csv.QUOTE_*常量。使用QUOTE_MINIMAL(0)、QUOTE_ALL(1)、QUOTE_NONNUMERIC(2)或QUOTE_NONE(3)中的一个。

doublequoteboolean,默认为True

当指定了quotechar并且quoting不是QUOTE_NONE时,指示是否将字段内连续的两个quotechar元素解释为单个quotechar元素。

escapecharstr(长度为 1),默认为None

当引号为 QUOTE_NONE 时,用于转义分隔符的单字符字符串。

commentstr,默认为 None

表示不应解析行的其余部分。如果在行的开头找到,则整行将被忽略。此参数必须是一个单字符。像空行一样(只要skip_blank_lines=True),完全注释的行由参数header忽略,但不由skiprows忽略。例如,如果comment='#',使用header=0解析‘#empty\na,b,c\n1,2,3’将导致‘a,b,c’被视为标题。

encodingstr,默认为 None

读取/写入 UTF 时要使用的编码(例如,'utf-8')。Python 标准编码列表

dialectstr 或csv.Dialect 实例,默认为 None

如果提供了此参数,它将覆盖以下参数的值(默认值或非默认值):delimiterdoublequoteescapecharskipinitialspacequotecharquoting。如果需要覆盖值,将发出 ParserWarning。有关更多详细信息,请参见csv.Dialect文档。

错误处理

on_bad_lines(‘error’, ‘warn’, ‘skip’), 默认为 ‘error’

指定遇到坏行(字段太多的行)时要执行的操作。允许的值为:

  • ‘error’,在遇到坏行时引发 ParserError。

  • ‘warn’,在遇到坏行时打印警告并跳过该行。

  • ‘skip’,在遇到坏行时跳过而不提高或警告它们。

版本 1.3.0 中的新增内容。

指定列数据类型

您可以指定整个 DataFrame 或单独列的数据类型:

In [9]: import numpy as np

In [10]: data = "a,b,c,d\n1,2,3,4\n5,6,7,8\n9,10,11"

In [11]: print(data)
a,b,c,d
1,2,3,4
5,6,7,8
9,10,11

In [12]: df = pd.read_csv(StringIO(data), dtype=object)

In [13]: df
Out[13]: 
 a   b   c    d
0  1   2   3    4
1  5   6   7    8
2  9  10  11  NaN

In [14]: df["a"][0]
Out[14]: '1'

In [15]: df = pd.read_csv(StringIO(data), dtype={"b": object, "c": np.float64, "d": "Int64"})

In [16]: df.dtypes
Out[16]: 
a      int64
b     object
c    float64
d      Int64
dtype: object 

幸运的是,pandas 提供了多种方法来确保您的列(s)只包含一个 dtype。如果您对这些概念不熟悉,可以在这里了解更多关于 dtypes 的信息,并在这里了解更多关于 pandas 中 object 转换的信息。

例如,您可以使用read_csv()converters参数:

In [17]: data = "col_1\n1\n2\n'A'\n4.22"

In [18]: df = pd.read_csv(StringIO(data), converters={"col_1": str})

In [19]: df
Out[19]: 
 col_1
0     1
1     2
2   'A'
3  4.22

In [20]: df["col_1"].apply(type).value_counts()
Out[20]: 
col_1
<class 'str'>    4
Name: count, dtype: int64 

或者您可以在读取数据后使用to_numeric()函数来强制转换数据类型,

In [21]: df2 = pd.read_csv(StringIO(data))

In [22]: df2["col_1"] = pd.to_numeric(df2["col_1"], errors="coerce")

In [23]: df2
Out[23]: 
 col_1
0   1.00
1   2.00
2    NaN
3   4.22

In [24]: df2["col_1"].apply(type).value_counts()
Out[24]: 
col_1
<class 'float'>    4
Name: count, dtype: int64 

这将把所有有效的解析转换为浮点数,将无效的解析留为 NaN

最终,如何处理包含混合数据类型的列取决于您的具体需求。在上述情况下,如果您想要将数据异常值设为NaN,那么to_numeric()可能是您最好的选择。然而,如果您希望所有数据都被强制转换,无论类型如何,那么使用read_csv()converters参数肯定值得一试。

注意

在某些情况下,读取包含混合数据类型的列的异常数据将导致数据集不一致。如果依赖 pandas 推断列的 dtype,解析引擎将会推断数据的不同块的 dtype,而不是一次推断整个数据集的 dtype。因此,您可能会得到具有混合 dtype 的列。例如,

In [25]: col_1 = list(range(500000)) + ["a", "b"] + list(range(500000))

In [26]: df = pd.DataFrame({"col_1": col_1})

In [27]: df.to_csv("foo.csv")

In [28]: mixed_df = pd.read_csv("foo.csv")

In [29]: mixed_df["col_1"].apply(type).value_counts()
Out[29]: 
col_1
<class 'int'>    737858
<class 'str'>    262144
Name: count, dtype: int64

In [30]: mixed_df["col_1"].dtype
Out[30]: dtype('O') 

将导致mixed_df包含某些列块的int dtype,而其他列块由于读取的数据中的混合 dtype 而包含str。重要的是要注意,整体列将标记为objectdtype,用于具��混合 dtype 的列。

设置dtype_backend="numpy_nullable"将导致每列都具有可空 dtype。

In [31]: data = """a,b,c,d,e,f,g,h,i,j
 ....: 1,2.5,True,a,,,,,12-31-2019,
 ....: 3,4.5,False,b,6,7.5,True,a,12-31-2019,
 ....: """
 ....: 

In [32]: df = pd.read_csv(StringIO(data), dtype_backend="numpy_nullable", parse_dates=["i"])

In [33]: df
Out[33]: 
 a    b      c  d     e     f     g     h          i     j
0  1  2.5   True  a  <NA>  <NA>  <NA>  <NA> 2019-12-31  <NA>
1  3  4.5  False  b     6   7.5  True     a 2019-12-31  <NA>

In [34]: df.dtypes
Out[34]: 
a             Int64
b           Float64
c           boolean
d    string[python]
e             Int64
f           Float64
g           boolean
h    string[python]
i    datetime64[ns]
j             Int64
dtype: object 

指定分类 dtype

可以通过指定dtype='category'dtype=CategoricalDtype(categories, ordered)直接解析Categorical列。

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'时,生成的类别将始终被解析为字符串(对象 dtype)。如果类别是数字,可以使用to_numeric()函数进行转换,或者根据需要使用另一个转换器,如to_datetime()

dtype是具有同质categories(全部为数字、全部为日期时间等)的CategoricalDtype时,转换会自动完成。

In [45]: df = pd.read_csv(StringIO(data), dtype="category")

In [46]: df.dtypes
Out[46]: 
col1    category
col2    category
col3    category
dtype: object

In [47]: df["col3"]
Out[47]: 
0    1
1    2
2    3
Name: col3, dtype: category
Categories (3, object): ['1', '2', '3']

In [48]: new_categories = pd.to_numeric(df["col3"].cat.categories)

In [49]: df["col3"] = df["col3"].cat.rename_categories(new_categories)

In [50]: df["col3"]
Out[50]: 
0    1
1    2
2    3
Name: col3, dtype: category
Categories (3, int64): [1, 2, 3] 

命名和使用列

处理列名

文件可能有或没有标题行。pandas 假定第一行应该用作列名:

In [51]: data = "a,b,c\n1,2,3\n4,5,6\n7,8,9"

In [52]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9

In [53]: pd.read_csv(StringIO(data))
Out[53]: 
 a  b  c
0  1  2  3
1  4  5  6
2  7  8  9 

通过在header中与names参数一起指定,您可以指示要使用的其他名称以及是否丢弃标题行(如果有的话):

In [54]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9

In [55]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=0)
Out[55]: 
 foo  bar  baz
0    1    2    3
1    4    5    6
2    7    8    9

In [56]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=None)
Out[56]: 
 foo bar baz
0   a   b   c
1   1   2   3
2   4   5   6
3   7   8   9 

如果标题在第一行之外的某一行中,将该行号传递给header。这将跳过前面的行:

In [57]: data = "skip this skip it\na,b,c\n1,2,3\n4,5,6\n7,8,9"

In [58]: pd.read_csv(StringIO(data), header=1)
Out[58]: 
 a  b  c
0  1  2  3
1  4  5  6
2  7  8  9 

注意

默认行为是推断列名:如果没有传递名称,则行为与header=0相同,并且列名是从文件的第一行推断出来的,如果显式传递了列名,则行为与header=None相同。 #### 处理列名

文件可能有或可能没有标题行。pandas 假定第一行应该用作列名:

In [51]: data = "a,b,c\n1,2,3\n4,5,6\n7,8,9"

In [52]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9

In [53]: pd.read_csv(StringIO(data))
Out[53]: 
 a  b  c
0  1  2  3
1  4  5  6
2  7  8  9 

通过在header中与names参数一起指定其他名称,您可以指示要使用的其他名称以及是否丢弃标题行(如果有):

In [54]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9

In [55]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=0)
Out[55]: 
 foo  bar  baz
0    1    2    3
1    4    5    6
2    7    8    9

In [56]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=None)
Out[56]: 
 foo bar baz
0   a   b   c
1   1   2   3
2   4   5   6
3   7   8   9 

如果标题不在第一行中,则将行号传递给header。这将跳过前面的行:

In [57]: data = "skip this skip it\na,b,c\n1,2,3\n4,5,6\n7,8,9"

In [58]: pd.read_csv(StringIO(data), header=1)
Out[58]: 
 a  b  c
0  1  2  3
1  4  5  6
2  7  8  9 

注意

默认行为是推断列名:如果没有传递名称,则行为与header=0相同,并且列名是从文件的第一行推断出来的,如果显式传递了列名,则行为与header=None相同。

重复名称解析

如果文件或标题包含重复名称,pandas 默认会区分它们,以防止覆盖数据:

In [59]: data = "a,b,a\n0,1,2\n3,4,5"

In [60]: pd.read_csv(StringIO(data))
Out[60]: 
 a  b  a.1
0  0  1    2
1  3  4    5 

不再有重复数据,因为重复列‘X’,…,‘X’变成‘X’,‘X.1’,…,‘X.N’。

过滤列(usecols

usecols参数允许您选择文件中的任何列的子集,可以使用列名称、位置编号或可调用函数:

In [61]: data = "a,b,c,d\n1,2,3,foo\n4,5,6,bar\n7,8,9,baz"

In [62]: pd.read_csv(StringIO(data))
Out[62]: 
 a  b  c    d
0  1  2  3  foo
1  4  5  6  bar
2  7  8  9  baz

In [63]: pd.read_csv(StringIO(data), usecols=["b", "d"])
Out[63]: 
 b    d
0  2  foo
1  5  bar
2  8  baz

In [64]: pd.read_csv(StringIO(data), usecols=[0, 2, 3])
Out[64]: 
 a  c    d
0  1  3  foo
1  4  6  bar
2  7  9  baz

In [65]: pd.read_csv(StringIO(data), usecols=lambda x: x.upper() in ["A", "C"])
Out[65]: 
 a  c
0  1  3
1  4  6
2  7  9 

usecols参数也可以用于指定最终结果中不使用的列:

In [66]: pd.read_csv(StringIO(data), usecols=lambda x: x not in ["a", "c"])
Out[66]: 
 b    d
0  2  foo
1  5  bar
2  8  baz 

在这种情况下,可调用函数指定我们从输出中排除“a”和“c”列。 #### 过滤列(usecols

usecols参数允许您选择文件中的任何列的子集,可以使用列名称、位置编号或可调用函数:

In [61]: data = "a,b,c,d\n1,2,3,foo\n4,5,6,bar\n7,8,9,baz"

In [62]: pd.read_csv(StringIO(data))
Out[62]: 
 a  b  c    d
0  1  2  3  foo
1  4  5  6  bar
2  7  8  9  baz

In [63]: pd.read_csv(StringIO(data), usecols=["b", "d"])
Out[63]: 
 b    d
0  2  foo
1  5  bar
2  8  baz

In [64]: pd.read_csv(StringIO(data), usecols=[0, 2, 3])
Out[64]: 
 a  c    d
0  1  3  foo
1  4  6  bar
2  7  9  baz

In [65]: pd.read_csv(StringIO(data), usecols=lambda x: x.upper() in ["A", "C"])
Out[65]: 
 a  c
0  1  3
1  4  6
2  7  9 

usecols参数也可以用于指定最终结果中不使用的列:

In [66]: pd.read_csv(StringIO(data), usecols=lambda x: x not in ["a", "c"])
Out[66]: 
 b    d
0  2  foo
1  5  bar
2  8  baz 

在这种情况下,可调用函数指定我们从输出中排除“a”和“c”列。

注释和空行

忽略行注释和空行

如果指定了comment参数,则完全注释的行将被忽略。默认情况下,完全空白的行也将被忽略。

In [67]: data = "\na,b,c\n  \n# commented line\n1,2,3\n\n4,5,6"

In [68]: print(data)

a,b,c

# commented line
1,2,3

4,5,6

In [69]: pd.read_csv(StringIO(data), comment="#")
Out[69]: 
 a  b  c
0  1  2  3
1  4  5  6 

如果skip_blank_lines=False,那么read_csv将不会忽略空行:

In [70]: data = "a,b,c\n\n1,2,3\n\n\n4,5,6"

In [71]: pd.read_csv(StringIO(data), skip_blank_lines=False)
Out[71]: 
 a    b    c
0  NaN  NaN  NaN
1  1.0  2.0  3.0
2  NaN  NaN  NaN
3  NaN  NaN  NaN
4  4.0  5.0  6.0 

警告

忽略行的存在可能会导致涉及行号的歧义;参数header使用行号(忽略注释/空行),而skiprows使用行号(包括注释/空行):

In [72]: data = "#comment\na,b,c\nA,B,C\n1,2,3"

In [73]: pd.read_csv(StringIO(data), comment="#", header=1)
Out[73]: 
 A  B  C
0  1  2  3

In [74]: data = "A,B,C\n#comment\na,b,c\n1,2,3"

In [75]: pd.read_csv(StringIO(data), comment="#", skiprows=2)
Out[75]: 
 a  b  c
0  1  2  3 

如果同时指定了headerskiprowsheader将相对于skiprows的末尾。例如:

In [76]: data = (
 ....:    "# empty\n"
 ....:    "# second empty line\n"
 ....:    "# third emptyline\n"
 ....:    "X,Y,Z\n"
 ....:    "1,2,3\n"
 ....:    "A,B,C\n"
 ....:    "1,2.,4.\n"
 ....:    "5.,NaN,10.0\n"
 ....: )
 ....: 

In [77]: print(data)
# empty
# second empty line
# third emptyline
X,Y,Z
1,2,3
A,B,C
1,2.,4.
5.,NaN,10.0

In [78]: pd.read_csv(StringIO(data), comment="#", skiprows=4, header=1)
Out[78]: 
 A    B     C
0  1.0  2.0   4.0
1  5.0  NaN  10.0 
```  #### 注释

有时文件中可能包含注释或元数据:

```py
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 
```  #### 忽略行注释和空行

如果指定了`comment`参数,则完全注释的行将被忽略。默认情况下,完全空白的行也将被忽略。

```py
In [67]: data = "\na,b,c\n  \n# commented line\n1,2,3\n\n4,5,6"

In [68]: print(data)

a,b,c

# commented line
1,2,3

4,5,6

In [69]: pd.read_csv(StringIO(data), comment="#")
Out[69]: 
 a  b  c
0  1  2  3
1  4  5  6 

如果skip_blank_lines=False,那么read_csv将不会忽略空行:

In [70]: data = "a,b,c\n\n1,2,3\n\n\n4,5,6"

In [71]: pd.read_csv(StringIO(data), skip_blank_lines=False)
Out[71]: 
 a    b    c
0  NaN  NaN  NaN
1  1.0  2.0  3.0
2  NaN  NaN  NaN
3  NaN  NaN  NaN
4  4.0  5.0  6.0 

警告

忽略行的存在可能会导致涉及行号的歧义;参数header使用行号(忽略注释/空行),而skiprows使用行号(包括注释/空行):

In [72]: data = "#comment\na,b,c\nA,B,C\n1,2,3"

In [73]: pd.read_csv(StringIO(data), comment="#", header=1)
Out[73]: 
 A  B  C
0  1  2  3

In [74]: data = "A,B,C\n#comment\na,b,c\n1,2,3"

In [75]: pd.read_csv(StringIO(data), comment="#", skiprows=2)
Out[75]: 
 a  b  c
0  1  2  3 

如果同时指定了headerskiprows,那么header将相对于skiprows的末尾。例如:

In [76]: data = (
 ....:    "# empty\n"
 ....:    "# second empty line\n"
 ....:    "# third emptyline\n"
 ....:    "X,Y,Z\n"
 ....:    "1,2,3\n"
 ....:    "A,B,C\n"
 ....:    "1,2.,4.\n"
 ....:    "5.,NaN,10.0\n"
 ....: )
 ....: 

In [77]: print(data)
# empty
# second empty line
# third emptyline
X,Y,Z
1,2,3
A,B,C
1,2.,4.
5.,NaN,10.0

In [78]: pd.read_csv(StringIO(data), comment="#", skiprows=4, header=1)
Out[78]: 
 A    B     C
0  1.0  2.0   4.0
1  5.0  NaN  10.0 

注释

有时文件中可能包含注释或元数据:

In [79]: data = (
 ....:    "ID,level,category\n"
 ....:    "Patient1,123000,x # really unpleasant\n"
 ....:    "Patient2,23000,y # wouldn't take his medicine\n"
 ....:    "Patient3,1234018,z # awesome"
 ....: )
 ....: 

In [80]: with open("tmp.csv", "w") as fh:
 ....:    fh.write(data)
 ....: 

In [81]: print(open("tmp.csv").read())
ID,level,category
Patient1,123000,x # really unpleasant
Patient2,23000,y # wouldn't take his medicine
Patient3,1234018,z # awesome 

默认情况下,解析器会将注释包含在输出中:

In [82]: df = pd.read_csv("tmp.csv")

In [83]: df
Out[83]: 
 ID    level                        category
0  Patient1   123000           x # really unpleasant
1  Patient2    23000  y # wouldn't take his medicine
2  Patient3  1234018                     z # awesome 

我们可以使用comment关键字来抑制注释:

In [84]: df = pd.read_csv("tmp.csv", comment="#")

In [85]: df
Out[85]: 
 ID    level category
0  Patient1   123000       x 
1  Patient2    23000       y 
2  Patient3  1234018       z 

处理 Unicode 数据

encoding参数应用于编码的 Unicode 数据,这将导致字节字符串在结果中被解码为 Unicode:

In [86]: from io import BytesIO

In [87]: data = b"word,length\n" b"Tr\xc3\xa4umen,7\n" b"Gr\xc3\xbc\xc3\x9fe,5"

In [88]: data = data.decode("utf8").encode("latin-1")

In [89]: df = pd.read_csv(BytesIO(data), encoding="latin-1")

In [90]: df
Out[90]: 
 word  length
0  Träumen       7
1    Grüße       5

In [91]: df["word"][1]
Out[91]: 'Grüße' 

一些将所有字符编码为多字节的格式,如 UTF-16,如果不指定编码,将无法正确解析。Python 标准编码的完整列表

索引列和尾随分隔符

如果文件的数据列数比列名数多一个,第一列将用作DataFrame的行名:

In [92]: data = "a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"

In [93]: pd.read_csv(StringIO(data))
Out[93]: 
 a    b     c
4   apple  bat   5.7
8  orange  cow  10.0 
In [94]: data = "index,a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"

In [95]: pd.read_csv(StringIO(data), index_col=0)
Out[95]: 
 a    b     c
index 
4       apple  bat   5.7
8      orange  cow  10.0 

通常情况下,您可以使用index_col选项实现此行为。

有一些特殊情况,当文件在每个数据行的末尾都有分隔符时,会使解析器混淆。要显式禁用索引列推断并丢弃最后一列,请传递index_col=False

In [96]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"

In [97]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,

In [98]: pd.read_csv(StringIO(data))
Out[98]: 
 a    b   c
4   apple  bat NaN
8  orange  cow NaN

In [99]: pd.read_csv(StringIO(data), index_col=False)
Out[99]: 
 a       b    c
0  4   apple  bat
1  8  orange  cow 

如果使用usecols选项解析数据的子集,则index_col规范是基于该子集而不是原始数据。

In [100]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"

In [101]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,

In [102]: pd.read_csv(StringIO(data), usecols=["b", "c"])
Out[102]: 
 b   c
4  bat NaN
8  cow NaN

In [103]: pd.read_csv(StringIO(data), usecols=["b", "c"], index_col=0)
Out[103]: 
 b   c
4  bat NaN
8  cow NaN 

日期处理

指定日期列

为了更好地处理日期时间数据,read_csv()使用关键字参数parse_datesdate_format,允许用户指定各种列和日期/时间格式,将输入文本数据转换为datetime对象。

最简单的情况是只传入parse_dates=True

In [104]: with open("foo.csv", mode="w") as f:
 .....:    f.write("date,A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5")
 .....: 

# Use a column as an index, and parse it as dates.
In [105]: df = pd.read_csv("foo.csv", index_col=0, parse_dates=True)

In [106]: df
Out[106]: 
 A  B  C
date 
2009-01-01  a  1  2
2009-01-02  b  3  4
2009-01-03  c  4  5

# These are Python datetime objects
In [107]: df.index
Out[107]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', name='date', freq=None) 

通常情况下,我们可能希望将日期和时间数据分开存储,或将各种日期字段分开存储。parse_dates关键字可用于指定要从中解析日期和/或时间的列的组合。

您可以将列列表的列表指定给parse_dates,生成的日期列将被添加到输出中(以不影响现有列顺序),新列名将是组件列名的连接:

In [108]: data = (
 .....:    "KORD,19990127, 19:00:00, 18:56:00, 0.8100\n"
 .....:    "KORD,19990127, 20:00:00, 19:56:00, 0.0100\n"
 .....:    "KORD,19990127, 21:00:00, 20:56:00, -0.5900\n"
 .....:    "KORD,19990127, 21:00:00, 21:18:00, -0.9900\n"
 .....:    "KORD,19990127, 22:00:00, 21:56:00, -0.5900\n"
 .....:    "KORD,19990127, 23:00:00, 22:56:00, -0.5900"
 .....: )
 .....: 

In [109]: with open("tmp.csv", "w") as fh:
 .....:    fh.write(data)
 .....: 

In [110]: df = pd.read_csv("tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]])

In [111]: df
Out[111]: 
 1_2                 1_3     0     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59 

默认情况下,解析器会删除组件日期列,但您可以选择通过keep_date_col关键字保留它们:

In [112]: df = pd.read_csv(
 .....:    "tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]], keep_date_col=True
 .....: )
 .....: 

In [113]: df
Out[113]: 
 1_2                 1_3     0  ...          2          3     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  ...   19:00:00   18:56:00  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  ...   20:00:00   19:56:00  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD  ...   21:00:00   20:56:00 -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD  ...   21:00:00   21:18:00 -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD  ...   22:00:00   21:56:00 -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD  ...   23:00:00   22:56:00 -0.59

[6 rows x 7 columns] 

请注意,如果您希望将多个列合并为单个日期列,则必须使用嵌套列表。换句话说,parse_dates=[1, 2]表示第二列和第三列应分别解析为单独的日期列,而parse_dates=[[1, 2]]表示这两列应解析为单个列。

您还可以使用字典来指定自定义名称列:

In [114]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}

In [115]: df = pd.read_csv("tmp.csv", header=None, parse_dates=date_spec)

In [116]: df
Out[116]: 
 nominal              actual     0     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59 

重要的是要记住,如果要将多个文本列解析为单个日期列,则会在数据前添加一个新列。index_col规范基于这组新列而不是原始数据列:

In [117]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}

In [118]: df = pd.read_csv(
 .....:    "tmp.csv", header=None, parse_dates=date_spec, index_col=0
 .....: )  # index is the nominal column
 .....: 

In [119]: df
Out[119]: 
 actual     0     4
nominal 
1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59 

注意

如果列或索引包含无法解析的日期,则整个列或索引将作为对象数据类型不变返回。对于非标准日期时间解析,请在pd.read_csv之后使用to_datetime()

注意

read_csv 对于解析 iso8601 格式的日期时间字符串有一个快速路径,例如“2000-01-01T00:01:02+00:00”和类似变体。如果您可以安排数据以这种格式存储日期时间,加载时间将显着更快,已观察到约 20 倍的速度。

自版本 2.2.0 起弃用:在 read_csv 中合并日期列已弃用。请改为在相关结果列上使用pd.to_datetime

日期解析函数

最后,解析器允许您指定自定义date_format。在性能方面,应按顺序尝试这些日期解析方法:

  1. 如果知道格式,请使用date_format,例如:date_format="%d/%m/%Y"date_format={column_name: "%d/%m/%Y"}

  2. 如果不同列有不同格式,或者想要传递任何额外选项(如utc)给to_datetime,那么应该将数据读取为object dtype,然后使用to_datetime

解析具有混合时区的 CSV

pandas 无法本地表示具有混合时区的列或索引。如果您的 CSV 文件包含具有混合时区的列,则默认结果将是一个带有字符串的对象 dtype 列,即使使用parse_dates也是如此。要将混合时区值解析为日期时间列,请以object dtype 读取,然后调用to_datetime()并使用utc=True

In [120]: content = """\
 .....: a
 .....: 2000-01-01T00:00:00+05:00
 .....: 2000-01-01T00:00:00+06:00"""
 .....: 

In [121]: df = pd.read_csv(StringIO(content))

In [122]: df["a"] = pd.to_datetime(df["a"], utc=True)

In [123]: df["a"]
Out[123]: 
0   1999-12-31 19:00:00+00:00
1   1999-12-31 18:00:00+00:00
Name: a, dtype: datetime64[ns, UTC] 
```  #### 推断日期时间格式

这里有一些可以猜测的日期时间字符串示例(都代表 2011 年 12 月 30 日 00:00:00):

+   “20111230”

+   “2011/12/30”

+   “20111230 00:00:00”

+   “12/30/2011 00:00:00”

+   “30/Dec/2011 00:00:00”

+   “30/December/2011 00:00:00”

请注意,格式推断对`dayfirst`敏感。使用`dayfirst=True`,它会猜测“01/12/2011”是 12 月 1 日。使用`dayfirst=False`(默认)它会猜测“01/12/2011”是 1 月 12 日。

如果尝试解析日期字符串列,pandas 将尝试从第一个非 NaN 元素猜测格式,然后使用该格式解析列的其余部分。如果 pandas 无法猜测格式(例如,如果您的第一个字符串是`'01 December US/Pacific 2000'`),则会发出警告,并且每一行将通过`dateutil.parser.parse`单独解析。解析日期的最安全方式是明确设置`format=`。

```py
In [124]: df = pd.read_csv(
 .....:    "foo.csv",
 .....:    index_col=0,
 .....:    parse_dates=True,
 .....: )
 .....: 

In [125]: df
Out[125]: 
 A  B  C
date 
2009-01-01  a  1  2
2009-01-02  b  3  4
2009-01-03  c  4  5 

如果在同一列中有混合的日期时间格式,可以传递format='mixed'

In [126]: data = StringIO("date\n12 Jan 2000\n2000-01-13\n")

In [127]: df = pd.read_csv(data)

In [128]: df['date'] = pd.to_datetime(df['date'], format='mixed')

In [129]: df
Out[129]: 
 date
0 2000-01-12
1 2000-01-13 

或者,如果您的日期时间格式都是 ISO8601(可能不完全相同格式):

In [130]: data = StringIO("date\n2020-01-01\n2020-01-01 03:00\n")

In [131]: df = pd.read_csv(data)

In [132]: df['date'] = pd.to_datetime(df['date'], format='ISO8601')

In [133]: df
Out[133]: 
 date
0 2020-01-01 00:00:00
1 2020-01-01 03:00:00 

国际日期格式

虽然美国日期格式倾向于 MM/DD/YYYY,但许多国际格式使用的是 DD/MM/YYYY。为了方便起见,提供了一个 dayfirst 关键字:

In [134]: data = "date,value,cat\n1/6/2000,5,a\n2/6/2000,10,b\n3/6/2000,15,c"

In [135]: print(data)
date,value,cat
1/6/2000,5,a
2/6/2000,10,b
3/6/2000,15,c

In [136]: with open("tmp.csv", "w") as fh:
 .....:    fh.write(data)
 .....: 

In [137]: pd.read_csv("tmp.csv", parse_dates=[0])
Out[137]: 
 date  value cat
0 2000-01-06      5   a
1 2000-02-06     10   b
2 2000-03-06     15   c

In [138]: pd.read_csv("tmp.csv", dayfirst=True, parse_dates=[0])
Out[138]: 
 date  value cat
0 2000-06-01      5   a
1 2000-06-02     10   b
2 2000-06-03     15   c 

将 CSV 写入二进制文件对象

自版本 1.2.0 新增。

df.to_csv(..., mode="wb") 允许将 CSV 写入以二进制模式打开的文件对象。在大多数情况下,不需要指定 mode,因为 Pandas 将自动检测文件对象是以文本模式还是二进制模式打开的。

In [139]: import io

In [140]: data = pd.DataFrame([0, 1, 2])

In [141]: buffer = io.BytesIO()

In [142]: data.to_csv(buffer, encoding="utf-8", compression="gzip") 

指定日期列

为了更好地处理日期时间数据,read_csv() 使用关键参数 parse_datesdate_format,允许用户指定各种列和日期/时间格式,将输入文本数据转换为 datetime 对象。

最简单的情况就是只传入 parse_dates=True

In [104]: with open("foo.csv", mode="w") as f:
 .....:    f.write("date,A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5")
 .....: 

# Use a column as an index, and parse it as dates.
In [105]: df = pd.read_csv("foo.csv", index_col=0, parse_dates=True)

In [106]: df
Out[106]: 
 A  B  C
date 
2009-01-01  a  1  2
2009-01-02  b  3  4
2009-01-03  c  4  5

# These are Python datetime objects
In [107]: df.index
Out[107]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', name='date', freq=None) 

通常情况下,我们可能希望将日期和时间数据分开存储,或者将各种日期字段分开存储。parse_dates 关键字可用于指定要从中解析日期和/或时间的列的组合。

您可以将列列表的列表指定为 parse_dates,生成的日期列将添加到输出中(以不影响现有列顺序)并且新列名称将是组件列名称的连接:

In [108]: data = (
 .....:    "KORD,19990127, 19:00:00, 18:56:00, 0.8100\n"
 .....:    "KORD,19990127, 20:00:00, 19:56:00, 0.0100\n"
 .....:    "KORD,19990127, 21:00:00, 20:56:00, -0.5900\n"
 .....:    "KORD,19990127, 21:00:00, 21:18:00, -0.9900\n"
 .....:    "KORD,19990127, 22:00:00, 21:56:00, -0.5900\n"
 .....:    "KORD,19990127, 23:00:00, 22:56:00, -0.5900"
 .....: )
 .....: 

In [109]: with open("tmp.csv", "w") as fh:
 .....:    fh.write(data)
 .....: 

In [110]: df = pd.read_csv("tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]])

In [111]: df
Out[111]: 
 1_2                 1_3     0     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59 

默认情况下,解析器会删除组件日期列,但您可以通过 keep_date_col 关键字选择保留它们:

In [112]: df = pd.read_csv(
 .....:    "tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]], keep_date_col=True
 .....: )
 .....: 

In [113]: df
Out[113]: 
 1_2                 1_3     0  ...          2          3     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  ...   19:00:00   18:56:00  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  ...   20:00:00   19:56:00  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD  ...   21:00:00   20:56:00 -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD  ...   21:00:00   21:18:00 -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD  ...   22:00:00   21:56:00 -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD  ...   23:00:00   22:56:00 -0.59

[6 rows x 7 columns] 

请注意,如果您希望将多个列合并为单个日期列,则必须使用嵌套列表。换句话说,parse_dates=[1, 2] 表示第二和第三列应分别解析为单独的日期列,而 parse_dates=[[1, 2]] 表示这两列应解析为单个列。

您还可以使用字典指定自定义列名:

In [114]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}

In [115]: df = pd.read_csv("tmp.csv", header=None, parse_dates=date_spec)

In [116]: df
Out[116]: 
 nominal              actual     0     4
0 1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59 

重要的是要记住,如果要将多个文本列解析为单个日期列,则会在数据前添加新列。index_col 规范是基于这组新列而不是原始数据列的:

In [117]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}

In [118]: df = pd.read_csv(
 .....:    "tmp.csv", header=None, parse_dates=date_spec, index_col=0
 .....: )  # index is the nominal column
 .....: 

In [119]: df
Out[119]: 
 actual     0     4
nominal 
1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  0.81
1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  0.01
1999-01-27 21:00:00 1999-01-27 20:56:00  KORD -0.59
1999-01-27 21:00:00 1999-01-27 21:18:00  KORD -0.99
1999-01-27 22:00:00 1999-01-27 21:56:00  KORD -0.59
1999-01-27 23:00:00 1999-01-27 22:56:00  KORD -0.59 

注意

如果列或索引包含无法解析的日期,则整个列或索引将以对象数据类型返回。对于非标准的日期时间解析,请在 pd.read_csv 后使用 to_datetime()

注意

read_csv 对于解析 iso8601 格式的日期时间字符串有一个快速路径,例如“2000-01-01T00:01:02+00:00”和类似变体。如果您可以安排数据以这种格式存储日期时间,加载时间将显着更快,已观察到约 20 倍的速度。

自版本 2.2.0 起已弃用:在 read_csv 中合并日期列已弃用。请改用 pd.to_datetime 处理相关结果列。

日期解析函数

最后,解析器允许您指定自定义的date_format。从性能角度考虑,应按顺序尝试这些日期解析方法:

  1. 如果您知道格式,请使用date_format,例如:date_format="%d/%m/%Y"date_format={column_name: "%d/%m/%Y"}

  2. 如果您希望为不同列使用不同格式,或者想要传递任何额外选项(如utc)给to_datetime,那么您应该将数据读取为object dtype,然后使用to_datetime

解析具有混合时区的 CSV

pandas 无法原生表示具有混合时区的列或索引。如果您的 CSV 文件包含具有混合时区的列,即使使用parse_dates,默认结果也将是一个带有字符串的 object-dtype 列。要将混合时区值解析为日期时间列,请以object dtype 读取,然后调用to_datetime()并设置utc=True

In [120]: content = """\
 .....: a
 .....: 2000-01-01T00:00:00+05:00
 .....: 2000-01-01T00:00:00+06:00"""
 .....: 

In [121]: df = pd.read_csv(StringIO(content))

In [122]: df["a"] = pd.to_datetime(df["a"], utc=True)

In [123]: df["a"]
Out[123]: 
0   1999-12-31 19:00:00+00:00
1   1999-12-31 18:00:00+00:00
Name: a, dtype: datetime64[ns, UTC] 

推断日期时间格式

以下是一些可以被猜测的日期时间字符串示例(都代表 2011 年 12 月 30 日 00:00:00):

  • “20111230”

  • “2011/12/30”

  • “20111230 00:00:00”

  • “12/30/2011 00:00:00”

  • “30/Dec/2011 00:00:00”

  • “30/December/2011 00:00:00”

请注意,格式推断对dayfirst很敏感。当dayfirst=True时,它会猜测“01/12/2011”是 12 月 1 日。当dayfirst=False(默认)时,它会猜测“01/12/2011”是 1 月 12 日。

如果尝试解析日期字符串列,pandas 将尝试从第一个非 NaN 元素猜测格式,然后使用该格式解析列的其余部分。如果 pandas 无法猜测格式(例如,如果您的第一个字符串是'01 December US/Pacific 2000'),则会发出警告,并且每行将通过dateutil.parser.parse单独解析。解析日期的最安全方式是明确设置format=

In [124]: df = pd.read_csv(
 .....:    "foo.csv",
 .....:    index_col=0,
 .....:    parse_dates=True,
 .....: )
 .....: 

In [125]: df
Out[125]: 
 A  B  C
date 
2009-01-01  a  1  2
2009-01-02  b  3  4
2009-01-03  c  4  5 

如果您在同一列中有混合的日期时间格式,可以传递format='mixed'

In [126]: data = StringIO("date\n12 Jan 2000\n2000-01-13\n")

In [127]: df = pd.read_csv(data)

In [128]: df['date'] = pd.to_datetime(df['date'], format='mixed')

In [129]: df
Out[129]: 
 date
0 2000-01-12
1 2000-01-13 

或者,如果您的日期时间格式都是 ISO8601(可能不完全相同格式):

In [130]: data = StringIO("date\n2020-01-01\n2020-01-01 03:00\n")

In [131]: df = pd.read_csv(data)

In [132]: df['date'] = pd.to_datetime(df['date'], format='ISO8601')

In [133]: df
Out[133]: 
 date
0 2020-01-01 00:00:00
1 2020-01-01 03:00:00 

国际日期格式

尽管美国的日期格式倾向于是 MM/DD/YYYY,但许多国际格式使用的是 DD/MM/YYYY。为了方便起见,提供了一个dayfirst关键字:

In [134]: data = "date,value,cat\n1/6/2000,5,a\n2/6/2000,10,b\n3/6/2000,15,c"

In [135]: print(data)
date,value,cat
1/6/2000,5,a
2/6/2000,10,b
3/6/2000,15,c

In [136]: with open("tmp.csv", "w") as fh:
 .....:    fh.write(data)
 .....: 

In [137]: pd.read_csv("tmp.csv", parse_dates=[0])
Out[137]: 
 date  value cat
0 2000-01-06      5   a
1 2000-02-06     10   b
2 2000-03-06     15   c

In [138]: pd.read_csv("tmp.csv", dayfirst=True, parse_dates=[0])
Out[138]: 
 date  value cat
0 2000-06-01      5   a
1 2000-06-02     10   b
2 2000-06-03     15   c 

将 CSV 写入二进制文件对象

版本 1.2.0 中的新功能。

df.to_csv(..., mode="wb")允许将 CSV 写入以二进制模式打开的文件对象。在大多数情况下,不需要指定mode,因为 Pandas 将自动检测文件对象是以文本模式还是二进制模式打开。

In [139]: import io

In [140]: data = pd.DataFrame([0, 1, 2])

In [141]: buffer = io.BytesIO()

In [142]: data.to_csv(buffer, encoding="utf-8", compression="gzip") 

指定浮点数转换方法

参数float_precision可以在使用 C 引擎解析时指定使用特定的浮点数转换器。选项包括普通转换器、高精度转换器和往返转换器(在写入文件后保证往返值)。例如:

In [143]: val = "0.3066101993807095471566981359501369297504425048828125"

In [144]: data = "a,b,c\n1,2,{0}".format(val)

In [145]: abs(
 .....:    pd.read_csv(
 .....:        StringIO(data),
 .....:        engine="c",
 .....:        float_precision=None,
 .....:    )["c"][0] - float(val)
 .....: )
 .....: 
Out[145]: 5.551115123125783e-17

In [146]: abs(
 .....:    pd.read_csv(
 .....:        StringIO(data),
 .....:        engine="c",
 .....:        float_precision="high",
 .....:    )["c"][0] - float(val)
 .....: )
 .....: 
Out[146]: 5.551115123125783e-17

In [147]: abs(
 .....:    pd.read_csv(StringIO(data), engine="c", float_precision="round_trip")["c"][0]
 .....:    - float(val)
 .....: )
 .....: 
Out[147]: 0.0 

千位分隔符

对于使用千位分隔符编写的大数字,您可以将 thousands 关键字设置为长度为 1 的字符串,以便整数被正确解析:

默认情况下,带有千位分隔符的数字将被解析为字符串:

In [148]: data = (
 .....:    "ID|level|category\n"
 .....:    "Patient1|123,000|x\n"
 .....:    "Patient2|23,000|y\n"
 .....:    "Patient3|1,234,018|z"
 .....: )
 .....: 

In [149]: with open("tmp.csv", "w") as fh:
 .....:    fh.write(data)
 .....: 

In [150]: df = pd.read_csv("tmp.csv", sep="|")

In [151]: df
Out[151]: 
 ID      level category
0  Patient1    123,000        x
1  Patient2     23,000        y
2  Patient3  1,234,018        z

In [152]: df.level.dtype
Out[152]: dtype('O') 

thousands 关键字允许整数被正确解析:

In [153]: df = pd.read_csv("tmp.csv", sep="|", thousands=",")

In [154]: df
Out[154]: 
 ID    level category
0  Patient1   123000        x
1  Patient2    23000        y
2  Patient3  1234018        z

In [155]: df.level.dtype
Out[155]: dtype('int64') 

NA 值

要控制哪些值被解析为缺失值(用 NaN 表示),请在 na_values 中指定一个字符串。如果指定一个字符串列表,则其中的所有值都被视为缺失值。如果指定一个数字(一个 float,比如 5.0 或一个 integer,比如 5),则相应的等效值也将被视为缺失值(在这种情况下,实际上 [5.0, 5] 被识别为 NaN)。

要完全覆盖被识别为缺失的默认值,请指定 keep_default_na=False

默认识别的 NaN 值为 ['-1.#IND', '1.#QNAN', '1.#IND', '-1.#QNAN', '#N/A N/A', '#N/A', 'N/A', 'n/a', 'NA', '<NA>', '#NA', 'NULL', 'null', 'NaN', '-NaN', 'nan', '-nan', 'None', '']

让我们考虑一些例子:

pd.read_csv("path_to_file.csv", na_values=[5]) 

在上面的示例中,55.0 将被识别为 NaN,除了默认值。字符串首先被解释为数值 5,然后作为 NaN

pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=[""]) 

在上面,只有空字段将被识别为 NaN

pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=["NA", "0"]) 

在上面,作为字符串的 NA0 都被视为 NaN

pd.read_csv("path_to_file.csv", na_values=["Nope"]) 

默认值,除了字符串 "Nope" 外,也被识别为 NaN

无穷大

类似 inf 的值将被解析为 np.inf(正无穷大),-inf 将被解析为 -np.inf(负无穷大)。这些将忽略值的大小写,意味着 Inf 也将被解析为 np.inf

布尔值

常见值 TrueFalseTRUEFALSE 都被识别为布尔值。偶尔您可能希望将其他值识别为布尔值���为此,请使用 true_valuesfalse_values 选项如下:

In [156]: data = "a,b,c\n1,Yes,2\n3,No,4"

In [157]: print(data)
a,b,c
1,Yes,2
3,No,4

In [158]: pd.read_csv(StringIO(data))
Out[158]: 
 a    b  c
0  1  Yes  2
1  3   No  4

In [159]: pd.read_csv(StringIO(data), true_values=["Yes"], false_values=["No"])
Out[159]: 
 a      b  c
0  1   True  2
1  3  False  4 

处理“错误”行

一些文件可能存在字段过少或过多的格式不正确的行。字段过少的行将在尾部填充 NA 值。字段过多的行将默认引发错误:

In [160]: data = "a,b,c\n1,2,3\n4,5,6,7\n8,9,10"

In [161]: pd.read_csv(StringIO(data))
---------------------------------------------------------------------------
ParserError  Traceback (most recent call last)
Cell In[161], line 1
----> 1 pd.read_csv(StringIO(data))

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1026, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
  1013 kwds_defaults = _refine_defaults_read(
  1014     dialect,
  1015     delimiter,
   (...)
  1022     dtype_backend=dtype_backend,
  1023 )
  1024 kwds.update(kwds_defaults)
-> 1026 return _read(filepath_or_buffer, kwds)

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:626, in _read(filepath_or_buffer, kwds)
  623     return parser
  625 with parser:
--> 626     return parser.read(nrows)

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1923, in TextFileReader.read(self, nrows)
  1916 nrows = validate_integer("nrows", nrows)
  1917 try:
  1918     # error: "ParserBase" has no attribute "read"
  1919     (
  1920         index,
  1921         columns,
  1922         col_dict,
-> 1923     ) = self._engine.read(  # type: ignore[attr-defined]
  1924         nrows
  1925     )
  1926 except Exception:
  1927     self.close()

File ~/work/pandas/pandas/pandas/io/parsers/c_parser_wrapper.py:234, in CParserWrapper.read(self, nrows)
  232 try:
  233     if self.low_memory:
--> 234         chunks = self._reader.read_low_memory(nrows)
  235         # destructive to chunks
  236         data = _concatenate_chunks(chunks)

File parsers.pyx:838, in pandas._libs.parsers.TextReader.read_low_memory()

File parsers.pyx:905, in pandas._libs.parsers.TextReader._read_rows()

File parsers.pyx:874, in pandas._libs.parsers.TextReader._tokenize_rows()

File parsers.pyx:891, in pandas._libs.parsers.TextReader._check_tokenize_status()

File parsers.pyx:2061, in pandas._libs.parsers.raise_parser_error()

ParserError: Error tokenizing data. C error: Expected 3 fields in line 3, saw 4 

您可以选择跳过错误行:

In [162]: data = "a,b,c\n1,2,3\n4,5,6,7\n8,9,10"

In [163]: pd.read_csv(StringIO(data), on_bad_lines="skip")
Out[163]: 
 a  b   c
0  1  2   3
1  8  9  10 

1.4.0 版本中的新功能。

或者传递一个可调用函数来处理错误行,如果 engine="python"。错误行将是由 sep 分割的字符串列表:

In [164]: external_list = []

In [165]: def bad_lines_func(line):
 .....:    external_list.append(line)
 .....:    return line[-3:]
 .....: 

In [166]: external_list
Out[166]: [] 

注意

可调用函数将仅处理字段过多的行。由其他错误引起的错误行将被静默跳过。

In [167]: bad_lines_func = lambda line: print(line)

In [168]: data = 'name,type\nname a,a is of type a\nname b,"b\" is of type b"'

In [169]: data
Out[169]: 'name,type\nname a,a is of type a\nname b,"b" is of type b"'

In [170]: pd.read_csv(StringIO(data), on_bad_lines=bad_lines_func, engine="python")
Out[170]: 
 name            type
0  name a  a is of type a 

在这种情况下,该行未被处理,因为这里的“错误行”是由转义字符引起的。

您还可以使用 usecols 参数来消除一些行中出现但其他行中没有的多余列数据:

In [171]: pd.read_csv(StringIO(data), usecols=[0, 1, 2])
---------------------------------------------------------------------------
ValueError  Traceback (most recent call last)
Cell In[171], line 1
----> 1 pd.read_csv(StringIO(data), usecols=[0, 1, 2])

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1026, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
  1013 kwds_defaults = _refine_defaults_read(
  1014     dialect,
  1015     delimiter,
   (...)
  1022     dtype_backend=dtype_backend,
  1023 )
  1024 kwds.update(kwds_defaults)
-> 1026 return _read(filepath_or_buffer, kwds)

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:620, in _read(filepath_or_buffer, kwds)
  617 _validate_names(kwds.get("names", None))
  619 # Create the parser.
--> 620 parser = TextFileReader(filepath_or_buffer, **kwds)
  622 if chunksize or iterator:
  623     return parser

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1620, in TextFileReader.__init__(self, f, engine, **kwds)
  1617     self.options["has_index_names"] = kwds["has_index_names"]
  1619 self.handles: IOHandles | None = None
-> 1620 self._engine = self._make_engine(f, self.engine)

File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1898, in TextFileReader._make_engine(self, f, engine)
  1895     raise ValueError(msg)
  1897 try:
-> 1898     return mappingengine
  1899 except Exception:
  1900     if self.handles is not None:

File ~/work/pandas/pandas/pandas/io/parsers/c_parser_wrapper.py:155, in CParserWrapper.__init__(self, src, **kwds)
  152     # error: Cannot determine type of 'names'
  153     if len(self.names) < len(usecols):  # type: ignore[has-type]
  154         # error: Cannot determine type of 'names'
--> 155         self._validate_usecols_names(
  156             usecols,
  157             self.names,  # type: ignore[has-type]
  158         )
  160 # error: Cannot determine type of 'names'
  161 self._validate_parse_dates_presence(self.names)  # type: ignore[has-type]

File ~/work/pandas/pandas/pandas/io/parsers/base_parser.py:979, in ParserBase._validate_usecols_names(self, usecols, names)
  977 missing = [c for c in usecols if c not in names]
  978 if len(missing) > 0:
--> 979     raise ValueError(
  980         f"Usecols do not match columns, columns expected but not found: "
  981         f"{missing}"
  982     )
  984 return usecols

ValueError: Usecols do not match columns, columns expected but not found: [0, 1, 2] 

如果您想保留所有数据,包括字段过多的行,可以指定足够数量的 names。这样可以确保字段不足的行被填充为 NaN

In [172]: pd.read_csv(StringIO(data), names=['a', 'b', 'c', 'd'])
Out[172]: 
 a                b   c   d
0    name             type NaN NaN
1  name a   a is of type a NaN NaN
2  name b  b is of type b" NaN NaN 

方言

dialect 关键字在指定文件格式时提供了更大的灵活性。默认情况下,它使用 Excel 方言,但你可以指定方言名称或一个csv.Dialect 实例。

假设你有未封闭引号的数据:

In [173]: data = "label1,label2,label3\n" 'index1,"a,c,e\n' "index2,b,d,f"

In [174]: print(data)
label1,label2,label3
index1,"a,c,e
index2,b,d,f 

默认情况下,read_csv 使用 Excel 方言,并将双引号视为引号字符,这会导致在找到闭合双引号之前找到换行符时失败。

我们可以使用 dialect 来解决这个问题:

In [175]: import csv

In [176]: dia = csv.excel()

In [177]: dia.quoting = csv.QUOTE_NONE

In [178]: pd.read_csv(StringIO(data), dialect=dia)
Out[178]: 
 label1 label2 label3
index1     "a      c      e
index2      b      d      f 

所有方言选项都可以通过关键字参数单独指定:

In [179]: data = "a,b,c~1,2,3~4,5,6"

In [180]: pd.read_csv(StringIO(data), lineterminator="~")
Out[180]: 
 a  b  c
0  1  2  3
1  4  5  6 

另一个常见的方言选项是 skipinitialspace,用于跳过分隔符后的任何空格:

In [181]: data = "a, b, c\n1, 2, 3\n4, 5, 6"

In [182]: print(data)
a, b, c
1, 2, 3
4, 5, 6

In [183]: pd.read_csv(StringIO(data), skipinitialspace=True)
Out[183]: 
 a  b  c
0  1  2  3
1  4  5  6 

解析器会尽一切努力“做正确的事情”而不会变得脆弱。类型推断是一件大事。如果可以将列强制转换为整数 dtype 而不改变内容,解析器将这样做。任何非数字列将像其他 pandas 对象一样以对象 dtype 的形式出现。

引用和转义字符

嵌入字段中的引号(和其他转义字符)可以以多种方式处理。一种方法是使用反斜杠;为了正确解析这些数据,你应该传递escapechar选项:

In [184]: data = 'a,b\n"hello, \\"Bob\\", nice to see you",5'

In [185]: print(data)
a,b
"hello, \"Bob\", nice to see you",5

In [186]: pd.read_csv(StringIO(data), escapechar="\\")
Out[186]: 
 a  b
0  hello, "Bob", nice to see you  5 

具有固定宽度列的文件

read_csv()读取分隔数据时,read_fwf() 函数处理具有已知和固定列宽的数据文件。read_fwf 的函数参数与 read_csv 大致相同,有两个额外参数,并且 delimiter 参数的使用方式不同:

  • colspecs:一个给出每行固定宽度字段范围的对(元组)列表,作为半开区间(即,[from, to[)。字符串值‘infer’可以用来指示解析器尝试从数据的前 100 行中检测列规范。如果未指定,默认行为是推断。

  • widths:一个字段宽度列表,如果间隔是连续的,可以使用它来代替‘colspecs’。

  • delimiter:在固定宽度文件中视为填充字符的字符。可以用来指定字段的填充字符,如果不是空格(例如,‘~’)。

考虑一个典型的固定宽度数据文件:

In [187]: data1 = (
 .....:    "id8141    360.242940   149.910199   11950.7\n"
 .....:    "id1594    444.953632   166.985655   11788.4\n"
 .....:    "id1849    364.136849   183.628767   11806.2\n"
 .....:    "id1230    413.836124   184.375703   11916.8\n"
 .....:    "id1948    502.953953   173.237159   12468.3"
 .....: )
 .....: 

In [188]: with open("bar.csv", "w") as f:
 .....:    f.write(data1)
 .....: 

为了将此文件解析为 DataFrame,我们只需将列规范与文件名一起提供给 read_fwf 函数:

# Column specifications are a list of half-intervals
In [189]: colspecs = [(0, 6), (8, 20), (21, 33), (34, 43)]

In [190]: df = pd.read_fwf("bar.csv", colspecs=colspecs, header=None, index_col=0)

In [191]: df
Out[191]: 
 1           2        3
0 
id8141  360.242940  149.910199  11950.7
id1594  444.953632  166.985655  11788.4
id1849  364.136849  183.628767  11806.2
id1230  413.836124  184.375703  11916.8
id1948  502.953953  173.237159  12468.3 

注意当指定 header=None 参数时,解析器会自动选择列名 X.。或者,你可以只提供连续列的列宽度:

# Widths are a list of integers
In [192]: widths = [6, 14, 13, 10]

In [193]: df = pd.read_fwf("bar.csv", widths=widths, header=None)

In [194]: df
Out[194]: 
 0           1           2        3
0  id8141  360.242940  149.910199  11950.7
1  id1594  444.953632  166.985655  11788.4
2  id1849  364.136849  183.628767  11806.2
3  id1230  413.836124  184.375703  11916.8
4  id1948  502.953953  173.237159  12468.3 

解析器会处理列周围的额外空格,因此在文件中列之间有额外的分隔是可以的。

默认情况下,read_fwf将尝试通过使用文件的前 100 行来推断文件的colspecs。只有在列对齐并正确由提供的delimiter(默认分隔符是空格)分隔的情况下才能做到这一点。

In [195]: df = pd.read_fwf("bar.csv", header=None, index_col=0)

In [196]: df
Out[196]: 
 1           2        3
0 
id8141  360.242940  149.910199  11950.7
id1594  444.953632  166.985655  11788.4
id1849  364.136849  183.628767  11806.2
id1230  413.836124  184.375703  11916.8
id1948  502.953953  173.237159  12468.3 

read_fwf支持dtype参数,用于指定解析列的类型与推断类型不同。

In [197]: pd.read_fwf("bar.csv", header=None, index_col=0).dtypes
Out[197]: 
1    float64
2    float64
3    float64
dtype: object

In [198]: pd.read_fwf("bar.csv", header=None, dtype={2: "object"}).dtypes
Out[198]: 
0     object
1    float64
2     object
3    float64
dtype: object 

索引

具有“隐式”索引列的文件

考虑一个标题中的条目比数据列数少一个的文件:

In [199]: data = "A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5"

In [200]: print(data)
A,B,C
20090101,a,1,2
20090102,b,3,4
20090103,c,4,5

In [201]: with open("foo.csv", "w") as f:
 .....:    f.write(data)
 .....: 

在这种特殊情况下,read_csv假定第一列将用作DataFrame的索引:

In [202]: pd.read_csv("foo.csv")
Out[202]: 
 A  B  C
20090101  a  1  2
20090102  b  3  4
20090103  c  4  5 

请注意日期没有自动解析。在这种情况下,您需要像以前一样操作:

In [203]: df = pd.read_csv("foo.csv", parse_dates=True)

In [204]: df.index
Out[204]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', freq=None) 

读取具有MultiIndex的索引

假设您的数据由两列索引:

In [205]: data = 'year,indiv,zit,xit\n1977,"A",1.2,.6\n1977,"B",1.5,.5'

In [206]: print(data)
year,indiv,zit,xit
1977,"A",1.2,.6
1977,"B",1.5,.5

In [207]: with open("mindex_ex.csv", mode="w") as f:
 .....:    f.write(data)
 .....: 

read_csvindex_col参数可以接受列号列表,将多列转换为返回对象的MultiIndex

In [208]: df = pd.read_csv("mindex_ex.csv", index_col=[0, 1])

In [209]: df
Out[209]: 
 zit  xit
year indiv 
1977 A      1.2  0.6
 B      1.5  0.5

In [210]: df.loc[1977]
Out[210]: 
 zit  xit
indiv 
A      1.2  0.6
B      1.5  0.5 

读取具有MultiIndex的列

通过为header参数指定行位置列表,您可以读取列的MultiIndex。指定非连续行将跳过中间行。

In [211]: mi_idx = pd.MultiIndex.from_arrays([[1, 2, 3, 4], list("abcd")], names=list("ab"))

In [212]: mi_col = pd.MultiIndex.from_arrays([[1, 2], list("ab")], names=list("cd"))

In [213]: df = pd.DataFrame(np.ones((4, 2)), index=mi_idx, columns=mi_col)

In [214]: df.to_csv("mi.csv")

In [215]: print(open("mi.csv").read())
c,,1,2
d,,a,b
a,b,,
1,a,1.0,1.0
2,b,1.0,1.0
3,c,1.0,1.0
4,d,1.0,1.0

In [216]: pd.read_csv("mi.csv", header=[0, 1, 2, 3], index_col=[0, 1])
Out[216]: 
c                    1                  2
d                    a                  b
a   Unnamed: 2_level_2 Unnamed: 3_level_2
1                  1.0                1.0
2 b                1.0                1.0
3 c                1.0                1.0
4 d                1.0                1.0 

read_csv还能够解释更常见的多列索引格式。

In [217]: data = ",a,a,a,b,c,c\n,q,r,s,t,u,v\none,1,2,3,4,5,6\ntwo,7,8,9,10,11,12"

In [218]: print(data)
,a,a,a,b,c,c
,q,r,s,t,u,v
one,1,2,3,4,5,6
two,7,8,9,10,11,12

In [219]: with open("mi2.csv", "w") as fh:
 .....:    fh.write(data)
 .....: 

In [220]: pd.read_csv("mi2.csv", header=[0, 1], index_col=0)
Out[220]: 
 a         b   c 
 q  r  s   t   u   v
one  1  2  3   4   5   6
two  7  8  9  10  11  12 

注意

如果未指定index_col(例如,您没有索引,或者使用df.to_csv(..., index=False)写入它),则列索引上的任何names丢失

具有“隐式”索引列的文件

考虑一个标题中的条目比数据列数少一个的文件:

In [199]: data = "A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5"

In [200]: print(data)
A,B,C
20090101,a,1,2
20090102,b,3,4
20090103,c,4,5

In [201]: with open("foo.csv", "w") as f:
 .....:    f.write(data)
 .....: 

在这种特殊情况下,read_csv假定第一列将用作DataFrame的索引:

In [202]: pd.read_csv("foo.csv")
Out[202]: 
 A  B  C
20090101  a  1  2
20090102  b  3  4
20090103  c  4  5 

请注意日期没有自动解析。在这种情况下,您需要像以前一样操作:

In [203]: df = pd.read_csv("foo.csv", parse_dates=True)

In [204]: df.index
Out[204]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', freq=None) 

读取具有MultiIndex的索引

假设您的数据由两列索引:

In [205]: data = 'year,indiv,zit,xit\n1977,"A",1.2,.6\n1977,"B",1.5,.5'

In [206]: print(data)
year,indiv,zit,xit
1977,"A",1.2,.6
1977,"B",1.5,.5

In [207]: with open("mindex_ex.csv", mode="w") as f:
 .....:    f.write(data)
 .....: 

read_csvindex_col参数可以接受列号列表,将多列转换为返回对象的MultiIndex

In [208]: df = pd.read_csv("mindex_ex.csv", index_col=[0, 1])

In [209]: df
Out[209]: 
 zit  xit
year indiv 
1977 A      1.2  0.6
 B      1.5  0.5

In [210]: df.loc[1977]
Out[210]: 
 zit  xit
indiv 
A      1.2  0.6
B      1.5  0.5 

读取具有MultiIndex的列

通过为header参数指定行位置列表,您可以读取列的MultiIndex。指定非连续行将跳过中间行。

In [211]: mi_idx = pd.MultiIndex.from_arrays([[1, 2, 3, 4], list("abcd")], names=list("ab"))

In [212]: mi_col = pd.MultiIndex.from_arrays([[1, 2], list("ab")], names=list("cd"))

In [213]: df = pd.DataFrame(np.ones((4, 2)), index=mi_idx, columns=mi_col)

In [214]: df.to_csv("mi.csv")

In [215]: print(open("mi.csv").read())
c,,1,2
d,,a,b
a,b,,
1,a,1.0,1.0
2,b,1.0,1.0
3,c,1.0,1.0
4,d,1.0,1.0

In [216]: pd.read_csv("mi.csv", header=[0, 1, 2, 3], index_col=[0, 1])
Out[216]: 
c                    1                  2
d                    a                  b
a   Unnamed: 2_level_2 Unnamed: 3_level_2
1                  1.0                1.0
2 b                1.0                1.0
3 c                1.0                1.0
4 d                1.0                1.0 

read_csv还能够解释更常见的多列索引格式。

In [217]: data = ",a,a,a,b,c,c\n,q,r,s,t,u,v\none,1,2,3,4,5,6\ntwo,7,8,9,10,11,12"

In [218]: print(data)
,a,a,a,b,c,c
,q,r,s,t,u,v
one,1,2,3,4,5,6
two,7,8,9,10,11,12

In [219]: with open("mi2.csv", "w") as fh:
 .....:    fh.write(data)
 .....: 

In [220]: pd.read_csv("mi2.csv", header=[0, 1], index_col=0)
Out[220]: 
 a         b   c 
 q  r  s   t   u   v
one  1  2  3   4   5   6
two  7  8  9  10  11  12 

注意

如果未指定index_col(例如,您没有索引,或者使用df.to_csv(..., index=False)写入它),则列索引上的任何names丢失

自动“嗅探”分隔符

read_csv能够推断分隔(不一定是逗号分隔)的文件,因为 pandas 使用 csv 模块的csv.Sniffer类。为此,您必须指定sep=None

In [221]: df = pd.DataFrame(np.random.randn(10, 4))

In [222]: df.to_csv("tmp2.csv", sep=":", index=False)

In [223]: pd.read_csv("tmp2.csv", sep=None, engine="python")
Out[223]: 
 0         1         2         3
0  0.469112 -0.282863 -1.509059 -1.135632
1  1.212112 -0.173215  0.119209 -1.044236
2 -0.861849 -2.104569 -0.494929  1.071804
3  0.721555 -0.706771 -1.039575  0.271860
4 -0.424972  0.567020  0.276232 -1.087401
5 -0.673690  0.113648 -1.478427  0.524988
6  0.404705  0.577046 -1.715002 -1.039268
7 -0.370647 -1.157892 -1.344312  0.844885
8  1.075770 -0.109050  1.643563 -1.469388
9  0.357021 -0.674600 -1.776904 -0.968914 

读取多个文件以创建单个 DataFrame

最好使用concat()来合并多个文件。查看 cookbook 以获取示例。

逐块迭代文件

假设您希望懒惰地迭代(可能非常大的)文件,而不是将整个文件读入内存,可以使用如下所示的方法:

In [224]: df = pd.DataFrame(np.random.randn(10, 4))

In [225]: df.to_csv("tmp.csv", index=False)

In [226]: table = pd.read_csv("tmp.csv")

In [227]: table
Out[227]: 
 0         1         2         3
0 -1.294524  0.413738  0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2  0.895717  0.805244 -1.206412  2.565646
3  1.431256  1.340309 -1.170299 -0.226169
4  0.410835  0.813850  0.132003 -0.827317
5 -0.076467 -1.187678  1.130127 -1.436737
6 -1.413681  1.607920  1.024180  0.569605
7  0.875906 -2.211372  0.974466 -2.006747
8 -0.410001 -0.078638  0.545952 -1.219217
9 -1.226825  0.769804 -1.281247 -0.727707 

通过在read_csv中指定chunksize,返回值将是TextFileReader类型的可迭代对象:

In [228]: with pd.read_csv("tmp.csv", chunksize=4) as reader:
 .....:    print(reader)
 .....:    for chunk in reader:
 .....:        print(chunk)
 .....: 
<pandas.io.parsers.readers.TextFileReader object at 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 

指定解析引擎

Pandas 目前支持三种引擎,C 引擎、python 引擎和一个实验性的 pyarrow 引擎(需要pyarrow包)。一般来说,pyarrow 引擎在较大的工作负载上速度最快,在大多数其他工作负载上与 C 引擎的速度相当。python 引擎在大多数工作负载上比 pyarrow 和 C 引擎慢。然而,与 C 引擎相比,pyarrow 引擎要不那么稳定,缺少一些与 Python 引擎相比的功能。

在可能的情况下,pandas 使用 C 解析器(指定为engine='c'),但如果指定了不受 C 支持的选项,可能会退回到 Python。

目前,C 和 pyarrow 引擎不支持的选项包括:

  • sep 不是单个字符(例如正则表达式分隔符)

  • skipfooter

  • sep=Nonedelim_whitespace=False

指定上述任何选项都会产生一个ParserWarning,除非显式选择engine='python'来选择 python 引擎。

不受 pyarrow 引擎支持的选项,未在上述列表中列出的包括:

  • float_precision

  • chunksize

  • comment

  • nrows

  • thousands

  • memory_map

  • dialect

  • on_bad_lines

  • delim_whitespace

  • quoting

  • lineterminator

  • converters

  • decimal

  • iterator

  • dayfirst

  • infer_datetime_format

  • verbose

  • skipinitialspace

  • low_memory

使用engine='pyarrow'指定这些选项将引发ValueError

读取/写入远程文件

您可以将 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 文档 中列出的几种方式之一中定义凭据。对于几个存储后端,情况也是如此,您应该遵循 fsimpl1 中内置到 fsspec 中的实现的链接,以及 fsimpl2 中未包含在主 fsspec 发行版中的实现的链接。

您还可以直接将参数传递给后端驱动程序。由于 fsspec 不使用 AWS_S3_HOST 环境变量,我们可以直接定义一个包含 endpoint_url 的字典,并将对象传递给 storage 选项参数:

storage_options = {"client_kwargs": {"endpoint_url": "http://127.0.0.1:5555"}}}
df = pd.read_json("s3://pandas-test/test-1", storage_options=storage_options) 

可以在 S3Fs 文档 中找到更多示例配置和文档。

如果您没有 S3 凭据,仍然可以通过指定匿名连接来访问公共数据,例如

版本 1.2.0 中的新功能。

pd.read_csv(
    "s3://ncei-wcsd-archive/data/processed/SH1305/18kHz/SaKe2013"
    "-D20130523-T080854_to_SaKe2013-D20130523-T085643.csv",
    storage_options={"anon": True},
) 

fsspec 还允许复杂的 URL,用于访问压缩存档中的数据,本地缓存文件等。要在本地缓存上述示例,您可以修改调用为

pd.read_csv(
    "simplecache::s3://ncei-wcsd-archive/data/processed/SH1305/18kHz/"
    "SaKe2013-D20130523-T080854_to_SaKe2013-D20130523-T085643.csv",
    storage_options={"s3": {"anon": True}},
) 

在这里我们指定 “anon” 参数是针对实现的 “s3” 部分,而不是缓存实现。请注意,这仅在会话期间缓存到临时目录,但您也可以指定永久存储。

写出数据

写入 CSV 格式

SeriesDataFrame 对象有一个实例方法 to_csv,允许将对象的内容存储为逗号分隔值文件。该函数接受多个参数。只有第一个是必需的。

  • path_or_buf: 要写入的文件的字符串路径或文件对象。如果是文件对象,必须使用 newline='' 打开

  • sep : 输出文件的字段分隔符(默认为“,”)

  • na_rep: 缺失值的字符串表示(默认为‘’)

  • float_format: 浮点数的格式字符串

  • columns: 要写入的列(默认为 None)

  • header: 是否写出列名(默认为 True)

  • index: 是否写入行(索引)名称(默认为 True)

  • index_label: 如果需要,用于索引列的列标签。如果为 None(默认),且 headerindex 为 True,则使用索引名称。(如果 DataFrame 使用 MultiIndex,则应给出一个序列)。

  • mode : Python 写入模式,默认为‘w’

  • encoding: 表示要使用的编码的字符串,如果内容为非 ASCII,在 Python 3 之前的版本中

  • lineterminator: 表示行结束的字符序列(默认为 os.linesep

  • quoting: 设置引用规则,如 csv 模块中的设定(默认为 csv.QUOTE_MINIMAL)。请注意,如果设置了float_format,那么浮点数会被转换为字符串,而 csv.QUOTE_NONNUMERIC 会将其视为非数字。

  • quotechar: 用于引用字段的字符(默认为‘”’)

  • doublequote: 控制在字段中引用quotechar(默认为 True)

  • escapechar: 用于适当时转义sepquotechar的字符(默认为 None)

  • chunksize: 每次写入的行数

  • date_format: 日期时间对象的格式字符串

写入格式化字符串

DataFrame对象有一个实例方法to_string,允许控制对象的字符串表示。所有参数都是可选的:

  • buf 默认为 None,例如一个 StringIO 对象

  • columns 默认为 None,要写入的列

  • col_space 默认为 None,每列的最小宽度。

  • na_rep 默认为NaN,表示 NA 值

  • formatters 默认为 None,一个字典(按列)的函数,每个函数接受一个参数并返回一个格式化的字符串

  • float_format 默认为 None,一个函数,接受一个(浮点)参数并返回一个格式化的字符串;应用于 DataFrame 中的浮点数。

  • sparsify 默认为 True,设置为 False 以在具有分层索引的 DataFrame 中打印每个 MultiIndex 键的每一行。

  • index_names 默认为 True,将打印索引的名称

  • index 默认为 True,将打印索引(即,行标签)

  • header 默认为 True,将打印列标签

  • justify 默认为left,将列标题左对齐或右对齐

Series对象还有一个to_string方法,但只有bufna_repfloat_format参数。还有一个length参数,如果设置为True,还会额外输出 Series 的长度。

写入 CSV 格式

SeriesDataFrame对象有一个实例方法to_csv,允许将对象的内容存储为逗号分隔值文件。该函数接受多个参数。只有第一个是必需的。

  • path_or_buf: 要写入的文件的字符串路径或文件对象。如果是文件对象,必须使用newline=''打开

  • sep:输出文件的字段分隔符(默认为“,”)

  • na_rep: 缺失值的字符串表示(默认为‘’)

  • float_format: 浮点数的格式字符串

  • columns: 要写入的列(默认为 None)

  • header: 是否写出列名(默认为 True)

  • index: 是否写入行(索引)名称(默认为 True)

  • index_label: 如果需要,用于索引列的列标签。如果为 None(默认),并且headerindex为 True,则使用索引名称。(如果 DataFrame 使用 MultiIndex,则应给出一个序列)。

  • mode:Python 写入模式,默认为‘w’

  • encoding: 一个表示要使用的编码的字符串,如果内容是非 ASCII 的,适用于 Python 3 之前的版本

  • lineterminator: 表示行结束的字符序列(默认为os.linesep

  • quoting:设置引用规则,如 csv 模块(默认 csv.QUOTE_MINIMAL)。请注意,如果您设置了float_format,那么浮点数将被转换为字符串,并且 csv.QUOTE_NONNUMERIC 将将它们视为非数字

  • quotechar:用于引用字段的字符(默认为‘”’)

  • doublequote:控制字段中quotechar的引用(默认为 True)

  • escapechar:在适当时使用来转义sepquotechar的字符(默认为 None)

  • chunksize:一次写入的行数

  • date_format:日期时间对象的格式字符串

写入格式化字符串

DataFrame对象有一个实例方法to_string,允许控制对象的字符串表示。所有参数都是可选的:

  • buf默认为 None,例如一个 StringIO 对象

  • columns默认为 None,要写入的列

  • col_space默认为 None,每列的最小宽度。

  • na_rep默认为NaN,NA 值的表示

  • formatters默认为 None,一个字典(按列)的函数,每个函数接受一个参数并返回一个格式化的字符串

  • float_format默认为 None,一个函数,接受一个(浮点)参数并返回一个格式化的字符串;应用于DataFrame中的浮点数。

  • sparsify默认为 True,设置为 False 以打印具有分层索引的DataFrame中每个 MultiIndex 键的每行。

  • index_names默认为 True,将打印索引的名称

  • index默认为 True,将打印索引(即,行标签)

  • header默认为 True,将打印列标签

  • justify默认为left,将打印列标题左对齐或右对齐

Series对象也有一个to_string方法,但只有bufna_repfloat_format参数。还有一个length参数,如果设置为True,还会输出 Series 的长度。

JSON

读取和写入JSON格式文件和字符串。

写入 JSON

SeriesDataFrame可以转换为有效的 JSON 字符串。使用to_json和可选参数:

  • path_or_buf:要写入输出的路径名或缓冲区。这可以是None,在这种情况下将返回一个 JSON 字符串。

  • orient

    Series

    • 默认是index

    • 允许的值为{splitrecordsindex}

    DataFrame

    • 默认是columns

    • 允许的值为{splitrecordsindexcolumnsvaluestable}

    JSON 字符串的格式

    split 类似字典
    records 类似列表 [{column -> value}, … , {column -> value}]
    index 类似字典 {index -> {column -> value}}
    columns 类似字典 {column -> {index -> value}}
    values 仅值数组
    table 符合 JSON Table Schema
  • date_format:字符串,日期转换类型,‘epoch’表示时间戳,‘iso’表示 ISO8601。

  • double_precision:编码浮点值时要使用的小数位数,默认为 10。

  • force_ascii:强制编码的字符串为 ASCII,默认为 True。

  • date_unit:编码的时间单位,控制时间戳和 ISO8601 的精度。‘s’、‘ms’、‘us’或‘ns’之一,分别表示秒、毫秒、微秒和纳秒。默认为‘ms’。

  • default_handler:如果对象无法以其他方式转换为适合 JSON 格式的格式,则调用处理程序。接受一个参数,即要转换的对象,并返回可序列化对象。

  • lines:如果是records方向,则将每个记录写入为一行 JSON。

  • mode:写入路径时的字符串,写入模式。‘w’表示写入,‘a’表示追加。默认为‘w’

注意NaNNaTNone将转换为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}}' 

Epoch 时间戳,以秒为单位:

In [252]: json = dfd.to_json(date_format="epoch", date_unit="s")

In [253]: json
Out[253]: '{"date":{"0":1,"1":1,"2":1,"3":1,"4":1},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}' 

写入文件,带有日期索引和日期列:

In [254]: dfj2 = dfj.copy()

In [255]: dfj2["date"] = pd.Timestamp("20130101")

In [256]: dfj2["ints"] = list(range(5))

In [257]: dfj2["bools"] = True

In [258]: dfj2.index = pd.date_range("20130101", periods=5)

In [259]: dfj2.to_json("test.json")

In [260]: with open("test.json") as fh:
 .....:    print(fh.read())
 .....: 
{"A":{"1356998400000":-0.1213062281,"1357084800000":0.6957746499,"1357171200000":0.9597255933,"1357257600000":-0.6199759194,"1357344000000":-0.7323393705},"B":{"1356998400000":-0.0978826728,"1357084800000":0.3417343559,"1357171200000":-1.1103361029,"1357257600000":0.1497483186,"1357344000000":0.6877383895},"date":{"1356998400000":1356,"1357084800000":1356,"1357171200000":1356,"1357257600000":1356,"1357344000000":1356},"ints":{"1356998400000":0,"1357084800000":1,"1357171200000":2,"1357257600000":3,"1357344000000":4},"bools":{"1356998400000":true,"1357084800000":true,"1357171200000":true,"1357257600000":true,"1357344000000":true}} 

回退行为

如果 JSON 序列化程序无法直接处理容器内容,则会以以下方式回退:

  • 如果数据类型不受支持(例如np.complex_),则会调用default_handler,如果提供了的话,否则会引发异常。

  • 如果一个对象不受支持,它将尝试以下操作:

    • 检查对象是否定义了toDict方法并调用它。toDict方法应返回一个将被 JSON 序列化的dict
    • 如果提供了default_handler,则调用它。
    • 通过遍历其内容将对象转换为dict。然而,这通常会导致OverflowError或产生意外结果。

通常,对于不支持的对象或数据类型,最好提供一个default_handler。例如:

>>> DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json()  # raises
RuntimeError: Unhandled numpy dtype 15 

可以通过指定一个简单的default_handler来处理。

In [261]: pd.DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json(default_handler=str)
Out[261]: '{"0":{"0":"(1+0j)","1":"(2+0j)","2":"(1+2j)"}}' 
```  ### 读取 JSON

将 JSON 字符串读取为 pandas 对象可以使用多个参数。如果未提供`typ`或为`None`,解析器将尝试解析`DataFrame`。要明确强制解析`Series`,请传递`typ=series`。

+   `filepath_or_buffer`:一个**有效**的 JSON 字符串或文件句柄/ StringIO。字符串可以是 URL。有效的 URL 方案包括 http,ftp,S3 和文件。对于文件 URL,需要一个主机。例如,本地文件可以是 file ://localhost/path/to/table.json

+   `typ`:要恢复的对象类型(系列或框架),默认为“frame”。

+   `orient`:

    Series:

    +   默认为`index`。

    +   允许的值为{`split`,`records`,`index`}。

    DataFrame

    +   默认为`columns`。

    +   允许的值为{`split`,`records`,`index`,`columns`,`values`,`table`}。

    JSON 字符串的格式

    | `split` | 类似于字典{index -> [index],columns -> [columns],data -> [values]} |
    | --- | --- |
    | `records` | 类似于列表[{column -> value},…,{column -> value}] |
    | `index` | 类似于字典{index -> {column -> value}} |
    | `columns` | 类似于字典{column -> {index -> value}} |
    | `values` | 仅值数组 |
    | `table` | 符合 JSON [Table Schema](https://specs.frictionlessdata.io/table-schema/) |

+   `dtype`:如果为 True,则推断数据类型,如果为列到数据类型的字典,则使用这些数据类型,如果为`False`,则根本不推断数据类型,默认为 True,仅适用于数据。

+   `convert_axes`:布尔值,尝试将轴转换为适当的数据类型,默认为`True`。

+   `convert_dates`:要解析日期的列的列表;如果为`True`,则尝试解析类似日期的列,默认为`True`。

+   `keep_default_dates`:布尔值,默认为`True`。如果解析日期,则解析默认的类似日期列。

+   `precise_float`:布尔值,默认为`False`。设置为启用更高精度(strtod)函数在将字符串解码为双精度值时。默认(`False`)是使用快速但不太精确的内置功能。

+   `date_unit`:字符串,用于检测转换日期的时间戳单位。默认为无。默认情况下,将检测时间戳精度,如果不需要,则传递‘s’,‘ms’,‘us’或‘ns’中的一个,以强制时间戳精度为秒,毫秒,微秒或纳秒。

+   `lines`:将文件作为每行一个 JSON 对象读取。

+   `encoding`:用于解码 py3 字节的编码。

+   `chunksize`:与`lines=True`结合使用时,返回一个`pandas.api.typing.JsonReader`,每次迭代读取`chunksize`行。

+   `engine`:要么是`"ujson"`,内置的 JSON 解析器,要么是`"pyarrow"`,它分派到 pyarrow 的`pyarrow.json.read_json`。当`lines=True`时,仅可用`"pyarrow"`。

如果 JSON 无法解析,解析器将引发`ValueError/TypeError/AssertionError`中的一个。

如果在编码为 JSON 时使用了非默认的 `orient`,请确保在此处传递相同的选项,以便解码产生合理的结果,请参阅 定向选项 进行概述。

#### 数据转换

`convert_axes=True`、`dtype=True` 和 `convert_dates=True` 的默认值将尝试将轴和所有数据解析为适当的类型,包括日期。如果需要覆盖特定数据类型,请将字典传递给 `dtype`。只有在需要保留类似字符串的数字(例如 ‘1’、‘2’)时,才应将 `convert_axes` 设置为 `False`。

注意

如果 `convert_dates=True` 并且数据和/或列标签看起来像是日期,则大整数值可能会转换为日期。确切的阈值取决于指定的 `date_unit`。‘看起来像日期’ 意味着列标签符合以下标准之一:

+   以 `'_at'` 结尾

+   以 `'_time'` 结尾

+   以 `'timestamp'` 开头

+   它是 `'modified'`

+   它是 `'date'`

警告

在读取 JSON 数据时,自动强制转换为数据类型会有��些怪癖:

+   索引可以按不同顺序重建,即,返回的顺序不能保证与序列化前相同

+   如果可以安全地执行,将 `float` 数据转换为 `integer`,例如 `1.` 的列将转换为 `integer`

+   布尔列在重建时将转换为 `integer`

因此,有时您可能希望通过 `dtype` 关键字参数指定特定的数据类型。

从 JSON 字符串读取:

```py
In [262]: from io import StringIO

In [263]: pd.read_json(StringIO(json))
Out[263]: 
 date         B         A
0     1  0.403310  0.176444
1     1  0.301624 -0.154951
2     1 -1.369849 -2.179861
3     1  1.462696 -0.954208
4     1 -0.826591 -1.743161 

从文件读取:

In [264]: pd.read_json("test.json")
Out[264]: 
 A         B  date  ints  bools
2013-01-01 -0.121306 -0.097883  1356     0   True
2013-01-02  0.695775  0.341734  1356     1   True
2013-01-03  0.959726 -1.110336  1356     2   True
2013-01-04 -0.619976  0.149748  1356     3   True
2013-01-05 -0.732339  0.687738  1356     4   True 

不转换任何数据(但仍转换轴和日期):

In [265]: pd.read_json("test.json", dtype=object).dtypes
Out[265]: 
A        object
B        object
date     object
ints     object
bools    object
dtype: object 

指定转换的数据类型:

In [266]: pd.read_json("test.json", dtype={"A": "float32", "bools": "int8"}).dtypes
Out[266]: 
A        float32
B        float64
date       int64
ints       int64
bools       int8
dtype: object 

保留字符串索引:

In [267]: from io import StringIO

In [268]: si = pd.DataFrame(
 .....:    np.zeros((4, 4)), columns=list(range(4)), index=[str(i) for i in range(4)]
 .....: )
 .....: 

In [269]: si
Out[269]: 
 0    1    2    3
0  0.0  0.0  0.0  0.0
1  0.0  0.0  0.0  0.0
2  0.0  0.0  0.0  0.0
3  0.0  0.0  0.0  0.0

In [270]: si.index
Out[270]: Index(['0', '1', '2', '3'], dtype='object')

In [271]: si.columns
Out[271]: Index([0, 1, 2, 3], dtype='int64')

In [272]: json = si.to_json()

In [273]: sij = pd.read_json(StringIO(json), convert_axes=False)

In [274]: sij
Out[274]: 
 0  1  2  3
0  0  0  0  0
1  0  0  0  0
2  0  0  0  0
3  0  0  0  0

In [275]: sij.index
Out[275]: Index(['0', '1', '2', '3'], dtype='object')

In [276]: sij.columns
Out[276]: Index(['0', '1', '2', '3'], dtype='object') 

以纳秒为单位编写的日期需要以纳秒为单位读取:

In [277]: from io import StringIO

In [278]: json = dfj2.to_json(date_unit="ns")

# Try to parse timestamps as milliseconds -> Won't Work
In [279]: dfju = pd.read_json(StringIO(json), date_unit="ms")

In [280]: dfju
Out[280]: 
 A         B        date  ints  bools
1356998400000000000 -0.121306 -0.097883  1356998400     0   True
1357084800000000000  0.695775  0.341734  1356998400     1   True
1357171200000000000  0.959726 -1.110336  1356998400     2   True
1357257600000000000 -0.619976  0.149748  1356998400     3   True
1357344000000000000 -0.732339  0.687738  1356998400     4   True

# Let pandas detect the correct precision
In [281]: dfju = pd.read_json(StringIO(json))

In [282]: dfju
Out[282]: 
 A         B       date  ints  bools
2013-01-01 -0.121306 -0.097883 2013-01-01     0   True
2013-01-02  0.695775  0.341734 2013-01-01     1   True
2013-01-03  0.959726 -1.110336 2013-01-01     2   True
2013-01-04 -0.619976  0.149748 2013-01-01     3   True
2013-01-05 -0.732339  0.687738 2013-01-01     4   True

# Or specify that all timestamps are in nanoseconds
In [283]: dfju = pd.read_json(StringIO(json), date_unit="ns")

In [284]: dfju
Out[284]: 
 A         B        date  ints  bools
2013-01-01 -0.121306 -0.097883  1356998400     0   True
2013-01-02  0.695775  0.341734  1356998400     1   True
2013-01-03  0.959726 -1.110336  1356998400     2   True
2013-01-04 -0.619976  0.149748  1356998400     3   True
2013-01-05 -0.732339  0.687738  1356998400     4   True 

通过设置 dtype_backend 参数,您可以控制生成的 DataFrame 使用的默认数据类型。

In [285]: data = (
 .....: '{"a":{"0":1,"1":3},"b":{"0":2.5,"1":4.5},"c":{"0":true,"1":false},"d":{"0":"a","1":"b"},'
 .....: '"e":{"0":null,"1":6.0},"f":{"0":null,"1":7.5},"g":{"0":null,"1":true},"h":{"0":null,"1":"a"},'
 .....: '"i":{"0":"12-31-2019","1":"12-31-2019"},"j":{"0":null,"1":null}}'
 .....: )
 .....: 

In [286]: df = pd.read_json(StringIO(data), dtype_backend="pyarrow")

In [287]: df
Out[287]: 
 a    b      c  d     e     f     g     h           i     j
0  1  2.5   True  a  <NA>  <NA>  <NA>  <NA>  12-31-2019  None
1  3  4.5  False  b     6   7.5  True     a  12-31-2019  None

In [288]: df.dtypes
Out[288]: 
a     int64[pyarrow]
b    double[pyarrow]
c      bool[pyarrow]
d    string[pyarrow]
e     int64[pyarrow]
f    double[pyarrow]
g      bool[pyarrow]
h    string[pyarrow]
i    string[pyarrow]
j      null[pyarrow]
dtype: object 
```  ### 规范化

pandas 提供了一个实用函数,可以将字典或字典列表 *规范化* 为平面表。

```py
In [289]: data = [
 .....:    {"id": 1, "name": {"first": "Coleen", "last": "Volk"}},
 .....:    {"name": {"given": "Mark", "family": "Regner"}},
 .....:    {"id": 2, "name": "Faye Raker"},
 .....: ]
 .....: 

In [290]: pd.json_normalize(data)
Out[290]: 
 id name.first name.last name.given name.family        name
0  1.0     Coleen      Volk        NaN         NaN         NaN
1  NaN        NaN       NaN       Mark      Regner         NaN
2  2.0        NaN       NaN        NaN         NaN  Faye Raker 
In [291]: data = [
 .....:    {
 .....:        "state": "Florida",
 .....:        "shortname": "FL",
 .....:        "info": {"governor": "Rick Scott"},
 .....:        "county": [
 .....:            {"name": "Dade", "population": 12345},
 .....:            {"name": "Broward", "population": 40000},
 .....:            {"name": "Palm Beach", "population": 60000},
 .....:        ],
 .....:    },
 .....:    {
 .....:        "state": "Ohio",
 .....:        "shortname": "OH",
 .....:        "info": {"governor": "John Kasich"},
 .....:        "county": [
 .....:            {"name": "Summit", "population": 1234},
 .....:            {"name": "Cuyahoga", "population": 1337},
 .....:        ],
 .....:    },
 .....: ]
 .....: 

In [292]: pd.json_normalize(data, "county", ["state", "shortname", ["info", "governor"]])
Out[292]: 
 name  population    state shortname info.governor
0        Dade       12345  Florida        FL    Rick Scott
1     Broward       40000  Florida        FL    Rick Scott
2  Palm Beach       60000  Florida        FL    Rick Scott
3      Summit        1234     Ohio        OH   John Kasich
4    Cuyahoga        1337     Ohio        OH   John Kasich 

max_level 参数提供了更多控制,可以指定规范化结束的级别。使用 max_level=1,以下代码片段将规范化到所提供字典的第一个嵌套级别。

In [293]: data = [
 .....:    {
 .....:        "CreatedBy": {"Name": "User001"},
 .....:        "Lookup": {
 .....:            "TextField": "Some text",
 .....:            "UserField": {"Id": "ID001", "Name": "Name001"},
 .....:        },
 .....:        "Image": {"a": "b"},
 .....:    }
 .....: ]
 .....: 

In [294]: pd.json_normalize(data, max_level=1)
Out[294]: 
 CreatedBy.Name Lookup.TextField                    Lookup.UserField Image.a
0        User001        Some text  {'Id': 'ID001', 'Name': 'Name001'}       b 
```  ### 行分隔的 json

pandas 能够读取和写入使用 Hadoop 或 Spark 的数据处理流水线中常见的行分隔的 json 文件。

对于行限定的 json 文件,pandas 还可以返回一个迭代器,每次读取 `chunksize` 行。这对于大文件或从流中读取非常有用。

```py
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 读取器读取行限定的 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 中。 ### 表模式

表模式 是一种描述表格数据集的 JSON 对象的规范。JSON 包括有关字段名称、类型和其他属性的信息。您可以使用 orient table 来构建一个具有两个字段 schemadata 的 JSON 字符串。

In [304]: df = pd.DataFrame(
 .....:    {
 .....:        "A": [1, 2, 3],
 .....:        "B": ["a", "b", "c"],
 .....:        "C": pd.date_range("2016-01-01", freq="d", periods=3),
 .....:    },
 .....:    index=pd.Index(range(3), name="idx"),
 .....: )
 .....: 

In [305]: df
Out[305]: 
 A  B          C
idx 
0    1  a 2016-01-01
1    2  b 2016-01-02
2    3  c 2016-01-03

In [306]: df.to_json(orient="table", date_format="iso")
Out[306]: '{"schema":{"fields":[{"name":"idx","type":"integer"},{"name":"A","type":"integer"},{"name":"B","type":"string"},{"name":"C","type":"datetime"}],"primaryKey":["idx"],"pandas_version":"1.4.0"},"data":[{"idx":0,"A":1,"B":"a","C":"2016-01-01T00:00:00.000"},{"idx":1,"A":2,"B":"b","C":"2016-01-02T00:00:00.000"},{"idx":2,"A":3,"B":"c","C":"2016-01-03T00:00:00.000"}]}' 

schema 字段包含 fields 键,它本身包含列名到类型对的列表,包括 IndexMultiIndex(有关类型的列表,请参见下文)。如果(Multi)索引是唯一的,则 schema 字段还包含一个 primaryKey 字段。

第二个字段 data 包含了使用 records 方向序列化的数据。索引已包含,并且任何日期时间都符合 ISO 8601 格式,符合 Table Schema 规范的要求。

支持的类型的完整列表在表模式规范中描述。此表显示了从 pandas 类型的映射:

pandas type Table Schema type
int64 integer
float64 number
bool boolean
datetime64[ns] datetime
timedelta64[ns] duration
categorical any
object str

关于生成的表模式的一些注释:

  • schema 对象包含一个 pandas_version 字段。这包含了 pandas 模式方言的版本,并且每次修订都会递增。

  • 所有日期在序列化时都会转换为 UTC。即使是时区无关的值,也会被视为具有 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'} 
    
  • 具有时区的日期时间(在序列化之前)包含一个名为 tz 的附加字段,其中包含时区名称(例如 'US/Central')。

    In [310]: s_tz = pd.Series(pd.date_range("2016", periods=12, tz="US/Central"))
    
    In [311]: build_table_schema(s_tz)
    Out[311]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
     {'name': 'values', 'type': 'datetime', 'tz': 'US/Central'}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'} 
    
  • 在序列化之前,周期会转换为时间戳,因此具有相同的行为会被转换为 UTC。此外,周期将包含一个额外的字段 freq,其中包含周期的频率,例如 'A-DEC'

    In [312]: s_per = pd.Series(1, index=pd.period_range("2016", freq="Y-DEC", periods=4))
    
    In [313]: build_table_schema(s_per)
    Out[313]: 
    {'fields': [{'name': 'index', 'type': 'datetime', 'freq': 'YE-DEC'},
     {'name': 'values', 'type': 'integer'}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'} 
    
  • 分类使用 any 类型和一个枚举约束列表,列出可能值的集合。此外,还包括一个 ordered 字段:

    In [314]: s_cat = pd.Series(pd.Categorical(["a", "b", "a"]))
    
    In [315]: build_table_schema(s_cat)
    Out[315]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
     {'name': 'values',
     'type': 'any',
     'constraints': {'enum': ['a', 'b']},
     'ordered': False}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'} 
    
  • 如果索引是唯一的,则包含一个包含标签数组的 primaryKey 字段:

    In [316]: s_dupe = pd.Series([1, 2], index=[1, 1])
    
    In [317]: build_table_schema(s_dupe)
    Out[317]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
     {'name': 'values', 'type': 'integer'}],
     'pandas_version': '1.4.0'} 
    
  • primaryKey 的行为与 MultiIndexes 相同,但在这种情况下,primaryKey 是一个数组:

    In [318]: s_multi = pd.Series(1, index=pd.MultiIndex.from_product([("a", "b"), (0, 1)]))
    
    In [319]: build_table_schema(s_multi)
    Out[319]: 
    {'fields': [{'name': 'level_0', 'type': 'string'},
     {'name': 'level_1', 'type': 'integer'},
     {'name': 'values', 'type': 'integer'}],
     'primaryKey': FrozenList(['level_0', 'level_1']),
     'pandas_version': '1.4.0'} 
    
  • 默认命名大致遵循以下规则:

    • 对于序列,使用 object.name。如果没有,则名称为 values
    • 对于 DataFrame,使用列名的字符串版本
    • 对于 Index(而不是 MultiIndex),使用 index.name,如果为 None,则使用 index
    • 对于 MultiIndex,使用 mi.names。如果任何级别没有名称,则使用 level_<i>

read_json 也接受 orient='table' 作为参数。这允许以往复传递的方式保留元数据,如 dtypes 和索引名称。

In [320]: df = pd.DataFrame(
 .....:    {
 .....:        "foo": [1, 2, 3, 4],
 .....:        "bar": ["a", "b", "c", "d"],
 .....:        "baz": pd.date_range("2018-01-01", freq="d", periods=4),
 .....:        "qux": pd.Categorical(["a", "b", "c", "c"]),
 .....:    },
 .....:    index=pd.Index(range(4), name="idx"),
 .....: )
 .....: 

In [321]: df
Out[321]: 
 foo bar        baz qux
idx 
0      1   a 2018-01-01   a
1      2   b 2018-01-02   b
2      3   c 2018-01-03   c
3      4   d 2018-01-04   c

In [322]: df.dtypes
Out[322]: 
foo             int64
bar            object
baz    datetime64[ns]
qux          category
dtype: object

In [323]: df.to_json("test.json", orient="table")

In [324]: new_df = pd.read_json("test.json", orient="table")

In [325]: new_df
Out[325]: 
 foo bar        baz qux
idx 
0      1   a 2018-01-01   a
1      2   b 2018-01-02   b
2      3   c 2018-01-03   c
3      4   d 2018-01-04   c

In [326]: new_df.dtypes
Out[326]: 
foo             int64
bar            object
baz    datetime64[ns]
qux          category
dtype: object 

请注意,作为 Index 名称的字面字符串 ‘index’ 不可往复传递,任何以 'level_' 开头的名称也不可在 MultiIndex 内传递。这些默认用于 DataFrame.to_json() 来指示缺失值,后续读取无法区分意图。

In [327]: df.index.name = "index"

In [328]: df.to_json("test.json", orient="table")

In [329]: new_df = pd.read_json("test.json", orient="table")

In [330]: print(new_df.index.name)
None 

当使用orient='table'与用户定义的ExtensionArray一起时,生成的模式将在相应的fields元素中包含一个额外的extDtype键。这个额外的键不是标准的,但确实可以为扩展类型(例如read_json(df.to_json(orient="table"), orient="table"))启用 JSON 往返。

extDtype键携带扩展的名称,如果您已经正确注册了ExtensionDtype,pandas 将使用该名称来执行查找并重新将序列化的数据转换为您的自定义 dtype。### 写入 JSON

SeriesDataFrame可以转换为有效的 JSON 字符串。使用带有可选参数的to_json

  • path_or_buf:要写入输出的路径名或缓冲区。这可以是None,在这种情况下将返回一个 JSON 字符串。

  • orient

    Series

    • 默认为index

    • 允许的值为{splitrecordsindex}

    DataFrame

    • 默认为columns

    • 允许的值为{splitrecordsindexcolumnsvaluestable}

    JSON 字符串的格式

    split 类似字典
    records 类似列表 [{column -> value}, … , {column -> value}]
    index 类似字典 {index -> {column -> value}}
    columns 类似字典 {column -> {index -> value}}
    values 仅值数组
    table 遵循 JSON Table Schema
  • date_format:日期转换类型,‘epoch’表示时间戳,‘iso’表示 ISO8601。

  • double_precision:编码浮点值时要使用的小数位数,默认为 10。

  • force_ascii:强制编码字符串为 ASCII,默认为 True。

  • date_unit:要编码为的时间单位,控制时间戳和 ISO8601 的精度。‘s’表示秒,‘ms’表示毫秒,‘us’表示微秒,‘ns’表示纳秒。默认为‘ms’。

  • default_handler:如果对象无法以其他方式转换为适合 JSON 格式的格式,则调用的处理程序。接受一个参数,即要转换的对象,并返回一个可序列化对象。

  • lines:如果是records方向,则将每个记录写成一行 json。

  • mode:写入路径时的字符串,写入模式。‘w’表示写入,‘a’表示追加。默认为‘w’

注意NaNNaTNone将被转换为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}' 

面向记录(record oriented)将数据序列化为列->值记录的 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]' 

面向值(value oriented)是一个简单的选项,它将值仅序列化为嵌套的 JSON 数组,不包括列和索引标签:

In [242]: dfjo.to_json(orient="values")
Out[242]: '[[1,4,7],[2,5,8],[3,6,9]]'

# Not available for Series 

面向分割(split oriented)序列化为一个包含值、索引和列的独立条目的 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]}' 

面向表(table oriented)序列化为 JSON Table Schema,允许保留元数据,包括但不限于 dtypes 和索引名称。

注意

任何编码为 JSON 对象的 orient 选项都不会在往返序列化过程中保留索引和列标签的顺序。如果要保留标签顺序,请使用split选项,因为它使用有序容器。

日期处理

以 ISO 日期格式编写:

In [245]: dfd = pd.DataFrame(np.random.randn(5, 2), columns=list("AB"))

In [246]: dfd["date"] = pd.Timestamp("20130101")

In [247]: dfd = dfd.sort_index(axis=1, ascending=False)

In [248]: json = dfd.to_json(date_format="iso")

In [249]: json
Out[249]: '{"date":{"0":"2013-01-01T00:00:00.000","1":"2013-01-01T00:00:00.000","2":"2013-01-01T00:00:00.000","3":"2013-01-01T00:00:00.000","4":"2013-01-01T00:00:00.000"},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}' 

以 ISO 日期格式编写,带有微秒:

In [250]: json = dfd.to_json(date_format="iso", date_unit="us")

In [251]: json
Out[251]: '{"date":{"0":"2013-01-01T00:00:00.000000","1":"2013-01-01T00:00:00.000000","2":"2013-01-01T00:00:00.000000","3":"2013-01-01T00:00:00.000000","4":"2013-01-01T00:00:00.000000"},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}' 

Epoch 时间戳,以秒为单位:

In [252]: json = dfd.to_json(date_format="epoch", date_unit="s")

In [253]: json
Out[253]: '{"date":{"0":1,"1":1,"2":1,"3":1,"4":1},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}' 

写入文件,带有日期索引和日期列:

In [254]: dfj2 = dfj.copy()

In [255]: dfj2["date"] = pd.Timestamp("20130101")

In [256]: dfj2["ints"] = list(range(5))

In [257]: dfj2["bools"] = True

In [258]: dfj2.index = pd.date_range("20130101", periods=5)

In [259]: dfj2.to_json("test.json")

In [260]: with open("test.json") as fh:
 .....:    print(fh.read())
 .....: 
{"A":{"1356998400000":-0.1213062281,"1357084800000":0.6957746499,"1357171200000":0.9597255933,"1357257600000":-0.6199759194,"1357344000000":-0.7323393705},"B":{"1356998400000":-0.0978826728,"1357084800000":0.3417343559,"1357171200000":-1.1103361029,"1357257600000":0.1497483186,"1357344000000":0.6877383895},"date":{"1356998400000":1356,"1357084800000":1356,"1357171200000":1356,"1357257600000":1356,"1357344000000":1356},"ints":{"1356998400000":0,"1357084800000":1,"1357171200000":2,"1357257600000":3,"1357344000000":4},"bools":{"1356998400000":true,"1357084800000":true,"1357171200000":true,"1357257600000":true,"1357344000000":true}} 

回退行为

如果 JSON 序列化程序无法直接处理容器内容,则会以以下方式退回:

  • 如果 dtype 不受支持(例如np.complex_),则对于每个值,如果提供了default_handler,则会调用它,否则会引发异常。

  • 如果一个对象不受支持,它将尝试以下操作:

    • 检查对象是否定义了toDict方法,并调用它。toDict方法应返回一个dict,然后将被 JSON 序列化。
    • 调用提供的default_handler(如果提供了)。
    • 通过遍历其内容将对象转换为dict。但是,这通常会导致OverflowError或给出意外的结果。

通常,对于不受支持的对象或 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)"}}' 

Orient 选项

结果 JSON 文件/字符串的格式有多种不同的选项。考虑以下DataFrameSeries

In [233]: dfjo = pd.DataFrame(
 .....:    dict(A=range(1, 4), B=range(4, 7), C=range(7, 10)),
 .....:    columns=list("ABC"),
 .....:    index=list("xyz"),
 .....: )
 .....: 

In [234]: dfjo
Out[234]: 
 A  B  C
x  1  4  7
y  2  5  8
z  3  6  9

In [235]: sjo = pd.Series(dict(x=15, y=16, z=17), name="D")

In [236]: sjo
Out[236]: 
x    15
y    16
z    17
Name: D, dtype: int64 

面向列(column oriented)(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 

面向索引(index oriented)(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}' 

面向记录(record oriented)将数据序列化为列->值记录的 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]' 

面向值(value oriented)是一个简单的选项,它将值仅序列化为嵌套的 JSON 数组,不包括列和索引标签:

In [242]: dfjo.to_json(orient="values")
Out[242]: '[[1,4,7],[2,5,8],[3,6,9]]'

# Not available for Series 

面向分割(split oriented)序列化为一个包含值、索引和列的独立条目的 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,允许保留元数据,包括但不限于数据类型和索引名称。

注意

任何编码为 JSON 对象的 orient 选项在往返序列化过程中都不会保留索引和列标签的顺序。如果希望保留标签顺序,请使用 split 选项,因为它使用了有序容器。

日期处理

写入 ISO 日期格式:

In [245]: dfd = pd.DataFrame(np.random.randn(5, 2), columns=list("AB"))

In [246]: dfd["date"] = pd.Timestamp("20130101")

In [247]: dfd = dfd.sort_index(axis=1, ascending=False)

In [248]: json = dfd.to_json(date_format="iso")

In [249]: json
Out[249]: '{"date":{"0":"2013-01-01T00:00:00.000","1":"2013-01-01T00:00:00.000","2":"2013-01-01T00:00:00.000","3":"2013-01-01T00:00:00.000","4":"2013-01-01T00:00:00.000"},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}' 

以 ISO 日期格式写入,带有微秒:

In [250]: json = dfd.to_json(date_format="iso", date_unit="us")

In [251]: json
Out[251]: '{"date":{"0":"2013-01-01T00:00:00.000000","1":"2013-01-01T00:00:00.000000","2":"2013-01-01T00:00:00.000000","3":"2013-01-01T00:00:00.000000","4":"2013-01-01T00:00:00.000000"},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}' 

Epoch 时间戳,单位为秒:

In [252]: json = dfd.to_json(date_format="epoch", date_unit="s")

In [253]: json
Out[253]: '{"date":{"0":1,"1":1,"2":1,"3":1,"4":1},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}' 

写入文件,带有日期索引和日期列:

In [254]: dfj2 = dfj.copy()

In [255]: dfj2["date"] = pd.Timestamp("20130101")

In [256]: dfj2["ints"] = list(range(5))

In [257]: dfj2["bools"] = True

In [258]: dfj2.index = pd.date_range("20130101", periods=5)

In [259]: dfj2.to_json("test.json")

In [260]: with open("test.json") as fh:
 .....:    print(fh.read())
 .....: 
{"A":{"1356998400000":-0.1213062281,"1357084800000":0.6957746499,"1357171200000":0.9597255933,"1357257600000":-0.6199759194,"1357344000000":-0.7323393705},"B":{"1356998400000":-0.0978826728,"1357084800000":0.3417343559,"1357171200000":-1.1103361029,"1357257600000":0.1497483186,"1357344000000":0.6877383895},"date":{"1356998400000":1356,"1357084800000":1356,"1357171200000":1356,"1357257600000":1356,"1357344000000":1356},"ints":{"1356998400000":0,"1357084800000":1,"1357171200000":2,"1357257600000":3,"1357344000000":4},"bools":{"1356998400000":true,"1357084800000":true,"1357171200000":true,"1357257600000":true,"1357344000000":true}} 

回退行为

如果 JSON 序列化程序无法直接处理容器内容,它将以以下方式回退:

  • 如果数据类型不受支持(例如 np.complex_),则会为每个值调用 default_handler(如果提供),否则会引发异常。

  • 如果一个对象不受支持,它将尝试以下操作:

    • 检查对象是否定义了 toDict 方法并调用它。toDict 方法应返回一个 dict,然后将对其进行 JSON 序列化。
    • 如果提供了 default_handler,则调用它。
    • 通过遍历其内容将对象转换为 dict。然而,这通常会导致 OverflowError 或给出意外结果。

通常,对于不支持的对象或数据类型,最好的方法是提供一个 default_handler。例如:

>>> DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json()  # raises
RuntimeError: Unhandled numpy dtype 15 

可以通过指定一个简单的 default_handler 来处理:

In [261]: pd.DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json(default_handler=str)
Out[261]: '{"0":{"0":"(1+0j)","1":"(2+0j)","2":"(1+2j)"}}' 

读取 JSON

读取 JSON 字符串到 pandas 对象可以使用多个参数。如果未提供或为 None,解析器将尝试解析 DataFrame。要明确强制解析 Series,请传递 typ=series

  • filepath_or_buffer : 一个有效的 JSON 字符串或文件句柄 / StringIO。该字符串可以是一个 URL。有效的 URL 方案包括 http、ftp、S3 和 file。对于文件 URL,需要一个主机。例如,本地文件可以是 file 😕/localhost/path/to/table.json

  • typ : 要恢复的对象类型(series 或 frame),默认为 ‘frame’

  • orient :

    Series :

    • 默认为 index

    • 允许的取值为{split, records, index}

    DataFrame

    • 默认为 columns

    • 允许的取值为{split, records, index, columns, values, table}

    JSON 字符串的格式

    split 类似字典
    records 类似列表 [{column -> value}, … , {column -> value}]
    index 类似字典 {index -> {column -> value}}
    columns 类似字典 {column -> {index -> value}}
    values 仅值数组
    table 符合 JSON Table Schema
  • dtype : 如果为 True,则推断数据类型,如果为列到数据类型的字典,则使用这些数据类型,如果为 False,则根本不推断数据类型,默认为 True,仅适用于数据。

  • convert_axes : 布尔值,尝试将轴转换为适当的数据类型,默认为 True

  • convert_dates:要解析日期的列的列表;如果为True,则尝试解析类似日期的列,默认为True

  • keep_default_dates:布尔值,默认为True。如果解析日期,则解析默认的类似日期列。

  • precise_float:布尔值,默认为False。设置为启用在将字符串解码为双精度值时使用更高精度(strtod)函数。默认值(False)是使用快速但不太精确的内置功能。

  • date_unit:字符串,用于检测转换日期时的时间戳单位。默认为 None。默认情况下,将检测时间戳精度,如果不需要,则传递‘s’、‘ms’、‘us’或‘ns’中的一个,以强制时间戳精度为秒、毫秒、微秒或纳秒。

  • lines:按行读取文件,每行一个 JSON 对象。

  • encoding:用于解码 py3 字节的编码。

  • chunksize:与lines=True结合使用时,返回一个pandas.api.typing.JsonReader,每次迭代读取chunksize行。

  • engine:要么是"ujson",内置的 JSON 解析器,要么是"pyarrow",它会分派到 pyarrow 的pyarrow.json.read_json。当lines=True时,只有"pyarrow"可用。

如果 JSON 无法解析,解析器将引发ValueError/TypeError/AssertionError中的一个。

如果在编码为 JSON 时使用了非默认的orient,请确保在此处传递相同的选项,以便解码产生合理的结果,请参阅定向选项进行概述。

数据转换

convert_axes=Truedtype=Trueconvert_dates=True的默认值将尝试将轴和所有数据解析为适当的类型,包括日期。如果需要覆盖特定的数据类型,请将字典传递给dtype。只有在需要保留类似字符串的数字(例如‘1’、‘2’)时,才应将convert_axes设置为False

注意

如果convert_dates=True且数据和/或列标签看起来类似于‘日期’,则可能会将大整数值转换为日期。确切的阈值取决于指定的date_unit。‘类似日期’意味着列标签符合以下标准之一:

  • '_at'结尾

  • '_time'结尾

  • 'timestamp'开头

  • 'modified'结尾

  • 'date'结尾

警告

在读取 JSON 数据时,自动强制转换为数据类型会有一些怪癖:

  • 索引可以按不同顺序重建,即返回的顺序不能保证与序列化前相同

  • 如果可以安全地执行,例如将float数据列转换为integer,则将转换为integer,例如1.

  • 布尔列在重建时将转换为integer

因此,有时您可能需要通过dtype关键字参数指定特定的数据类型。

从 JSON 字符串中读取:

In [262]: from io import StringIO

In [263]: pd.read_json(StringIO(json))
Out[263]: 
 date         B         A
0     1  0.403310  0.176444
1     1  0.301624 -0.154951
2     1 -1.369849 -2.179861
3     1  1.462696 -0.954208
4     1 -0.826591 -1.743161 

从文件中读取:

In [264]: pd.read_json("test.json")
Out[264]: 
 A         B  date  ints  bools
2013-01-01 -0.121306 -0.097883  1356     0   True
2013-01-02  0.695775  0.341734  1356     1   True
2013-01-03  0.959726 -1.110336  1356     2   True
2013-01-04 -0.619976  0.149748  1356     3   True
2013-01-05 -0.732339  0.687738  1356     4   True 

不要转换任何数据(但仍然转换轴和日期):

In [265]: pd.read_json("test.json", dtype=object).dtypes
Out[265]: 
A        object
B        object
date     object
ints     object
bools    object
dtype: object 

指定转换的数据类型:

In [266]: pd.read_json("test.json", dtype={"A": "float32", "bools": "int8"}).dtypes
Out[266]: 
A        float32
B        float64
date       int64
ints       int64
bools       int8
dtype: object 

保留字符串索引:

In [267]: from io import StringIO

In [268]: si = pd.DataFrame(
 .....:    np.zeros((4, 4)), columns=list(range(4)), index=[str(i) for i in range(4)]
 .....: )
 .....: 

In [269]: si
Out[269]: 
 0    1    2    3
0  0.0  0.0  0.0  0.0
1  0.0  0.0  0.0  0.0
2  0.0  0.0  0.0  0.0
3  0.0  0.0  0.0  0.0

In [270]: si.index
Out[270]: Index(['0', '1', '2', '3'], dtype='object')

In [271]: si.columns
Out[271]: Index([0, 1, 2, 3], dtype='int64')

In [272]: json = si.to_json()

In [273]: sij = pd.read_json(StringIO(json), convert_axes=False)

In [274]: sij
Out[274]: 
 0  1  2  3
0  0  0  0  0
1  0  0  0  0
2  0  0  0  0
3  0  0  0  0

In [275]: sij.index
Out[275]: Index(['0', '1', '2', '3'], dtype='object')

In [276]: sij.columns
Out[276]: Index(['0', '1', '2', '3'], dtype='object') 

以纳秒写入的日期需要以纳秒读取:

In [277]: from io import StringIO

In [278]: json = dfj2.to_json(date_unit="ns")

# Try to parse timestamps as milliseconds -> Won't Work
In [279]: dfju = pd.read_json(StringIO(json), date_unit="ms")

In [280]: dfju
Out[280]: 
 A         B        date  ints  bools
1356998400000000000 -0.121306 -0.097883  1356998400     0   True
1357084800000000000  0.695775  0.341734  1356998400     1   True
1357171200000000000  0.959726 -1.110336  1356998400     2   True
1357257600000000000 -0.619976  0.149748  1356998400     3   True
1357344000000000000 -0.732339  0.687738  1356998400     4   True

# Let pandas detect the correct precision
In [281]: dfju = pd.read_json(StringIO(json))

In [282]: dfju
Out[282]: 
 A         B       date  ints  bools
2013-01-01 -0.121306 -0.097883 2013-01-01     0   True
2013-01-02  0.695775  0.341734 2013-01-01     1   True
2013-01-03  0.959726 -1.110336 2013-01-01     2   True
2013-01-04 -0.619976  0.149748 2013-01-01     3   True
2013-01-05 -0.732339  0.687738 2013-01-01     4   True

# Or specify that all timestamps are in nanoseconds
In [283]: dfju = pd.read_json(StringIO(json), date_unit="ns")

In [284]: dfju
Out[284]: 
 A         B        date  ints  bools
2013-01-01 -0.121306 -0.097883  1356998400     0   True
2013-01-02  0.695775  0.341734  1356998400     1   True
2013-01-03  0.959726 -1.110336  1356998400     2   True
2013-01-04 -0.619976  0.149748  1356998400     3   True
2013-01-05 -0.732339  0.687738  1356998400     4   True 

通过设置dtype_backend参数,您可以控制生成的 DataFrame 使用的默认数据类型。

In [285]: data = (
 .....: '{"a":{"0":1,"1":3},"b":{"0":2.5,"1":4.5},"c":{"0":true,"1":false},"d":{"0":"a","1":"b"},'
 .....: '"e":{"0":null,"1":6.0},"f":{"0":null,"1":7.5},"g":{"0":null,"1":true},"h":{"0":null,"1":"a"},'
 .....: '"i":{"0":"12-31-2019","1":"12-31-2019"},"j":{"0":null,"1":null}}'
 .....: )
 .....: 

In [286]: df = pd.read_json(StringIO(data), dtype_backend="pyarrow")

In [287]: df
Out[287]: 
 a    b      c  d     e     f     g     h           i     j
0  1  2.5   True  a  <NA>  <NA>  <NA>  <NA>  12-31-2019  None
1  3  4.5  False  b     6   7.5  True     a  12-31-2019  None

In [288]: df.dtypes
Out[288]: 
a     int64[pyarrow]
b    double[pyarrow]
c      bool[pyarrow]
d    string[pyarrow]
e     int64[pyarrow]
f    double[pyarrow]
g      bool[pyarrow]
h    string[pyarrow]
i    string[pyarrow]
j      null[pyarrow]
dtype: object 

数据转换

默认值convert_axes=Truedtype=Trueconvert_dates=True将尝试解析轴和所有数据为适当的类型,包括日期。如果需要覆盖特定的数据类型,请将字典传递给dtype。仅当您需要保留类似字符串的数字(例如‘1’、‘2’)时,应将convert_axes设置为False

注意

如果convert_dates=True,且数据和/或列标签看起来像是‘日期’,则大整数值可能会转换为日期。确切的阈值取决于指定的date_unit。‘日期’指的是列标签符合以下条件之一:

  • 它以'_at'结尾

  • 它以'_time'结尾

  • 它以'timestamp'开头

  • 它是'modified'

  • 它是'date'

警告

在读取 JSON 数据时,自动强制转换成数据类型会有一些怪异之处:

  • 索引可以在反序列化后以不同的顺序重构,也就是说,返回的顺序不能保证与序列化前相同。

  • 如果可以安全地执行,则将float数据列转换为integer,例如1.列将被转换为integer

  • 重构时,布尔列将被转换为integer

因此,有时您可能希望通过dtype关键字参数指定特定的数据类型。

从 JSON 字符串读取:

In [262]: from io import StringIO

In [263]: pd.read_json(StringIO(json))
Out[263]: 
 date         B         A
0     1  0.403310  0.176444
1     1  0.301624 -0.154951
2     1 -1.369849 -2.179861
3     1  1.462696 -0.954208
4     1 -0.826591 -1.743161 

从文件读取:

In [264]: pd.read_json("test.json")
Out[264]: 
 A         B  date  ints  bools
2013-01-01 -0.121306 -0.097883  1356     0   True
2013-01-02  0.695775  0.341734  1356     1   True
2013-01-03  0.959726 -1.110336  1356     2   True
2013-01-04 -0.619976  0.149748  1356     3   True
2013-01-05 -0.732339  0.687738  1356     4   True 

不要转换任何数据(但仍转换轴和日期)。

In [265]: pd.read_json("test.json", dtype=object).dtypes
Out[265]: 
A        object
B        object
date     object
ints     object
bools    object
dtype: object 

指定转换的数据类型:

In [266]: pd.read_json("test.json", dtype={"A": "float32", "bools": "int8"}).dtypes
Out[266]: 
A        float32
B        float64
date       int64
ints       int64
bools       int8
dtype: object 

保留字符串索引:

In [267]: from io import StringIO

In [268]: si = pd.DataFrame(
 .....:    np.zeros((4, 4)), columns=list(range(4)), index=[str(i) for i in range(4)]
 .....: )
 .....: 

In [269]: si
Out[269]: 
 0    1    2    3
0  0.0  0.0  0.0  0.0
1  0.0  0.0  0.0  0.0
2  0.0  0.0  0.0  0.0
3  0.0  0.0  0.0  0.0

In [270]: si.index
Out[270]: Index(['0', '1', '2', '3'], dtype='object')

In [271]: si.columns
Out[271]: Index([0, 1, 2, 3], dtype='int64')

In [272]: json = si.to_json()

In [273]: sij = pd.read_json(StringIO(json), convert_axes=False)

In [274]: sij
Out[274]: 
 0  1  2  3
0  0  0  0  0
1  0  0  0  0
2  0  0  0  0
3  0  0  0  0

In [275]: sij.index
Out[275]: Index(['0', '1', '2', '3'], dtype='object')

In [276]: sij.columns
Out[276]: Index(['0', '1', '2', '3'], dtype='object') 

以纳秒为单位编写的日期需要以纳秒读取:

In [277]: from io import StringIO

In [278]: json = dfj2.to_json(date_unit="ns")

# Try to parse timestamps as milliseconds -> Won't Work
In [279]: dfju = pd.read_json(StringIO(json), date_unit="ms")

In [280]: dfju
Out[280]: 
 A         B        date  ints  bools
1356998400000000000 -0.121306 -0.097883  1356998400     0   True
1357084800000000000  0.695775  0.341734  1356998400     1   True
1357171200000000000  0.959726 -1.110336  1356998400     2   True
1357257600000000000 -0.619976  0.149748  1356998400     3   True
1357344000000000000 -0.732339  0.687738  1356998400     4   True

# Let pandas detect the correct precision
In [281]: dfju = pd.read_json(StringIO(json))

In [282]: dfju
Out[282]: 
 A         B       date  ints  bools
2013-01-01 -0.121306 -0.097883 2013-01-01     0   True
2013-01-02  0.695775  0.341734 2013-01-01     1   True
2013-01-03  0.959726 -1.110336 2013-01-01     2   True
2013-01-04 -0.619976  0.149748 2013-01-01     3   True
2013-01-05 -0.732339  0.687738 2013-01-01     4   True

# Or specify that all timestamps are in nanoseconds
In [283]: dfju = pd.read_json(StringIO(json), date_unit="ns")

In [284]: dfju
Out[284]: 
 A         B        date  ints  bools
2013-01-01 -0.121306 -0.097883  1356998400     0   True
2013-01-02  0.695775  0.341734  1356998400     1   True
2013-01-03  0.959726 -1.110336  1356998400     2   True
2013-01-04 -0.619976  0.149748  1356998400     3   True
2013-01-05 -0.732339  0.687738  1356998400     4   True 

通过设置dtype_backend参数,您可以控制生成的 DataFrame 使用的默认数据类型。

In [285]: data = (
 .....: '{"a":{"0":1,"1":3},"b":{"0":2.5,"1":4.5},"c":{"0":true,"1":false},"d":{"0":"a","1":"b"},'
 .....: '"e":{"0":null,"1":6.0},"f":{"0":null,"1":7.5},"g":{"0":null,"1":true},"h":{"0":null,"1":"a"},'
 .....: '"i":{"0":"12-31-2019","1":"12-31-2019"},"j":{"0":null,"1":null}}'
 .....: )
 .....: 

In [286]: df = pd.read_json(StringIO(data), dtype_backend="pyarrow")

In [287]: df
Out[287]: 
 a    b      c  d     e     f     g     h           i     j
0  1  2.5   True  a  <NA>  <NA>  <NA>  <NA>  12-31-2019  None
1  3  4.5  False  b     6   7.5  True     a  12-31-2019  None

In [288]: df.dtypes
Out[288]: 
a     int64[pyarrow]
b    double[pyarrow]
c      bool[pyarrow]
d    string[pyarrow]
e     int64[pyarrow]
f    double[pyarrow]
g      bool[pyarrow]
h    string[pyarrow]
i    string[pyarrow]
j      null[pyarrow]
dtype: object 

规范化

pandas 提供了一个实用函数,可以将字典或字典列表中的半结构化数据规范化为平面表。

In [289]: data = [
 .....:    {"id": 1, "name": {"first": "Coleen", "last": "Volk"}},
 .....:    {"name": {"given": "Mark", "family": "Regner"}},
 .....:    {"id": 2, "name": "Faye Raker"},
 .....: ]
 .....: 

In [290]: pd.json_normalize(data)
Out[290]: 
 id name.first name.last name.given name.family        name
0  1.0     Coleen      Volk        NaN         NaN         NaN
1  NaN        NaN       NaN       Mark      Regner         NaN
2  2.0        NaN       NaN        NaN         NaN  Faye Raker 
In [291]: data = [
 .....:    {
 .....:        "state": "Florida",
 .....:        "shortname": "FL",
 .....:        "info": {"governor": "Rick Scott"},
 .....:        "county": [
 .....:            {"name": "Dade", "population": 12345},
 .....:            {"name": "Broward", "population": 40000},
 .....:            {"name": "Palm Beach", "population": 60000},
 .....:        ],
 .....:    },
 .....:    {
 .....:        "state": "Ohio",
 .....:        "shortname": "OH",
 .....:        "info": {"governor": "John Kasich"},
 .....:        "county": [
 .....:            {"name": "Summit", "population": 1234},
 .....:            {"name": "Cuyahoga", "population": 1337},
 .....:        ],
 .....:    },
 .....: ]
 .....: 

In [292]: pd.json_normalize(data, "county", ["state", "shortname", ["info", "governor"]])
Out[292]: 
 name  population    state shortname info.governor
0        Dade       12345  Florida        FL    Rick Scott
1     Broward       40000  Florida        FL    Rick Scott
2  Palm Beach       60000  Florida        FL    Rick Scott
3      Summit        1234     Ohio        OH   John Kasich
4    Cuyahoga        1337     Ohio        OH   John Kasich 

max_level参数提供了更多控制,可以指定将规范化结束的级别。使用max_level=1,以下代码片段将规范化提供的字典的第一个嵌套级别。

In [293]: data = [
 .....:    {
 .....:        "CreatedBy": {"Name": "User001"},
 .....:        "Lookup": {
 .....:            "TextField": "Some text",
 .....:            "UserField": {"Id": "ID001", "Name": "Name001"},
 .....:        },
 .....:        "Image": {"a": "b"},
 .....:    }
 .....: ]
 .....: 

In [294]: pd.json_normalize(data, max_level=1)
Out[294]: 
 CreatedBy.Name Lookup.TextField                    Lookup.UserField Image.a
0        User001        Some text  {'Id': 'ID001', 'Name': 'Name001'}       b 

行分隔的 json

pandas 能够读取和写入使用 Hadoop 或 Spark 的数据处理流水线中常见的行分隔的 json 文件。

对于行分隔的 json 文件,pandas 还可以返回一个迭代器,该迭代器一次读取chunksize行。这对于大文件或从流中读取非常有用。

In [295]: from io import StringIO

In [296]: jsonl = """
 .....:    {"a": 1, "b": 2}
 .....:    {"a": 3, "b": 4}
 .....: """
 .....: 

In [297]: df = pd.read_json(StringIO(jsonl), lines=True)

In [298]: df
Out[298]: 
 a  b
0  1  2
1  3  4

In [299]: df.to_json(orient="records", lines=True)
Out[299]: '{"a":1,"b":2}\n{"a":3,"b":4}\n'

# reader is an iterator that returns ``chunksize`` lines each iteration
In [300]: with pd.read_json(StringIO(jsonl), lines=True, chunksize=1) as reader:
 .....:    reader
 .....:    for chunk in reader:
 .....:        print(chunk)
 .....: 
Empty DataFrame
Columns: []
Index: []
 a  b
0  1  2
 a  b
1  3  4 

使用engine="pyarrow"指定 pyarrow 读取器可以读取行限定的 json。

In [301]: from io import BytesIO

In [302]: df = pd.read_json(BytesIO(jsonl.encode()), lines=True, engine="pyarrow")

In [303]: df
Out[303]: 
 a  b
0  1  2
1  3  4 

2.0.0 版本中的新功能。

表模式

表模式是描述表格数据集的规范,它是一个 JSON 对象。JSON 包括字段名称、类型和其他属性的信息。您可以使用orient table构建一个具有两个字段schemadata的 JSON 字符串。

In [304]: df = pd.DataFrame(
 .....:    {
 .....:        "A": [1, 2, 3],
 .....:        "B": ["a", "b", "c"],
 .....:        "C": pd.date_range("2016-01-01", freq="d", periods=3),
 .....:    },
 .....:    index=pd.Index(range(3), name="idx"),
 .....: )
 .....: 

In [305]: df
Out[305]: 
 A  B          C
idx 
0    1  a 2016-01-01
1    2  b 2016-01-02
2    3  c 2016-01-03

In [306]: df.to_json(orient="table", date_format="iso")
Out[306]: '{"schema":{"fields":[{"name":"idx","type":"integer"},{"name":"A","type":"integer"},{"name":"B","type":"string"},{"name":"C","type":"datetime"}],"primaryKey":["idx"],"pandas_version":"1.4.0"},"data":[{"idx":0,"A":1,"B":"a","C":"2016-01-01T00:00:00.000"},{"idx":1,"A":2,"B":"b","C":"2016-01-02T00:00:00.000"},{"idx":2,"A":3,"B":"c","C":"2016-01-03T00:00:00.000"}]}' 

schema字段包含fields键,它本身包含列名到类型对的列表,包括IndexMultiIndex(请参阅下面的类型列表)。如果(Multi)索引是唯一的,则schema字段还包含一个primaryKey字段。

第二个字段data包含了使用records方向序列化的数据。索引已包含,并且任何日期时间都符合 ISO 8601 格式,符合 Table Schema 规范的要求。

支持的类型的完整列表在 Table Schema 规范中有描述。此表显示了从 pandas 类型到 Table Schema 类型的映射:

pandas 类型 Table Schema 类型
int64 integer
float64 number
bool boolean
datetime64[ns] datetime
timedelta64[ns] duration
categorical any
object str

生成的表模式的一些注意事项:

  • schema对象包含一个pandas_version字段。这包含了 pandas 模式的版本,每次修订都会递增。

  • 所有日期在序列化时都被转换为 UTC。即使是时区无关的值,也被视为具有偏移量为 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'} 
    
  • 具有时区的日期时间(在序列化之前),包括一个额外的字段tz,其中包含时区名称(例如'US/Central')。

    In [310]: s_tz = pd.Series(pd.date_range("2016", periods=12, tz="US/Central"))
    
    In [311]: build_table_schema(s_tz)
    Out[311]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
     {'name': 'values', 'type': 'datetime', 'tz': 'US/Central'}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'} 
    
  • 期间在序列化之前被转换为时间戳,因此具有被转换为 UTC 的相同行为。此外,期间将包含一个额外的字段freq,其中包含期间的频率,例如'A-DEC'

    In [312]: s_per = pd.Series(1, index=pd.period_range("2016", freq="Y-DEC", periods=4))
    
    In [313]: build_table_schema(s_per)
    Out[313]: 
    {'fields': [{'name': 'index', 'type': 'datetime', 'freq': 'YE-DEC'},
     {'name': 'values', 'type': 'integer'}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'} 
    
  • 分类使用any类型和列出可能值集合的enum约束。此外,还包括一个ordered字段:

    In [314]: s_cat = pd.Series(pd.Categorical(["a", "b", "a"]))
    
    In [315]: build_table_schema(s_cat)
    Out[315]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
     {'name': 'values',
     'type': 'any',
     'constraints': {'enum': ['a', 'b']},
     'ordered': False}],
     'primaryKey': ['index'],
     'pandas_version': '1.4.0'} 
    
  • 包含一个primaryKey字段,其中包含一个标签数组,如果索引是唯一的

    In [316]: s_dupe = pd.Series([1, 2], index=[1, 1])
    
    In [317]: build_table_schema(s_dupe)
    Out[317]: 
    {'fields': [{'name': 'index', 'type': 'integer'},
     {'name': 'values', 'type': 'integer'}],
     'pandas_version': '1.4.0'} 
    
  • primaryKey的行为与 MultiIndexes 相同,但在这种情况下,primaryKey是一个数组:

    In [318]: s_multi = pd.Series(1, index=pd.MultiIndex.from_product([("a", "b"), (0, 1)]))
    
    In [319]: build_table_schema(s_multi)
    Out[319]: 
    {'fields': [{'name': 'level_0', 'type': 'string'},
     {'name': 'level_1', 'type': 'integer'},
     {'name': 'values', 'type': 'integer'}],
     'primaryKey': FrozenList(['level_0', 'level_1']),
     'pandas_version': '1.4.0'} 
    
  • 默认命名大致遵循以下规则:

    • 对于系列,使用object.name。如果为 None,则名称为values
    • 对于DataFrames,使用列名的字符串���本。
    • 对于Index(而不是MultiIndex),使用index.name,如果为 None,则使用index
    • 对于MultiIndex,使用mi.names。如果任何级别没有名称,则使用level_<i>

read_json还接受orient='table'作为参数。这允许以往返的方式保留元数据,如 dtypes 和索引名称。

In [320]: df = pd.DataFrame(
 .....:    {
 .....:        "foo": [1, 2, 3, 4],
 .....:        "bar": ["a", "b", "c", "d"],
 .....:        "baz": pd.date_range("2018-01-01", freq="d", periods=4),
 .....:        "qux": pd.Categorical(["a", "b", "c", "c"]),
 .....:    },
 .....:    index=pd.Index(range(4), name="idx"),
 .....: )
 .....: 

In [321]: df
Out[321]: 
 foo bar        baz qux
idx 
0      1   a 2018-01-01   a
1      2   b 2018-01-02   b
2      3   c 2018-01-03   c
3      4   d 2018-01-04   c

In [322]: df.dtypes
Out[322]: 
foo             int64
bar            object
baz    datetime64[ns]
qux          category
dtype: object

In [323]: df.to_json("test.json", orient="table")

In [324]: new_df = pd.read_json("test.json", orient="table")

In [325]: new_df
Out[325]: 
 foo bar        baz qux
idx 
0      1   a 2018-01-01   a
1      2   b 2018-01-02   b
2      3   c 2018-01-03   c
3      4   d 2018-01-04   c

In [326]: new_df.dtypes
Out[326]: 
foo             int64
bar            object
baz    datetime64[ns]
qux          category
dtype: object 

请注意,字面字符串‘index’作为Index的名称不可往返,任何以'level_'开头的名称在MultiIndex中也是如此。这些在DataFrame.to_json()中默认用于指示缺失值,随后的读取无法区分意图。

In [327]: df.index.name = "index"

In [328]: df.to_json("test.json", orient="table")

In [329]: new_df = pd.read_json("test.json", orient="table")

In [330]: print(new_df.index.name)
None 

当与用户定义的 ExtensionArray 一起使用 orient='table' 时,生成的模式将在相应的 fields 元素中包含一个额外的 extDtype 键。这个额外的键不是标准的,但确实使得扩展类型(例如 read_json(df.to_json(orient="table"), orient="table"))能够进行 JSON 往返。

extDtype 键携带了扩展的名称,如果你已经正确注册了 ExtensionDtype,pandas 将使用该名称来在注册表中进行查找并重新将序列化数据转换为你的自定义 dtype。

HTML

读取 HTML 内容

警告

我们强烈建议你阅读下面关于 BeautifulSoup4/html5lib/lxml 解析器的 HTML 表解析陷阱。

顶层的 read_html() 函数可以接受一个 HTML 字符串/文件/URL,并将 HTML 表格解析为 pandas 的 DataFrame 列表。让我们看几个例子。

注意

read_html 返回一个 DataFrame 对象的 list,即使 HTML 内容中只包含单个表格。

读取不带任何选项的 URL:

In [320]: url = "https://www.fdic.gov/resources/resolutions/bank-failures/failed-bank-list"
In [321]: pd.read_html(url)
Out[321]:
[                         Bank NameBank           CityCity StateSt  ...              Acquiring InstitutionAI Closing DateClosing FundFund
 0                    Almena State Bank             Almena      KS  ...                          Equity Bank    October 23, 2020    10538
 1           First City Bank of Florida  Fort Walton Beach      FL  ...            United Fidelity Bank, fsb    October 16, 2020    10537
 2                 The First State Bank      Barboursville      WV  ...                       MVB Bank, Inc.       April 3, 2020    10536
 3                   Ericson State Bank            Ericson      NE  ...           Farmers and Merchants Bank   February 14, 2020    10535
 4     City National Bank of New Jersey             Newark      NJ  ...                      Industrial Bank    November 1, 2019    10534
 ..                                 ...                ...     ...  ...                                  ...                 ...      ...
 558                 Superior Bank, FSB           Hinsdale      IL  ...                Superior Federal, FSB       July 27, 2001     6004
 559                Malta National Bank              Malta      OH  ...                    North Valley Bank         May 3, 2001     4648
 560    First Alliance Bank & Trust Co.         Manchester      NH  ...  Southern New Hampshire Bank & Trust    February 2, 2001     4647
 561  National State Bank of Metropolis         Metropolis      IL  ...              Banterra Bank of Marion   December 14, 2000     4646
 562                   Bank of Honolulu           Honolulu      HI  ...                   Bank of the Orient    October 13, 2000     4645

 [563 rows x 7 columns]] 

注意

上述 URL 中的数据每个周一都会更改,因此上述结果可能略有不同。

在 HTTP 请求旁边传递标头时读取 URL:

In [322]: url = 'https://www.sump.org/notes/request/' # HTTP request reflector
In [323]: pd.read_html(url)
Out[323]:
[                   0                    1
 0     Remote Socket:  51.15.105.256:51760
 1  Protocol Version:             HTTP/1.1
 2    Request Method:                  GET
 3       Request URI:      /notes/request/
 4     Request Query:                  NaN,
 0   Accept-Encoding:             identity
 1              Host:         www.sump.org
 2        User-Agent:    Python-urllib/3.8
 3        Connection:                close]
In [324]: headers = {
In [325]:    'User-Agent':'Mozilla Firefox v14.0',
In [326]:    'Accept':'application/json',
In [327]:    'Connection':'keep-alive',
In [328]:    'Auth':'Bearer 2*/f3+fe68df*4'
In [329]: }
In [340]: pd.read_html(url, storage_options=headers)
Out[340]:
[                   0                    1
 0     Remote Socket:  51.15.105.256:51760
 1  Protocol Version:             HTTP/1.1
 2    Request Method:                  GET
 3       Request URI:      /notes/request/
 4     Request Query:                  NaN,
 0        User-Agent: Mozilla Firefox v14.0
 1    AcceptEncoding:   gzip,  deflate,  br
 2            Accept:      application/json
 3        Connection:             keep-alive
 4              Auth:  Bearer 2*/f3+fe68df*4] 

注意

我们可以看到我们传递的标头在 HTTP 请求中反映出来。

从上述 URL 的文件内容中读取并将其作为字符串传递给 read_html

In [331]: html_str = """
 .....:         <table>
 .....:             <tr>
 .....:                 <th>A</th>
 .....:                 <th colspan="1">B</th>
 .....:                 <th rowspan="1">C</th>
 .....:             </tr>
 .....:             <tr>
 .....:                 <td>a</td>
 .....:                 <td>b</td>
 .....:                 <td>c</td>
 .....:             </tr>
 .....:         </table>
 .....:     """
 .....: 

In [332]: with open("tmp.html", "w") as f:
 .....:    f.write(html_str)
 .....: 

In [333]: df = pd.read_html("tmp.html")

In [334]: df[0]
Out[334]: 
 A  B  C
0  a  b  c 

如果你愿意,甚至可以传入StringIO的实例:

In [335]: dfs = pd.read_html(StringIO(html_str))

In [336]: dfs[0]
Out[336]: 
 A  B  C
0  a  b  c 

注意

由于有许多涉及网络访问的函数会拖慢文档构建过程,因此下面的示例不会由 IPython 评估器运行。如果你发现错误或无法运行的示例,请毫不犹豫地在pandas GitHub 问题页面上报告。

读取 URL 并匹配包含特定文本的表:

match = "Metcalf Bank"
df_list = pd.read_html(url, match=match) 

指定标题行(默认情况下,<thead> 中位于 <th><td> 元素内的元素用于形成列索引,如果 <thead> 中包含多行,则会创建多级索引);如果指定了,则标题行取自数据减去解析的标题元素(<th> 元素)。

dfs = pd.read_html(url, header=0) 

指定索引列:

dfs = pd.read_html(url, index_col=0) 

指定要跳过的行数:

dfs = pd.read_html(url, skiprows=0) 

使用列表指定要跳过的行数(range 也可以):

dfs = pd.read_html(url, skiprows=range(2)) 

指定 HTML 属性:

dfs1 = pd.read_html(url, attrs={"id": "table"})
dfs2 = pd.read_html(url, attrs={"class": "sortable"})
print(np.array_equal(dfs1[0], dfs2[0]))  # Should be True 

指定应转换为 NaN 的值:

dfs = pd.read_html(url, na_values=["No Acquirer"]) 

指定是否保留默认的 NaN 值集合:

dfs = pd.read_html(url, keep_default_na=False) 

指定列的转换器。这对于具有前导零的数值文本数据非常有用。默认情况下,数值列会被转换为数值类型,前导零会丢失。为了避免这种情况,我们可以将这些列转换为字符串。

url_mcc = "https://en.wikipedia.org/wiki/Mobile_country_code?oldid=899173761"
dfs = pd.read_html(
    url_mcc,
    match="Telekom Albania",
    header=0,
    converters={"MNC": str},
) 

使用上述某些组合:

dfs = pd.read_html(url, match="Metcalf Bank", index_col=0) 

读取 pandas to_html 输出(浮点精度有所损失):

df = pd.DataFrame(np.random.randn(2, 2))
s = df.to_html(float_format="{0:.40g}".format)
dfin = pd.read_html(s, index_col=0) 

如果lxml后端在失败的情况下会引发错误,那么只提供该解析器。如果你只有一个解析器,你可以只提供一个字符串,但是如果函数期望一系列字符串,那么传递一个包含一个字符串的列表被认为是一种良好的做法。你可以使用:

dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor=["lxml"]) 

或者你可以传递flavor='lxml'而不使用列表:

dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor="lxml") 

但是,如果你已经安装了 bs4 和 html5lib 并传递了None['lxml', 'bs4'],那么解析很可能会成功。请注意,一旦解析成功,函数将返回

dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor=["lxml", "bs4"]) 

可以使用extract_links="all"从单元格中提取链接和文本。

In [337]: html_table = """
 .....: <table>
 .....:  <tr>
 .....:    <th>GitHub</th>
 .....:  </tr>
 .....:  <tr>
 .....:    <td><a href="https://github.com/pandas-dev/pandas">pandas</a></td>
 .....:  </tr>
 .....: </table>
 .....: """
 .....: 

In [338]: df = pd.read_html(
 .....:    StringIO(html_table),
 .....:    extract_links="all"
 .....: )[0]
 .....: 

In [339]: df
Out[339]: 
 (GitHub, None)
0  (pandas, https://github.com/pandas-dev/pandas)

In [340]: df[("GitHub", None)]
Out[340]: 
0    (pandas, https://github.com/pandas-dev/pandas)
Name: (GitHub, None), dtype: object

In [341]: df[("GitHub", None)].str[1]
Out[341]: 
0    https://github.com/pandas-dev/pandas
Name: (GitHub, None), dtype: object 

1.5.0 版本中的新功能。### 写入 HTML 文件

DataFrame对象有一个实例方法to_html,它将DataFrame的内容呈现为 HTML 表格。函数参数如上述to_string方法中所述。

注意

为了简洁起见,此处未显示DataFrame.to_html的所有可能选项。请参阅DataFrame.to_html()以获取完整的选项集。

注意

在支持 HTML 渲染的环境中,比如 Jupyter Notebook,display(HTML(...))将把原始 HTML 呈现到环境中。

In [342]: from IPython.display import display, HTML

In [343]: df = pd.DataFrame(np.random.randn(2, 2))

In [344]: df
Out[344]: 
 0         1
0 -0.345352  1.314232
1  0.690579  0.995761

In [345]: html = df.to_html()

In [346]: print(html)  # raw html
<table border="1" class="dataframe">
 <thead>
 <tr style="text-align: right;">
 <th></th>
 <th>0</th>
 <th>1</th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <th>0</th>
 <td>-0.345352</td>
 <td>1.314232</td>
 </tr>
 <tr>
 <th>1</th>
 <td>0.690579</td>
 <td>0.995761</td>
 </tr>
 </tbody>
</table>

In [347]: display(HTML(html))
<IPython.core.display.HTML object> 

columns参数将限制显示的列:

In [348]: html = df.to_html(columns=[0])

In [349]: print(html)
<table border="1" class="dataframe">
 <thead>
 <tr style="text-align: right;">
 <th></th>
 <th>0</th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <th>0</th>
 <td>-0.345352</td>
 </tr>
 <tr>
 <th>1</th>
 <td>0.690579</td>
 </tr>
 </tbody>
</table>

In [350]: display(HTML(html))
<IPython.core.display.HTML object> 

float_format采用 Python 可调用对象来控制浮点值的精度:

In [351]: html = df.to_html(float_format="{0:.10f}".format)

In [352]: print(html)
<table border="1" class="dataframe">
 <thead>
 <tr style="text-align: right;">
 <th></th>
 <th>0</th>
 <th>1</th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <th>0</th>
 <td>-0.3453521949</td>
 <td>1.3142323796</td>
 </tr>
 <tr>
 <th>1</th>
 <td>0.6905793352</td>
 <td>0.9957609037</td>
 </tr>
 </tbody>
</table>

In [353]: display(HTML(html))
<IPython.core.display.HTML object> 

bold_rows将默认使行标签加粗,但你可以将其关闭:

In [354]: html = df.to_html(bold_rows=False)

In [355]: print(html)
<table border="1" class="dataframe">
 <thead>
 <tr style="text-align: right;">
 <th></th>
 <th>0</th>
 <th>1</th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <td>0</td>
 <td>-0.345352</td>
 <td>1.314232</td>
 </tr>
 <tr>
 <td>1</td>
 <td>0.690579</td>
 <td>0.995761</td>
 </tr>
 </tbody>
</table>

In [356]: display(HTML(html))
<IPython.core.display.HTML object> 

classes参数提供了给生成的 HTML 表格添加 CSS 类的功能。请注意,这些类会附加到现有的'dataframe'类上。

In [357]: print(df.to_html(classes=["awesome_table_class", "even_more_awesome_class"]))
<table border="1" class="dataframe awesome_table_class even_more_awesome_class">
 <thead>
 <tr style="text-align: right;">
 <th></th>
 <th>0</th>
 <th>1</th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <th>0</th>
 <td>-0.345352</td>
 <td>1.314232</td>
 </tr>
 <tr>
 <th>1</th>
 <td>0.690579</td>
 <td>0.995761</td>
 </tr>
 </tbody>
</table> 

render_links参数提供了在包含 URL 的单元格中添加超链接的功能。

In [358]: url_df = pd.DataFrame(
 .....:    {
 .....:        "name": ["Python", "pandas"],
 .....:        "url": ["https://www.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 表格解析陷阱

有关在顶级 pandas io 函数read_html中用于解析 HTML 表格的库的版本问题。

关于 lxml 的问题

  • 好处

    • lxml非常快。
    • lxml需要 Cython 才能正确安装。
  • 缺点

    • lxml 对其解析结果做出任何保证,除非给出严格有效的标记
    • 鉴于上述情况,我们选择允许您,用户,使用lxml后端,但如果lxml无法解析,此后端将使用 html5lib
    • 因此强烈建议您安装BeautifulSoup4html5lib,这样即使lxml失败,您仍将获得有效结果(前提是其他一切都有效)。

使用lxml作为后端时,BeautifulSoup4存在的问题

  • 以上问题在此处同样存在,因为BeautifulSoup4本质上只是一个围绕解析器后端的包装器。

使用html5lib作为后端时,BeautifulSoup4存在的问题

  • 好处

    • html5liblxml宽松得多,因此以更理智的方式处理现实生活中的标记,而不仅仅是,例如,删除元素而不通知您。
    • html5lib会自动从无效标记中生成有效的 HTML5 标记。这对于解析 HTML 表格非常重要,因为它保证了一个有效的文档。但是,这并不意味着它是“正确的”,因为修复标记的过程没有一个单一的定义。
    • html5lib是纯 Python,除了安装它自己之外,不需要任何其他构建步骤。
  • 缺点

    • 使用html5lib的最大缺点是它慢得像糖浆一样。但是请考虑到网上的许多表格都不足以使解析算法运行时间成为问题。更有可能的是瓶颈将在从 URL 读取原始文本的过程中,即 IO(输入-输出)。对于非常大的表格,这可能不成立。 ### 读取 HTML 内容

警告

我们强烈建议您阅读下面关于 BeautifulSoup4/html5lib/lxml 解析器的问题的 HTML 表格解析陷阱。

顶层 read_html() 函数可以接受 HTML 字符串/文件/URL,并将 HTML 表解析为一组 pandas DataFrames。让我们看几个例子。

注意

read_html 返回一个 DataFrame 对象的列表,即使 HTML 内容中只包含一个表格。

读取没有选项的 URL:

In [320]: url = "https://www.fdic.gov/resources/resolutions/bank-failures/failed-bank-list"
In [321]: pd.read_html(url)
Out[321]:
[                         Bank NameBank           CityCity StateSt  ...              Acquiring InstitutionAI Closing DateClosing FundFund
 0                    Almena State Bank             Almena      KS  ...                          Equity Bank    October 23, 2020    10538
 1           First City Bank of Florida  Fort Walton Beach      FL  ...            United Fidelity Bank, fsb    October 16, 2020    10537
 2                 The First State Bank      Barboursville      WV  ...                       MVB Bank, Inc.       April 3, 2020    10536
 3                   Ericson State Bank            Ericson      NE  ...           Farmers and Merchants Bank   February 14, 2020    10535
 4     City National Bank of New Jersey             Newark      NJ  ...                      Industrial Bank    November 1, 2019    10534
 ..                                 ...                ...     ...  ...                                  ...                 ...      ...
 558                 Superior Bank, FSB           Hinsdale      IL  ...                Superior Federal, FSB       July 27, 2001     6004
 559                Malta National Bank              Malta      OH  ...                    North Valley Bank         May 3, 2001     4648
 560    First Alliance Bank & Trust Co.         Manchester      NH  ...  Southern New Hampshire Bank & Trust    February 2, 2001     4647
 561  National State Bank of Metropolis         Metropolis      IL  ...              Banterra Bank of Marion   December 14, 2000     4646
 562                   Bank of Honolulu           Honolulu      HI  ...                   Bank of the Orient    October 13, 2000     4645

 [563 rows x 7 columns]] 

注意

上述 URL 的数据每周一更改,因此上述结果可能略有不同。

通过 HTTP 请求时传递标题以读取 URL:

In [322]: url = 'https://www.sump.org/notes/request/' # HTTP request reflector
In [323]: pd.read_html(url)
Out[323]:
[                   0                    1
 0     Remote Socket:  51.15.105.256:51760
 1  Protocol Version:             HTTP/1.1
 2    Request Method:                  GET
 3       Request URI:      /notes/request/
 4     Request Query:                  NaN,
 0   Accept-Encoding:             identity
 1              Host:         www.sump.org
 2        User-Agent:    Python-urllib/3.8
 3        Connection:                close]
In [324]: headers = {
In [325]:    'User-Agent':'Mozilla Firefox v14.0',
In [326]:    'Accept':'application/json',
In [327]:    'Connection':'keep-alive',
In [328]:    'Auth':'Bearer 2*/f3+fe68df*4'
In [329]: }
In [340]: pd.read_html(url, storage_options=headers)
Out[340]:
[                   0                    1
 0     Remote Socket:  51.15.105.256:51760
 1  Protocol Version:             HTTP/1.1
 2    Request Method:                  GET
 3       Request URI:      /notes/request/
 4     Request Query:                  NaN,
 0        User-Agent: Mozilla Firefox v14.0
 1    AcceptEncoding:   gzip,  deflate,  br
 2            Accept:      application/json
 3        Connection:             keep-alive
 4              Auth:  Bearer 2*/f3+fe68df*4] 

注意

我们可以看到我们传递的标题反映在 HTTP 请求中。

从上述 URL 的文件内容中读取并将其作为字符串传递给 read_html

In [331]: html_str = """
 .....:         <table>
 .....:             <tr>
 .....:                 <th>A</th>
 .....:                 <th colspan="1">B</th>
 .....:                 <th rowspan="1">C</th>
 .....:             </tr>
 .....:             <tr>
 .....:                 <td>a</td>
 .....:                 <td>b</td>
 .....:                 <td>c</td>
 .....:             </tr>
 .....:         </table>
 .....:     """
 .....: 

In [332]: with open("tmp.html", "w") as f:
 .....:    f.write(html_str)
 .....: 

In [333]: df = pd.read_html("tmp.html")

In [334]: df[0]
Out[334]: 
 A  B  C
0  a  b  c 

如果您愿意,甚至可以传递 StringIO 的实例:

In [335]: dfs = pd.read_html(StringIO(html_str))

In [336]: dfs[0]
Out[336]: 
 A  B  C
0  a  b  c 

注意

由于有很多涉及网络访问的函数会减慢文档构建速度,因此 IPython 评估器不会运行以下示例。如果你发现错误或无法运行的示例,请毫不犹豫地在 pandas GitHub 问题页面 上报告。

读取一个 URL 并匹配包含特定文本的表格:

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) 

读取 pandas to_html 输出(会有一些浮点精度的损失):

df = pd.DataFrame(np.random.randn(2, 2))
s = df.to_html(float_format="{0:.40g}".format)
dfin = pd.read_html(s, index_col=0) 

如果 lxml 是唯一提供的解析器,那么在解析失败时 lxml 后端会引发错误。如果你只有一个解析器,可以只提供一个字符串,但是最好传递一个包含一个字符串的列表,例如,如果函数期望一个字符串序列。你可以使用:

dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor=["lxml"]) 

或者你可以传递 flavor='lxml' 而不使用列表:

dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor="lxml") 

然而,如果你安装了 bs4 和 html5lib 并传递 None['lxml', 'bs4'],那么解析很可能会成功。请注意,一旦解析成功,函数将立即返回

dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor=["lxml", "bs4"]) 

链接可以从单元格中提取出来,同时使用 extract_links="all" 可以提取文本和链接。

In [337]: html_table = """
 .....: <table>
 .....:  <tr>
 .....:    <th>GitHub</th>
 .....:  </tr>
 .....:  <tr>
 .....:    <td><a href="https://github.com/pandas-dev/pandas">pandas</a></td>
 .....:  </tr>
 .....: </table>
 .....: """
 .....: 

In [338]: df = pd.read_html(
 .....:    StringIO(html_table),
 .....:    extract_links="all"
 .....: )[0]
 .....: 

In [339]: df
Out[339]: 
 (GitHub, None)
0  (pandas, https://github.com/pandas-dev/pandas)

In [340]: df[("GitHub", None)]
Out[340]: 
0    (pandas, https://github.com/pandas-dev/pandas)
Name: (GitHub, None), dtype: object

In [341]: df[("GitHub", None)].str[1]
Out[341]: 
0    https://github.com/pandas-dev/pandas
Name: (GitHub, None), dtype: object 

版本 1.5.0 中的新功能。

写入 HTML 文件

DataFrame 对象有一个实例方法 to_html,它将 DataFrame 的内容呈现为 HTML 表格。函数参数与上面描述的 to_string 方法相同。

注意

为了简洁起见,这里没有展示 DataFrame.to_html 的所有可能选项。请查看 DataFrame.to_html() 获取完整的选项。

注意

在支持 HTML 渲染的环境中,比如 Jupyter Notebook,display(HTML(...)) 将���原始 HTML 渲染到环境中。

In [342]: from IPython.display import display, HTML

In [343]: df = pd.DataFrame(np.random.randn(2, 2))

In [344]: df
Out[344]: 
 0         1
0 -0.345352  1.314232
1  0.690579  0.995761

In [345]: html = df.to_html()

In [346]: print(html)  # raw html
<table border="1" class="dataframe">
 <thead>
 <tr style="text-align: right;">
 <th></th>
 <th>0</th>
 <th>1</th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <th>0</th>
 <td>-0.345352</td>
 <td>1.314232</td>
 </tr>
 <tr>
 <th>1</th>
 <td>0.690579</td>
 <td>0.995761</td>
 </tr>
 </tbody>
</table>

In [347]: display(HTML(html))
<IPython.core.display.HTML object> 

columns 参数将限制显示的列:

In [348]: html = df.to_html(columns=[0])

In [349]: print(html)
<table border="1" class="dataframe">
 <thead>
 <tr style="text-align: right;">
 <th></th>
 <th>0</th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <th>0</th>
 <td>-0.345352</td>
 </tr>
 <tr>
 <th>1</th>
 <td>0.690579</td>
 </tr>
 </tbody>
</table>

In [350]: display(HTML(html))
<IPython.core.display.HTML object> 

float_format 接受一个 Python 可调用对象来控制浮点值的精度:

In [351]: html = df.to_html(float_format="{0:.10f}".format)

In [352]: print(html)
<table border="1" class="dataframe">
 <thead>
 <tr style="text-align: right;">
 <th></th>
 <th>0</th>
 <th>1</th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <th>0</th>
 <td>-0.3453521949</td>
 <td>1.3142323796</td>
 </tr>
 <tr>
 <th>1</th>
 <td>0.6905793352</td>
 <td>0.9957609037</td>
 </tr>
 </tbody>
</table>

In [353]: display(HTML(html))
<IPython.core.display.HTML object> 

bold_rows 默认会使行标签加粗,但你可以关闭这个选项:

In [354]: html = df.to_html(bold_rows=False)

In [355]: print(html)
<table border="1" class="dataframe">
 <thead>
 <tr style="text-align: right;">
 <th></th>
 <th>0</th>
 <th>1</th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <td>0</td>
 <td>-0.345352</td>
 <td>1.314232</td>
 </tr>
 <tr>
 <td>1</td>
 <td>0.690579</td>
 <td>0.995761</td>
 </tr>
 </tbody>
</table>

In [356]: display(HTML(html))
<IPython.core.display.HTML object> 

classes 参数提供了给生成的 HTML 表格添加 CSS 类的功能。请注意,这些类会追加到现有的 'dataframe' 类中。

In [357]: print(df.to_html(classes=["awesome_table_class", "even_more_awesome_class"]))
<table border="1" class="dataframe awesome_table_class even_more_awesome_class">
 <thead>
 <tr style="text-align: right;">
 <th></th>
 <th>0</th>
 <th>1</th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <th>0</th>
 <td>-0.345352</td>
 <td>1.314232</td>
 </tr>
 <tr>
 <th>1</th>
 <td>0.690579</td>
 <td>0.995761</td>
 </tr>
 </tbody>
</table> 

render_links 参数提供了在包含 URL 的单元格中添加超链接的功能。

In [358]: url_df = pd.DataFrame(
 .....:    {
 .....:        "name": ["Python", "pandas"],
 .....:        "url": ["https://www.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 表格解析陷阱

顶级 pandas io 函数read_html中用于解析 HTML 表格的库存在一些版本问题。

lxml的问题

  • 优点

    • lxml非常快。
    • lxml需要 Cython 正确安装。
  • 缺点

    • lxml不会保证其解析结果的结果除非给出了严格有效的标记
    • 鉴于上述情况,我们选择允许用户使用lxml后端,但是**如果lxml无法解析,则会使用html5lib作为后备方案。
    • 因此,强烈建议您同时安装BeautifulSoup4html5lib,这样即使lxml失败,您仍将获得有效的结果(前提是其他一切都有效)。

BeautifulSoup4使用lxml作为后端的问题

  • 以上问题在这里同样存在,因为BeautifulSoup4本质上只是一个围绕解析器后端的包装器。

BeautifulSoup4使用html5lib作为后端的问题

  • 优点

    • html5liblxml宽松得多,因此更合理地处理实际标记,而不仅仅是,例如,删除元素而不通知您。
    • html5lib 会自动从无效标记中生成有效的 HTML5 标记。这对于解析 HTML 表格非常重要,因为它保证了一个有效的文档。但是,这并不意味着它是“正确的”,因为修复标记的过程没有一个单一的定义。
    • html5lib是纯 Python,并且除了自身的安装外,不需要任何额外的构建步骤。
  • 缺点

    • 使用 html5lib 的最大缺点是速度慢得要命。但是要考虑到,许多网页上的表格并不足以使解析算法的运行时间成为瓶颈。更有可能的是瓶颈出现在通过网络从 URL 读取原始文本的过程中,即 IO(输入-输出)。对于非常大的表格,这可能并不成立。

LaTeX

新增于版本 1.3.0。

目前没有从 LaTeX 中读取的方法,只有输出方法。

写入 LaTeX 文件

注意

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} 

写入 LaTeX 文件

注意

DataFrame Styler 对象目前都有一个 to_latex 方法。我们建议使用 Styler.to_latex() 方法而不是 DataFrame.to_latex(),因为前者在条件样式上更灵活,而后者可能在未来被弃用。

查看 Styler.to_latex 的文档,其中提供了条件样式的示例,并解释了其关键字参数的操作。

对于简单的应用,以下模式就足够了。

In [369]: df = pd.DataFrame([[1, 2], [3, 4]], index=["a", "b"], columns=["c", "d"])

In [370]: print(df.style.to_latex())
\begin{tabular}{lrr}
 & c & d \\
a & 1 & 2 \\
b & 3 & 4 \\
\end{tabular} 

在输出之前格式化值,使用 Styler.format 方法进行链接。

In [371]: print(df.style.format("€ {}").to_latex())
\begin{tabular}{lrr}
 & c & d \\
a & € 1 & € 2 \\
b & € 3 & € 4 \\
\end{tabular} 

XML

读取 XML

新增于版本 1.3.0。

顶级的 read_xml() 函数可以接受 XML 字符串/文件/URL,并将节点和属性解析为 pandas 的 DataFrame

注意

由于没有标准的 XML 结构,设计类型可以以多种方式变化,因此,read_xml 最适用于较为平坦、浅层的版本。如果 XML 文档嵌套较深,请使用 stylesheet 功能将 XML 转换为较为平坦的版本。

让我们看几个例子。

读取一个 XML 字符串:

In [372]: from io import StringIO

In [373]: xml = """<?xml version="1.0" encoding="UTF-8"?>
 .....: <bookstore>
 .....:  <book category="cooking">
 .....:    <title lang="en">Everyday Italian</title>
 .....:    <author>Giada De Laurentiis</author>
 .....:    <year>2005</year>
 .....:    <price>30.00</price>
 .....:  </book>
 .....:  <book category="children">
 .....:    <title lang="en">Harry Potter</title>
 .....:    <author>J K. Rowling</author>
 .....:    <year>2005</year>
 .....:    <price>29.99</price>
 .....:  </book>
 .....:  <book category="web">
 .....:    <title lang="en">Learning XML</title>
 .....:    <author>Erik T. Ray</author>
 .....:    <year>2003</year>
 .....:    <price>39.95</price>
 .....:  </book>
 .....: </bookstore>"""
 .....: 

In [374]: df = pd.read_xml(StringIO(xml))

In [375]: df
Out[375]: 
 category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95 

读取没有选项的 URL:

In [376]: df = pd.read_xml("https://www.w3schools.com/xml/books.xml")

In [377]: df
Out[377]: 
 category              title                  author  year  price      cover
0   cooking   Everyday Italian     Giada De Laurentiis  2005  30.00       None
1  children       Harry Potter            J K. Rowling  2005  29.99       None
2       web  XQuery Kick Start  Vaidyanathan Nagarajan  2003  49.99       None
3       web       Learning XML             Erik T. Ray  2003  39.95  paperback 

读取 “books.xml” 文件的内容,并将其作为字符串传递给 read_xml

In [378]: file_path = "books.xml"

In [379]: with open(file_path, "w") as f:
 .....:    f.write(xml)
 .....: 

In [380]: with open(file_path, "r") as f:
 .....:    df = pd.read_xml(StringIO(f.read()))
 .....: 

In [381]: df
Out[381]: 
 category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95 

读取 “books.xml” 的内容作为 StringIOBytesIO 实例,并将其传递给 read_xml

In [382]: with open(file_path, "r") as f:
 .....:    sio = StringIO(f.read())
 .....: 

In [383]: df = pd.read_xml(sio)

In [384]: df
Out[384]: 
 category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95 
In [385]: with open(file_path, "rb") as f:
 .....:    bio = BytesIO(f.read())
 .....: 

In [386]: df = pd.read_xml(bio)

In [387]: df
Out[387]: 
 category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95 

甚至可以从 AWS S3 桶中读取 XML,比如 NIH NCBI PMC Article Datasets 提供的生物医学和生命科学期刊:

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,您可以访问扩展 Python 的 ElementTree API 的功能齐全的 XML 库。一个强大的工具是能够使用更具表现力的 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 >
 .....:  <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 >
 .....: <row>
 .....:   <shape>square</shape>
 .....:   <degrees>360</degrees>
 .....:   <sides>4.0</sides>
 .....: </row>
 .....: <row>
 .....:   <shape>circle</shape>
 .....:   <degrees>360</degrees>
 .....:   <sides/>
 .....: </row>
 .....: <row>
 .....:   <shape>triangle</shape>
 .....:   <degrees>180</degrees>
 .....:   <sides>3.0</sides>
 .....: </row>
 .....: </data>"""
 .....: 

In [400]: df = pd.read_xml(StringIO(xml),
 .....:                 xpath="//pandas:row",
 .....:                 namespaces={"pandas": "https://example.com"})
 .....: 

In [401]: df
Out[401]: 
 shape  degrees  sides
0    square      360    4.0
1    circle      360    NaN
2  triangle      180    3.0 

但是,如果 XPath 不引用节点名称,如默认的 /*,则不需要namespaces

注意

由于 xpath 标识要解析的内容的父级,因此仅解析包括子节点或当前属性的直接后代。因此,read_xml 不会解析子代的文本或其他后代的文本,并且不会解析任何后代的属性。要检索更低级别的内容,请将 xpath 调整为更低级别。例如,

In [402]: xml = """
 .....: <data>
 .....:  <row>
 .....:    <shape sides="4">square</shape>
 .....:    <degrees>360</degrees>
 .....:  </row>
 .....:  <row>
 .....:    <shape sides="0">circle</shape>
 .....:    <degrees>360</degrees>
 .....:  </row>
 .....:  <row>
 .....:    <shape sides="3">triangle</shape>
 .....:    <degrees>180</degrees>
 .....:  </row>
 .....: </data>"""
 .....: 

In [403]: df = pd.read_xml(StringIO(xml), xpath="./row")

In [404]: df
Out[404]: 
 shape  degrees
0    square      360
1    circle      360
2  triangle      180 

显示了在 shape 元素上的属性sides未被解析为预期,因为该属性位于 row 元素的子级而不是 row 元素本身。换句话说,sides 属性是 row 元素的孙级后代。但是,xpath 目标 row 元素仅覆盖其子级和属性。

使用 lxml 作为解析器,您可以使用 XSLT 脚本来展平嵌套的 XML 文档,该脚本也可以是字符串/文件/URL 类型。作为背景,XSLT 是一种特殊目的的语言,写在一个特殊的 XML 文件中,可以使用 XSLT 处理器将原始 XML 文档转换为其他 XML、HTML,甚至文本(CSV、JSON 等)。

例如,考虑芝加哥“L”轻轨的这种有些嵌套的结构,其中站和乘车元素封装了它们自己部分的数据。通过以下 XSLT,lxml 可以将原始的嵌套文档转换为更平坦的输出(如下所示作为演示)以便更容易地解析为 DataFrame

In [405]: xml = """<?xml version='1.0' encoding='utf-8'?>
 .....: <response>
 .....:  <row>
 .....:    <station id="40850" name="Library"/>
 .....:    <month>2020-09-01T00:00:00</month>
 .....:    <rides>
 .....:      <avg_weekday_rides>864.2</avg_weekday_rides>
 .....:      <avg_saturday_rides>534</avg_saturday_rides>
 .....:      <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
 .....:    </rides>
 .....:  </row>
 .....:  <row>
 .....:    <station id="41700" name="Washington/Wabash"/>
 .....:    <month>2020-09-01T00:00:00</month>
 .....:    <rides>
 .....:      <avg_weekday_rides>2707.4</avg_weekday_rides>
 .....:      <avg_saturday_rides>1909.8</avg_saturday_rides>
 .....:      <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
 .....:    </rides>
 .....:  </row>
 .....:  <row>
 .....:    <station id="40380" name="Clark/Lake"/>
 .....:    <month>2020-09-01T00:00:00</month>
 .....:    <rides>
 .....:      <avg_weekday_rides>2949.6</avg_weekday_rides>
 .....:      <avg_saturday_rides>1657</avg_saturday_rides>
 .....:      <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
 .....:    </rides>
 .....:  </row>
 .....: </response>"""
 .....: 

In [406]: xsl = """<xsl:stylesheet version="1.0" >
 .....:   <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
 .....:   <xsl:strip-space elements="*"/>
 .....:   <xsl:template match="/response">
 .....:      <xsl:copy>
 .....:        <xsl:apply-templates select="row"/>
 .....:      </xsl:copy>
 .....:   </xsl:template>
 .....:   <xsl:template match="row">
 .....:      <xsl:copy>
 .....:        <station_id><xsl:value-of select="station/@id"/></station_id>
 .....:        <station_name><xsl:value-of select="station/@name"/></station_name>
 .....:        <xsl:copy-of select="month|rides/*"/>
 .....:      </xsl:copy>
 .....:   </xsl:template>
 .....: </xsl:stylesheet>"""
 .....: 

In [407]: output = """<?xml version='1.0' encoding='utf-8'?>
 .....: <response>
 .....:   <row>
 .....:      <station_id>40850</station_id>
 .....:      <station_name>Library</station_name>
 .....:      <month>2020-09-01T00:00:00</month>
 .....:      <avg_weekday_rides>864.2</avg_weekday_rides>
 .....:      <avg_saturday_rides>534</avg_saturday_rides>
 .....:      <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
 .....:   </row>
 .....:   <row>
 .....:      <station_id>41700</station_id>
 .....:      <station_name>Washington/Wabash</station_name>
 .....:      <month>2020-09-01T00:00:00</month>
 .....:      <avg_weekday_rides>2707.4</avg_weekday_rides>
 .....:      <avg_saturday_rides>1909.8</avg_saturday_rides>
 .....:      <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
 .....:   </row>
 .....:   <row>
 .....:      <station_id>40380</station_id>
 .....:      <station_name>Clark/Lake</station_name>
 .....:      <month>2020-09-01T00:00:00</month>
 .....:      <avg_weekday_rides>2949.6</avg_weekday_rides>
 .....:      <avg_saturday_rides>1657</avg_saturday_rides>
 .....:      <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
 .....:   </row>
 .....: </response>"""
 .....: 

In [408]: df = pd.read_xml(StringIO(xml), stylesheet=xsl)

In [409]: df
Out[409]: 
 station_id       station_name  ... avg_saturday_rides  avg_sunday_holiday_rides
0       40850            Library  ...              534.0                     417.2
1       41700  Washington/Wabash  ...             1909.8                    1438.6
2       40380         Clark/Lake  ...             1657.0                    1453.8

[3 rows x 6 columns] 

对于可以达到几百兆到几个千兆字节的非常大的 XML 文件,pandas.read_xml() 支持使用 lxml 的 iterparseetree 的 iterparse 来解析这些庞大的文件,这些方法是内存高效的方法,用于遍历 XML 树并提取特定元素和属性,而无需在内存中保存整个树。

自 1.5.0 版开始。

要使用此功能,必须将物理 XML 文件路径传递给 read_xml 并使用 iterparse 参数。文件不应压缩或指向在线来源,而应存储在本地磁盘上。此外,iterparse 应该是一个字典,其中键是文档中的重复节点(成为行),值是重复节点的任何元素或属性的列表(即后代,例如子代、孙代)。由于此方法不使用 XPath,后代不需要彼此共享相同的关系。下面是读取维基百科最新文章数据转储(12 GB+)的示例。

In [1]: df = pd.read_xml(
...         "/path/to/downloaded/enwikisource-latest-pages-articles.xml",
...         iterparse = {"page": ["title", "ns", "id"]}
...     )
...     df
Out[2]:
 title   ns        id
0                                       Gettysburg Address    0     21450
1                                                Main Page    0     42950
2                            Declaration by United Nations    0      8435
3             Constitution of the United States of America    0      8435
4                     Declaration of Independence (Israel)    0     17858
...                                                    ...  ...       ...
3578760               Page:Black cat 1897 07 v2 n10.pdf/17  104    219649
3578761               Page:Black cat 1897 07 v2 n10.pdf/43  104    219649
3578762               Page:Black cat 1897 07 v2 n10.pdf/44  104    219649
3578763      The History of Tom Jones, a Foundling/Book IX    0  12084291
3578764  Page:Shakespeare of Stratford (1926) Yale.djvu/91  104     21450

[3578765 rows x 3 columns] 
```  ### 编写 XML

新版本 1.3.0 中新增。

`DataFrame` 对象有一个实例方法 `to_xml`,它将 `DataFrame` 的内容呈现为 XML 文档。

注意

此方法不支持 XML 的特殊属性,包括 DTD、CData、XSD 模式、处理指令、注释等。仅支持根级别的命名空间。但是,`stylesheet` 允许在初始输出后进行设计更改。

让我们看几个例子。

写一个没有选项的 XML:

```py
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 >
 <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 >
 <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" >
 .....:   <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
 .....:   <xsl:strip-space elements="*"/>
 .....:   <xsl:template match="/data">
 .....:     <geometry>
 .....:       <xsl:apply-templates select="row"/>
 .....:     </geometry>
 .....:   </xsl:template>
 .....:   <xsl:template match="row">
 .....:     <object index="{index}">
 .....:       <xsl:if test="shape!='circle'">
 .....:           <xsl:attribute name="type">polygon</xsl:attribute>
 .....:       </xsl:if>
 .....:       <xsl:copy-of select="shape"/>
 .....:       <property>
 .....:           <xsl:copy-of select="degrees|sides"/>
 .....:       </property>
 .....:     </object>
 .....:   </xsl:template>
 .....: </xsl:stylesheet>"""
 .....: 

In [423]: print(geom_df.to_xml(stylesheet=xsl))
<?xml version="1.0"?>
<geometry>
 <object index="0" type="polygon">
 <shape>square</shape>
 <property>
 <degrees>360</degrees>
 <sides>4.0</sides>
 </property>
 </object>
 <object index="1">
 <shape>circle</shape>
 <property>
 <degrees>360</degrees>
 <sides/>
 </property>
 </object>
 <object index="2" type="polygon">
 <shape>triangle</shape>
 <property>
 <degrees>180</degrees>
 <sides>3.0</sides>
 </property>
 </object>
</geometry> 

XML 最终笔记

  • 所有 XML 文档遵循 W3C 规范etreelxml 解析器将无法解析任何不符合良好形式或遵循 XML 语法规则的标记文档。请注意,除非遵循 XHTML 规范,否则 HTML 不是 XML 文档。但是,其他流行的标记类型,包括 KML、XAML、RSS、MusicML、MathML,都符合 XML 模式

  • 基于上述原因,如果您的应用在进行 pandas 操作之前构建 XML,请使用适当的 DOM 库(如 etreelxml)来构建必要的文档,而不是通过字符串连接或正则调整。永远记住 XML 是带有标记规则的 特殊 文本文件。

  • 对于非常大的 XML 文件(几百兆字节至几十亿字节),XPath 和 XSLT 可能会变得占用大量内存。确保有足够的可用 RAM 来读取和写入大型 XML 文件(大约为文本大小的 5 倍)。

  • 因为 XSLT 是一种编程语言,所以要谨慎使用,因为这样的脚本可能会在您的环境中带来安全风险,并且可能会运行大型或无限递归操作。始终在完整运行之前在小片段上测试脚本。

  • etree 解析器支持 read_xmlto_xml 的所有功能,但不支持复杂的 XPath 和任何 XSLT。尽管功能有限,但 etree 仍然是一个可靠且功能强大的解析器和树构建器。对于较大的文件,其性能可能在某种程度上落后于 lxml,但在小到中等大小的文件上相对不太明显。

读取 XML

新版本 1.3.0 中的新功能。

顶层的 read_xml() 函数可以接受 XML 字符串/文件/URL,并将节点和属性解析为 pandas DataFrame

注意

由于没有标准的 XML 结构,其中设计类型可以以许多方式变化,因此 read_xml 最适合于较平坦、较浅的版本。如果 XML 文档嵌套层次很深,请使用 stylesheet 功能将 XML 转换为较平坦的版本。

让我们看几个例子。

读取 XML 字符串:

In [372]: from io import StringIO

In [373]: xml = """<?xml version="1.0" encoding="UTF-8"?>
 .....: <bookstore>
 .....:  <book category="cooking">
 .....:    <title lang="en">Everyday Italian</title>
 .....:    <author>Giada De Laurentiis</author>
 .....:    <year>2005</year>
 .....:    <price>30.00</price>
 .....:  </book>
 .....:  <book category="children">
 .....:    <title lang="en">Harry Potter</title>
 .....:    <author>J K. Rowling</author>
 .....:    <year>2005</year>
 .....:    <price>29.99</price>
 .....:  </book>
 .....:  <book category="web">
 .....:    <title lang="en">Learning XML</title>
 .....:    <author>Erik T. Ray</author>
 .....:    <year>2003</year>
 .....:    <price>39.95</price>
 .....:  </book>
 .....: </bookstore>"""
 .....: 

In [374]: df = pd.read_xml(StringIO(xml))

In [375]: df
Out[375]: 
 category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95 

不使用任何选项读取 URL:

In [376]: df = pd.read_xml("https://www.w3schools.com/xml/books.xml")

In [377]: df
Out[377]: 
 category              title                  author  year  price      cover
0   cooking   Everyday Italian     Giada De Laurentiis  2005  30.00       None
1  children       Harry Potter            J K. Rowling  2005  29.99       None
2       web  XQuery Kick Start  Vaidyanathan Nagarajan  2003  49.99       None
3       web       Learning XML             Erik T. Ray  2003  39.95  paperback 

读取 “books.xml” 文件的内容并将其作为字符串传递给 read_xml

In [378]: file_path = "books.xml"

In [379]: with open(file_path, "w") as f:
 .....:    f.write(xml)
 .....: 

In [380]: with open(file_path, "r") as f:
 .....:    df = pd.read_xml(StringIO(f.read()))
 .....: 

In [381]: df
Out[381]: 
 category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95 

将 “books.xml” 的内容读取为 StringIOBytesIO 的实例,并将其传递给 read_xml

In [382]: with open(file_path, "r") as f:
 .....:    sio = StringIO(f.read())
 .....: 

In [383]: df = pd.read_xml(sio)

In [384]: df
Out[384]: 
 category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95 
In [385]: with open(file_path, "rb") as f:
 .....:    bio = BytesIO(f.read())
 .....: 

In [386]: df = pd.read_xml(bio)

In [387]: df
Out[387]: 
 category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99
2       web      Learning XML          Erik T. Ray  2003  39.95 

甚至可以从 AWS S3 存储桶中读取 XML,例如 NIH NCBI PMC 文章数据集,提供生物医学和生命科学期刊:

In [388]: df = pd.read_xml(
 .....:    "s3://pmc-oa-opendata/oa_comm/xml/all/PMC1236943.xml",
 .....:    xpath=".//journal-meta",
 .....: )
 .....: 

In [389]: df
Out[389]: 
 journal-id              journal-title       issn  publisher
0  Cardiovasc Ultrasound  Cardiovascular Ultrasound  1476-7120        NaN 

使用 lxml 作为默认的 parser,您可以访问功能齐全的 XML 库,该库扩展了 Python 的 ElementTree API。其中一个强大的工具是能够使用更具表现力的 XPath 有选择地或有条件地查询节点:

In [390]: df = pd.read_xml(file_path, xpath="//book[year=2005]")

In [391]: df
Out[391]: 
 category             title               author  year  price
0   cooking  Everyday Italian  Giada De Laurentiis  2005  30.00
1  children      Harry Potter         J K. Rowling  2005  29.99 

仅指定要解析的元素或属性:

In [392]: df = pd.read_xml(file_path, elems_only=True)

In [393]: df
Out[393]: 
 title               author  year  price
0  Everyday Italian  Giada De Laurentiis  2005  30.00
1      Harry Potter         J K. Rowling  2005  29.99
2      Learning XML          Erik T. Ray  2003  39.95 
In [394]: df = pd.read_xml(file_path, attrs_only=True)

In [395]: df
Out[395]: 
 category
0   cooking
1  children
2       web 

XML 文档可以具有带有前缀和不带前缀的默认命名空间,两者均用特殊属性 xmlns 表示。为了在命名空间上下文中通过节点进行解析,xpath 必须引用一个前缀。

例如,以下 XML 包含了一个带有前缀 doc 和 URI 为 https://example.com 的命名空间。为了解析 doc:row 节点,必须使用 namespaces

In [396]: xml = """<?xml version='1.0' encoding='utf-8'?>
 .....: <doc:data >
 .....:  <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 >
 .....: <row>
 .....:   <shape>square</shape>
 .....:   <degrees>360</degrees>
 .....:   <sides>4.0</sides>
 .....: </row>
 .....: <row>
 .....:   <shape>circle</shape>
 .....:   <degrees>360</degrees>
 .....:   <sides/>
 .....: </row>
 .....: <row>
 .....:   <shape>triangle</shape>
 .....:   <degrees>180</degrees>
 .....:   <sides>3.0</sides>
 .....: </row>
 .....: </data>"""
 .....: 

In [400]: df = pd.read_xml(StringIO(xml),
 .....:                 xpath="//pandas:row",
 .....:                 namespaces={"pandas": "https://example.com"})
 .....: 

In [401]: df
Out[401]: 
 shape  degrees  sides
0    square      360    4.0
1    circle      360    NaN
2  triangle      180    3.0 

但是,如果 XPath 不引用节点名称,例如默认的 /*,则不需要 namespaces

注意

由于 xpath 识别要解析的内容的父级,因此仅解析即时的子节点或当前属性。因此,read_xml 不会解析孙子节点或其他后代的文本,并且不会解析任何后代的属性。要检索较低级别的内容,请将 xpath 调整为较低级别。例如,

In [402]: xml = """
 .....: <data>
 .....:  <row>
 .....:    <shape sides="4">square</shape>
 .....:    <degrees>360</degrees>
 .....:  </row>
 .....:  <row>
 .....:    <shape sides="0">circle</shape>
 .....:    <degrees>360</degrees>
 .....:  </row>
 .....:  <row>
 .....:    <shape sides="3">triangle</shape>
 .....:    <degrees>180</degrees>
 .....:  </row>
 .....: </data>"""
 .....: 

In [403]: df = pd.read_xml(StringIO(xml), xpath="./row")

In [404]: df
Out[404]: 
 shape  degrees
0    square      360
1    circle      360
2  triangle      180 

:在 shape 元素上显示的 sides 属性未按预期解析,因为此属性位于 row 元素的子元素上而不是 row 元素本身。换句话说,sides 属性是 row 元素的孙级后代。然而,xpath 的目标是 row 元素,它仅包括其子元素和属性。

使用 lxml 作为解析器,您可以使用 XSLT 脚本展平嵌套的 XML 文档,该脚本也可以是字符串/文件/URL 类型。背景是,XSLT 是一种特殊用途的语言,写在一个特殊的 XML 文件中,可以使用 XSLT 处理器将原始 XML 文档转换为其他 XML、HTML,甚至文本(CSV、JSON 等)。

例如,考虑芝加哥“L”列车的某种嵌套结构,其中站点和乘车元素封装了各自部分的数据。通过下面的 XSLT,lxml 可以将原始嵌套文档转换为更平坦的输出(如下所示以进行演示),以便更容易地解析为 DataFrame

In [405]: xml = """<?xml version='1.0' encoding='utf-8'?>
 .....: <response>
 .....:  <row>
 .....:    <station id="40850" name="Library"/>
 .....:    <month>2020-09-01T00:00:00</month>
 .....:    <rides>
 .....:      <avg_weekday_rides>864.2</avg_weekday_rides>
 .....:      <avg_saturday_rides>534</avg_saturday_rides>
 .....:      <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
 .....:    </rides>
 .....:  </row>
 .....:  <row>
 .....:    <station id="41700" name="Washington/Wabash"/>
 .....:    <month>2020-09-01T00:00:00</month>
 .....:    <rides>
 .....:      <avg_weekday_rides>2707.4</avg_weekday_rides>
 .....:      <avg_saturday_rides>1909.8</avg_saturday_rides>
 .....:      <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
 .....:    </rides>
 .....:  </row>
 .....:  <row>
 .....:    <station id="40380" name="Clark/Lake"/>
 .....:    <month>2020-09-01T00:00:00</month>
 .....:    <rides>
 .....:      <avg_weekday_rides>2949.6</avg_weekday_rides>
 .....:      <avg_saturday_rides>1657</avg_saturday_rides>
 .....:      <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
 .....:    </rides>
 .....:  </row>
 .....: </response>"""
 .....: 

In [406]: xsl = """<xsl:stylesheet version="1.0" >
 .....:   <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
 .....:   <xsl:strip-space elements="*"/>
 .....:   <xsl:template match="/response">
 .....:      <xsl:copy>
 .....:        <xsl:apply-templates select="row"/>
 .....:      </xsl:copy>
 .....:   </xsl:template>
 .....:   <xsl:template match="row">
 .....:      <xsl:copy>
 .....:        <station_id><xsl:value-of select="station/@id"/></station_id>
 .....:        <station_name><xsl:value-of select="station/@name"/></station_name>
 .....:        <xsl:copy-of select="month|rides/*"/>
 .....:      </xsl:copy>
 .....:   </xsl:template>
 .....: </xsl:stylesheet>"""
 .....: 

In [407]: output = """<?xml version='1.0' encoding='utf-8'?>
 .....: <response>
 .....:   <row>
 .....:      <station_id>40850</station_id>
 .....:      <station_name>Library</station_name>
 .....:      <month>2020-09-01T00:00:00</month>
 .....:      <avg_weekday_rides>864.2</avg_weekday_rides>
 .....:      <avg_saturday_rides>534</avg_saturday_rides>
 .....:      <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
 .....:   </row>
 .....:   <row>
 .....:      <station_id>41700</station_id>
 .....:      <station_name>Washington/Wabash</station_name>
 .....:      <month>2020-09-01T00:00:00</month>
 .....:      <avg_weekday_rides>2707.4</avg_weekday_rides>
 .....:      <avg_saturday_rides>1909.8</avg_saturday_rides>
 .....:      <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
 .....:   </row>
 .....:   <row>
 .....:      <station_id>40380</station_id>
 .....:      <station_name>Clark/Lake</station_name>
 .....:      <month>2020-09-01T00:00:00</month>
 .....:      <avg_weekday_rides>2949.6</avg_weekday_rides>
 .....:      <avg_saturday_rides>1657</avg_saturday_rides>
 .....:      <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
 .....:   </row>
 .....: </response>"""
 .....: 

In [408]: df = pd.read_xml(StringIO(xml), stylesheet=xsl)

In [409]: df
Out[409]: 
 station_id       station_name  ... avg_saturday_rides  avg_sunday_holiday_rides
0       40850            Library  ...              534.0                     417.2
1       41700  Washington/Wabash  ...             1909.8                    1438.6
2       40380         Clark/Lake  ...             1657.0                    1453.8

[3 rows x 6 columns] 

对于非常大的 XML 文件,可以达到数百兆字节到几十吉字节的范围,pandas.read_xml() 支持使用 lxml 的 iterparseetree 的 iterparse 解析这些庞大文件的方法,这些方法是内存高效的方法,可以迭代遍历 XML 树并提取特定元素和属性,而不需要在内存中保存整个树。

版本 1.5.0 中的新功能。

要使用此功能,您必须将物理 XML 文件路径传递给 read_xml 并使用 iterparse 参数。文件不应该被压缩或指向在线资源,而应存储在本地磁盘上。此外,iterparse 应该是一个字典,其中键是文档中重复的节点(成为行),值是重复节点的任何元素或属性的列表(即子节点、孙子节点)。由于此方法不使用 XPath,因此后代不需要彼此共享相同的关系。下面展示了读取维基百科非常大(12 GB+)最新文章数据转储的示例。

In [1]: df = pd.read_xml(
...         "/path/to/downloaded/enwikisource-latest-pages-articles.xml",
...         iterparse = {"page": ["title", "ns", "id"]}
...     )
...     df
Out[2]:
 title   ns        id
0                                       Gettysburg Address    0     21450
1                                                Main Page    0     42950
2                            Declaration by United Nations    0      8435
3             Constitution of the United States of America    0      8435
4                     Declaration of Independence (Israel)    0     17858
...                                                    ...  ...       ...
3578760               Page:Black cat 1897 07 v2 n10.pdf/17  104    219649
3578761               Page:Black cat 1897 07 v2 n10.pdf/43  104    219649
3578762               Page:Black cat 1897 07 v2 n10.pdf/44  104    219649
3578763      The History of Tom Jones, a Foundling/Book IX    0  12084291
3578764  Page:Shakespeare of Stratford (1926) Yale.djvu/91  104     21450

[3578765 rows x 3 columns] 

编写 XML

版本 1.3.0 中的新功能。

DataFrame 对象具有一个实例��法 to_xml,它将 DataFrame 的内容呈现为 XML 文档。

注意

此方法不支持 XML 的特殊属性,包括 DTD、CData、XSD 模式、处理指令、注释等。仅支持根级别的命名空间。但是,stylesheet 允许在初始输出后进行设计更改。

让我们看几个例子。

编写一个没有选项的 XML:

In [410]: geom_df = pd.DataFrame(
 .....:    {
 .....:        "shape": ["square", "circle", "triangle"],
 .....:        "degrees": [360, 360, 180],
 .....:        "sides": [4, np.nan, 3],
 .....:    }
 .....: )
 .....: 

In [411]: print(geom_df.to_xml())
<?xml version='1.0' encoding='utf-8'?>
<data>
 <row>
 <index>0</index>
 <shape>square</shape>
 <degrees>360</degrees>
 <sides>4.0</sides>
 </row>
 <row>
 <index>1</index>
 <shape>circle</shape>
 <degrees>360</degrees>
 <sides/>
 </row>
 <row>
 <index>2</index>
 <shape>triangle</shape>
 <degrees>180</degrees>
 <sides>3.0</sides>
 </row>
</data> 

编写一个具有新根和行名称的 XML:

In [412]: print(geom_df.to_xml(root_name="geometry", row_name="objects"))
<?xml version='1.0' encoding='utf-8'?>
<geometry>
 <objects>
 <index>0</index>
 <shape>square</shape>
 <degrees>360</degrees>
 <sides>4.0</sides>
 </objects>
 <objects>
 <index>1</index>
 <shape>circle</shape>
 <degrees>360</degrees>
 <sides/>
 </objects>
 <objects>
 <index>2</index>
 <shape>triangle</shape>
 <degrees>180</degrees>
 <sides>3.0</sides>
 </objects>
</geometry> 

编写一个以属性为中心的 XML:

In [413]: print(geom_df.to_xml(attr_cols=geom_df.columns.tolist()))
<?xml version='1.0' encoding='utf-8'?>
<data>
 <row index="0" shape="square" degrees="360" sides="4.0"/>
 <row index="1" shape="circle" degrees="360"/>
 <row index="2" shape="triangle" degrees="180" sides="3.0"/>
</data> 

编写元素和属性的混合:

In [414]: print(
 .....:    geom_df.to_xml(
 .....:        index=False,
 .....:        attr_cols=['shape'],
 .....:        elem_cols=['degrees', 'sides'])
 .....: )
 .....: 
<?xml version='1.0' encoding='utf-8'?>
<data>
 <row shape="square">
 <degrees>360</degrees>
 <sides>4.0</sides>
 </row>
 <row shape="circle">
 <degrees>360</degrees>
 <sides/>
 </row>
 <row shape="triangle">
 <degrees>180</degrees>
 <sides>3.0</sides>
 </row>
</data> 

任何具有分层列的 DataFrame 将被展平,以便 XML 元素名称以下划线分隔的级别表示:

In [415]: ext_geom_df = pd.DataFrame(
 .....:    {
 .....:        "type": ["polygon", "other", "polygon"],
 .....:        "shape": ["square", "circle", "triangle"],
 .....:        "degrees": [360, 360, 180],
 .....:        "sides": [4, np.nan, 3],
 .....:    }
 .....: )
 .....: 

In [416]: pvt_df = ext_geom_df.pivot_table(index='shape',
 .....:                                 columns='type',
 .....:                                 values=['degrees', 'sides'],
 .....:                                 aggfunc='sum')
 .....: 

In [417]: pvt_df
Out[417]: 
 degrees         sides 
type       other polygon other polygon
shape 
circle     360.0     NaN   0.0     NaN
square       NaN   360.0   NaN     4.0
triangle     NaN   180.0   NaN     3.0

In [418]: print(pvt_df.to_xml())
<?xml version='1.0' encoding='utf-8'?>
<data>
 <row>
 <shape>circle</shape>
 <degrees_other>360.0</degrees_other>
 <degrees_polygon/>
 <sides_other>0.0</sides_other>
 <sides_polygon/>
 </row>
 <row>
 <shape>square</shape>
 <degrees_other/>
 <degrees_polygon>360.0</degrees_polygon>
 <sides_other/>
 <sides_polygon>4.0</sides_polygon>
 </row>
 <row>
 <shape>triangle</shape>
 <degrees_other/>
 <degrees_polygon>180.0</degrees_polygon>
 <sides_other/>
 <sides_polygon>3.0</sides_polygon>
 </row>
</data> 

编写一个具有默认命名空间的 XML:

In [419]: print(geom_df.to_xml(namespaces={"": "https://example.com"}))
<?xml version='1.0' encoding='utf-8'?>
<data >
 <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 >
 <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" >
 .....:   <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
 .....:   <xsl:strip-space elements="*"/>
 .....:   <xsl:template match="/data">
 .....:     <geometry>
 .....:       <xsl:apply-templates select="row"/>
 .....:     </geometry>
 .....:   </xsl:template>
 .....:   <xsl:template match="row">
 .....:     <object index="{index}">
 .....:       <xsl:if test="shape!='circle'">
 .....:           <xsl:attribute name="type">polygon</xsl:attribute>
 .....:       </xsl:if>
 .....:       <xsl:copy-of select="shape"/>
 .....:       <property>
 .....:           <xsl:copy-of select="degrees|sides"/>
 .....:       </property>
 .....:     </object>
 .....:   </xsl:template>
 .....: </xsl:stylesheet>"""
 .....: 

In [423]: print(geom_df.to_xml(stylesheet=xsl))
<?xml version="1.0"?>
<geometry>
 <object index="0" type="polygon">
 <shape>square</shape>
 <property>
 <degrees>360</degrees>
 <sides>4.0</sides>
 </property>
 </object>
 <object index="1">
 <shape>circle</shape>
 <property>
 <degrees>360</degrees>
 <sides/>
 </property>
 </object>
 <object index="2" type="polygon">
 <shape>triangle</shape>
 <property>
 <degrees>180</degrees>
 <sides>3.0</sides>
 </property>
 </object>
</geometry> 

XML 最终说明

  • 所有 XML 文档遵循W3C 规范。如果不符合 XML 语法规则或遵循 XML 语法规则的标记文档,etreelxml解析器都将无法解析。请注意,除非遵循 XHTML 规范,否则 HTML 不是 XML 文档���但是,其他流行的标记类型,包括 KML、XAML、RSS、MusicML、MathML 都符合XML 模式

  • 由于上述原因,如果您的应用在进行 pandas 操作之前构建 XML,请使用适当的 DOM 库,如etreelxml来构建必要的文档,而不是通过字符串拼接或正则表达式调整。永远记住 XML 是一个带有标记规则的特殊文本文件。

  • 对于非常大的 XML 文件(几百 MB 到 GB),XPath 和 XSLT 可能会成为占用内存的操作。确保有足够的可用 RAM 用于读取和写入大型 XML 文件(大约是文本大小的 5 倍)。

  • 因为 XSLT 是一种编程语言,请谨慎使用,因为这样的脚本可能在您的环境中构成安全风险,并且可能运行大型或无限递归操作。始终在小片段上测试脚本,然后再进行完整运行。

  • etree解析器支持read_xmlto_xml的所有功能,除了复杂的 XPath 和任何 XSLT。尽管功能有限,etree仍然是一个可靠且功能强大的解析器和树构建器。对于较大的文件,其性能可能在一定程度上落后于lxml,但在小到中等大小的文件上相对不明显。

Excel 文件

read_excel()方法可以使用openpyxl Python 模块读取 Excel 2007+(.xlsx)文件。可以使用xlrd读取 Excel 2003(.xls)文件。可以使用pyxlsb读取二进制 Excel(.xlsb)文件。所有格式都可以使用 calamine 引擎读取。to_excel()实例方法用于将DataFrame保存为 Excel。通常语义与处理 csv 数据类似。查看 cookbook 以获取一些高级策略。

注意

engine=None时,将使用以下逻辑来确定引擎:

  • 如果path_or_buffer是开放文档格式(.odf,.ods,.odt),那么将使用odf

  • 否则,如果path_or_buffer是 xls 格式,则将使用xlrd

  • 否则,如果path_or_buffer是 xlsb 格式,则将使用pyxlsb

  • 否则将使用openpyxl

读取 Excel 文件

在最基本的用例中,read_excel接受一个指向 Excel 文件的路径,并且sheet_name指示要解析的工作表。

在使用engine_kwargs参数时,pandas 将这些参数传递给引擎。因此,重要的是要知道 pandas 内部使用的是哪个函数。

  • 对于引擎 openpyxl,pandas 使用openpyxl.load_workbook()来读取(.xlsx)和(.xlsm)文件。

  • 对于引擎 xlrd,pandas 使用xlrd.open_workbook()来读取(.xls)文件。

  • 对于引擎 pyxlsb,pandas 使用pyxlsb.open_workbook()来读取(.xlsb)文件。

  • 对于引擎 odf,pandas 使用odf.opendocument.load()来读取(.ods)文件。

  • 对于引擎 calamine,pandas 使用python_calamine.load_workbook()来读取(.xlsx)、(.xlsm)、(.xls)、(.xlsb)、(.ods)文件。

# Returns a DataFrame
pd.read_excel("path_to_file.xls", sheet_name="Sheet1") 

ExcelFile

为了方便处理来自同一文件的多个工作表,可以使用ExcelFile类来包装文件,并将其传递到read_excel中。读取多个工作表时会有性能上的好处,因为文件只读入内存一次。

xlsx = pd.ExcelFile("path_to_file.xls")
df = pd.read_excel(xlsx, "Sheet1") 

ExcelFile类也可以用作上下文管理器。

with pd.ExcelFile("path_to_file.xls") as xls:
    df1 = pd.read_excel(xls, "Sheet1")
    df2 = pd.read_excel(xls, "Sheet2") 

sheet_names属性将生成文件中工作表名称的列表。

ExcelFile的主要用途是解析具有不同参数的多个工作表:

data = {}
# For when Sheet1's format differs from Sheet2
with pd.ExcelFile("path_to_file.xls") as xls:
    data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
    data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=1) 

请注意,如果对所有工作表使用相同的解析参数,则可以简单地将工作表名称列表传递给read_excel,而不会降低性能。

# using the ExcelFile class
data = {}
with pd.ExcelFile("path_to_file.xls") as xls:
    data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
    data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=None, na_values=["NA"])

# equivalent using the read_excel function
data = pd.read_excel(
    "path_to_file.xls", ["Sheet1", "Sheet2"], index_col=None, na_values=["NA"]
) 

ExcelFile也可以使用xlrd.book.Book对象调用为参数。这允许用户控制如何读取 Excel 文件。例如,可以通过调用xlrd.open_workbook()并传递on_demand=True来按需加载工作表。

import xlrd

xlrd_book = xlrd.open_workbook("path_to_file.xls", on_demand=True)
with pd.ExcelFile(xlrd_book) as xls:
    df1 = pd.read_excel(xls, "Sheet1")
    df2 = pd.read_excel(xls, "Sheet2") 
```  #### 指定工作表

注意

第二个参数是`sheet_name`,不要与`ExcelFile.sheet_names`混淆。

注意

ExcelFile 的属性`sheet_names`提供了对工作表列表的访问。

+   参数`sheet_name`允许指定要读取的工作表。

+   `sheet_name`的默认值为 0,表示读取第一个工作表

+   传递字符串以引用工作簿中特定工作表的名称。

+   传递一个整数来引用工作表的索引。索引遵循 Python 约定,从 0 开始。

+   传递字符串或整数列表,以返回指定工作表的字典。

+   传递`None`以返回所有可用工作表的字典。

```py
# Returns a DataFrame
pd.read_excel("path_to_file.xls", "Sheet1", index_col=None, na_values=["NA"]) 

使用工作表索引:

# Returns a DataFrame
pd.read_excel("path_to_file.xls", 0, index_col=None, na_values=["NA"]) 

使用所有默认值:

# Returns a DataFrame
pd.read_excel("path_to_file.xls") 

使用 None 来获取所有工作表:

# Returns a dictionary of DataFrames
pd.read_excel("path_to_file.xls", sheet_name=None) 

使用列表获取多个工作表:

# Returns the 1st and 4th sheet, as a dictionary of DataFrames.
pd.read_excel("path_to_file.xls", sheet_name=["Sheet1", 3]) 

read_excel可以通过将sheet_name设置为工作表名称列表、工作表位置列表或None来读取多个工作表。工作表可以通过工作表索引或工作表名称来指定,分别使用整数或字符串。 #### 读取MultiIndex

read_excel可以通过将列的列表传递给index_col和将行的列表传递给header来读取MultiIndex索引。如果索引或列具有序列化的级别名称,则可以通过指定组成级别的行/列来读取它们。

例如,要读取没有名称的MultiIndex索引:

In [424]: df = pd.DataFrame(
 .....:    {"a": [1, 2, 3, 4], "b": [5, 6, 7, 8]},
 .....:    index=pd.MultiIndex.from_product([["a", "b"], ["c", "d"]]),
 .....: )
 .....: 

In [425]: df.to_excel("path_to_file.xlsx")

In [426]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1])

In [427]: df
Out[427]: 
 a  b
a c  1  5
 d  2  6
b c  3  7
 d  4  8 

如果索引具有级别名称,它们也将被解析,使用相同的参数。

In [428]: df.index = df.index.set_names(["lvl1", "lvl2"])

In [429]: df.to_excel("path_to_file.xlsx")

In [430]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1])

In [431]: df
Out[431]: 
 a  b
lvl1 lvl2 
a    c     1  5
 d     2  6
b    c     3  7
 d     4  8 

如果源文件既有MultiIndex索引又有列,则应将分别指定的列表传递给index_colheader

In [432]: df.columns = pd.MultiIndex.from_product([["a"], ["b", "d"]], names=["c1", "c2"])

In [433]: df.to_excel("path_to_file.xlsx")

In [434]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1], header=[0, 1])

In [435]: df
Out[435]: 
c1         a 
c2         b  d
lvl1 lvl2 
a    c     1  5
 d     2  6
b    c     3  7
 d     4  8 

index_col中指定的列中缺少值将被填充以允许使用to_excel进行往返处理,对于merged_cells=True。要避免填充缺失值,请在读取数据后使用set_index而不是index_col

解析特定列

通常情况下,用户会在 Excel 中插入列进行临时计算,您可能不想读取这些列。read_excel接受usecols关键字,允许您指定要解析的列的子集。

您可以指定一个以逗号分隔的 Excel 列和范围集合作为字符串:

pd.read_excel("path_to_file.xls", "Sheet1", usecols="A,C:E") 

如果usecols是整数列表,则假定为要解析的文件列索引。

pd.read_excel("path_to_file.xls", "Sheet1", usecols=[0, 2, 3]) 

元素顺序被忽略,因此usecols=[0, 1][1, 0]相同。

如果usecols是字符串列表,则假定每个字符串对应于用户在names中提供的列名或从文档标题行(行)中推断出的列名。这些字符串定义了将解析哪些列:

pd.read_excel("path_to_file.xls", "Sheet1", usecols=["foo", "bar"]) 

元素顺序被忽略,因此usecols=['baz', 'joe']['joe', 'baz']相同。

如果usecols是可调用的,则可调用函数将针对列名进行评估,返回在可调用函数评估为True时的名称。

pd.read_excel("path_to_file.xls", "Sheet1", usecols=lambda x: x.isalpha()) 

解析日期

类似于日期时间的值在读取 excel 文件时通常会自动转换为适当的 dtype。但是,如果您有一个看起来像日期的字符串列(但实际上在 excel 中未格式化为日期),则可以使用parse_dates关键字将这些字符串解析为日期时间:

pd.read_excel("path_to_file.xls", "Sheet1", parse_dates=["date_strings"]) 

单元格转换器

可以通过converters选项转换 Excel 单元格的内容。例如,要将列转换为布尔值:

pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyBools": bool}) 

此选项处理缺失值并将转换器中的异常视为缺失数据。转换是逐个单元格应用的,而不是作为整体列应用的,因此不能保证数组 dtype。例如,具有缺失值的整数列无法转换为具有整数 dtype 的数组,因为 NaN 严格来说是浮点数。您可以手动屏蔽缺失数据以恢复整数 dtype:

def cfun(x):
    return int(x) if x else -1

pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyInts": cfun}) 

Dtype 规格

作为转换器的替代,可以使用dtype关键字指定整个列的类型,它接受一个将列名映射到类型的字典。要解释没有类型推断的数据,请使用类型strobject

pd.read_excel("path_to_file.xls", dtype={"MyInts": "int64", "MyText": str}) 
```  ### 写入 Excel 文件

#### 将 Excel 文件写入磁盘

要将`DataFrame`对象写入 Excel 文件的一个工作表,可以使用`to_excel`实例方法。参数大部分与上面描述的`to_csv`相同,第一个参数是 Excel 文件的名称,可选的第二个参数是应将`DataFrame`写入的工作表的名称。例如:

```py
df.to_excel("path_to_file.xlsx", sheet_name="Sheet1") 

具有.xlsx扩展名的文件将使用xlsxwriter(如果可用)或openpyxl进行编写。

DataFrame 将以尝试模拟 REPL 输出的方式写入。index_label 将放在第二行而不是第一行。通过将 to_excel() 中的 merge_cells 选项设置为 False,可以将其放在第一行:

df.to_excel("path_to_file.xlsx", index_label="label", merge_cells=False) 

要将单个 Excel 文件的不同 DataFrame 写入单独的工作表中,可以传递一个 ExcelWriter

with pd.ExcelWriter("path_to_file.xlsx") as writer:
    df1.to_excel(writer, sheet_name="Sheet1")
    df2.to_excel(writer, sheet_name="Sheet2") 

使用 engine_kwargs 参数时,pandas 将这些参数传递给引擎。因此,了解 pandas 内部使用的函数非常重要。

  • 对于引擎 openpyxl,pandas 使用 openpyxl.Workbook() 来创建新工作表,使用 openpyxl.load_workbook() 来向现有工作表添加数据。openpyxl 引擎可以写入(.xlsx)和(.xlsm)文件。

  • 对于引擎 xlsxwriter,pandas 使用 xlsxwriter.Workbook() 来写入(.xlsx)文件。

  • 对于引擎 odf,pandas 使用 odf.opendocument.OpenDocumentSpreadsheet() 来写入(.ods)文件。

将 Excel 文件写入内存

pandas 支持将 Excel 文件写入类似缓冲区的对象,如 StringIOBytesIO,使用 ExcelWriter

from io import BytesIO

bio = BytesIO()

# By setting the 'engine' in the ExcelWriter constructor.
writer = pd.ExcelWriter(bio, engine="xlsxwriter")
df.to_excel(writer, sheet_name="Sheet1")

# Save the workbook
writer.save()

# Seek to the beginning and read to copy the workbook to a variable in memory
bio.seek(0)
workbook = bio.read() 

注意

engine 是可选的但建议使用。设置引擎确定所生成的工作簿版本。设置 engine='xlrd' 将生成 Excel 2003 格式的工作簿(xls)。使用 'openpyxl''xlsxwriter' 将生成 Excel 2007 格式的工作簿(xlsx)。如果省略,则生成 Excel 2007 格式的工作簿。### Excel 写入引擎

pandas 通过两种方法选择 Excel 写入器:

  1. 引擎关键字参数

  2. 文件名扩展名(通过配置选项中指定的默认值)

默认情况下,pandas 使用 XlsxWriter 来处理 .xlsxopenpyxl 来处理 .xlsm。如果安装了多个引擎,可以通过设置配置选项 io.excel.xlsx.writerio.excel.xls.writer 来设置默认引擎。如果 Xlsxwriter 不可用,pandas 将回退到 openpyxl 来处理 .xlsx 文件。

要指定要使用的写入器,可以将引擎关键字参数传递给 to_excelExcelWriter。内置引擎有:

  • openpyxl:需要版本 2.4 或更高版本

  • xlsxwriter

# By setting the 'engine' in the DataFrame 'to_excel()' methods.
df.to_excel("path_to_file.xlsx", sheet_name="Sheet1", engine="xlsxwriter")

# By setting the 'engine' in the ExcelWriter constructor.
writer = pd.ExcelWriter("path_to_file.xlsx", engine="xlsxwriter")

# Or via pandas configuration.
from pandas import options  # noqa: E402

options.io.excel.xlsx.writer = "xlsxwriter"

df.to_excel("path_to_file.xlsx", sheet_name="Sheet1") 
```### 样式和格式

通过以下参数可以修改从 pandas 创建的 Excel 工作表的外观和感觉,这些参数在 `DataFrame` 的 `to_excel` 方法上使用。

+   `float_format` :浮点数格式字符串(默认 `None`)。

+   `freeze_panes` :表示要冻结的最下方行和最右方列的两个整数的元组。每个参数都是基于一的,因此 (1, 1) 将冻结第一行和第一列(默认 `None`)。

使用[Xlsxwriter](https://xlsxwriter.readthedocs.io)引擎提供了许多控制使用`to_excel`方法创建的 Excel 工作表格式的选项。在[Xlsxwriter](https://xlsxwriter.readthedocs.io)文档中可以找到优秀的示例:[`xlsxwriter.readthedocs.io/working_with_pandas.html`](https://xlsxwriter.readthedocs.io/working_with_pandas.html)  ### 读取 Excel 文件

在最基本的用例中,`read_excel`接受 Excel 文件的路径和指示要解析哪个工作表的`sheet_name`。

在使用`engine_kwargs`参数时,pandas 将这些参数传递给引擎。为此,了解 pandas 内部使用的函数非常重要。

+   对于引擎 openpyxl,pandas 使用`openpyxl.load_workbook()`来读取(`.xlsx`)和(`.xlsm`)文件。

+   对于引擎 xlrd,pandas 使用`xlrd.open_workbook()`来读取(`.xls`)文件。

+   对于引擎 pyxlsb,pandas 使用`pyxlsb.open_workbook()`来读取(`.xlsb`)文件。

+   对于引擎 odf,pandas 使用`odf.opendocument.load()`来读取(`.ods`)文件。

+   对于引擎 calamine,pandas 使用`python_calamine.load_workbook()`来读取(`.xlsx`)、(`.xlsm`)、(`.xls`)、(`.xlsb`)、(`.ods`)文件。

```py
# Returns a DataFrame
pd.read_excel("path_to_file.xls", sheet_name="Sheet1") 

ExcelFile

为了方便处理来自同一文件的多个工作表,可以使用ExcelFile类来包装文件,并将其传递给read_excel。读取多个工作表时,只需将文件读入内存一次,将获得性能优势。

xlsx = pd.ExcelFile("path_to_file.xls")
df = pd.read_excel(xlsx, "Sheet1") 

ExcelFile类也可以用作上下文管理器。

with pd.ExcelFile("path_to_file.xls") as xls:
    df1 = pd.read_excel(xls, "Sheet1")
    df2 = pd.read_excel(xls, "Sheet2") 

sheet_names属性将生成文件中工作表名称的列表。

ExcelFile的主要用例是使用不同参数解析多个工作表:

data = {}
# For when Sheet1's format differs from Sheet2
with pd.ExcelFile("path_to_file.xls") as xls:
    data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
    data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=1) 

请注意,如果对所有工作表使用相同的解析参数,则可以简单地将工作表名称列表传递给read_excel,而不会降低性能。

# using the ExcelFile class
data = {}
with pd.ExcelFile("path_to_file.xls") as xls:
    data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
    data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=None, na_values=["NA"])

# equivalent using the read_excel function
data = pd.read_excel(
    "path_to_file.xls", ["Sheet1", "Sheet2"], index_col=None, na_values=["NA"]
) 

ExcelFile也可以使用xlrd.book.Book对象作为参数调用。这允许用户控制如何读取 Excel 文件。例如,可以通过调用xlrd.open_workbook()并使用on_demand=True来按需加载工作表。

import xlrd

xlrd_book = xlrd.open_workbook("path_to_file.xls", on_demand=True)
with pd.ExcelFile(xlrd_book) as xls:
    df1 = pd.read_excel(xls, "Sheet1")
    df2 = pd.read_excel(xls, "Sheet2") 
```  #### 指定工作表

注意

第二个参数是`sheet_name`,不要与`ExcelFile.sheet_names`混淆。

注意

ExcelFile 的属性`sheet_names`提供了对工作表列表的访问。

+   参数`sheet_name`允许指定要读取的工作表或工作表。

+   `sheet_name`的默认值为 0,表示读取第一个工作表

+   传递一个字符串以引用工作簿中特定工作表的名称。

+   传递一个整数以引用工作表的索引。索引遵循 Python 约定,从 0 开始。

+   传递一个字符串或整数的列表,以返回指定工作表的字典。

+   传递一个`None`以返回所有可用工作表的字典。

```py
# Returns a DataFrame
pd.read_excel("path_to_file.xls", "Sheet1", index_col=None, na_values=["NA"]) 

使用工作表索引:

# Returns a DataFrame
pd.read_excel("path_to_file.xls", 0, index_col=None, na_values=["NA"]) 

使用所有默认值:

# Returns a DataFrame
pd.read_excel("path_to_file.xls") 

使用 None 获取所有工作表:

# Returns a dictionary of DataFrames
pd.read_excel("path_to_file.xls", sheet_name=None) 

使用列表获取多个工作表:

# Returns the 1st and 4th sheet, as a dictionary of DataFrames.
pd.read_excel("path_to_file.xls", sheet_name=["Sheet1", 3]) 

read_excel可以读取多个工作表,通过将sheet_name设置为工作表名称列表、工作表位置列表或None以读取所有工作表。可以通过工作表索引或工作表名称指定工作表,分别使用整数或字符串。#### 读取MultiIndex

通过将列列表传递给index_col和通过将行列表传递给headerread_excel可以读取MultiIndex索引。如果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中指定的列中的缺失值将被向前填充,以允许使用merged_cells=True进行往返转换与to_excel。为了避免向前填充缺失值,请在读取数据后使用set_index而不是index_col

解析特定列

用户通常会在 Excel 中插入列进行临时计算,您可能不希望读取这些列。read_excel使用usecols关键字允许您指定要解析的列的子集。

您可以将 Excel 列和范围指定为逗号分隔的字符串:

pd.read_excel("path_to_file.xls", "Sheet1", usecols="A,C:E") 

如果usecols是整数列表,则假定为要解析的文件列索引。

pd.read_excel("path_to_file.xls", "Sheet1", usecols=[0, 2, 3]) 

元素顺序被忽略,因此usecols=[0, 1][1, 0]相同。

如果usecols是一个字符串列表,则假定每个字符串对应于用户在names中提供的列名或从文档标题行中推断出来的列名。这些字符串定义了将被解析的列:

pd.read_excel("path_to_file.xls", "Sheet1", usecols=["foo", "bar"]) 

元素顺序被忽略,因此usecols=['baz', 'joe']['joe', 'baz']相同。

如果usecols是可调用的,则将对列名评估可调用函数,返回可调用函数评估为True的名称。

pd.read_excel("path_to_file.xls", "Sheet1", usecols=lambda x: x.isalpha()) 

解析日期

当读取 excel 文件时,类似日期时间的值通常会自动转换为适当的 dtype。但是,如果您有一个看起来像日期的字符串列(但实际上在 excel 中没有格式化为日期),您可以使用parse_dates关键字将这些字符串解析为日期时间:

pd.read_excel("path_to_file.xls", "Sheet1", parse_dates=["date_strings"]) 

单元格转换器

可以通过converters选项转换 Excel 单元格的内容。例如,将列转换为布尔值:

pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyBools": bool}) 

此选项处理缺失值,并将转换器中的异常视为缺失数据。转换是逐个单元格应用而不是整个列,因此不能保证数组 dtype。例如,具有缺失值的整数列无法转换为具有整数 dtype 的数组,因为 NaN 严格是浮点数。您可以手动屏蔽缺失数据以恢复整数 dtype:

def cfun(x):
    return int(x) if x else -1

pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyInts": cfun}) 

Dtype 规范

作为转换器的替代方案,可以使用dtype关键字指定整个列的类型,该关键字接受将��名称映射到类型的��典。 要解释没有类型推断的数据,请使用类型strobject

pd.read_excel("path_to_file.xls", dtype={"MyInts": "int64", "MyText": str}) 

ExcelFile

为了方便从同一文件中处理多个工作表,可以使用ExcelFile类来包装文件,并将其传递给read_excel。 读取多个工作表时,只需将文件读入内存一次,将获得性能优势。

xlsx = pd.ExcelFile("path_to_file.xls")
df = pd.read_excel(xlsx, "Sheet1") 

ExcelFile类也可以用作上下文管理器。

with pd.ExcelFile("path_to_file.xls") as xls:
    df1 = pd.read_excel(xls, "Sheet1")
    df2 = pd.read_excel(xls, "Sheet2") 

sheet_names属性将生成文件中工作表名称的列表。

使用ExcelFile的主要用例是解析具有不同参数的多个工作表:

data = {}
# For when Sheet1's format differs from Sheet2
with pd.ExcelFile("path_to_file.xls") as xls:
    data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
    data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=1) 

请注意,如果所有工作表都使用相同的解析参数,则可以简单地将工作表名称列表传递给read_excel,而不会降低性能。

# using the ExcelFile class
data = {}
with pd.ExcelFile("path_to_file.xls") as xls:
    data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
    data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=None, na_values=["NA"])

# equivalent using the read_excel function
data = pd.read_excel(
    "path_to_file.xls", ["Sheet1", "Sheet2"], index_col=None, na_values=["NA"]
) 

ExcelFile也可以使用xlrd.book.Book对象作为参数调用。 这允许用户控制如何读取 excel 文件。 例如,可以通过使用on_demand=True调用xlrd.open_workbook()来按需加载工作表。

import xlrd

xlrd_book = xlrd.open_workbook("path_to_file.xls", on_demand=True)
with pd.ExcelFile(xlrd_book) as xls:
    df1 = pd.read_excel(xls, "Sheet1")
    df2 = pd.read_excel(xls, "Sheet2") 

指定工作表

注意

第二个参数是sheet_name,不要与ExcelFile.sheet_names混淆。

注意

ExcelFile的属性sheet_names提供对工作表列表的访问。

  • 参数sheet_name允许指定要读取的工作表或工作表。

  • sheet_name的默认值为 0,表示读取第一个工作表

  • 将字符串传递以引用工作簿中特定工作表的名称。

  • 传递整数以引用工作表的索引。 索引遵循 Python 约定,从 0 开始。

  • 传递字符串或整数列表,以返回指定工作表的字典。

  • 传递None以返回所有可用工作表的字典。

# Returns a DataFrame
pd.read_excel("path_to_file.xls", "Sheet1", index_col=None, na_values=["NA"]) 

使用工作表索引:

# Returns a DataFrame
pd.read_excel("path_to_file.xls", 0, index_col=None, na_values=["NA"]) 

使用所有默认值:

# Returns a DataFrame
pd.read_excel("path_to_file.xls") 

使用None获取所有工作表:

# Returns a dictionary of DataFrames
pd.read_excel("path_to_file.xls", sheet_name=None) 

使用列表获取多个工作表:

# Returns the 1st and 4th sheet, as a dictionary of DataFrames.
pd.read_excel("path_to_file.xls", sheet_name=["Sheet1", 3]) 

通过将sheet_name设置为工作表名称列表、工作表位置列表或None来读取所有工作表,read_excel可以读取多个工作表。 工作表可以通过工作表索引或工作表名称指定,分别使用整数或字符串。

读取MultiIndex

通过将列列表传递给index_col和将行列表传递给headerread_excel可以读取MultiIndex索引。 如果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中指定的列中的缺失值将被向前填充,以允许使用merged_cells=True进行往返转换以进行to_excel。为了避免向前填充缺失值,请在读取数据后使用set_index而不是index_col

解析特定列

通常情况下,用户会在 Excel 中插入列进行临时计算,您可能不希望读取这些列。read_excel接受一个usecols关键字,允许您指定要解析的列的子集。

可以将逗号分隔的一组 Excel 列和范围指定为字符串:

pd.read_excel("path_to_file.xls", "Sheet1", usecols="A,C:E") 

如果usecols是一个整数列表,则假定它是要解析的文件列索引。

pd.read_excel("path_to_file.xls", "Sheet1", usecols=[0, 2, 3]) 

元素顺序被忽略,因此usecols=[0, 1][1, 0]相同。

如果usecols是一个字符串列表,则假定每个字符串对应于用户在names中提供的列名或从文档标题行中推断出的列名。这些字符串定义了将被解析的列:

pd.read_excel("path_to_file.xls", "Sheet1", usecols=["foo", "bar"]) 

元素顺序被忽略,因此usecols=['baz', 'joe']['joe', 'baz']相同。

如果usecols是可调用的,则将对列名评估可调用函数,返回可调用函数评估为True的名称。

pd.read_excel("path_to_file.xls", "Sheet1", usecols=lambda x: x.isalpha()) 

解析日期

类似日期时间的值通常在读取 excel 文件时会自动转换为适当的 dtype。但是,如果您有一个看起来像日期的字符串列(但实际上在 excel 中没有格式化为日期),则可以使用parse_dates关键字将这些字符串解析为日期时间:

pd.read_excel("path_to_file.xls", "Sheet1", parse_dates=["date_strings"]) 

单元格转换器

可以通过converters选项转换 Excel 单元格的内容。例如,将一列转换为布尔值:

pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyBools": bool}) 

此选项处理缺失值并将转换器中的异常视为缺失数据。转换是逐个单元格应用而不是整个列,因此不能保证数组 dtype。例如,具有缺失值的整数列无法转换为具有整数 dtype 的数组,因为 NaN 严格是浮点数。您可以手动屏蔽缺失数据以恢复整数 dtype:

def cfun(x):
    return int(x) if x else -1

pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyInts": cfun}) 

Dtype 规范

作为转换器的替代方案,可以使用dtype关键字指定整个列的类型,该关键字接受将列名映射到类型的字典。要解释没有类型推断的数据,请使用类型strobject

pd.read_excel("path_to_file.xls", dtype={"MyInts": "int64", "MyText": str}) 

写入 Excel 文件

将 Excel 文件写入磁盘

要将DataFrame对象写入 Excel 文件的一个工作表中,可以使用to_excel实例方法。参数与上述描述的to_csv基本相同,第一个参数是 excel 文件的名称,可选的第二个参数是应将DataFrame写入的工作表的名称。例如:

df.to_excel("path_to_file.xlsx", sheet_name="Sheet1") 

具有.xlsx扩展名的文件将使用xlsxwriter(如果可用)或openpyxl进行写入。

DataFrame 将以尝试模仿 REPL 输出的方式进行写入。index_label 将被放置在第二行而不是第一行。您可以通过在 to_excel() 中设置 merge_cells 选项为 False 来将其放置在第一行:

df.to_excel("path_to_file.xlsx", index_label="label", merge_cells=False) 

为了将单个 Excel 文件中的不同 DataFrame 写入不同的工作表中,可以传递一个 ExcelWriter

with pd.ExcelWriter("path_to_file.xlsx") as writer:
    df1.to_excel(writer, sheet_name="Sheet1")
    df2.to_excel(writer, sheet_name="Sheet2") 

在使用 engine_kwargs 参数时,pandas 将这些参数传递给引擎。为此,重要的是要知道 pandas 内部使用的函数。

  • 对于引擎 openpyxl,pandas 使用 openpyxl.Workbook() 来创建新工作表,并使用 openpyxl.load_workbook() 来将数据追加到现有工作表中。openpyxl 引擎写入(.xlsx)和(.xlsm)文件。

  • 对于引擎 xlsxwriter,pandas 使用 xlsxwriter.Workbook() 来写入(.xlsx)文件。

  • 对于引擎 odf,pandas 使用 odf.opendocument.OpenDocumentSpreadsheet() 来写入(.ods)文件。

将 Excel 文件写入内存

pandas 支持将 Excel 文件写入类似缓冲区的对象,如 StringIOBytesIO,使用 ExcelWriter

from io import BytesIO

bio = BytesIO()

# By setting the 'engine' in the ExcelWriter constructor.
writer = pd.ExcelWriter(bio, engine="xlsxwriter")
df.to_excel(writer, sheet_name="Sheet1")

# Save the workbook
writer.save()

# Seek to the beginning and read to copy the workbook to a variable in memory
bio.seek(0)
workbook = bio.read() 

注意

engine 是可选的但建议使用。设置引擎确定生成的工作簿版本。设置 engine='xlrd' 将生成 Excel 2003 格式的工作簿(xls)。使用 'openpyxl''xlsxwriter' 将生成 Excel 2007 格式的工作簿(xlsx)。如果省略,将生成一个 Excel 2007 格式的工作簿。

将 Excel 文件写入磁盘

要将 DataFrame 对象写入 Excel 文件的工作表中,您可以使用 to_excel 实例方法。参数与上述描述的 to_csv 大致相同,第一个参数是 Excel 文件的名称,可选的第二个参数是应将 DataFrame 写入的工作表的名称。例如:

df.to_excel("path_to_file.xlsx", sheet_name="Sheet1") 

具有 .xlsx 扩展名的文件将使用 xlsxwriter(如果可用)或 openpyxl 进行写入。

DataFrame 将以尝试模仿 REPL 输出的方式进行写入。index_label 将被放置在第二行而不是第一行。您可以通过在 to_excel() 中设置 merge_cells 选项为 False 来将其放置在第一行:

df.to_excel("path_to_file.xlsx", index_label="label", merge_cells=False) 

为了将单个 Excel 文件中的不同 DataFrame 写入不同的工作表中,可以传递一个 ExcelWriter

with pd.ExcelWriter("path_to_file.xlsx") as writer:
    df1.to_excel(writer, sheet_name="Sheet1")
    df2.to_excel(writer, sheet_name="Sheet2") 

在使用 engine_kwargs 参数时,pandas 将这些参数传递给引擎。为此,重要的是要知道 pandas 内部使用的函数。

  • 对于引擎 openpyxl,pandas 使用 openpyxl.Workbook() 来创建新工作表,并使用 openpyxl.load_workbook() 来将数据追加到现有工作表中。openpyxl 引擎写入(.xlsx)和(.xlsm)文件。

  • 对于引擎 xlsxwriter,pandas 使用 xlsxwriter.Workbook() 来写入(.xlsx)文件。

  • 对于引擎 odf,pandas 使用 odf.opendocument.OpenDocumentSpreadsheet() 来写入(.ods)文件。

将 Excel 文件写入内存

pandas 支持将 Excel 文件写入类似缓冲区的对象,如 StringIOBytesIO,使用 ExcelWriter

from io import BytesIO

bio = BytesIO()

# By setting the 'engine' in the ExcelWriter constructor.
writer = pd.ExcelWriter(bio, engine="xlsxwriter")
df.to_excel(writer, sheet_name="Sheet1")

# Save the workbook
writer.save()

# Seek to the beginning and read to copy the workbook to a variable in memory
bio.seek(0)
workbook = bio.read() 

注意

engine是可选的但建议使用。设置引擎确定生成的工作簿版本。设置engine='xlrd'将生成 Excel 2003 格式的工作簿(xls)。使用'openpyxl''xlsxwriter'将生成 Excel 2007 格式的工作簿(xlsx)。如果省略,将生成一个 Excel 2007 格式的工作簿。

Excel 写入器引擎

pandas 通过两种方法选择 Excel 写入器:

  1. engine关键字参数

  2. 文件扩展名(通过配置选项中指定的默认值)

默认情况下,pandas 使用XlsxWriter来处理.xlsxopenpyxl来处理.xlsm。如果安装了多个引擎,可以通过设置配置选项io.excel.xlsx.writerio.excel.xls.writer来设置默认引擎。如果Xlsxwriter不可用,pandas 将回退到openpyxl来处理.xlsx文件。

要指定要使用的写入器,可以将引擎关键字参数传递给to_excelExcelWriter。内置引擎有:

  • openpyxl:需要 2.4 版本或更高版本。

  • xlsxwriter

# By setting the 'engine' in the DataFrame 'to_excel()' methods.
df.to_excel("path_to_file.xlsx", sheet_name="Sheet1", engine="xlsxwriter")

# By setting the 'engine' in the ExcelWriter constructor.
writer = pd.ExcelWriter("path_to_file.xlsx", engine="xlsxwriter")

# Or via pandas configuration.
from pandas import options  # noqa: E402

options.io.excel.xlsx.writer = "xlsxwriter"

df.to_excel("path_to_file.xlsx", sheet_name="Sheet1") 

样式和格式

可以使用DataFrameto_excel方法上的以下参数来修改从 pandas 创建的 Excel 工作表的外观和感觉。

  • float_format:浮点数的格式字符串(默认为None)。

  • freeze_panes:表示要冻结的最底行和最右列的两个整数的元组。这些参数都是基于一的,因此(1, 1)将冻结第一行和第一列(默认为None)。

使用Xlsxwriter引擎提供了许多选项来控制使用to_excel方法创建的 Excel 工作表的格式。在Xlsxwriter文档中可以找到出色的示例:xlsxwriter.readthedocs.io/working_with_pandas.html

OpenDocument 电子表格

Excel 文件的 io 方法还支持使用odfpy模块读取和写入 OpenDocument 电子表格。读取和写入 OpenDocument 电子表格的语义和功能与使用engine='odf'的 Excel 文件可以做的相匹配。可选依赖项‘odfpy’需要被安装。

read_excel()方法可以读取 OpenDocument 电子表格

# Returns a DataFrame
pd.read_excel("path_to_file.ods", engine="odf") 

类似地,to_excel()方法可以写入 OpenDocument 电子表格

# Writes DataFrame to a .ods file
df.to_excel("path_to_file.ods", engine="odf") 

二进制 Excel(.xlsb)文件

read_excel() 方法还可以使用pyxlsb模块读取二进制 Excel 文件。读取二进制 Excel 文件的语义和功能大部分与使用engine='pyxlsb'读取 Excel 文件的内容相匹配。pyxlsb不识别文件中的日期时间类型,而会返回浮点数(如果需要识别日期时间类型,可以使用 calamine)。

# Returns a DataFrame
pd.read_excel("path_to_file.xlsb", engine="pyxlsb") 

注意

目前 pandas 仅支持读取二进制 Excel 文件。写入尚未实现。

Calamine(Excel 和 ODS 文件)

read_excel() 方法可以使用python-calamine模块读取 Excel 文件(.xlsx.xlsm.xls.xlsb)和 OpenDocument 电子表格(.ods)。该模块是 Rust 库calamine的绑定,在大多数情况下比其他引擎更快。需要安装可选依赖python-calamine

# Returns a DataFrame
pd.read_excel("path_to_file.xlsb", engine="calamine") 

剪贴板

一个方便的方法是使用read_clipboard()方法,该方法获取剪贴板缓冲区的内容并将其传递给read_csv方法。例如,您可以将以下文本复制到剪贴板(在许多操作系统上按 CTRL-C):

 A B C
x 1 4 p
y 2 5 q
z 3 6 r 

然后通过调用以下方法直接将数据导入到DataFrame中:

>>> clipdf = pd.read_clipboard()
>>> clipdf
 A B C
x 1 4 p
y 2 5 q
z 3 6 r 

to_clipboard方法可用于将DataFrame的内容写入剪贴板。然后,您可以将剪贴板内容粘贴到其他应用程序中(在许多操作系统上按 CTRL-V)。这里我们演示将DataFrame写入剪贴板并读取回来。

>>> df = pd.DataFrame(
...     {"A": [1, 2, 3], "B": [4, 5, 6], "C": ["p", "q", "r"]}, index=["x", "y", "z"]
... )

>>> df
 A B C
x 1 4 p
y 2 5 q
z 3 6 r
>>> df.to_clipboard()
>>> pd.read_clipboard()
 A B C
x 1 4 p
y 2 5 q
z 3 6 r 

我们可以看到,我们得到了与之前写入剪贴板的内容相同的内容。

注意

您可能需要在 Linux 上安装 xclip 或 xsel(带有 PyQt5、PyQt4 或 qtpy)才能使用这些方法。

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函数可用于从文件加载任何 pickled pandas 对象(或任何其他 pickled 对象):

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 

警告

从不受信任的来源接收 pickled 数据可能不安全。

参见:docs.python.org/3/library/pickle.html

警告

read_pickle() 仅向后兼容到几个次要版本。

压缩的 pickle 文件

read_pickle(), DataFrame.to_pickle()Series.to_pickle() 可以读取和写入压缩的 pickle 文件。支持gzipbz2xzzstd这些压缩类型进行读取和写入。zip文件格式仅支持读取,并且必须只包含一个要读取的数据文件。

压缩类型可以是显式参数,也可以从文件扩展名中推断。如果为‘infer’,则如果文件名以'.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}) 
```  ### 压缩的 pickle 文件

`read_pickle()`, `DataFrame.to_pickle()` 和 `Series.to_pickle()` 可以读取和写入压缩的 pickle 文件。支持`gzip`、`bz2`、`xz`、`zstd`这些压缩类型进行读取和写入。`zip`文件格式仅支持读取,并且必须只包含一个要读取的数据文件。

压缩类型可以是显式参数,也可以从文件扩展名中推断。如果为‘infer’,则如果文件名以`'.gz'`、`'.bz2'`、`'.zip'`、`'.xz'`或`'.zst'`结尾,则使用`gzip`、`bz2`、`zip`、`xz`、`zstd`。

压缩参数也可以是一个`dict`,以便向压缩协议传递选项。必须有一个设置为压缩协议名称的 `'method'` 键,必须是`'zip'`、`'gzip'`、`'bz2'`、`'xz'`、`'zstd'`中的一个。所有其他键值对都将传递给底层压缩库。

```py
In [439]: df = pd.DataFrame(
 .....:    {
 .....:        "A": np.random.randn(1000),
 .....:        "B": "foo",
 .....:        "C": pd.date_range("20130101", periods=1000, freq="s"),
 .....:    }
 .....: )
 .....: 

In [440]: df
Out[440]: 
 A    B                   C
0   -0.317441  foo 2013-01-01 00:00:00
1   -1.236269  foo 2013-01-01 00:00:01
2    0.896171  foo 2013-01-01 00:00:02
3   -0.487602  foo 2013-01-01 00:00:03
4   -0.082240  foo 2013-01-01 00:00:04
..        ...  ...                 ...
995 -0.171092  foo 2013-01-01 00:16:35
996  1.786173  foo 2013-01-01 00:16:36
997 -0.575189  foo 2013-01-01 00:16:37
998  0.820750  foo 2013-01-01 00:16:38
999 -1.256530  foo 2013-01-01 00:16:39

[1000 rows x 3 columns] 

使用显式压缩类型:

In [441]: df.to_pickle("data.pkl.compress", compression="gzip")

In [442]: rt = pd.read_pickle("data.pkl.compress", compression="gzip")

In [443]: rt
Out[443]: 
 A    B                   C
0   -0.317441  foo 2013-01-01 00:00:00
1   -1.236269  foo 2013-01-01 00:00:01
2    0.896171  foo 2013-01-01 00:00:02
3   -0.487602  foo 2013-01-01 00:00:03
4   -0.082240  foo 2013-01-01 00:00:04
..        ...  ...                 ...
995 -0.171092  foo 2013-01-01 00:16:35
996  1.786173  foo 2013-01-01 00:16:36
997 -0.575189  foo 2013-01-01 00:16:37
998  0.820750  foo 2013-01-01 00:16:38
999 -1.256530  foo 2013-01-01 00:16:39

[1000 rows x 3 columns] 

从扩展名推断压缩类型:

In [444]: df.to_pickle("data.pkl.xz", compression="infer")

In [445]: rt = pd.read_pickle("data.pkl.xz", compression="infer")

In [446]: rt
Out[446]: 
 A    B                   C
0   -0.317441  foo 2013-01-01 00:00:00
1   -1.236269  foo 2013-01-01 00:00:01
2    0.896171  foo 2013-01-01 00:00:02
3   -0.487602  foo 2013-01-01 00:00:03
4   -0.082240  foo 2013-01-01 00:00:04
..        ...  ...                 ...
995 -0.171092  foo 2013-01-01 00:16:35
996  1.786173  foo 2013-01-01 00:16:36
997 -0.575189  foo 2013-01-01 00:16:37
998  0.820750  foo 2013-01-01 00:16:38
999 -1.256530  foo 2013-01-01 00:16:39

[1000 rows x 3 columns] 

默认为‘infer’:

In [447]: df.to_pickle("data.pkl.gz")

In [448]: rt = pd.read_pickle("data.pkl.gz")

In [449]: rt
Out[449]: 
 A    B                   C
0   -0.317441  foo 2013-01-01 00:00:00
1   -1.236269  foo 2013-01-01 00:00:01
2    0.896171  foo 2013-01-01 00:00:02
3   -0.487602  foo 2013-01-01 00:00:03
4   -0.082240  foo 2013-01-01 00:00:04
..        ...  ...                 ...
995 -0.171092  foo 2013-01-01 00:16:35
996  1.786173  foo 2013-01-01 00:16:36
997 -0.575189  foo 2013-01-01 00:16:37
998  0.820750  foo 2013-01-01 00:16:38
999 -1.256530  foo 2013-01-01 00:16:39

[1000 rows x 3 columns]

In [450]: df["A"].to_pickle("s1.pkl.bz2")

In [451]: rt = pd.read_pickle("s1.pkl.bz2")

In [452]: rt
Out[452]: 
0     -0.317441
1     -1.236269
2      0.896171
3     -0.487602
4     -0.082240
 ... 
995   -0.171092
996    1.786173
997   -0.575189
998    0.820750
999   -1.256530
Name: A, Length: 1000, dtype: float64 

传递选项给压缩协议以加快压缩速度:

In [453]: df.to_pickle("data.pkl.gz", compression={"method": "gzip", "compresslevel": 1}) 

msgpack

pandas 在版本 1.0.0 中已移除对msgpack的支持。建议改用 pickle。

或者,您还可以使用 Arrow IPC 序列化格式进行 pandas 对象的在线传输。有关 pyarrow 的文档,请参见此处

HDF5(PyTables)

HDFStore是一个类似字典的对象,使用高性能 HDF5 格式读取和写入 pandas,使用优秀的PyTables库。查看 cookbook 以获取一些高级策略

警告

pandas 使用 PyTables 读取和写入 HDF5 文件,允许使用 pickle 对对象数据类型数据进行序列化。从不受信任的来源加载 pickled 数据可能不安全。

查看更多信息:docs.python.org/3/library/pickle.html

In [454]: store = pd.HDFStore("store.h5")

In [455]: print(store)
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5 

对象可以像向字典添加键值对一样写入文件:

In [456]: index = pd.date_range("1/1/2000", periods=8)

In [457]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])

In [458]: df = pd.DataFrame(np.random.randn(8, 3), index=index, columns=["A", "B", "C"])

# store.put('s', s) is an equivalent method
In [459]: store["s"] = s

In [460]: store["df"] = df

In [461]: store
Out[461]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5 

在当前或以后的 Python 会话中,您可以检索存储的对象:

# store.get('df') is an equivalent method
In [462]: store["df"]
Out[462]: 
 A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

# dotted (attribute) access provides get as well
In [463]: store.df
Out[463]: 
 A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517 

删除由键指定的对象:

# store.remove('df') is an equivalent method
In [464]: del store["df"]

In [465]: store
Out[465]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5 

关闭存储并使用上下文管理器:

In [466]: store.close()

In [467]: store
Out[467]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

In [468]: store.is_open
Out[468]: False

# Working with, and automatically closing the store using a context manager
In [469]: with pd.HDFStore("store.h5") as store:
 .....:    store.keys()
 .....: 

读/写 API

HDFStore支持使用read_hdf进行读取和to_hdf进行写入的顶级 API,类似于read_csvto_csv的工作方式。

In [470]: df_tl = pd.DataFrame({"A": list(range(5)), "B": list(range(5))})

In [471]: df_tl.to_hdf("store_tl.h5", key="table", append=True)

In [472]: pd.read_hdf("store_tl.h5", "table", where=["index>2"])
Out[472]: 
 A  B
3  3  3
4  4  4 

HDFStore 默认情况下不会删除所有缺失的行。可以通过设置dropna=True来更改此行为。

In [473]: df_with_missing = pd.DataFrame(
 .....:    {
 .....:        "col1": [0, np.nan, 2],
 .....:        "col2": [1, np.nan, np.nan],
 .....:    }
 .....: )
 .....: 

In [474]: df_with_missing
Out[474]: 
 col1  col2
0   0.0   1.0
1   NaN   NaN
2   2.0   NaN

In [475]: df_with_missing.to_hdf("file.h5", key="df_with_missing", format="table", mode="w")

In [476]: pd.read_hdf("file.h5", "df_with_missing")
Out[476]: 
 col1  col2
0   0.0   1.0
1   NaN   NaN
2   2.0   NaN

In [477]: df_with_missing.to_hdf(
 .....:    "file.h5", key="df_with_missing", format="table", mode="w", dropna=True
 .....: )
 .....: 

In [478]: pd.read_hdf("file.h5", "df_with_missing")
Out[478]: 
 col1  col2
0   0.0   1.0
2   2.0   NaN 

固定格式

上面的示例显示了使用put进行存储,它将 HDF5 写入PyTables以固定数组格式存储,称为fixed格式。这些类型的存储一旦写入就不可追加(尽管您可以简单地删除它们并重新写入)。它们也不可查询;必须完全检索它们。它们也不支持具有非唯一列名称的数据帧。fixed格式存储提供非常快速的写入和比table存储稍快的读取。此格式在使用putto_hdf时默认指定,或者通过format='fixed'format='f'指定。

警告

使用fixed格式在尝试使用where检索时会引发TypeError

In [479]: pd.DataFrame(np.random.randn(10, 2)).to_hdf("test_fixed.h5", key="df")

In [480]: pd.read_hdf("test_fixed.h5", "df", where="index>5")
---------------------------------------------------------------------------
TypeError  Traceback (most recent call last)
Cell In[480], line 1
----> 1 pd.read_hdf("test_fixed.h5", "df", where="index>5")

File ~/work/pandas/pandas/pandas/io/pytables.py:452, in read_hdf(path_or_buf, key, mode, errors, where, start, stop, columns, iterator, chunksize, **kwargs)
  447                 raise ValueError(
  448                     "key must be provided when HDF5 "
  449                     "file contains multiple datasets."
  450                 )
  451         key = candidate_only_group._v_pathname
--> 452     return store.select(
  453         key,
  454         where=where,
  455         start=start,
  456         stop=stop,
  457         columns=columns,
  458         iterator=iterator,
  459         chunksize=chunksize,
  460         auto_close=auto_close,
  461     )
  462 except (ValueError, TypeError, LookupError):
  463     if not isinstance(path_or_buf, HDFStore):
  464         # if there is an error, close the store if we opened it.

File ~/work/pandas/pandas/pandas/io/pytables.py:906, in HDFStore.select(self, key, where, start, stop, columns, iterator, chunksize, auto_close)
  892 # create the iterator
  893 it = TableIterator(
  894     self,
  895     s,
   (...)
  903     auto_close=auto_close,
  904 )
--> 906 return it.get_result()

File ~/work/pandas/pandas/pandas/io/pytables.py:2029, in TableIterator.get_result(self, coordinates)
  2026     where = self.where
  2028 # directly return the result
-> 2029 results = self.func(self.start, self.stop, where)
  2030 self.close()
  2031 return results

File ~/work/pandas/pandas/pandas/io/pytables.py:890, in HDFStore.select.<locals>.func(_start, _stop, _where)
  889 def func(_start, _stop, _where):
--> 890     return s.read(start=_start, stop=_stop, where=_where, columns=columns)

File ~/work/pandas/pandas/pandas/io/pytables.py:3278, in BlockManagerFixed.read(self, where, columns, start, stop)
  3270 def read(
  3271     self,
  3272     where=None,
   (...)
  3276 ) -> DataFrame:
  3277     # start, stop applied to rows, so 0th axis only
-> 3278     self.validate_read(columns, where)
  3279     select_axis = self.obj_type()._get_block_manager_axis(0)
  3281     axes = []

File ~/work/pandas/pandas/pandas/io/pytables.py:2922, in GenericFixed.validate_read(self, columns, where)
  2917     raise TypeError(
  2918         "cannot pass a column specification when reading "
  2919         "a Fixed format store. this store must be selected in its entirety"
  2920     )
  2921 if where is not None:
-> 2922     raise TypeError(
  2923         "cannot pass a where specification when reading "
  2924         "from a Fixed format store. this store must be selected in its entirety"
  2925     )

TypeError: cannot pass a where specification when reading from a Fixed format store. this store must be selected in its entirety 
```  ### 表格格式

`HDFStore`还支持磁盘上的另一种`PyTables`格式,即`table`格式。在概念上,`table`非常类似于 DataFrame,具有行和列。`table`可以在相同或其他会话中追加。此外,支持删除和查询类型操作。此格式由`format='table'`或`format='t'`指定为`append`或`put`或`to_hdf`。

也可以将此格式设置为选项 `pd.set_option('io.hdf.default_format','table')` 以便默认使用`table`格式存储`put/append/to_hdf`。

```py
In [481]: store = pd.HDFStore("store.h5")

In [482]: df1 = df[0:4]

In [483]: df2 = df[4:]

# append data (creates a table automatically)
In [484]: store.append("df", df1)

In [485]: store.append("df", df2)

In [486]: store
Out[486]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

# select the entire object
In [487]: store.select("df")
Out[487]: 
 A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

# the type of stored data
In [488]: store.root.df._v_attrs.pandas_type
Out[488]: 'frame_table' 

注意

您还可以通过将format='table'format='t'传递给put操作来创建一个table。 ### 分层键

可以将键指定为字符串。这些可以采用分层路径名格式(例如 foo/bar/bah)��这将生成子存储的层次结构(或 PyTables 术语中的 Groups)。键可以指定为没有前导 '/' 的,并且始终是绝对的(例如,'foo' 指的是 '/foo')。删除操作可以删除子存储中的所有内容以及以下内容,因此要小心

In [489]: store.put("foo/bar/bah", df)

In [490]: store.append("food/orange", df)

In [491]: store.append("food/apple", df)

In [492]: store
Out[492]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

# a list of keys are returned
In [493]: store.keys()
Out[493]: ['/df', '/food/apple', '/food/orange', '/foo/bar/bah']

# remove all nodes under this level
In [494]: store.remove("food")

In [495]: store
Out[495]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5 

您可以使用 walk 方法遍历组层次结构,该方法将为每个组键生成一个元组,以及其内容的相对键。

In [496]: for (path, subgroups, subkeys) in store.walk():
 .....:    for subgroup in subgroups:
 .....:        print("GROUP: {}/{}".format(path, subgroup))
 .....:    for subkey in subkeys:
 .....:        key = "/".join([path, subkey])
 .....:        print("KEY: {}".format(key))
 .....:        print(store.get(key))
 .....: 
GROUP: /foo
KEY: /df
 A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517
GROUP: /foo/bar
KEY: /foo/bar/bah
 A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517 

警告

无法像上面描述的存储在根节点下的项目那样检索分层键作为点分隔符(属性)访问。

In [497]: store.foo.bar.bah
---------------------------------------------------------------------------
TypeError  Traceback (most recent call last)
Cell In[497], line 1
----> 1 store.foo.bar.bah

File ~/work/pandas/pandas/pandas/io/pytables.py:613, in HDFStore.__getattr__(self, name)
  611  """allow attribute access to get stores"""
  612 try:
--> 613     return self.get(name)
  614 except (KeyError, ClosedFileError):
  615     pass

File ~/work/pandas/pandas/pandas/io/pytables.py:813, in HDFStore.get(self, key)
  811 if group is None:
  812     raise KeyError(f"No object named {key} in the file")
--> 813 return self._read_group(group)

File ~/work/pandas/pandas/pandas/io/pytables.py:1878, in HDFStore._read_group(self, group)
  1877 def _read_group(self, group: Node):
-> 1878     s = self._create_storer(group)
  1879     s.infer_axes()
  1880     return s.read()

File ~/work/pandas/pandas/pandas/io/pytables.py:1752, in HDFStore._create_storer(self, group, format, value, encoding, errors)
  1750         tt = "generic_table"
  1751     else:
-> 1752         raise TypeError(
  1753             "cannot create a storer if the object is not existing "
  1754             "nor a value are passed"
  1755         )
  1756 else:
  1757     if isinstance(value, Series):

TypeError: cannot create a storer if the object is not existing nor a value are passed 
# you can directly access the actual PyTables node but using the root node
In [498]: store.root.foo.bar.bah
Out[498]: 
/foo/bar/bah (Group) ''
 children := ['axis0' (Array), 'axis1' (Array), 'block0_items' (Array), 'block0_values' (Array)] 

相反,使用显式的基于字符串的键:

In [499]: store["foo/bar/bah"]
Out[499]: 
 A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517 
```  ### 存储类型

#### 在表中存储混合类型

支持存储混合类型数据。字符串以附加列的最大大小存储为固定宽度。尝试附加更长字符串将引发 `ValueError`。

将 `min_itemsize={`values`: size}` 作为参数传递给 append 将为字符串列设置更大的最小值。目前支持存储 `floats, strings, ints, bools, datetime64`。对于字符串列,将 `nan_rep = 'nan'` 传递给 append 将更改磁盘上的默认 nan 表示(转换为/从 `np.nan`),默认为 `nan`。

```py
In [500]: df_mixed = pd.DataFrame(
 .....:    {
 .....:        "A": np.random.randn(8),
 .....:        "B": np.random.randn(8),
 .....:        "C": np.array(np.random.randn(8), dtype="float32"),
 .....:        "string": "string",
 .....:        "int": 1,
 .....:        "bool": True,
 .....:        "datetime64": pd.Timestamp("20010102"),
 .....:    },
 .....:    index=list(range(8)),
 .....: )
 .....: 

In [501]: df_mixed.loc[df_mixed.index[3:5], ["A", "B", "string", "datetime64"]] = np.nan

In [502]: store.append("df_mixed", df_mixed, min_itemsize={"values": 50})

In [503]: df_mixed1 = store.select("df_mixed")

In [504]: df_mixed1
Out[504]: 
 A         B         C  ... int  bool                    datetime64
0  0.013747 -1.166078 -1.292080  ...   1  True 1970-01-01 00:00:00.978393600
1 -0.712009  0.247572  1.526911  ...   1  True 1970-01-01 00:00:00.978393600
2 -0.645096  1.687406  0.288504  ...   1  True 1970-01-01 00:00:00.978393600
3       NaN       NaN  0.097771  ...   1  True                           NaT
4       NaN       NaN  1.536408  ...   1  True                           NaT
5 -0.023202  0.043702  0.926790  ...   1  True 1970-01-01 00:00:00.978393600
6  2.359782  0.088224 -0.676448  ...   1  True 1970-01-01 00:00:00.978393600
7 -0.143428 -0.813360 -0.179724  ...   1  True 1970-01-01 00:00:00.978393600

[8 rows x 7 columns]

In [505]: df_mixed1.dtypes.value_counts()
Out[505]: 
float64           2
float32           1
object            1
int64             1
bool              1
datetime64[ns]    1
Name: count, dtype: int64

# we have provided a minimum string column size
In [506]: store.root.df_mixed.table
Out[506]: 
/df_mixed/table (Table(8,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": Float64Col(shape=(2,), dflt=0.0, pos=1),
 "values_block_1": Float32Col(shape=(1,), dflt=0.0, pos=2),
 "values_block_2": StringCol(itemsize=50, shape=(1,), dflt=b'', pos=3),
 "values_block_3": Int64Col(shape=(1,), dflt=0, pos=4),
 "values_block_4": BoolCol(shape=(1,), dflt=False, pos=5),
 "values_block_5": Int64Col(shape=(1,), dflt=0, pos=6)}
 byteorder := 'little'
 chunkshape := (689,)
 autoindex := True
 colindexes := {
 "index": Index(6, mediumshuffle, zlib(1)).is_csi=False} 

存储 MultiIndex DataFrames

将 MultiIndex DataFrames 存储为表与存储/选择同质索引 DataFrames 非常相似。

In [507]: index = pd.MultiIndex(
 .....:   levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
 .....:   codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
 .....:   names=["foo", "bar"],
 .....: )
 .....: 

In [508]: df_mi = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])

In [509]: df_mi
Out[509]: 
 A         B         C
foo bar 
foo one   -1.303456 -0.642994 -0.649456
 two    1.012694  0.414147  1.950460
 three  1.094544 -0.802899 -0.583343
bar one    0.410395  0.618321  0.560398
 two    1.434027 -0.033270  0.343197
baz two   -1.646063 -0.695847 -0.429156
 three -0.244688 -1.428229 -0.138691
qux one    1.866184 -1.446617  0.036660
 two   -1.660522  0.929553 -1.298649
 three  3.565769  0.682402  1.041927

In [510]: store.append("df_mi", df_mi)

In [511]: store.select("df_mi")
Out[511]: 
 A         B         C
foo bar 
foo one   -1.303456 -0.642994 -0.649456
 two    1.012694  0.414147  1.950460
 three  1.094544 -0.802899 -0.583343
bar one    0.410395  0.618321  0.560398
 two    1.434027 -0.033270  0.343197
baz two   -1.646063 -0.695847 -0.429156
 three -0.244688 -1.428229 -0.138691
qux one    1.866184 -1.446617  0.036660
 two   -1.660522  0.929553 -1.298649
 three  3.565769  0.682402  1.041927

# the levels are automatically included as data columns
In [512]: store.select("df_mi", "foo=bar")
Out[512]: 
 A         B         C
foo bar 
bar one  0.410395  0.618321  0.560398
 two  1.434027 -0.033270  0.343197 

注意

index 关键字被保留,不能用作级别名称。 ### 查询

查询表

selectdelete 操作具有可选的条件,可以指定选择/删除数据的子集。这允许用户拥有一个非常大的磁盘表,并且只检索数据的一部分。

查询是使用 Term 类在底层指定的,作为布尔表达式。

  • indexcolumnsDataFrames 的支持索引器。

  • 如果指定了 data_columns,则可以将其用作附加的索引器。

  • 在 MultiIndex 中的级别名称,默认名称为 level_0level_1,如果未提供。

有效的比较运算符为:

=, ==, !=, >, >=, <, <=

有效的布尔表达式与以下内容结合使用:

  • |:或

  • &:和

  • ():用于分组

这些规则类似于在 pandas 中用于索引的布尔表达式的使用方式。

注意

  • = 将自动扩展为比较运算符 ==

  • ~ 是非运算符,但只能在非常有限的情况下使用

  • 如果传递了表达式的列表/元组,它们将通过 & 组合。

以下是有效的表达式:

  • 'index >= date'

  • "columns = ['A', 'D']"

  • "columns in ['A', 'D']"

  • 'columns = A'

  • 'columns == A'

  • "~(columns = ['A', 'B'])"

  • 'index > df.index[3] & string = "bar"'

  • '(index > df.index[3] & index <= df.index[6]) | string = "bar"'

  • "ts >= Timestamp('2012-02-01')"

  • "major_axis>=20130101"

indexers 位于子表达式的左侧:

columnsmajor_axists

子表达式的右侧(比较运算符之后)可以是:

  • 将被评估的函数,例如Timestamp('2012-02-01')

  • 字符串,例如"bar"

  • 类似日期的,例如20130101,或"20130101"

  • 列表,例如"['A', 'B']"

  • 在本地命名空间中定义的变量,例如date

注意

不建议通过将字符串插值到查询表达式中来将字符串传递给查询。只需将感兴趣的字符串分配给一个变量,并在表达式中使用该变量。例如,这样做

string = "HolyMoly'"
store.select("df", "index == string") 

而不是这样

string = "HolyMoly'"
store.select('df', f'index == {string}') 

后者将不会起作用,并将引发SyntaxError。请注意string变量中有一个单引号后跟一个双引号。

如果必须进行插值,请使用'%r'格式说明符

store.select("df", "index == %r" % string) 

这将引用string

这里有一些例子:

In [513]: dfq = pd.DataFrame(
 .....:    np.random.randn(10, 4),
 .....:    columns=list("ABCD"),
 .....:    index=pd.date_range("20130101", periods=10),
 .....: )
 .....: 

In [514]: store.append("dfq", dfq, format="table", data_columns=True) 

使用布尔表达式,进行内联函数评估。

In [515]: store.select("dfq", "index>pd.Timestamp('20130104') & columns=['A', 'B']")
Out[515]: 
 A         B
2013-01-05 -0.830545 -0.457071
2013-01-06  0.431186  1.049421
2013-01-07  0.617509 -0.811230
2013-01-08  0.947422 -0.671233
2013-01-09 -0.183798 -1.211230
2013-01-10  0.361428  0.887304 

使用内联列引用。

In [516]: store.select("dfq", where="A>0 or C>0")
Out[516]: 
 A         B         C         D
2013-01-02  0.658179  0.362814 -0.917897  0.010165
2013-01-03  0.905122  1.848731 -1.184241  0.932053
2013-01-05 -0.830545 -0.457071  1.565581  1.148032
2013-01-06  0.431186  1.049421  0.383309  0.595013
2013-01-07  0.617509 -0.811230 -2.088563 -1.393500
2013-01-08  0.947422 -0.671233 -0.847097 -1.187785
2013-01-10  0.361428  0.887304  0.266457 -0.399641 

可以提供columns关键字以选择要返回的列列表,这相当于传递一个'columns=list_of_columns_to_filter'

In [517]: store.select("df", "columns=['A', 'B']")
Out[517]: 
 A         B
2000-01-01  0.858644 -0.851236
2000-01-02 -0.080372 -1.268121
2000-01-03  0.816983  1.965656
2000-01-04  0.712795 -0.062433
2000-01-05 -0.298721 -1.988045
2000-01-06  1.103675  1.382242
2000-01-07 -0.729161 -0.142928
2000-01-08 -1.005977  0.465222 

可以指定startstop参数来限制总搜索空间。这些是以表中总行数为单位的。

注意

select如果查询表达式中有未知变量引用,将会引发ValueError。通常这意味着你试图选择一个不是数据列的列。

如果查询表达式无效,select将引发SyntaxError

查询 timedelta64[ns]

你可以使用timedelta64[ns]类型进行存储和查询。项可以以<float>(<unit>)的格式指定,其中 float 可以是有符号的(和分数的),unit 可以是D,s,ms,us,ns用于时间间隔。这里有一个例子:

In [518]: from datetime import timedelta

In [519]: dftd = pd.DataFrame(
 .....:    {
 .....:        "A": pd.Timestamp("20130101"),
 .....:        "B": [
 .....:            pd.Timestamp("20130101") + timedelta(days=i, seconds=10)
 .....:            for i in range(10)
 .....:        ],
 .....:    }
 .....: )
 .....: 

In [520]: dftd["C"] = dftd["A"] - dftd["B"]

In [521]: dftd
Out[521]: 
 A                   B                  C
0 2013-01-01 2013-01-01 00:00:10  -1 days +23:59:50
1 2013-01-01 2013-01-02 00:00:10  -2 days +23:59:50
2 2013-01-01 2013-01-03 00:00:10  -3 days +23:59:50
3 2013-01-01 2013-01-04 00:00:10  -4 days +23:59:50
4 2013-01-01 2013-01-05 00:00:10  -5 days +23:59:50
5 2013-01-01 2013-01-06 00:00:10  -6 days +23:59:50
6 2013-01-01 2013-01-07 00:00:10  -7 days +23:59:50
7 2013-01-01 2013-01-08 00:00:10  -8 days +23:59:50
8 2013-01-01 2013-01-09 00:00:10  -9 days +23:59:50
9 2013-01-01 2013-01-10 00:00:10 -10 days +23:59:50

In [522]: store.append("dftd", dftd, data_columns=True)

In [523]: store.select("dftd", "C<'-3.5D'")
Out[523]: 
 A                   B                  C
4 1970-01-01 00:00:01.356998400 2013-01-05 00:00:10  -5 days +23:59:50
5 1970-01-01 00:00:01.356998400 2013-01-06 00:00:10  -6 days +23:59:50
6 1970-01-01 00:00:01.356998400 2013-01-07 00:00:10  -7 days +23:59:50
7 1970-01-01 00:00:01.356998400 2013-01-08 00:00:10  -8 days +23:59:50
8 1970-01-01 00:00:01.356998400 2013-01-09 00:00:10  -9 days +23:59:50
9 1970-01-01 00:00:01.356998400 2013-01-10 00:00:10 -10 days +23:59:50 
```  #### 查询 MultiIndex

通过使用级别的名称,可以实现从`MultiIndex`中选择。

```py
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关键字自动使用MultiIndex的级别,其中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为表创建/修改索引。强烈建议创建表索引。当您使用具有索引维度作为whereselect时,这将大大加快查询速度。

注意

索引会自动在可索引和您指定的任何数据列上创建。通过向append传递index=False可以关闭此行为。

# we have automagically already created an index (in the first section)
In [531]: i = store.root.df.table.cols.index.index

In [532]: i.optlevel, i.kind
Out[532]: (6, 'medium')

# change an index by passing new parameters
In [533]: store.create_table_index("df", optlevel=9, kind="full")

In [534]: i = store.root.df.table.cols.index.index

In [535]: i.optlevel, i.kind
Out[535]: (9, 'full') 

在向存储附加大量数据时,通常将每次附加时关闭索引创建是有用的,然后在最后重新创建索引。

In [536]: df_1 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [537]: df_2 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [538]: st = pd.HDFStore("appends.h5", mode="w")

In [539]: st.append("df", df_1, data_columns=["B"], index=False)

In [540]: st.append("df", df_2, data_columns=["B"], index=False)

In [541]: st.get_storer("df").table
Out[541]: 
/df/table (Table(20,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
 "B": Float64Col(shape=(), dflt=0.0, pos=2)}
 byteorder := 'little'
 chunkshape := (2730,) 

然后在附加完成后创建索引。

In [542]: st.create_table_index("df", columns=["B"], optlevel=9, kind="full")

In [543]: st.get_storer("df").table
Out[543]: 
/df/table (Table(20,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
 "B": Float64Col(shape=(), dflt=0.0, pos=2)}
 byteorder := 'little'
 chunkshape := (2730,)
 autoindex := True
 colindexes := {
 "B": Index(9, fullshuffle, zlib(1)).is_csi=True}

In [544]: st.close() 

查看这里如何在现有存储上创建完全排序索引(CSI)。

通过数据列查询

您可以指定(并索引)您希望能够执行查询的特定列(除了可始终查询的indexable列之外)。例如,假设您想在磁盘上执行这个常见操作,并返回与此查询匹配的框架。您可以指定data_columns = True来强制所有列都成为data_columns

In [545]: df_dc = df.copy()

In [546]: df_dc["string"] = "foo"

In [547]: df_dc.loc[df_dc.index[4:6], "string"] = np.nan

In [548]: df_dc.loc[df_dc.index[7:9], "string"] = "bar"

In [549]: df_dc["string2"] = "cool"

In [550]: df_dc.loc[df_dc.index[1:3], ["B", "C"]] = 1.0

In [551]: df_dc
Out[551]: 
 A         B         C string string2
2000-01-01  0.858644 -0.851236  1.058006    foo    cool
2000-01-02 -0.080372  1.000000  1.000000    foo    cool
2000-01-03  0.816983  1.000000  1.000000    foo    cool
2000-01-04  0.712795 -0.062433  0.736755    foo    cool
2000-01-05 -0.298721 -1.988045  1.475308    NaN    cool
2000-01-06  1.103675  1.382242 -0.650762    NaN    cool
2000-01-07 -0.729161 -0.142928 -1.063038    foo    cool
2000-01-08 -1.005977  0.465222 -0.094517    bar    cool

# on-disk operations
In [552]: store.append("df_dc", df_dc, data_columns=["B", "C", "string", "string2"])

In [553]: store.select("df_dc", where="B > 0")
Out[553]: 
 A         B         C string string2
2000-01-02 -0.080372  1.000000  1.000000    foo    cool
2000-01-03  0.816983  1.000000  1.000000    foo    cool
2000-01-06  1.103675  1.382242 -0.650762    NaN    cool
2000-01-08 -1.005977  0.465222 -0.094517    bar    cool

# getting creative
In [554]: store.select("df_dc", "B > 0 & C > 0 & string == foo")
Out[554]: 
 A    B    C string string2
2000-01-02 -0.080372  1.0  1.0    foo    cool
2000-01-03  0.816983  1.0  1.0    foo    cool

# this is in-memory version of this type of selection
In [555]: df_dc[(df_dc.B > 0) & (df_dc.C > 0) & (df_dc.string == "foo")]
Out[555]: 
 A    B    C string string2
2000-01-02 -0.080372  1.0  1.0    foo    cool
2000-01-03  0.816983  1.0  1.0    foo    cool

# we have automagically created this index and the B/C/string/string2
# columns are stored separately as ``PyTables`` columns
In [556]: store.root.df_dc.table
Out[556]: 
/df_dc/table (Table(8,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
 "B": Float64Col(shape=(), dflt=0.0, pos=2),
 "C": Float64Col(shape=(), dflt=0.0, pos=3),
 "string": StringCol(itemsize=3, shape=(), dflt=b'', pos=4),
 "string2": StringCol(itemsize=4, shape=(), dflt=b'', pos=5)}
 byteorder := 'little'
 chunkshape := (1680,)
 autoindex := True
 colindexes := {
 "index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "B": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "C": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "string": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "string2": Index(6, mediumshuffle, zlib(1)).is_csi=False} 

将许多列变为data columns会导致一些性能下降,因此用户需要指定这些列。此外,在第一次追加/放置操作之后,您不能更改数据列(也不能更改索引列)(当然,您可以简单地读取数据并创建一个新表!)。

迭代器

您可以将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] 
```  ##### 使用 where mask 进行选择

有时,您的查询可能涉及创建要选择的行列表。通常,这个`mask`会是一个索引操作的结果`index`。这个示例选择了一个 datetimeindex 中为 5 的月份。

```py
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方法将给定的单个 DataFrame 拆分为多个表,根据d,一个将表名映射到您希望在该表中的‘列’列表的字典。如果在列表的位置使用None,那么该表将具有给定 DataFrame 的其余未指定的列。参数selector定义哪个表是选择器表(您可以从中进行查询)。参数dropna将从输入DataFrame中删除行,以确保表是同步的。这意味着如果一个表的一行完全是np.nan,那么该行将从所有表中删除。

如果dropna为 False,则用户负责同步表。请记住,完全是np.Nan的行不会写入 HDFStore,因此如果选择调用dropna=False,一些表可能比其他表有更多的行,因此select_as_multiple可能无法正常工作,或者可能返回意外结果。

In [578]: df_mt = pd.DataFrame(
 .....:    np.random.randn(8, 6),
 .....:    index=pd.date_range("1/1/2000", periods=8),
 .....:    columns=["A", "B", "C", "D", "E", "F"],
 .....: )
 .....: 

In [579]: df_mt["foo"] = "bar"

In [580]: df_mt.loc[df_mt.index[1], ("A", "B")] = np.nan

# you can also create the tables individually
In [581]: store.append_to_multiple(
 .....:    {"df1_mt": ["A", "B"], "df2_mt": None}, df_mt, selector="df1_mt"
 .....: )
 .....: 

In [582]: store
Out[582]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

# individual tables were created
In [583]: store.select("df1_mt")
Out[583]: 
 A         B
2000-01-01  0.162291 -0.430489
2000-01-02       NaN       NaN
2000-01-03  0.429207 -1.099274
2000-01-04  1.869081 -1.466039
2000-01-05  0.092130 -1.726280
2000-01-06  0.266901 -0.036854
2000-01-07 -0.517871 -0.990317
2000-01-08 -0.231342  0.557402

In [584]: store.select("df2_mt")
Out[584]: 
 C         D         E         F  foo
2000-01-01 -2.502042  0.668149  0.460708  1.834518  bar
2000-01-02  0.130441 -0.608465  0.439872  0.506364  bar
2000-01-03 -1.069546  1.236277  0.116634 -1.772519  bar
2000-01-04  0.137462  0.313939  0.748471 -0.943009  bar
2000-01-05  0.836517  2.049798  0.562167  0.189952  bar
2000-01-06  1.112750 -0.151596  1.503311  0.939470  bar
2000-01-07 -0.294348  0.335844 -0.794159  1.495614  bar
2000-01-08  0.860312 -0.538674 -0.541986 -1.759606  bar

# as a multiple
In [585]: store.select_as_multiple(
 .....:    ["df1_mt", "df2_mt"],
 .....:    where=["A>0", "B>0"],
 .....:    selector="df1_mt",
 .....: )
 .....: 
Out[585]: 
Empty DataFrame
Columns: [A, B, C, D, E, F, foo]
Index: [] 

从表中��除

您可以通过指定where有选择性地从表中删除。在删除行时,重要的是要了解PyTables通过擦除行,然后移动后续数据来删除行。因此,根据数据的方向,删除可能是一个非常昂贵的操作。为了获得最佳性能,最好将要删除的维度放在indexables的第一个位置。

数据按照indexables的顺序(在磁盘上)进行排序。这里有一个简单的用例。您存储面板类型数据,日期在major_axis中,id 在minor_axis中。然后数据交错存储如下:

  • date_1

    • id_1

    • id_2

    • id_n

  • date_2

    • id_1

    • id_n

应该清楚的是,在major_axis上的删除操作将非常快,因为一个块被移除,然后移动后续数据。另一方面,在minor_axis上的删除操作将非常昂贵。在这种情况下,重新编写使用选择所有但缺失数据的where的表几乎肯定会更快。

警告

请注意,HDF5 不会自动回收h5 文件中的空间。因此,重复删除(或删除节点)并再次添加,将增加文件大小

重新打包和清理文件,请使用 ptrepack。

注意事项和警告

压缩

PyTables允许对存储的数据进行压缩。这适用于所有类型的存储,而不仅仅是表。用于控制压缩的两个参数是:complevelcomplib

  • complevel指定数据压缩的难度和方式。complevel=0complevel=None禁用压缩,0<complevel<10启用压缩。

  • complib指定要使用的压缩库。如果未指定任何内容,则使用默认库zlib。压缩库通常针对良好的压缩率或速度进行优化,结果将取决于数据类型。选择哪种类型的压缩取决于您的特定需求和数据。支持的压缩库列表:

    • zlib:默认的压缩库。在压缩方面是经典的,实现了良好的压缩率,但有些慢。

    • lzo:快速压缩和解压。

    • bzip2:良好的压缩率。

    • blosc:快速压缩和解压。

      支持替代的 blosc 压缩器:

      • blosc:blosclz 这是blosc的默认压缩器

      • blosc:lz4:紧凑、非常流行和快速的压缩器。

      • blosc:lz4hc:LZ4 的一个调整版本,在牺牲速度的情况下产生更好的压缩比。

      • blosc:snappy:在许多地方使用的流行压缩器。

      • blosc:zlib:一个经典;比前面的几个稍慢,但实现了更好的压缩比。

      • blosc:zstd:一个非常平衡的编解码器;在以上其他编解码器中,它提供了最佳的压缩比,并且速度相当快。

    如果complib被定义为除列出 之外的其他库,将引发ValueError异常。

注意

如果在您的平台上缺少complib选项指定的库,则压缩默认为zlib,无需进一步操作。

为文件中的所有对象启用压缩:

store_compressed = pd.HDFStore(
    "store_compressed.h5", complevel=9, complib="blosc:blosclz"
) 

或者在未启用压缩的存储中对表进行即时压缩(仅适用于表):

store.append("df", df, complib="zlib", complevel=5) 

ptrepack

PyTables在表写入后进行压缩时提供更好的写入性能,而不是在一开始就打开压缩。您可以使用提供的PyTables实用程序ptrepack。此外,ptrepack可以在事后更改压缩级别。

ptrepack --chunkshape=auto --propindexes --complevel=9 --complib=blosc in.h5 out.h5 

此外,ptrepack in.h5 out.h5重新打包文件,以便您可以重用先前删除的空间。或者,您可以简单地删除文件并重新写入,或者使用copy方法。 #### 注意事项

警告

HDFStore对于写入来说不是线程安全的。底层的PyTables只支持并发读取(通过线程或进程)。如果您需要同时进行读取和写入,您需要在单 一个进程的单个线程中串行化这些操作。否则,您的数据将会损坏。有关更多信息,请参见(GH 2397)。

  • 如果您使用锁来管理多个进程之间的写访问权限,您可能希望在释放写锁之前使用fsync()。为了方便起见,您可以使用store.flush(fsync=True)来代替。

  • 一旦创建了table,列(DataFrame)就固定了;只能追加完全相同的列

  • 请注意,时区(例如,pytz.timezone('US/Eastern'))在不同的时区版本中不一定相等。因此,如果使用一个版本的时区库将数据本地化到 HDFStore 中的特定时区,并使用另一个版本更新数据,则数据将被转换为 UTC,因为这些时区被认为不相等。要么使用相同版本的时区库,要么使用带有更新时区定义的tz_convert

警告

如果列名不能用作属性选择器,PyTables将显示NaturalNameWarning自然标识符仅包含字母、数字和下划线,并且不能以数字开头。其他标识符不能在where子句中使用,通常是一个坏主意。### 数据类型

HDFStore将对象数据类型映射到PyTables底层数据类型。这意味着以下类型已知可以工作:

类型 表示缺失值
浮点数:float64,float32,float16 np.nan
整数:int64,int32,int8,uint64,uint32,uint8
布尔值
datetime64[ns] NaT
timedelta64[ns] NaT
分类:参见下面的部分
object:字符串 np.nan

不支持unicode列,会失败

分类数据

您可以将包含category数据类型的数据写入HDFStore。查询的工作方式与它是一个对象数组一样。然而,category数据类型的数据以更有效的方式存储。

In [586]: dfcat = pd.DataFrame(
 .....:    {"A": pd.Series(list("aabbcdba")).astype("category"), "B": np.random.randn(8)}
 .....: )
 .....: 

In [587]: dfcat
Out[587]: 
 A         B
0  a -1.520478
1  a -1.069391
2  b -0.551981
3  b  0.452407
4  c  0.409257
5  d  0.301911
6  b -0.640843
7  a -2.253022

In [588]: dfcat.dtypes
Out[588]: 
A    category
B     float64
dtype: object

In [589]: cstore = pd.HDFStore("cats.h5", mode="w")

In [590]: cstore.append("dfcat", dfcat, format="table", data_columns=["A"])

In [591]: result = cstore.select("dfcat", where="A in ['b', 'c']")

In [592]: result
Out[592]: 
 A         B
2  b -0.551981
3  b  0.452407
4  c  0.409257
6  b -0.640843

In [593]: result.dtypes
Out[593]: 
A    category
B     float64
dtype: object 

字符串列

min_itemsize

HDFStore的底层实现对字符串列使用固定的列宽(itemsize)。字符串列的 itemsize 是在第一次追加数据时传递给HDFStore的数据(对于该列)的长度的最大值。后续的追加操作可能会引入一个比列能容纳的字符串列更大的字符串,这将引发异常(否则可能会导致这些列的静默截断,导致信息丢失)。将来我们可能会放宽这一限制,允许发生用户指定的截断。

在第一次创建表时传递min_itemsize来预先指定特定字符串列的最小长度。min_itemsize可以是一个整数,或者是将列名映射到整数的字典。您可以传递values作为一个键,以允许所有可索引数据列具有这个 min_itemsize。

传递min_itemsize字典将导致所有传递的列自动创建为数据列

注意

如果您没有传递任何data_columns,那么min_itemsize将是传递的任何字符串的长度的最大值

In [594]: dfs = pd.DataFrame({"A": "foo", "B": "bar"}, index=list(range(5)))

In [595]: dfs
Out[595]: 
 A    B
0  foo  bar
1  foo  bar
2  foo  bar
3  foo  bar
4  foo  bar

# A and B have a size of 30
In [596]: store.append("dfs", dfs, min_itemsize=30)

In [597]: store.get_storer("dfs").table
Out[597]: 
/dfs/table (Table(5,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": StringCol(itemsize=30, shape=(2,), dflt=b'', pos=1)}
 byteorder := 'little'
 chunkshape := (963,)
 autoindex := True
 colindexes := {
 "index": Index(6, mediumshuffle, zlib(1)).is_csi=False}

# A is created as a data_column with a size of 30
# B is size is calculated
In [598]: store.append("dfs2", dfs, min_itemsize={"A": 30})

In [599]: store.get_storer("dfs2").table
Out[599]: 
/dfs2/table (Table(5,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": StringCol(itemsize=3, shape=(1,), dflt=b'', pos=1),
 "A": StringCol(itemsize=30, shape=(), dflt=b'', pos=2)}
 byteorder := 'little'
 chunkshape := (1598,)
 autoindex := True
 colindexes := {
 "index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "A": Index(6, mediumshuffle, zlib(1)).is_csi=False} 

nan_rep

字符串列将使用nan_rep字符串表示序列化一个np.nan(一个缺失值)。默认情况下,这将是字符串值nan。您可能会无意中将实际的nan值转换为缺失值。

In [600]: dfss = pd.DataFrame({"A": ["foo", "bar", "nan"]})

In [601]: dfss
Out[601]: 
 A
0  foo
1  bar
2  nan

In [602]: store.append("dfss", dfss)

In [603]: store.select("dfss")
Out[603]: 
 A
0  foo
1  bar
2  NaN

# here you need to specify a different nan rep
In [604]: store.append("dfss2", dfss, nan_rep="_nan_")

In [605]: store.select("dfss2")
Out[605]: 
 A
0  foo
1  bar
2  nan 

性能

  • tables 格式与 fixed 存储相比,写入性能会有所降低。其优点在于能够追加/删除和查询(可能是大量的数据)。与常规存储相比,写入时间通常较长。查询时间可能非常快,尤其是在索引轴上。

  • 您可以将 chunksize=<int> 传递给 append,指定写入块大小(默认为 50000)。这将显着降低写入时的内存使用。

  • 您可以将 expectedrows=<int> 传递给第一个 append,以设置 PyTables 预期的总行数。这将优化读/写性能。

  • 可以向表中写入重复行,但在选择时会被过滤掉(选择最后的项目;因此表在主要、次要对上是唯一的)

  • 如果尝试存储将由 PyTables 进行 pickle 处理的类型(而不是作为固有类型存储),将引发 PerformanceWarning。有关更多信息和一些解决方案,请参见这里

读/写 API

HDFStore 支持使用 read_hdf 进行读取和 to_hdf 进行写入的顶级 API,类似于 read_csvto_csv 的工作方式。

In [470]: df_tl = pd.DataFrame({"A": list(range(5)), "B": list(range(5))})

In [471]: df_tl.to_hdf("store_tl.h5", key="table", append=True)

In [472]: pd.read_hdf("store_tl.h5", "table", where=["index>2"])
Out[472]: 
 A  B
3  3  3
4  4  4 

HDFStore 默认情况下不会删除所有缺失的行。可以通过设置 dropna=True 来更改此行为。

In [473]: df_with_missing = pd.DataFrame(
 .....:    {
 .....:        "col1": [0, np.nan, 2],
 .....:        "col2": [1, np.nan, np.nan],
 .....:    }
 .....: )
 .....: 

In [474]: df_with_missing
Out[474]: 
 col1  col2
0   0.0   1.0
1   NaN   NaN
2   2.0   NaN

In [475]: df_with_missing.to_hdf("file.h5", key="df_with_missing", format="table", mode="w")

In [476]: pd.read_hdf("file.h5", "df_with_missing")
Out[476]: 
 col1  col2
0   0.0   1.0
1   NaN   NaN
2   2.0   NaN

In [477]: df_with_missing.to_hdf(
 .....:    "file.h5", key="df_with_missing", format="table", mode="w", dropna=True
 .....: )
 .....: 

In [478]: pd.read_hdf("file.h5", "df_with_missing")
Out[478]: 
 col1  col2
0   0.0   1.0
2   2.0   NaN 

固定格式

上面的示例显示了使用 put 进行存储,它将 HDF5 写入 PyTables 中的固定数组格式,称为 fixed 格式。这些类型的存储一旦写入就不可追加(尽管您可以简单地删除它们并重新写入)。它们也不可查询;必须完全检索它们。它们也不支持具有非唯一列名称的数据框。使用 putto_hdf 时,默认情况下指定此格式,或者通过 format='fixed'format='f' 指定。

警告

如果尝试使用 where 检索,fixed 格式将引发 TypeError

In [479]: pd.DataFrame(np.random.randn(10, 2)).to_hdf("test_fixed.h5", key="df")

In [480]: pd.read_hdf("test_fixed.h5", "df", where="index>5")
---------------------------------------------------------------------------
TypeError  Traceback (most recent call last)
Cell In[480], line 1
----> 1 pd.read_hdf("test_fixed.h5", "df", where="index>5")

File ~/work/pandas/pandas/pandas/io/pytables.py:452, in read_hdf(path_or_buf, key, mode, errors, where, start, stop, columns, iterator, chunksize, **kwargs)
  447                 raise ValueError(
  448                     "key must be provided when HDF5 "
  449                     "file contains multiple datasets."
  450                 )
  451         key = candidate_only_group._v_pathname
--> 452     return store.select(
  453         key,
  454         where=where,
  455         start=start,
  456         stop=stop,
  457         columns=columns,
  458         iterator=iterator,
  459         chunksize=chunksize,
  460         auto_close=auto_close,
  461     )
  462 except (ValueError, TypeError, LookupError):
  463     if not isinstance(path_or_buf, HDFStore):
  464         # if there is an error, close the store if we opened it.

File ~/work/pandas/pandas/pandas/io/pytables.py:906, in HDFStore.select(self, key, where, start, stop, columns, iterator, chunksize, auto_close)
  892 # create the iterator
  893 it = TableIterator(
  894     self,
  895     s,
   (...)
  903     auto_close=auto_close,
  904 )
--> 906 return it.get_result()

File ~/work/pandas/pandas/pandas/io/pytables.py:2029, in TableIterator.get_result(self, coordinates)
  2026     where = self.where
  2028 # directly return the result
-> 2029 results = self.func(self.start, self.stop, where)
  2030 self.close()
  2031 return results

File ~/work/pandas/pandas/pandas/io/pytables.py:890, in HDFStore.select.<locals>.func(_start, _stop, _where)
  889 def func(_start, _stop, _where):
--> 890     return s.read(start=_start, stop=_stop, where=_where, columns=columns)

File ~/work/pandas/pandas/pandas/io/pytables.py:3278, in BlockManagerFixed.read(self, where, columns, start, stop)
  3270 def read(
  3271     self,
  3272     where=None,
   (...)
  3276 ) -> DataFrame:
  3277     # start, stop applied to rows, so 0th axis only
-> 3278     self.validate_read(columns, where)
  3279     select_axis = self.obj_type()._get_block_manager_axis(0)
  3281     axes = []

File ~/work/pandas/pandas/pandas/io/pytables.py:2922, in GenericFixed.validate_read(self, columns, where)
  2917     raise TypeError(
  2918         "cannot pass a column specification when reading "
  2919         "a Fixed format store. this store must be selected in its entirety"
  2920     )
  2921 if where is not None:
-> 2922     raise TypeError(
  2923         "cannot pass a where specification when reading "
  2924         "from a Fixed format store. this store must be selected in its entirety"
  2925     )

TypeError: cannot pass a where specification when reading from a Fixed format store. this store must be selected in its entirety 

表格格式

HDFStore 支持另一种磁盘上的 PyTables 格式,即 table 格式。在概念上,table 的形状非常类似于 DataFrame,具有行和列。table 可以在相同或其他会话中追加。此外,支持删除和查询类型的操作。此格式由 format='table'format='t' 指定为 appendputto_hdf

还可以将此格式设置为选项 pd.set_option('io.hdf.default_format','table'),以便默认情况下使用 put/append/to_hdf 存储在 table 格式中。

In [481]: store = pd.HDFStore("store.h5")

In [482]: df1 = df[0:4]

In [483]: df2 = df[4:]

# append data (creates a table automatically)
In [484]: store.append("df", df1)

In [485]: store.append("df", df2)

In [486]: store
Out[486]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

# select the entire object
In [487]: store.select("df")
Out[487]: 
 A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

# the type of stored data
In [488]: store.root.df._v_attrs.pandas_type
Out[488]: 'frame_table' 

注意

您还可以通过将 format='table'format='t' 传递给 put 操作来创建一个 table

分层键

可以将存储的键指定为字符串。这些可以以分层路径名格式(例如 foo/bar/bah)指定,这将生成子存储的层次结构(或 PyTables 术语中的 Groups)。键可以指定为没有前导‘/’的形式,并且始终是绝对的(例如,‘foo’指的是‘/foo’)。删除操作可以删除子存储中的所有内容以及以下内容,因此要小心

In [489]: store.put("foo/bar/bah", df)

In [490]: store.append("food/orange", df)

In [491]: store.append("food/apple", df)

In [492]: store
Out[492]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

# a list of keys are returned
In [493]: store.keys()
Out[493]: ['/df', '/food/apple', '/food/orange', '/foo/bar/bah']

# remove all nodes under this level
In [494]: store.remove("food")

In [495]: store
Out[495]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5 

您可以使用 walk 方法遍历组层次结构,该方法将为每个组键生成一个元组,以及其内容的相对键。

In [496]: for (path, subgroups, subkeys) in store.walk():
 .....:    for subgroup in subgroups:
 .....:        print("GROUP: {}/{}".format(path, subgroup))
 .....:    for subkey in subkeys:
 .....:        key = "/".join([path, subkey])
 .....:        print("KEY: {}".format(key))
 .....:        print(store.get(key))
 .....: 
GROUP: /foo
KEY: /df
 A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517
GROUP: /foo/bar
KEY: /foo/bar/bah
 A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517 

警告

无法像上面描述的存储在根节点下的项目那样,以点(属性)访问的方式检索分层键。

In [497]: store.foo.bar.bah
---------------------------------------------------------------------------
TypeError  Traceback (most recent call last)
Cell In[497], line 1
----> 1 store.foo.bar.bah

File ~/work/pandas/pandas/pandas/io/pytables.py:613, in HDFStore.__getattr__(self, name)
  611  """allow attribute access to get stores"""
  612 try:
--> 613     return self.get(name)
  614 except (KeyError, ClosedFileError):
  615     pass

File ~/work/pandas/pandas/pandas/io/pytables.py:813, in HDFStore.get(self, key)
  811 if group is None:
  812     raise KeyError(f"No object named {key} in the file")
--> 813 return self._read_group(group)

File ~/work/pandas/pandas/pandas/io/pytables.py:1878, in HDFStore._read_group(self, group)
  1877 def _read_group(self, group: Node):
-> 1878     s = self._create_storer(group)
  1879     s.infer_axes()
  1880     return s.read()

File ~/work/pandas/pandas/pandas/io/pytables.py:1752, in HDFStore._create_storer(self, group, format, value, encoding, errors)
  1750         tt = "generic_table"
  1751     else:
-> 1752         raise TypeError(
  1753             "cannot create a storer if the object is not existing "
  1754             "nor a value are passed"
  1755         )
  1756 else:
  1757     if isinstance(value, Series):

TypeError: cannot create a storer if the object is not existing nor a value are passed 
# you can directly access the actual PyTables node but using the root node
In [498]: store.root.foo.bar.bah
Out[498]: 
/foo/bar/bah (Group) ''
 children := ['axis0' (Array), 'axis1' (Array), 'block0_items' (Array), 'block0_values' (Array)] 

相反,使用显式基于字符串的键:

In [499]: store["foo/bar/bah"]
Out[499]: 
 A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517 

存储类型

在表中存储混合类型

支持存储混合数据类型。字符串以固定宽度存储,使用附加列的最大大小。尝试追加更长的字符串将引发 ValueError

min_itemsize={values: size} 作为参数传递给 append 将为字符串列设置更大的最小值。目前支持存储 floats, strings, ints, bools, datetime64。对于字符串列,将 nan_rep = 'nan' 传递给 append 将更改磁盘上的默认 nan 表示(转换为/从 np.nan),默认为 nan

In [500]: df_mixed = pd.DataFrame(
 .....:    {
 .....:        "A": np.random.randn(8),
 .....:        "B": np.random.randn(8),
 .....:        "C": np.array(np.random.randn(8), dtype="float32"),
 .....:        "string": "string",
 .....:        "int": 1,
 .....:        "bool": True,
 .....:        "datetime64": pd.Timestamp("20010102"),
 .....:    },
 .....:    index=list(range(8)),
 .....: )
 .....: 

In [501]: df_mixed.loc[df_mixed.index[3:5], ["A", "B", "string", "datetime64"]] = np.nan

In [502]: store.append("df_mixed", df_mixed, min_itemsize={"values": 50})

In [503]: df_mixed1 = store.select("df_mixed")

In [504]: df_mixed1
Out[504]: 
 A         B         C  ... int  bool                    datetime64
0  0.013747 -1.166078 -1.292080  ...   1  True 1970-01-01 00:00:00.978393600
1 -0.712009  0.247572  1.526911  ...   1  True 1970-01-01 00:00:00.978393600
2 -0.645096  1.687406  0.288504  ...   1  True 1970-01-01 00:00:00.978393600
3       NaN       NaN  0.097771  ...   1  True                           NaT
4       NaN       NaN  1.536408  ...   1  True                           NaT
5 -0.023202  0.043702  0.926790  ...   1  True 1970-01-01 00:00:00.978393600
6  2.359782  0.088224 -0.676448  ...   1  True 1970-01-01 00:00:00.978393600
7 -0.143428 -0.813360 -0.179724  ...   1  True 1970-01-01 00:00:00.978393600

[8 rows x 7 columns]

In [505]: df_mixed1.dtypes.value_counts()
Out[505]: 
float64           2
float32           1
object            1
int64             1
bool              1
datetime64[ns]    1
Name: count, dtype: int64

# we have provided a minimum string column size
In [506]: store.root.df_mixed.table
Out[506]: 
/df_mixed/table (Table(8,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": Float64Col(shape=(2,), dflt=0.0, pos=1),
 "values_block_1": Float32Col(shape=(1,), dflt=0.0, pos=2),
 "values_block_2": StringCol(itemsize=50, shape=(1,), dflt=b'', pos=3),
 "values_block_3": Int64Col(shape=(1,), dflt=0, pos=4),
 "values_block_4": BoolCol(shape=(1,), dflt=False, pos=5),
 "values_block_5": Int64Col(shape=(1,), dflt=0, pos=6)}
 byteorder := 'little'
 chunkshape := (689,)
 autoindex := True
 colindexes := {
 "index": Index(6, mediumshuffle, zlib(1)).is_csi=False} 

存储 MultiIndex 数据框

将 MultiIndex 数据框存储为表与存储/选择同质索引数据框非常相似。

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 关键字被保留,不能用作级别名称。

在表中存储混合类型

支持存储混合数据类型。字符串以固定宽度存储,使用附加列的最大大小。尝试追加更长的字符串将引发 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 数据框

将 MultiIndex 数据框存储为表与存储/选择同质索引数据框非常相似。

In [507]: index = pd.MultiIndex(
 .....:   levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
 .....:   codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
 .....:   names=["foo", "bar"],
 .....: )
 .....: 

In [508]: df_mi = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])

In [509]: df_mi
Out[509]: 
 A         B         C
foo bar 
foo one   -1.303456 -0.642994 -0.649456
 two    1.012694  0.414147  1.950460
 three  1.094544 -0.802899 -0.583343
bar one    0.410395  0.618321  0.560398
 two    1.434027 -0.033270  0.343197
baz two   -1.646063 -0.695847 -0.429156
 three -0.244688 -1.428229 -0.138691
qux one    1.866184 -1.446617  0.036660
 two   -1.660522  0.929553 -1.298649
 three  3.565769  0.682402  1.041927

In [510]: store.append("df_mi", df_mi)

In [511]: store.select("df_mi")
Out[511]: 
 A         B         C
foo bar 
foo one   -1.303456 -0.642994 -0.649456
 two    1.012694  0.414147  1.950460
 three  1.094544 -0.802899 -0.583343
bar one    0.410395  0.618321  0.560398
 two    1.434027 -0.033270  0.343197
baz two   -1.646063 -0.695847 -0.429156
 three -0.244688 -1.428229 -0.138691
qux one    1.866184 -1.446617  0.036660
 two   -1.660522  0.929553 -1.298649
 three  3.565769  0.682402  1.041927

# the levels are automatically included as data columns
In [512]: store.select("df_mi", "foo=bar")
Out[512]: 
 A         B         C
foo bar 
bar one  0.410395  0.618321  0.560398
 two  1.434027 -0.033270  0.343197 

注意

index 关键字被保留,不能用作级别名称。

查询

查询表

selectdelete 操作具有可选条件,可指定仅选择/删除数据的子集。这允许在磁盘上有一个非常大的表,并且仅检索数据的一部分。

查询是使用 Term 类在幕后指定的,作为布尔表达式。

  • indexcolumns 是数据框的支持索引器。

  • 如果指定了 data_columns,则可以将其用作附加索引器。

  • 在 MultiIndex 中使用级别名称,默认名称为 level_0level_1,如果未提供。

有效的比较运算符是:

=, ==, !=, >, >=, <, <=

有效的布尔表达式与以下内容组合:

  • | : 或

  • & : 和

  • () : 用于分组

这些规则类似于 pandas 中用于索引的布尔表达式的使用方式。

注意

  • = 将自动扩展为比较运算符 ==

  • ~ 是非运算符,但只能在非常有限的情况下使用

  • 如果传递了表达式的列表/元组,它们将通过 & 组合

以下是有效的表达式:

  • 'index >= date'

  • "columns = ['A', 'D']"

  • "columns in ['A', 'D']"

  • 'columns = A'

  • 'columns == A'

  • "~(columns = ['A', 'B'])"

  • 'index > df.index[3] & string = "bar"'

  • '(index > df.index[3] & index <= df.index[6]) | string = "bar"'

  • "ts >= Timestamp('2012-02-01')"

  • "major_axis>=20130101"

indexers 在子表达式的左侧:

columns, major_axis, ts

子表达式的右侧(比较运算符之后)可以是:

  • 将被评估的函数,例如 Timestamp('2012-02-01')

  • 字符串,例如 "bar"

  • 类似日期,例如 20130101,或 "20130101"

  • 列表,例如 "['A', 'B']"

  • 在本地命名空间中定义的变量,例如 date

注意

不建议通过将其插入到查询表达式中来将字符串传递给查询。只需将感兴趣的字符串分配给一个变量,并在表达式��使用该变量。例如,这样做

string = "HolyMoly'"
store.select("df", "index == string") 

而不是这样

string = "HolyMoly'"
store.select('df', f'index == {string}') 

后者将起作用,并将引发 SyntaxError。请注意,在 string 变量中有一个单引号后跟一个双引号。

如果必须插值,请使用 '%r' 格式说明符

store.select("df", "index == %r" % string) 

将引用 string

以下是一些示例:

In [513]: dfq = pd.DataFrame(
 .....:    np.random.randn(10, 4),
 .....:    columns=list("ABCD"),
 .....:    index=pd.date_range("20130101", periods=10),
 .....: )
 .....: 

In [514]: store.append("dfq", dfq, format="table", data_columns=True) 

使用布尔表达式,带有内联函数评估。

In [515]: store.select("dfq", "index>pd.Timestamp('20130104') & columns=['A', 'B']")
Out[515]: 
 A         B
2013-01-05 -0.830545 -0.457071
2013-01-06  0.431186  1.049421
2013-01-07  0.617509 -0.811230
2013-01-08  0.947422 -0.671233
2013-01-09 -0.183798 -1.211230
2013-01-10  0.361428  0.887304 

使用内联列引用。

In [516]: store.select("dfq", where="A>0 or C>0")
Out[516]: 
 A         B         C         D
2013-01-02  0.658179  0.362814 -0.917897  0.010165
2013-01-03  0.905122  1.848731 -1.184241  0.932053
2013-01-05 -0.830545 -0.457071  1.565581  1.148032
2013-01-06  0.431186  1.049421  0.383309  0.595013
2013-01-07  0.617509 -0.811230 -2.088563 -1.393500
2013-01-08  0.947422 -0.671233 -0.847097 -1.187785
2013-01-10  0.361428  0.887304  0.266457 -0.399641 

可以提供 columns 关键字以选择要返回的列列表,这相当于传递 'columns=list_of_columns_to_filter'

In [517]: store.select("df", "columns=['A', 'B']")
Out[517]: 
 A         B
2000-01-01  0.858644 -0.851236
2000-01-02 -0.080372 -1.268121
2000-01-03  0.816983  1.965656
2000-01-04  0.712795 -0.062433
2000-01-05 -0.298721 -1.988045
2000-01-06  1.103675  1.382242
2000-01-07 -0.729161 -0.142928
2000-01-08 -1.005977  0.465222 

可以指定 startstop 参数以限制总搜索空间。这些是以表中总行数为单位的。

注意

如果查询表达式具有未知变量引用,select 将引发 ValueError。通常这意味着您正在尝试选择一个是数据列的列。

如果查询表达式无效,select 将引发 SyntaxError

查询 timedelta64[ns]

您可以使用 timedelta64[ns] 类型进行存储和查询。术语可以以 <float>(<unit>) 的格式指定,其中浮点数可以是有符号的(和分数),单位可以是 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` 中进行选择。

```py
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为表创建/修改索引。强烈建议创建表索引。当您使用带有索引维度作为whereselect时,这将大大加快查询速度。

注意

索引会自动在您指定的可索引列和任何数据列上创建。通过向append传递index=False可以关闭此行为。

# we have automagically already created an index (in the first section)
In [531]: i = store.root.df.table.cols.index.index

In [532]: i.optlevel, i.kind
Out[532]: (6, 'medium')

# change an index by passing new parameters
In [533]: store.create_table_index("df", optlevel=9, kind="full")

In [534]: i = store.root.df.table.cols.index.index

In [535]: i.optlevel, i.kind
Out[535]: (9, 'full') 

在向存储中追加大量数据时,通常会关闭每次追加的索引创建,然后在最后重新创建。

In [536]: df_1 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [537]: df_2 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [538]: st = pd.HDFStore("appends.h5", mode="w")

In [539]: st.append("df", df_1, data_columns=["B"], index=False)

In [540]: st.append("df", df_2, data_columns=["B"], index=False)

In [541]: st.get_storer("df").table
Out[541]: 
/df/table (Table(20,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
 "B": Float64Col(shape=(), dflt=0.0, pos=2)}
 byteorder := 'little'
 chunkshape := (2730,) 

然后在追加完成后创建索引。

In [542]: st.create_table_index("df", columns=["B"], optlevel=9, kind="full")

In [543]: st.get_storer("df").table
Out[543]: 
/df/table (Table(20,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
 "B": Float64Col(shape=(), dflt=0.0, pos=2)}
 byteorder := 'little'
 chunkshape := (2730,)
 autoindex := True
 colindexes := {
 "B": Index(9, fullshuffle, zlib(1)).is_csi=True}

In [544]: st.close() 

查看这里了解如何在现有存储上创建完全排序的索引(CSI)。

通过数据列查询

您可以指定(并索引)您希望能够执行查询的某些列(除了您始终可以查询的indexable列)。例如,假设您想要在磁盘上执行此常见操作,并仅返回与此查询匹配的框架。您可以指定data_columns = True以强制所有列成为data_columns

In [545]: df_dc = df.copy()

In [546]: df_dc["string"] = "foo"

In [547]: df_dc.loc[df_dc.index[4:6], "string"] = np.nan

In [548]: df_dc.loc[df_dc.index[7:9], "string"] = "bar"

In [549]: df_dc["string2"] = "cool"

In [550]: df_dc.loc[df_dc.index[1:3], ["B", "C"]] = 1.0

In [551]: df_dc
Out[551]: 
 A         B         C string string2
2000-01-01  0.858644 -0.851236  1.058006    foo    cool
2000-01-02 -0.080372  1.000000  1.000000    foo    cool
2000-01-03  0.816983  1.000000  1.000000    foo    cool
2000-01-04  0.712795 -0.062433  0.736755    foo    cool
2000-01-05 -0.298721 -1.988045  1.475308    NaN    cool
2000-01-06  1.103675  1.382242 -0.650762    NaN    cool
2000-01-07 -0.729161 -0.142928 -1.063038    foo    cool
2000-01-08 -1.005977  0.465222 -0.094517    bar    cool

# on-disk operations
In [552]: store.append("df_dc", df_dc, data_columns=["B", "C", "string", "string2"])

In [553]: store.select("df_dc", where="B > 0")
Out[553]: 
 A         B         C string string2
2000-01-02 -0.080372  1.000000  1.000000    foo    cool
2000-01-03  0.816983  1.000000  1.000000    foo    cool
2000-01-06  1.103675  1.382242 -0.650762    NaN    cool
2000-01-08 -1.005977  0.465222 -0.094517    bar    cool

# getting creative
In [554]: store.select("df_dc", "B > 0 & C > 0 & string == foo")
Out[554]: 
 A    B    C string string2
2000-01-02 -0.080372  1.0  1.0    foo    cool
2000-01-03  0.816983  1.0  1.0    foo    cool

# this is in-memory version of this type of selection
In [555]: df_dc[(df_dc.B > 0) & (df_dc.C > 0) & (df_dc.string == "foo")]
Out[555]: 
 A    B    C string string2
2000-01-02 -0.080372  1.0  1.0    foo    cool
2000-01-03  0.816983  1.0  1.0    foo    cool

# we have automagically created this index and the B/C/string/string2
# columns are stored separately as ``PyTables`` columns
In [556]: store.root.df_dc.table
Out[556]: 
/df_dc/table (Table(8,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
 "B": Float64Col(shape=(), dflt=0.0, pos=2),
 "C": Float64Col(shape=(), dflt=0.0, pos=3),
 "string": StringCol(itemsize=3, shape=(), dflt=b'', pos=4),
 "string2": StringCol(itemsize=4, shape=(), dflt=b'', pos=5)}
 byteorder := 'little'
 chunkshape := (1680,)
 autoindex := True
 colindexes := {
 "index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "B": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "C": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "string": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "string2": Index(6, mediumshuffle, zlib(1)).is_csi=False} 

将许多列转换为data columns会导致一些性能下降,因此用户需要指定这些列。此外,在第一次追加/放置操作之后无法更改数据列(也无法更改可索引列),但您可以简单地读取数据并创建新表!

迭代器

您可以将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] 
```  ##### 使用 where 掩码进行选择

有时,您的查询可能涉及创建要选择的行列表。通常,此`mask`将是索引操作的结果`index`。此示例选择 datetimeindex 的月份为 5 的行。

```py
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 拆分为多个表。如果在列表的位置使用 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: [] 

查询表格

selectdelete 操作具有可选的条件,可以指定选择/删除数据的子集。这允许在磁盘上有一个非常大的表,并且只检索数据的一部分。

查询是使用 Term 类来指定的,作为一个布尔表达式。

  • indexcolumnsDataFrames 的支持索引器。

  • 如果指定了 data_columns,则可以将其用作额外的索引器。

  • 在 MultiIndex 中的级别名称,默认名称为 level_0level_1,如果未提供。

有效的比较运算符是:

=, ==, !=, >, >=, <, <=

有效的布尔表达式是通过以下方式组合的:

  • | : 或

  • & : 和

  • () : 用于分组

这些规则类似于 pandas 中用于索引的布尔表达式的使用方式。

注意

  • = 将自动扩展为比较运算符 ==

  • ~ 是非运算符,但只能在非常有限的情况下使用

  • 如果传递了表达式的列表/元组,它们将通过 & 组合。

以下是有效的表达式:

  • 'index >= date'

  • "columns = ['A', 'D']"

  • "columns in ['A', 'D']"

  • 'columns = A'

  • 'columns == A'

  • "~(columns = ['A', 'B'])"

  • 'index > df.index[3] & string = "bar"'

  • '(index > df.index[3] & index <= df.index[6]) | string = "bar"'

  • "ts >= Timestamp('2012-02-01')"

  • "major_axis>=20130101"

indexers位于子表达式的左侧:

columnsmajor_axists

子表达式的右侧(比较运算符之后)可以是:

  • 将被评估的函数,例如Timestamp('2012-02-01')

  • 字符串,例如"bar"

  • 类似日期的,例如20130101,或"20130101"

  • 列表,例如"['A', 'B']"

  • 在本地命名空间中定义的变量,例如date

注意

不建议通过将字符串插入查询表达式来对查询进行插值。只需将感兴趣的字符串分配给一个变量,并在表达式中使用该变量。例如,这样做

string = "HolyMoly'"
store.select("df", "index == string") 

不是这样

string = "HolyMoly'"
store.select('df', f'index == {string}') 

后者将不会起作用,并引发SyntaxError。请注意string变量中有一个单引号后跟一个双引号。

如果必须进行插值,请使用'%r'格式说明符

store.select("df", "index == %r" % string) 

这将引用string

以下是一些示例:

In [513]: dfq = pd.DataFrame(
 .....:    np.random.randn(10, 4),
 .....:    columns=list("ABCD"),
 .....:    index=pd.date_range("20130101", periods=10),
 .....: )
 .....: 

In [514]: store.append("dfq", dfq, format="table", data_columns=True) 

使用布尔表达式,进行内联函数评估。

In [515]: store.select("dfq", "index>pd.Timestamp('20130104') & columns=['A', 'B']")
Out[515]: 
 A         B
2013-01-05 -0.830545 -0.457071
2013-01-06  0.431186  1.049421
2013-01-07  0.617509 -0.811230
2013-01-08  0.947422 -0.671233
2013-01-09 -0.183798 -1.211230
2013-01-10  0.361428  0.887304 

使用内联列引用。

In [516]: store.select("dfq", where="A>0 or C>0")
Out[516]: 
 A         B         C         D
2013-01-02  0.658179  0.362814 -0.917897  0.010165
2013-01-03  0.905122  1.848731 -1.184241  0.932053
2013-01-05 -0.830545 -0.457071  1.565581  1.148032
2013-01-06  0.431186  1.049421  0.383309  0.595013
2013-01-07  0.617509 -0.811230 -2.088563 -1.393500
2013-01-08  0.947422 -0.671233 -0.847097 -1.187785
2013-01-10  0.361428  0.887304  0.266457 -0.399641 

可以提供columns关键字以选择要返回的列列表,这相当于传递'columns=list_of_columns_to_filter'

In [517]: store.select("df", "columns=['A', 'B']")
Out[517]: 
 A         B
2000-01-01  0.858644 -0.851236
2000-01-02 -0.080372 -1.268121
2000-01-03  0.816983  1.965656
2000-01-04  0.712795 -0.062433
2000-01-05 -0.298721 -1.988045
2000-01-06  1.103675  1.382242
2000-01-07 -0.729161 -0.142928
2000-01-08 -1.005977  0.465222 

可以指定startstop参数以限制总搜索空间。这些是表中总行数的术语。

注意

如果查询表达式具有未知变量引用,select将引发ValueError。通常这意味着您试图选择一个不是数据列的列。

如果查询表达式无效,select将引发SyntaxError

查询 timedelta64[ns]

您可以使用timedelta64[ns]类型存储和查询。术语可以以<float>(<unit>)的格式指定,其中浮点数可以是有符号的(和分数的),单位可以是D,s,ms,us,ns用于时间间隔。这是一个例子:

In [518]: from datetime import timedelta

In [519]: dftd = pd.DataFrame(
 .....:    {
 .....:        "A": pd.Timestamp("20130101"),
 .....:        "B": [
 .....:            pd.Timestamp("20130101") + timedelta(days=i, seconds=10)
 .....:            for i in range(10)
 .....:        ],
 .....:    }
 .....: )
 .....: 

In [520]: dftd["C"] = dftd["A"] - dftd["B"]

In [521]: dftd
Out[521]: 
 A                   B                  C
0 2013-01-01 2013-01-01 00:00:10  -1 days +23:59:50
1 2013-01-01 2013-01-02 00:00:10  -2 days +23:59:50
2 2013-01-01 2013-01-03 00:00:10  -3 days +23:59:50
3 2013-01-01 2013-01-04 00:00:10  -4 days +23:59:50
4 2013-01-01 2013-01-05 00:00:10  -5 days +23:59:50
5 2013-01-01 2013-01-06 00:00:10  -6 days +23:59:50
6 2013-01-01 2013-01-07 00:00:10  -7 days +23:59:50
7 2013-01-01 2013-01-08 00:00:10  -8 days +23:59:50
8 2013-01-01 2013-01-09 00:00:10  -9 days +23:59:50
9 2013-01-01 2013-01-10 00:00:10 -10 days +23:59:50

In [522]: store.append("dftd", dftd, data_columns=True)

In [523]: store.select("dftd", "C<'-3.5D'")
Out[523]: 
 A                   B                  C
4 1970-01-01 00:00:01.356998400 2013-01-05 00:00:10  -5 days +23:59:50
5 1970-01-01 00:00:01.356998400 2013-01-06 00:00:10  -6 days +23:59:50
6 1970-01-01 00:00:01.356998400 2013-01-07 00:00:10  -7 days +23:59:50
7 1970-01-01 00:00:01.356998400 2013-01-08 00:00:10  -8 days +23:59:50
8 1970-01-01 00:00:01.356998400 2013-01-09 00:00:10  -9 days +23:59:50
9 1970-01-01 00:00:01.356998400 2013-01-10 00:00:10 -10 days +23:59:50 

查询 MultiIndex

通过使用级别名称可以从MultiIndex中选择。

In [524]: df_mi.index.names
Out[524]: FrozenList(['foo', 'bar'])

In [525]: store.select("df_mi", "foo=baz and bar=two")
Out[525]: 
 A         B         C
foo bar 
baz two -1.646063 -0.695847 -0.429156 

如果MultiIndex级别名称为None,则可以通过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为表创建/修改索引。强烈建议创建表索引。当您使用带有索引维度作为whereselect时,这将大大加快查询速度。

注意

索引会自动在可索引和您指定的任何数据列上创建。可以通过向append传递index=False来关闭此行为。

# we have automagically already created an index (in the first section)
In [531]: i = store.root.df.table.cols.index.index

In [532]: i.optlevel, i.kind
Out[532]: (6, 'medium')

# change an index by passing new parameters
In [533]: store.create_table_index("df", optlevel=9, kind="full")

In [534]: i = store.root.df.table.cols.index.index

In [535]: i.optlevel, i.kind
Out[535]: (9, 'full') 

在向存储附加大量数据时,通常很有用关闭每次附加的索引创建,然后在最后重新创建。

In [536]: df_1 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [537]: df_2 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [538]: st = pd.HDFStore("appends.h5", mode="w")

In [539]: st.append("df", df_1, data_columns=["B"], index=False)

In [540]: st.append("df", df_2, data_columns=["B"], index=False)

In [541]: st.get_storer("df").table
Out[541]: 
/df/table (Table(20,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
 "B": Float64Col(shape=(), dflt=0.0, pos=2)}
 byteorder := 'little'
 chunkshape := (2730,) 

在附加完成后创建索引。

In [542]: st.create_table_index("df", columns=["B"], optlevel=9, kind="full")

In [543]: st.get_storer("df").table
Out[543]: 
/df/table (Table(20,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
 "B": Float64Col(shape=(), dflt=0.0, pos=2)}
 byteorder := 'little'
 chunkshape := (2730,)
 autoindex := True
 colindexes := {
 "B": Index(9, fullshuffle, zlib(1)).is_csi=True}

In [544]: st.close() 

请参阅此处如何在现有存储上创建完全排序的索引(CSI)。

通过数据列查询

您可以指定(并索引)要能够执行查询的特定列(除了始终可以查询的indexable列之外)。例如,假设您想要在磁盘上执行此常见操作,并仅返回与此查询匹配的框架。您可以指定data_columns = True以强制所有列都成为data_columns

In [545]: df_dc = df.copy()

In [546]: df_dc["string"] = "foo"

In [547]: df_dc.loc[df_dc.index[4:6], "string"] = np.nan

In [548]: df_dc.loc[df_dc.index[7:9], "string"] = "bar"

In [549]: df_dc["string2"] = "cool"

In [550]: df_dc.loc[df_dc.index[1:3], ["B", "C"]] = 1.0

In [551]: df_dc
Out[551]: 
 A         B         C string string2
2000-01-01  0.858644 -0.851236  1.058006    foo    cool
2000-01-02 -0.080372  1.000000  1.000000    foo    cool
2000-01-03  0.816983  1.000000  1.000000    foo    cool
2000-01-04  0.712795 -0.062433  0.736755    foo    cool
2000-01-05 -0.298721 -1.988045  1.475308    NaN    cool
2000-01-06  1.103675  1.382242 -0.650762    NaN    cool
2000-01-07 -0.729161 -0.142928 -1.063038    foo    cool
2000-01-08 -1.005977  0.465222 -0.094517    bar    cool

# on-disk operations
In [552]: store.append("df_dc", df_dc, data_columns=["B", "C", "string", "string2"])

In [553]: store.select("df_dc", where="B > 0")
Out[553]: 
 A         B         C string string2
2000-01-02 -0.080372  1.000000  1.000000    foo    cool
2000-01-03  0.816983  1.000000  1.000000    foo    cool
2000-01-06  1.103675  1.382242 -0.650762    NaN    cool
2000-01-08 -1.005977  0.465222 -0.094517    bar    cool

# getting creative
In [554]: store.select("df_dc", "B > 0 & C > 0 & string == foo")
Out[554]: 
 A    B    C string string2
2000-01-02 -0.080372  1.0  1.0    foo    cool
2000-01-03  0.816983  1.0  1.0    foo    cool

# this is in-memory version of this type of selection
In [555]: df_dc[(df_dc.B > 0) & (df_dc.C > 0) & (df_dc.string == "foo")]
Out[555]: 
 A    B    C string string2
2000-01-02 -0.080372  1.0  1.0    foo    cool
2000-01-03  0.816983  1.0  1.0    foo    cool

# we have automagically created this index and the B/C/string/string2
# columns are stored separately as ``PyTables`` columns
In [556]: store.root.df_dc.table
Out[556]: 
/df_dc/table (Table(8,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
 "B": Float64Col(shape=(), dflt=0.0, pos=2),
 "C": Float64Col(shape=(), dflt=0.0, pos=3),
 "string": StringCol(itemsize=3, shape=(), dflt=b'', pos=4),
 "string2": StringCol(itemsize=4, shape=(), dflt=b'', pos=5)}
 byteorder := 'little'
 chunkshape := (1680,)
 autoindex := True
 colindexes := {
 "index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "B": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "C": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "string": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "string2": Index(6, mediumshuffle, zlib(1)).is_csi=False} 

将许多列转换为data columns会导致一些性能下降,因此用户需要指定这些列。此外,在第一次追加/放置操作之后,无法更改数据列(也无法更改可索引列)(当然,您可以简单地读取数据并创建新表!)。

迭代器

您可以将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] 
```  ##### 使用 where 掩码进行选择

有时,您的查询可能涉及创建要选择的行列表。通常,此`mask`将是从索引操作中得到的`index`。此示例选择 datetimeindex 的月份为 5。

```py
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 
选择单个列

要检索单个可索引或数据列,请使用方法select_column。例如,这将使您能够非常快速地获取索引。这些返回结果的Series,由行号索引。目前这些不接受where选择器。

In [565]: store.select_column("df_dc", "index")
Out[565]: 
0   2000-01-01
1   2000-01-02
2   2000-01-03
3   2000-01-04
4   2000-01-05
5   2000-01-06
6   2000-01-07
7   2000-01-08
Name: index, dtype: datetime64[ns]

In [566]: store.select_column("df_dc", "string")
Out[566]: 
0    foo
1    foo
2    foo
3    foo
4    NaN
5    NaN
6    foo
7    bar
Name: string, dtype: object 
选择坐标

有时您希望获取查询的坐标(也称为索引位置)。这将返回结果位置的Index。这些坐标也可以传递给后续的where操作。

In [567]: df_coord = pd.DataFrame(
 .....:    np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
 .....: )
 .....: 

In [568]: store.append("df_coord", df_coord)

In [569]: c = store.select_as_coordinates("df_coord", "index > 20020101")

In [570]: c
Out[570]: 
Index([732, 733, 734, 735, 736, 737, 738, 739, 740, 741,
 ...
 990, 991, 992, 993, 994, 995, 996, 997, 998, 999],
 dtype='int64', length=268)

In [571]: store.select("df_coord", where=c)
Out[571]: 
 0         1
2002-01-02  0.007717  1.168386
2002-01-03  0.759328 -0.638934
2002-01-04 -1.154018 -0.324071
2002-01-05 -0.804551 -1.280593
2002-01-06 -0.047208  1.260503
...              ...       ...
2002-09-22 -1.139583  0.344316
2002-09-23 -0.760643 -1.306704
2002-09-24  0.059018  1.775482
2002-09-25  1.242255 -0.055457
2002-09-26  0.410317  2.194489

[268 rows x 2 columns] 
使用 where 掩码进行选择

有时您的查询可能涉及创建要选择的行列表。通常,这个mask会是一个索引操作的结果index。此示例选择 datetimeindex 中为 5 的月份。

In [572]: df_mask = pd.DataFrame(
 .....:    np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
 .....: )
 .....: 

In [573]: store.append("df_mask", df_mask)

In [574]: c = store.select_column("df_mask", "index")

In [575]: where = c[pd.DatetimeIndex(c).month == 5].index

In [576]: store.select("df_mask", where=where)
Out[576]: 
 0         1
2000-05-01  1.479511  0.516433
2000-05-02 -0.334984 -1.493537
2000-05-03  0.900321  0.049695
2000-05-04  0.614266 -1.077151
2000-05-05  0.233881  0.493246
...              ...       ...
2002-05-27  0.294122  0.457407
2002-05-28 -1.102535  1.215650
2002-05-29 -0.432911  0.753606
2002-05-30 -1.105212  2.311877
2002-05-31  2.567296  2.610691

[93 rows x 2 columns] 
存储器对象

如果您想检查存储的对象,请通过get_storer检索。您可以在程序中使用这个方法来获取对象中的行数。

In [577]: store.get_storer("df_dc").nrows
Out[577]: 8 

多表查询

append_to_multipleselect_as_multiple方法可以同时从多个表中进行追加/选择操作。其思想是有一个表(称为选择器表),您可以在其中索引大部分/全部列,并执行查询。其他表是数据表,其索引与选择器表的索引匹配。然后您可以在选择器表上执行非常快速的查询,同时获得大量数据。这种方法类似于拥有一个非常宽的表,但可以实现更高效的查询。

append_to_multiple方法将给定的单个 DataFrame 根据d拆分为多个表,d是一个将表名映射到您想要在该表中的‘列’列表的字典。如果使用None代替列表,则该表将具有给定 DataFrame 的其余未指定的列。参数selector定义了哪个表是选择器表(您可以从中进行查询)。参数dropna将从输入的DataFrame中删除行,以确保表是同步的。这意味着如果要写入的表中的一行完全是np.nan,那么该行将从所有表中删除。

如果dropna为 False,用户有责任同步表格。请记住,完全是np.Nan的行不会被写入 HDFStore,因此如果选择调用dropna=False,一些表可能比其他表有更多的行,因此select_as_multiple可能无法工作,或者可能返回意外结果。

In [578]: df_mt = pd.DataFrame(
 .....:    np.random.randn(8, 6),
 .....:    index=pd.date_range("1/1/2000", periods=8),
 .....:    columns=["A", "B", "C", "D", "E", "F"],
 .....: )
 .....: 

In [579]: df_mt["foo"] = "bar"

In [580]: df_mt.loc[df_mt.index[1], ("A", "B")] = np.nan

# you can also create the tables individually
In [581]: store.append_to_multiple(
 .....:    {"df1_mt": ["A", "B"], "df2_mt": None}, df_mt, selector="df1_mt"
 .....: )
 .....: 

In [582]: store
Out[582]: 
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5

# individual tables were created
In [583]: store.select("df1_mt")
Out[583]: 
 A         B
2000-01-01  0.162291 -0.430489
2000-01-02       NaN       NaN
2000-01-03  0.429207 -1.099274
2000-01-04  1.869081 -1.466039
2000-01-05  0.092130 -1.726280
2000-01-06  0.266901 -0.036854
2000-01-07 -0.517871 -0.990317
2000-01-08 -0.231342  0.557402

In [584]: store.select("df2_mt")
Out[584]: 
 C         D         E         F  foo
2000-01-01 -2.502042  0.668149  0.460708  1.834518  bar
2000-01-02  0.130441 -0.608465  0.439872  0.506364  bar
2000-01-03 -1.069546  1.236277  0.116634 -1.772519  bar
2000-01-04  0.137462  0.313939  0.748471 -0.943009  bar
2000-01-05  0.836517  2.049798  0.562167  0.189952  bar
2000-01-06  1.112750 -0.151596  1.503311  0.939470  bar
2000-01-07 -0.294348  0.335844 -0.794159  1.495614  bar
2000-01-08  0.860312 -0.538674 -0.541986 -1.759606  bar

# as a multiple
In [585]: store.select_as_multiple(
 .....:    ["df1_mt", "df2_mt"],
 .....:    where=["A>0", "B>0"],
 .....:    selector="df1_mt",
 .....: )
 .....: 
Out[585]: 
Empty DataFrame
Columns: [A, B, C, D, E, F, foo]
Index: [] 

从表中删除

您可以通过指定where有选择性地从表中删除。在删除行时,重要的是要了解PyTables通过擦除行然后移动后续数据来删除行。因此,根据数据的方向,删除可能是一个非常昂贵的操作。为了获得最佳性能,值得将要删除的维度作为indexables的第一个维度。

数据按照indexables的顺序(在磁盘上)排序。这里有一个简单的用例。您存储面板类型的数据,日期在major_axis中,id 在minor_axis中。然后数据被交错存储如下:

  • date_1

    • id_1

    • id_2

    • .

    • id_n

  • date_2

    • id_1

    • .

    • id_n

应该明确,对major_axis进行删除操作将非常快,因为一个块被移除,然后后续数据被移动。另一方面,对minor_axis进行删除操作将非常昂贵。在这种情况下,重新编写使用where选择除缺失数据之外的所有数据的表几乎肯定会更快。

警告

请注意,HDF5 不会自动在 h5 文件中回收空间。因此,反复删除(或删除节点)然后再添加,会增加文件大小

若要重新打包和清理文件,请使用 ptrepack。

注意事项与警告

压缩

PyTables允许存储的数据进行压缩。这适用于所有类型的存储,而不仅仅是表格。有两个参数用于控制压缩:complevelcomplib

  • complevel指定了数据是否以及如何被压缩。 complevel=0complevel=None禁用压缩,0<complevel<10启用压缩。

  • complib指定要使用的压缩库。如果未指定任何内容,则使用默认库zlib。压缩库通常针对良好的压缩率或速度进行优化,结果将取决于数据类型。选择哪种类型的压缩取决于您的具体需求和数据。支持的压缩库列表:

    • zlib: 默认的压缩库。在压缩方面经典,能够获得良好的压缩率,但有些慢。

    • lzo: 快速的压缩和解压。

    • bzip2: 良好的压缩率。

    • blosc: 快速的压缩和解压。

      支持替代的 blosc 压缩器:

      • blosc:blosclz 这是blosc的默认压缩器

      • blosc:lz4: 一个紧凑、非常流行且快速的压缩器。

      • blosc:lz4hc: LZ4 的调整版本,在牺牲速度的情况下产生更好的压缩比。

      • blosc:snappy: 一个在许多地方使用的流行压缩器。

      • blosc:zlib: 一种经典;比前面的压缩器稍慢,但实现了更好的压缩比。

      • blosc:zstd: 一种极其平衡的编解码器;它在以上其他编解码器中提供了最佳的压缩比,并且具有相当快的速度。

    如果complib定义为除列出的库之外的内容,则会发出ValueError异常。

注意

如果使用complib选项指定的库在您的平台上丢失,则默认情况下压缩为zlib,无需进一步操作。

为文件中的所有对象启用压缩:

store_compressed = pd.HDFStore(
    "store_compressed.h5", complevel=9, complib="blosc:blosclz"
) 

或者在不启用压缩的存储中进行即时压缩(这仅适用于表格):

store.append("df", df, complib="zlib", complevel=5) 

ptrepack

当表格在写入后进行压缩时,PyTables提供了更好的写入性能,而不是在开始时就打开压缩。您可以使用提供的PyTables实用程序ptrepack。此外,ptrepack可以在事后更改压缩级别。

ptrepack --chunkshape=auto --propindexes --complevel=9 --complib=blosc in.h5 out.h5 

另外 ptrepack in.h5 out.h5 将会 重打包 文件,以便你可以重用先前删除的空间。或者,你可以直接删除文件并重新写入,或者使用 copy 方法。 #### 注意事项

警告

HDFStore 在写入时 不是线程安全的。底层的 PyTables 仅支持并发读取(通过线程或进程)。如果你需要 同时读写,你需要在单个线程中的单个进程中对这些操作进行串行化。否则你的数据将会损坏。参见 (GH 2397) 获取更多信息。

  • 如果您使用锁来管理多个进程之间的写访问,则在释放写锁之前可能需要使用 fsync()。为了方便起见,你可以使用 store.flush(fsync=True) 来为你执行此操作。

  • 一旦创建了 table,列(DataFrame)就是固定的;只能添加完全相同的列

  • 请注意,时区(例如 pytz.timezone('US/Eastern'))在不同的时区版本中可能并不相等。因此,如果使用一个版本的时区库将数据本地化到特定的时区,并使用另一个版本更新数据,则数据将被转换为 UTC,因为这些时区被认为不相等。要么使用相同版本的时区库,要么使用 tz_convert 来使用更新的时区定义。

警告

如果列名无法用作属性选择器,PyTables 将显示 NaturalNameWarning自然 标识符只包含字母、数字和下划线,并且不能以数字开头。其他标识符不能在 where 子句中使用,通常不是一个好主意。

压缩

PyTables 允许对存储的数据进行压缩。这适用于所有类型的存储,不仅限于表格。有两个参数用于控制压缩:complevelcomplib

  • complevel 指定数据是否以及以何种难度进行压缩。complevel=0complevel=None 禁用压缩,0<complevel<10 启用压缩。

  • complib 指定要使用哪个压缩库。如果未指定任何内容,则使用默认库 zlib。压缩库通常针对压缩率或速度进行优化,结果将取决于数据类型。选择哪种类型的压缩取决于您的具体需求和数据。支持的压缩库列表:

    • zlib:默认压缩库。在压缩方面经典,压缩率高但速度较慢。

    • lzo:快速压缩和解压。

    • bzip2:压缩率高。

    • blosc:快速压缩和解压。

      支持替代的 blosc 压缩器:

      • blosc:blosclz 这是 blosc 的默认压缩器

      • blosc:lz4:一种紧凑、非常流行且快速的压缩器。

      • blosc:lz4hc:LZ4 的调整版本,在速度上产生更好的压缩比。

      • blosc:snappy:在许多地方使用的流行压缩器。

      • blosc:zlib:一个经典;比前面的压缩器稍慢一些,但实现更好的压缩比。

      • blosc:zstd:一个极其平衡的编解码器;在上述其他编解码器中,它提供了最佳的压缩比,并且速度相当快。

    如果complib被定义为列出的库之外的内容,将发出ValueError异常。

注意

如果在您的平台上缺少使用complib选项指定的库,则压缩默认为zlib,无需进一步操作。

为文件中的所有对象启用压缩:

store_compressed = pd.HDFStore(
    "store_compressed.h5", complevel=9, complib="blosc:blosclz"
) 

或者在未启用压缩的存储中对表进行即时压缩:

store.append("df", df, complib="zlib", complevel=5) 

ptrepack

当表在写入后进行压缩时,PyTables提供更好的写入性能,而不是在一开始就打开压缩。您可以使用提供的PyTables实用程序ptrepack。此外,ptrepack可以在事后更改压缩级别。

ptrepack --chunkshape=auto --propindexes --complevel=9 --complib=blosc in.h5 out.h5 

此外,ptrepack in.h5 out.h5重新打包文件,以便您可以重用先前删除的空间。或者,您可以简单地删除文件并重新写入,或者使用copy方法。

注意事项

警告

HDFStore在写入时不是线程安全的。底层的PyTables仅支持并发读取(通过线程或进程)。如果您需要同时读取和写入,您需要在单个线程中的单个进程中串行化这些操作。否则,您将破坏您的数据。有关更多信息,请参见(GH 2397)。

  • 如果您使用锁来管理多个进程之间的写入访问,您可能希望在释放写入锁之前使用fsync()。为了方便起见,您可以使用store.flush(fsync=True)来为您执行此操作。

  • 一旦创建了table,列(DataFrame)就固定了;只能追加完全相同的列

  • 请注意,时区(例如,pytz.timezone('US/Eastern'))在不同的时区版本中不一定相等。因此,如果使用一个版本的时区库将数据本地化到特定时区,并使用另一个版本更新数据,则数据将被转换为 UTC,因为这些时区不被视为相等。要么使用相同版本的时区库,要么使用tz_convert与更新后的时区定义。

警告

如果列名不能用作属性选择器,PyTables将显示NaturalNameWarning自然标识符仅包含字母、数字和下划线,并且不能以数字开头。其他标识符不能在where子句中使用,通常是一个坏主意。

数据类型

HDFStore将对象数据类型映射到PyTables的底层数据类型。这意味着以下类型已知可用:

类型 表示缺失值
浮点数:float64,float32,float16 np.nan
整数:int64,int32,int8,uint64,uint32,uint8
布尔值
datetime64[ns] NaT
timedelta64[ns] NaT
分类:参见下面的部分
对象:字符串 np.nan

不支持unicode列,将失败

分类数据

您可以将包含category数据类型的数据写入HDFStore。查询的工作方式与对象数组相同。但是,category数据类型的数据以更有效的方式存储。

In [586]: dfcat = pd.DataFrame(
 .....:    {"A": pd.Series(list("aabbcdba")).astype("category"), "B": np.random.randn(8)}
 .....: )
 .....: 

In [587]: dfcat
Out[587]: 
 A         B
0  a -1.520478
1  a -1.069391
2  b -0.551981
3  b  0.452407
4  c  0.409257
5  d  0.301911
6  b -0.640843
7  a -2.253022

In [588]: dfcat.dtypes
Out[588]: 
A    category
B     float64
dtype: object

In [589]: cstore = pd.HDFStore("cats.h5", mode="w")

In [590]: cstore.append("dfcat", dfcat, format="table", data_columns=["A"])

In [591]: result = cstore.select("dfcat", where="A in ['b', 'c']")

In [592]: result
Out[592]: 
 A         B
2  b -0.551981
3  b  0.452407
4  c  0.409257
6  b -0.640843

In [593]: result.dtypes
Out[593]: 
A    category
B     float64
dtype: object 

字符串列

min_itemsize

HDFStore的底层实现对字符串列使用固定的列宽(itemsize)。字符串列的 itemsize 是在第一次追加时传递给HDFStore的数据的长度(对于该列)的最大值。后续的追加可能会引入一个比列能够容纳的字符串更大的列,将引发异常(否则可能会对这些列进行静默截断,导致信息丢失)。在未来,我们可能会放宽这一限制,并允许发生用户指定的截断。

在第一次创建表时传递min_itemsize以先验指定特定字符串列的最小长度。min_itemsize可以是整数,或将列名映射到整数的字典。您可以将values作为键传递,以允许所有可索引data_columns具有此最小长度。

传递min_itemsize字典将导致所有传递的列自动创建为data_columns

注意

如果您没有传递任何data_columns,那么min_itemsize将是传递的任何字符串的长度的最大值

In [594]: dfs = pd.DataFrame({"A": "foo", "B": "bar"}, index=list(range(5)))

In [595]: dfs
Out[595]: 
 A    B
0  foo  bar
1  foo  bar
2  foo  bar
3  foo  bar
4  foo  bar

# A and B have a size of 30
In [596]: store.append("dfs", dfs, min_itemsize=30)

In [597]: store.get_storer("dfs").table
Out[597]: 
/dfs/table (Table(5,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": StringCol(itemsize=30, shape=(2,), dflt=b'', pos=1)}
 byteorder := 'little'
 chunkshape := (963,)
 autoindex := True
 colindexes := {
 "index": Index(6, mediumshuffle, zlib(1)).is_csi=False}

# A is created as a data_column with a size of 30
# B is size is calculated
In [598]: store.append("dfs2", dfs, min_itemsize={"A": 30})

In [599]: store.get_storer("dfs2").table
Out[599]: 
/dfs2/table (Table(5,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": StringCol(itemsize=3, shape=(1,), dflt=b'', pos=1),
 "A": StringCol(itemsize=30, shape=(), dflt=b'', pos=2)}
 byteorder := 'little'
 chunkshape := (1598,)
 autoindex := True
 colindexes := {
 "index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "A": Index(6, mediumshuffle, zlib(1)).is_csi=False} 

nan_rep

字符串列将使用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 

分类数据

您可以将包含category数据类型的数据写入HDFStore。查询的工作方式与对象数组相同。但是,category数据类型的数据以更有效的方式存储。

In [586]: dfcat = pd.DataFrame(
 .....:    {"A": pd.Series(list("aabbcdba")).astype("category"), "B": np.random.randn(8)}
 .....: )
 .....: 

In [587]: dfcat
Out[587]: 
 A         B
0  a -1.520478
1  a -1.069391
2  b -0.551981
3  b  0.452407
4  c  0.409257
5  d  0.301911
6  b -0.640843
7  a -2.253022

In [588]: dfcat.dtypes
Out[588]: 
A    category
B     float64
dtype: object

In [589]: cstore = pd.HDFStore("cats.h5", mode="w")

In [590]: cstore.append("dfcat", dfcat, format="table", data_columns=["A"])

In [591]: result = cstore.select("dfcat", where="A in ['b', 'c']")

In [592]: result
Out[592]: 
 A         B
2  b -0.551981
3  b  0.452407
4  c  0.409257
6  b -0.640843

In [593]: result.dtypes
Out[593]: 
A    category
B     float64
dtype: object 

字符串列

min_itemsize

HDFStore的底层实现使用固定的列宽(itemsize)来存储字符串列。字符串列的 itemsize 是在第一次追加数据时计算的,为传递给HDFStore的数据(对于该列)的最大长度。后续的追加操作可能会引入一个比列能容纳的字符串更大的列,这将引发异常(否则可能会导致这些列被静默截断,导致信息丢失)。在未来,我们可能会放宽这一限制,允许用户指定的截断操作发生。

在第一次创建表时传递min_itemsize以预先指定特定字符串列的最小长度。min_itemsize可以是一个整数,或者是将列名映射到整数的字典。您可以将values作为一个键传递,以允许所有可索引data_columns具有此最小长度。

传递min_itemsize字典将导致所有传递的列自动创建为data_columns

注意

如果没有传递任何data_columns,那么min_itemsize将是传递的任何字符串的最大长度

In [594]: dfs = pd.DataFrame({"A": "foo", "B": "bar"}, index=list(range(5)))

In [595]: dfs
Out[595]: 
 A    B
0  foo  bar
1  foo  bar
2  foo  bar
3  foo  bar
4  foo  bar

# A and B have a size of 30
In [596]: store.append("dfs", dfs, min_itemsize=30)

In [597]: store.get_storer("dfs").table
Out[597]: 
/dfs/table (Table(5,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": StringCol(itemsize=30, shape=(2,), dflt=b'', pos=1)}
 byteorder := 'little'
 chunkshape := (963,)
 autoindex := True
 colindexes := {
 "index": Index(6, mediumshuffle, zlib(1)).is_csi=False}

# A is created as a data_column with a size of 30
# B is size is calculated
In [598]: store.append("dfs2", dfs, min_itemsize={"A": 30})

In [599]: store.get_storer("dfs2").table
Out[599]: 
/dfs2/table (Table(5,)) ''
 description := {
 "index": Int64Col(shape=(), dflt=0, pos=0),
 "values_block_0": StringCol(itemsize=3, shape=(1,), dflt=b'', pos=1),
 "A": StringCol(itemsize=30, shape=(), dflt=b'', pos=2)}
 byteorder := 'little'
 chunkshape := (1598,)
 autoindex := True
 colindexes := {
 "index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
 "A": Index(6, mediumshuffle, zlib(1)).is_csi=False} 

nan_rep

字符串列将使用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 

性能

  • fixed存储相比,tables格式会带来写入性能损失。好处在于能够追加/删除和查询(可能是非常大量的数据)。写入时间通常比常规存储更长。查询时间可能非常快,特别是在索引轴上。

  • 您可以在append中传递chunksize=<int>,指定写入的块大小(默认为 50000)。这将显著降低写入时的内存使用。

  • 您可以在第一次append中传递expectedrows=<int>,设置PyTables预期的总行数。这将优化读/写性能。

  • 可以向表中写入重复行,但在选择时会被过滤掉(选择最后的项目;因此表在主要、次要对上是唯一的)

  • 如果您尝试存储将由 PyTables 进行 pickle 处理的类型(而不是存储为固有类型),将引发PerformanceWarning。请参阅这里获取更多信息和一些解决方案。

Feather

Feather 为数据框提供了二进制列序列化。它旨在使数据框的读写高效,并使跨数据分析语言共享数据变得容易。

Feather 旨在忠实地序列化和反序列化数据框,支持所有 pandas dtypes,包括分类和带有时区的日期时间等扩展 dtypes。

几个注意事项:

  • 对于DataFrame,格式不会写入IndexMultiIndex,如果提供了非默认的索引,会引发错误。你可以使用.reset_index()来存储索引,或者使用.reset_index(drop=True)来忽略它。

  • 不支持重复的列名和非字符串列名。

  • 对象数据类型列中的实际 Python 对象不受支持。在尝试序列化时,这些对象将引发一个有用的错误消息。

参见完整文档

In [606]: df = pd.DataFrame(
 .....:    {
 .....:        "a": list("abc"),
 .....:        "b": list(range(1, 4)),
 .....:        "c": np.arange(3, 6).astype("u1"),
 .....:        "d": np.arange(4.0, 7.0, dtype="float64"),
 .....:        "e": [True, False, True],
 .....:        "f": pd.Categorical(list("abc")),
 .....:        "g": pd.date_range("20130101", periods=3),
 .....:        "h": pd.date_range("20130101", periods=3, tz="US/Eastern"),
 .....:        "i": pd.date_range("20130101", periods=3, freq="ns"),
 .....:    }
 .....: )
 .....: 

In [607]: df
Out[607]: 
 a  b  c  ...          g                         h                             i
0  a  1  3  ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1  b  2  4  ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2  c  3  5  ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002

[3 rows x 9 columns]

In [608]: df.dtypes
Out[608]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                      category
g                datetime64[ns]
h    datetime64[ns, US/Eastern]
i                datetime64[ns]
dtype: object 

写入 feather 文件。

In [609]: df.to_feather("example.feather") 

从 feather 文件中读取。

In [610]: result = pd.read_feather("example.feather")

In [611]: result
Out[611]: 
 a  b  c  ...          g                         h                             i
0  a  1  3  ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1  b  2  4  ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2  c  3  5  ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002

[3 rows x 9 columns]

# we preserve dtypes
In [612]: result.dtypes
Out[612]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                      category
g                datetime64[ns]
h    datetime64[ns, US/Eastern]
i                datetime64[ns]
dtype: object 

Parquet

Apache Parquet为数据框提供了一种分区二进制列式序列化。它旨在使读取和写入数据框变得高效,并使跨数据分析语言共享数据变得容易。Parquet 可以使用各种压缩技术来尽可能地缩小文件大小,同时仍然保持良好的读取性能。

Parquet 旨在忠实地序列化和反序列化DataFrame,支持所有 pandas 数据类型,包括带时区的日期时间等扩展数据类型。

有几个注意事项。

  • 不支持重复的列名和非字符串列名。

  • pyarrow 引擎始终将索引写入输出,但 fastparquet 仅写入非默认索引。这个额外的列可能会对不希望的非 pandas 消费者造成问题。你可以使用index参数强制包含或省略索引,而不管底层引擎如何。

  • 如果指定了索引级别名称,必须是字符串。

  • pyarrow 引擎中,非字符串类型的分类数据类型可以序列化为 parquet,但会反序列化为其原始数据类型。

  • pyarrow 引擎会保留具有字符串类型的分类数据类型的ordered标志。fastparquet 不会保留ordered标志。

  • 不支持的类型包括Interval和实际的 Python 对象类型。在尝试序列化时,这些对象将引发一个有用的错误消息。Period类型在 pyarrow >= 0.16.0 中受支持。

  • pyarrow 引擎会保留扩展数据类型,例如可空整数和字符串数据类型(需要 pyarrow >= 0.16.0,并且需要扩展类型实现所需的协议,请参阅扩展类型文档)。

您可以指定一个engine来指导序列化。这可以是pyarrowfastparquetauto之一。如果未指定引擎,则检查pd.options.io.parquet.engine选项;如果这也是auto,则尝试pyarrow,并回退到fastparquet

参见pyarrowfastparquet的文档。

注意

这些引擎非常相似,几乎可以读写完全相同的 parquet 格式文件。pyarrow>=8.0.0 支持 timedelta 数据,fastparquet>=0.1.4 支持时区感知的日期时间。这些库之间的不同在于它们具有不同的底层依赖关系(fastparquet 使用 numba,而 pyarrow 使用一个 C 库)。

In [613]: df = pd.DataFrame(
 .....:    {
 .....:        "a": list("abc"),
 .....:        "b": list(range(1, 4)),
 .....:        "c": np.arange(3, 6).astype("u1"),
 .....:        "d": np.arange(4.0, 7.0, dtype="float64"),
 .....:        "e": [True, False, True],
 .....:        "f": pd.date_range("20130101", periods=3),
 .....:        "g": pd.date_range("20130101", periods=3, tz="US/Eastern"),
 .....:        "h": pd.Categorical(list("abc")),
 .....:        "i": pd.Categorical(list("abc"), ordered=True),
 .....:    }
 .....: )
 .....: 

In [614]: df
Out[614]: 
 a  b  c    d      e          f                         g  h  i
0  a  1  3  4.0   True 2013-01-01 2013-01-01 00:00:00-05:00  a  a
1  b  2  4  5.0  False 2013-01-02 2013-01-02 00:00:00-05:00  b  b
2  c  3  5  6.0   True 2013-01-03 2013-01-03 00:00:00-05:00  c  c

In [615]: df.dtypes
Out[615]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                datetime64[ns]
g    datetime64[ns, US/Eastern]
h                      category
i                      category
dtype: object 

写入到 Parquet 文件。

In [616]: df.to_parquet("example_pa.parquet", engine="pyarrow")

In [617]: df.to_parquet("example_fp.parquet", engine="fastparquet") 

从 Parquet 文件中读取。

In [618]: result = pd.read_parquet("example_fp.parquet", engine="fastparquet")

In [619]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow")

In [620]: result.dtypes
Out[620]: 
a                        object
b                         int64
c                         uint8
d                       float64
e                          bool
f                datetime64[ns]
g    datetime64[ns, US/Eastern]
h                      category
i                      category
dtype: object 

通过设置 dtype_backend 参数,你可以控制生成的 DataFrame 使用的默认数据类型。

In [621]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow", dtype_backend="pyarrow")

In [622]: result.dtypes
Out[622]: 
a                                      string[pyarrow]
b                                       int64[pyarrow]
c                                       uint8[pyarrow]
d                                      double[pyarrow]
e                                        bool[pyarrow]
f                               timestamp[ns][pyarrow]
g                timestamp[ns, tz=US/Eastern][pyarrow]
h    dictionary<values=string, indices=int32, order...
i    dictionary<values=string, indices=int32, order...
dtype: object 

注意

注意,fastparquet 不支持此功能。

从 Parquet 文件中只读取特定列。

In [623]: result = pd.read_parquet(
 .....:    "example_fp.parquet",
 .....:    engine="fastparquet",
 .....:    columns=["a", "b"],
 .....: )
 .....: 

In [624]: result = pd.read_parquet(
 .....:    "example_pa.parquet",
 .....:    engine="pyarrow",
 .....:    columns=["a", "b"],
 .....: )
 .....: 

In [625]: result.dtypes
Out[625]: 
a    object
b     int64
dtype: object 

处理索引

DataFrame 序列化为 parquet 可能会在输出文件中包含隐式索引作为一个或多个列。因此,这段代码:

In [626]: df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})

In [627]: df.to_parquet("test.parquet", engine="pyarrow") 

如果你使用 pyarrow 进行序列化,会创建一个包含 列的 parquet 文件:ab__index_level_0__。如果你使用 fastparquet,则索引 可能会 写入文件,也可能不会。

这个意外的额外列会导致一些数据库(如亚马逊 Redshift)拒绝该文件,因为该列在目标表中不存在。

如果要在写入时省略数据框的索引,请将 index=False 传递给 to_parquet():

In [628]: df.to_parquet("test.parquet", index=False) 

这将创建一个只有两列 ab 的 parquet 文件。如果你的 DataFrame 有一个自定义索引,在加载此文件到一个 DataFrame 时,你将无法得到它。

传递 index=True始终 写入索引,即使这不是底层引擎的默认行为。

对 Parquet 文件进行分区

Parquet 支持根据一个或多个列的值对数据进行分区。

In [629]: df = pd.DataFrame({"a": [0, 0, 1, 1], "b": [0, 1, 0, 1]})

In [630]: df.to_parquet(path="test", engine="pyarrow", partition_cols=["a"], compression=None) 

path 指定了将要保存数据的父目录。partition_cols 是数据集将被分区的列名。列按给定顺序进行分区。分区拆分由分区列中的唯一值确定。上面的示例创建了一个可能如下所示的分区数据集:

test
├── a=0
│   ├── 0bac803e32dc42ae83fddfd029cbdebc.parquet
│   └──  ...
└── a=1
    ├── e6ab24a4f45147b49b54a662f0c412a3.parquet
    └── ... 

处理索引

DataFrame 序列化为 parquet 可能会在输出文件中包含隐式索引作为一个或多个列。因此,这段代码:

In [626]: df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})

In [627]: df.to_parquet("test.parquet", engine="pyarrow") 

如果你使用 pyarrow 进行序列化,会创建一个包含 列的 parquet 文件:ab__index_level_0__。如果你使用 fastparquet,则索引 可能会 写入文件,也可能不会。

这个意外的额外列会导致一些数据库(如亚马逊 Redshift)拒绝该文件,因为该列在目标表中不存在。

如果要在写入时省略数据框的索引,请将 index=False 传递给 to_parquet():

In [628]: df.to_parquet("test.parquet", index=False) 

这将创建一个仅包含两个预期列 ab 的 parquet 文件。如果您的 DataFrame 有自定义索引,则在将此文件加载到 DataFrame 中时将不会恢复它。

传递 index=True始终写入索引,即使这不是底层引擎的默认行为。

分区 Parquet 文件

Parquet 支持基于一个或多个列的值进行数据分区。

In [629]: df = pd.DataFrame({"a": [0, 0, 1, 1], "b": [0, 1, 0, 1]})

In [630]: df.to_parquet(path="test", engine="pyarrow", partition_cols=["a"], compression=None) 

path 指定了要保存数据的父目录。partition_cols 是将对数据集进行分区的列名。列按给定的顺序进行分区。分区拆分由分区列中的唯一值确定。上述示例创建了一个可能如下所示的分区数据集:

test
├── a=0
│   ├── 0bac803e32dc42ae83fddfd029cbdebc.parquet
│   └──  ...
└── a=1
    ├── e6ab24a4f45147b49b54a662f0c412a3.parquet
    └── ... 

ORC

与 parquet 格式类似,ORC Format 是用于数据框的二进制列序列化格式。它旨在使数据框的读取高效。pandas 提供了 ORC 格式的读取器和写入器,read_orc()to_orc()。这需要 pyarrow 库。

警告

  • 强烈建议使用 conda 安装 pyarrow,因为 pyarrow 出现了一些问题。

  • to_orc() 需要 pyarrow>=7.0.0。

  • read_orc()to_orc() 在 Windows 上尚不受支持,您可以在安装可选依赖项中找到有效的环境。

  • 有关支持的数据类型,请参阅Arrow 中支持的 ORC 特性

  • 当将日期时间列转换为 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 查询

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 文档 中找到每个 SQL 方言支持的驱动程序的概述。

如果未安装 SQLAlchemy,则可以在 SQLAlchemy 引擎、连接或 URI 字符串的位置使用 sqlite3.Connection

还可以参见一些烹饪示例来了解一些高级策略。

关键函数包括:

read_sql_table(table_name, con[, schema, ...]) 将 SQL 数据库表读取到 DataFrame 中。
read_sql_query(sql, con[, index_col, ...]) 将 SQL 查询读取到 DataFrame 中。
read_sql(sql, con[, index_col, ...]) 将 SQL 查询或数据库表读取到 DataFrame 中。
DataFrame.to_sql(name, con, *[, schema, ...]) 将 DataFrame 中存储的记录写入 SQL 数据库。

注意

函数 read_sql() 是对 read_sql_table()read_sql_query() 的便捷包装(以及向后兼容), 它会根据提供的输入(数据库表名或 SQL 查询)委派给特定的函数。如果表名有特殊字符,则不需要对表名加引号。

在以下示例中,我们使用了 SQlite SQL 数据库引擎。您可以使用一个临时 SQLite 数据库,其中的数据存储在“内存”中。

要使用 ADBC 驱动程序进行连接,您需要使用软件包管理器安装 adbc_driver_sqlite。安装完成后,您可以使用 ADBC 驱动程序提供的 DBAPI 接口来连接到您的数据库。

import adbc_driver_sqlite.dbapi as sqlite_dbapi

# Create the connection
with sqlite_dbapi.connect("sqlite:///:memory:") as conn:
     df = pd.read_sql_table("data", conn) 

要使用 SQLAlchemy 进行连接,您可以使用 create_engine() 函数从数据库 URI 创建一个引擎对象。您只需要针对每个要连接的数据库创建一次引擎。有关 create_engine() 和 URI 格式化的更多信息,请参见以下示例和 SQLAlchemy 文档

In [639]: from sqlalchemy import create_engine

# Create your engine.
In [640]: engine = create_engine("sqlite:///:memory:") 

如果您想管理自己的连接,可以传递其中一个。下面的示例使用 Python 上下文管理器打开了与数据库的连接,该上下文管理器在块完成后自动关闭连接。请参阅SQLAlchemy 文档了解数据库连接的处理方式。

with engine.connect() as conn, conn.begin():
    data = pd.read_sql_table("data", conn) 

警告

当你打开与数据库的连接时,你也负责关闭它。保持连接处于打开状态的副作用可能包括锁定数据库或其他破坏性行为。

写入数据帧

假设以下数据在DataFrame data中,我们可以使用to_sql()将其插入数据库。

id Date Col_1 Col_2 Col_3
26 2012-10-18 X 25.7 True
42 2012-10-19 Y -12.4 False
63 2012-10-20 Z 5.73 True
In [641]: import datetime

In [642]: c = ["id", "Date", "Col_1", "Col_2", "Col_3"]

In [643]: d = [
 .....:    (26, datetime.datetime(2010, 10, 18), "X", 27.5, True),
 .....:    (42, datetime.datetime(2010, 10, 19), "Y", -12.5, False),
 .....:    (63, datetime.datetime(2010, 10, 20), "Z", 5.73, True),
 .....: ]
 .....: 

In [644]: data = pd.DataFrame(d, columns=c)

In [645]: data
Out[645]: 
 id       Date Col_1  Col_2  Col_3
0  26 2010-10-18     X  27.50   True
1  42 2010-10-19     Y -12.50  False
2  63 2010-10-20     Z   5.73   True

In [646]: data.to_sql("data", con=engine)
Out[646]: 3 

对于一些数据库,写入大型数据帧可能会因为超过数据包大小限制而导致错误。可以通过在调用to_sql时设置chunksize参数来避免这种情况。例如,以下代码将data以每次 1000 行的批量写入数据库:

In [647]: data.to_sql("data_chunked", con=engine, chunksize=1000)
Out[647]: 3 

SQL 数据类型

在 SQL 数据库中确保一致的数据类型管理是具有挑战性的。并非每个 SQL 数据库都提供相同的类型,即使它们提供了,对给定类型的实现也可能存在细微差异,这些差异会对如何保留类型产生微妙的影响。

为了尽可能保留数据库类型,建议用户在可用时使用 ADBC 驱动程序。Arrow 类型系统提供了比历史上的 pandas/NumPy 类型系统更广泛的类型,更接近数据库类型。为了说明这一点,注意一下不同数据库和 pandas 后端中可用类型的(非穷尽)列表:

numpy/pandas arrow postgres sqlite
int16/Int16 int16 SMALLINT INTEGER
int32/Int32 int32 INTEGER INTEGER
int64/Int64 int64 BIGINT INTEGER
float32 float32 REAL REAL
float64 float64 DOUBLE PRECISION REAL
object string TEXT TEXT
bool bool_ BOOLEAN
datetime64[ns] timestamp(us) TIMESTAMP
datetime64[ns,tz] timestamp(us,tz) TIMESTAMPTZ
date32 DATE
month_day_nano_interval INTERVAL
binary BINARY BLOB
decimal128 DECIMAL [1]
list ARRAY [1]
struct

复合类型

[1]

脚注

如果您希望在 DataFrame 的整个生命周期中尽可能地保留数据库类型,建议用户利用read_sql()dtype_backend="pyarrow"参数。

# for roundtripping
with pg_dbapi.connect(uri) as conn:
    df2 = pd.read_sql("pandas_table", conn, dtype_backend="pyarrow") 

这将防止您的数据被转换为传统的 pandas/NumPy 类型系统,该系统经常以使 SQL 类型无法往返的方式转换 SQL 类型。

如果没有可用的 ADBC 驱动程序,to_sql() 将尝试根据数据的 dtype 将数据映射到适当的 SQL 数据类型。当您有 dtype 为 object 的列时,pandas 将尝试推断数据类型。

您始终可以通过使用 dtype 参数指定任何列的所需 SQL 类型来覆盖默认类型。该参数需要将列名映射到 SQLAlchemy 类型的字典(或字符串以用于 sqlite3 回退模式)。例如,指定为字符串列使用 sqlalchemy 的 String 类型而不是默认的 Text 类型:

In [648]: from sqlalchemy.types import String

In [649]: data.to_sql("data_dtype", con=engine, dtype={"Col_1": String})
Out[649]: 3 

注意

由于不同数据库风格对 timedelta 的支持有限,类型为 timedelta64 的列将被写入为整数值,以纳秒为单位,并引发警告。唯一的例外是使用 ADBC PostgreSQL 驱动程序的情况,其中 timedelta 将被写入数据库作为 INTERVAL

注意

category 数据类型的列将被转换为密集表示,就像使用 np.asarray(categorical) 一样(例如,对于字符串类别,这将得到一个字符串数组)。因此,读取数据库表时不会生成分类。

日期时间数据类型

使用 ADBC 或 SQLAlchemy,to_sql() 能够写入时区无关或时区感知的日期时间数据。然而,最终存储在数据库中的数据取决于所使用数据库系统的日期时间数据的支持数据类型。

以下表格列出了一些常见数据库中日期时间数据支持的数据类型。其他数据库方言可能对日期时间数据有不同的数据类型。

数据库 SQL 日期时间类型 时区支持
SQLite TEXT
MySQL TIMESTAMPDATETIME
PostgreSQL TIMESTAMPTIMESTAMP WITH TIME ZONE

在将带有时区信息的数据写入不支持时区的数据库时,数据将被写入为相对于时区的本地时间的时区无关时间戳。

read_sql_table() 也能够读取时区感知或时区无关的日期时间数据。当读取 TIMESTAMP WITH TIME ZONE 类型时,pandas 将数据转换为 UTC 时间。

插入方法

参数 method 控制要使用的 SQL 插入子句。可能的取值包括:

  • None: 使用标准 SQL INSERT 子句(每行一个)。

  • 'multi':在单个INSERT子句中传递多个值。它使用一种特殊的 SQL 语法,不是所有后端都支持。这通常为像 PrestoRedshift 这样的分析数据库提供更好的性能,但如果表包含许多列,则传统 SQL 后端的性能较差。有关更多信息,请查看 SQLAlchemy 文档

  • 具有签名(pd_table, conn, keys, data_iter)的可调用函数:这可以用于基于特定后端方言功能实现更高性能的插入方法。

使用 PostgreSQL COPY 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) 

读取表

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)系列。因此,如果查询输出为空,则所有生成的列将作为对象值返回(因为它们是最一般的)。如果您预见到您的查询有时会生成空结果,您可能希望在之后显式进行类型转换以确保 dtype 的完整性。

您还可以将列名指定为DataFrame索引,并指定要读取的列的子集。

In [651]: pd.read_sql_table("data", engine, index_col="id")
Out[651]: 
 index       Date Col_1  Col_2  Col_3
id 
26      0 2010-10-18     X  27.50   True
42      1 2010-10-19     Y -12.50  False
63      2 2010-10-20     Z   5.73   True

In [652]: pd.read_sql_table("data", engine, columns=["Col_1", "Col_2"])
Out[652]: 
 Col_1  Col_2
0     X  27.50
1     Y -12.50
2     Z   5.73 

你还可以明确地强制将列解析为日期:

In [653]: pd.read_sql_table("data", engine, parse_dates=["Date"])
Out[653]: 
 index  id       Date Col_1  Col_2  Col_3
0      0  26 2010-10-18     X  27.50   True
1      1  42 2010-10-19     Y -12.50  False
2      2  63 2010-10-20     Z   5.73   True 

如果需要,您可以明确指定格式字符串,或传递给pandas.to_datetime()的参数字典:

pd.read_sql_table("data", engine, parse_dates={"Date": "%Y-%m-%d"})
pd.read_sql_table(
    "data",
    engine,
    parse_dates={"Date": {"format": "%Y-%m-%d %H:%M:%S"}},
) 

您可以使用has_table()检查表是否存在

模式支持

通过read_sql_table()to_sql()函数中的schema关键字支持从不同模式读取和写入。但请注意,这取决于数据库类型(sqlite 不支持模式)。例如:

df.to_sql(name="table", con=engine, schema="other_schema")
pd.read_sql_table("table", engine, schema="other_schema") 

查询

您可以在read_sql_query()函数中使用原始 SQL 进行查询。在这种情况下,您必须使用适合您的数据库的 SQL 变体。使用 SQLAlchemy 时,您还可以传递数据库不可知的 SQLAlchemy 表达式语言构造。

In [654]: pd.read_sql_query("SELECT * FROM data", engine)
Out[654]: 
 index  id                        Date Col_1  Col_2  Col_3
0      0  26  2010-10-18 00:00:00.000000     X  27.50      1
1      1  42  2010-10-19 00:00:00.000000     Y -12.50      0
2      2  63  2010-10-20 00:00:00.000000     Z   5.73      1 

当然,您可以指定更“复杂”的查询。

In [655]: pd.read_sql_query("SELECT id, Col_1, Col_2 FROM data WHERE id = 42;", engine)
Out[655]: 
 id Col_1  Col_2
0  42     Y  -12.5 

read_sql_query()函数支持chunksize参数。指定这个参数将返回查询结果的迭代器:

In [656]: df = pd.DataFrame(np.random.randn(20, 3), columns=list("abc"))

In [657]: df.to_sql(name="data_chunks", con=engine, index=False)
Out[657]: 20 
In [658]: for chunk in pd.read_sql_query("SELECT * FROM data_chunks", engine, chunksize=5):
 .....:    print(chunk)
 .....: 
 a         b         c
0 -0.395347 -0.822726 -0.363777
1  1.676124 -0.908102 -1.391346
2 -1.094269  0.278380  1.205899
3  1.503443  0.932171 -0.709459
4 -0.645944 -1.351389  0.132023
 a         b         c
0  0.210427  0.192202  0.661949
1  1.690629 -1.046044  0.618697
2 -0.013863  1.314289  1.951611
3 -1.485026  0.304662  1.194757
4 -0.446717  0.528496 -0.657575
 a         b         c
0 -0.876654  0.336252  0.172668
1  0.337684 -0.411202 -0.828394
2 -0.244413  1.094948  0.087183
3  1.125934 -1.480095  1.205944
4 -0.451849  0.452214 -2.208192
 a         b         c
0 -2.061019  0.044184 -0.017118
1  1.248959 -0.675595 -1.908296
2 -0.125934  1.491974  0.648726
3  0.391214  0.438609  1.634248
4  1.208707 -1.535740  1.620399 

引擎连接示例

要连接到 SQLAlchemy,您可以使用create_engine()函数从数据库 URI 创建引擎对象。您只需为您连接的每个数据库创建一次引擎。

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]](/cdn-cgi/l/email-protection):1521/sidname")

engine = create_engine("mssql+pyodbc://mydsn")

# sqlite://<nohostname>/<path>
# where <path> is relative:
engine = create_engine("sqlite:///foo.db")

# or absolute, starting with a slash:
engine = create_engine("sqlite:////absolute/path/to/foo.db") 

欲了解更多信息,请参阅 SQLAlchemy 文档 中的示例。

高级 SQLAlchemy 查询

您可以使用 SQLAlchemy 构造描述您的查询。

使用sqlalchemy.text()以后端中立的方式指定查询参数。

In [659]: import sqlalchemy as sa

In [660]: pd.read_sql(
 .....:    sa.text("SELECT * FROM data where Col_1=:col1"), engine, params={"col1": "X"}
 .....: )
 .....: 
Out[660]: 
 index  id                        Date Col_1  Col_2  Col_3
0      0  26  2010-10-18 00:00:00.000000     X   27.5      1 

如果您有数据库的 SQLAlchemy 描述,您可以使用 SQLAlchemy 表达式来表示 where 条件。

In [661]: metadata = sa.MetaData()

In [662]: data_table = sa.Table(
 .....:    "data",
 .....:    metadata,
 .....:    sa.Column("index", sa.Integer),
 .....:    sa.Column("Date", sa.DateTime),
 .....:    sa.Column("Col_1", sa.String),
 .....:    sa.Column("Col_2", sa.Float),
 .....:    sa.Column("Col_3", sa.Boolean),
 .....: )
 .....: 

In [663]: pd.read_sql(sa.select(data_table).where(data_table.c.Col_3 is True), engine)
Out[663]: 
Empty DataFrame
Columns: [index, Date, Col_1, Col_2, Col_3]
Index: [] 

您可以将 SQLAlchemy 表达式与传递给read_sql()的参数结合使用sqlalchemy.bindparam()

In [664]: import datetime as dt

In [665]: expr = sa.select(data_table).where(data_table.c.Date > sa.bindparam("date"))

In [666]: pd.read_sql(expr, engine, params={"date": dt.datetime(2010, 10, 18)})
Out[666]: 
 index       Date Col_1  Col_2  Col_3
0      1 2010-10-19     Y -12.50  False
1      2 2010-10-20     Z   5.73   True 

Sqlite 后备

支持使用 SQLite 而不使用 SQLAlchemy。这种模式需要一个符合 Python DB-API 的 Python 数据库适配器。

您可以像这样创建连接:

import sqlite3

con = sqlite3.connect(":memory:") 

然后执行以下查询:

data.to_sql("data", con)
pd.read_sql_query("SELECT * FROM data", con) 

写入 DataFrames

假设以下数据在DataFrame data中,我们可以使用to_sql()将其插入数据库。

id Date Col_1 Col_2 Col_3
26 2012-10-18 X 25.7 True
42 2012-10-19 Y -12.4 False
63 2012-10-20 Z 5.73 True
In [641]: import datetime

In [642]: c = ["id", "Date", "Col_1", "Col_2", "Col_3"]

In [643]: d = [
 .....:    (26, datetime.datetime(2010, 10, 18), "X", 27.5, True),
 .....:    (42, datetime.datetime(2010, 10, 19), "Y", -12.5, False),
 .....:    (63, datetime.datetime(2010, 10, 20), "Z", 5.73, True),
 .....: ]
 .....: 

In [644]: data = pd.DataFrame(d, columns=c)

In [645]: data
Out[645]: 
 id       Date Col_1  Col_2  Col_3
0  26 2010-10-18     X  27.50   True
1  42 2010-10-19     Y -12.50  False
2  63 2010-10-20     Z   5.73   True

In [646]: data.to_sql("data", con=engine)
Out[646]: 3 

对于某些数据库,写入大型 DataFrame 可能会因超过数据包大小限制而导致错误。可以通过在调用to_sql时设置chunksize参数来避免这种情况。例如,以下内容将以每次 1000 行的批量方式将data写入数据库:

In [647]: data.to_sql("data_chunked", con=engine, chunksize=1000)
Out[647]: 3 

SQL 数据类型

确保跨 SQL 数据库一致的数据类型管理是具有挑战性的。并非每个 SQL 数据库都提供相同的类型,即使提供了,给定类型的实现也可能以会影响如何保留类型的方式有微妙的变化。

为了尽可能保留数据库类型,建议用户在可用时使用 ADBC 驱动程序。Arrow 类型系统提供了更多种类的类型,这些类型更接近数据库类型,而不是历史上的 pandas/NumPy 类型系统。为了说明这一点,请注意不同数据库和 pandas 后端中可用类型的(非穷尽)列表:

numpy/pandas arrow postgres sqlite
int16/Int16 int16 SMALLINT INTEGER
int32/Int32 int32 INTEGER INTEGER
int64/Int64 int64 BIGINT INTEGER
float32 float32 REAL REAL
float64 float64 DOUBLE PRECISION REAL
object string TEXT TEXT
bool bool_ BOOLEAN
datetime64[ns] timestamp(us) TIMESTAMP
datetime64[ns,tz] timestamp(us,tz) TIMESTAMPTZ
date32 DATE
month_day_nano_interval INTERVAL
binary BINARY BLOB
decimal128 DECIMAL [1]
list ARRAY [1]
struct

复合类型

[1]

脚注

如果您有兴趣在 DataFrame 的生命周期中尽可能保留数据库类型,建议用户利用 read_sql()dtype_backend="pyarrow" 参数

# for roundtripping
with pg_dbapi.connect(uri) as conn:
    df2 = pd.read_sql("pandas_table", conn, dtype_backend="pyarrow") 

这将防止您的数据转换为传统的 pandas/NumPy 类型系统,该系统经常以使其无法往返的方式转换 SQL 类型。

如果没有可用的 ADBC 驱动程序,to_sql() 将尝试根据数据的 dtype 将您的数据映射到适当的 SQL 数据类型。当您有 dtype 为 object 的列时,pandas 将尝试推断数据类型。

您始终可以通过使用 dtype 参数指定任何列的所需 SQL 类型来覆盖默认类型。此参数需要一个将列名映射到 SQLAlchemy 类型(或字符串以用于 sqlite3 回退模式)的字典。例如,指定为字符串列使用 sqlalchemy 的 String 类型而不是默认的 Text 类型:

In [648]: from sqlalchemy.types import String

In [649]: data.to_sql("data_dtype", con=engine, dtype={"Col_1": String})
Out[649]: 3 

注意

由于不同数据库版本对 timedelta 的支持有限,类型为 timedelta64 的列将被写入数据库为纳秒的整数值,并引发警告。唯一的例外是当使用 ADBC PostgreSQL 驱动程序时,timedelta 将被写入数据库为 INTERVAL

注意

category dtype 的列将转换为密集表示,就像您使用 np.asarray(categorical) 一样(例如,对于字符串类别,这会生成一个字符串数组)。因此,将数据库表读回时不会生成分类。

SQL 数据类型

确保跨 SQL 数据库的一致数据类型管理是具有挑战性的。并非每个 SQL 数据库都提供相同的类型,即使它们提供了,给定类型的实现也可能以使类型能够被保留的方式有所不同,而这种差异可能会对类型的保留产生微妙的影响。

为了最大程度地保留数据库类型,建议用户在可用时使用 ADBC 驱动程序。Arrow 类型系统提供了更多种类的类型,这些类型更接近数据库类型,而不是历史上的 pandas/NumPy 类型系统。为了说明,注意不同数据库和 pandas 后端中可用的(非穷尽)类型列表:

numpy/pandas arrow postgres sqlite
int16/Int16 int16 SMALLINT INTEGER
int32/Int32 int32 INTEGER INTEGER
int64/Int64 int64 BIGINT INTEGER
float32 float32 REAL REAL
float64 float64 DOUBLE PRECISION REAL
object string TEXT TEXT
bool bool_ BOOLEAN
datetime64[ns] timestamp(us) TIMESTAMP
datetime64[ns,tz] timestamp(us,tz) TIMESTAMPTZ
date32 DATE
month_day_nano_interval INTERVAL
binary BINARY BLOB
decimal128 DECIMAL [1]
list ARRAY [1]
struct

组合类型

[1]

脚注

如果您有兴趣在 DataFrame 的整个生命周期中尽可能保留数据库类型,建议用户利用 read_sql()dtype_backend="pyarrow" 参数

# for roundtripping
with pg_dbapi.connect(uri) as conn:
    df2 = pd.read_sql("pandas_table", conn, dtype_backend="pyarrow") 

这将防止您的数据被转换为传统的 pandas/NumPy 类型系统,该系统通常会以使它们不可能循环的方式转换 SQL 类型。

如果没有可用的 ADBC 驱动程序,to_sql() 将尝试根据数据的 dtype 将您的数据映射到适当的 SQL 数据类型。当您拥有 dtype 为 object 的列时,pandas 将尝试推断数据类型。

您可以通过使用 dtype 参数来覆盖任何列的默认类型,指定所需的任何列的 SQL 类型。该参数需要将列名映射到 SQLAlchemy 类型(或 sqlite3 回退模式的字符串)的字典。例如,指定对字符串列使用 sqlalchemy String 类型而不是默认的 Text 类型:

In [648]: from sqlalchemy.types import String

In [649]: data.to_sql("data_dtype", con=engine, dtype={"Col_1": String})
Out[649]: 3 

注意

由于不同数据库版本对 timedelta 的支持有限,具有类型 timedelta64 的列将以纳秒为单位写入整数值到数据库,并会引发警告。唯一的例外是当使用 ADBC PostgreSQL 驱动程序时,timedelta 将被写入数据库作为 INTERVAL

注意

category dtype 的列将被转换为密集表示,就像使用np.asarray(categorical)一样(例如,对于字符串类别,这将生成一个字符串数组)。因此,将数据库表重新读取时不会生成分类数据。

日期时间数据类型

使用 ADBC 或 SQLAlchemy,to_sql() 能够写入带有时区信息或无时区信息的日期时间数据。然而,最终存储在数据库中的数据取决于所使用的数据库系统对日期时间数据支持的数据类型。

以下表列出了一些常见数据库支持的日期时间数据类型。其他数据库方言可能对日期时间数据有不同的数据类型。

数据库 SQL 日期时间类型 时区支持
SQLite TEXT
MySQL TIMESTAMPDATETIME
PostgreSQL TIMESTAMPTIMESTAMP WITH TIME ZONE

当将带有时区信息的数据写入不支持时区的数据库时,数据将被写入为相对于时区的本地时间的无时区时间戳。

read_sql_table() 也能够读取带有时区信息或无时区信息的日期时间数据。在读取TIMESTAMP WITH TIME ZONE类型时,pandas 将数据转换为 UTC 时间。

插入方法

参数method控制所使用的 SQL 插入子句。可能的值包括:

  • None: 使用标准 SQL INSERT 子句(每行一个)。

  • 'multi': 在单个INSERT子句中传递多个值。它使用一种特殊的 SQL 语法,不是所有后端都支持。这通常对像PrestoRedshift这样的分析数据库提供更好的性能,但如果表包含许多列,则传统 SQL 后端的性能会更差。有关更多信息,请查看 SQLAlchemy 的文档

  • 具有签名(pd_table, conn, keys, data_iter)的可调用函数:这可用于基于特定后端方言功能实现更高性能的插入方法。

使用 PostgreSQL COPY 子句 的可调用函数示例:

# Alternative to_sql() *method* for DBs that support COPY FROM
import csv
from io import StringIO

def psql_insert_copy(table, conn, keys, data_iter):
  """
 Execute SQL statement inserting data

 Parameters
 ----------
 table : pandas.io.sql.SQLTable
 conn : sqlalchemy.engine.Engine or sqlalchemy.engine.Connection
 keys : list of str
 Column names
 data_iter : Iterable that iterates the values to be inserted
 """
    # gets a DBAPI connection that can provide a cursor
    dbapi_conn = conn.connection
    with dbapi_conn.cursor() as cur:
        s_buf = StringIO()
        writer = csv.writer(s_buf)
        writer.writerows(data_iter)
        s_buf.seek(0)

        columns = ', '.join(['"{}"'.format(k) for k in keys])
        if table.schema:
            table_name = '{}.{}'.format(table.schema, table.name)
        else:
            table_name = table.name

        sql = 'COPY {} ({}) FROM STDIN WITH CSV'.format(
            table_name, columns)
        cur.copy_expert(sql=sql, file=s_buf) 
```  #### 插入方法

参数`method`控制所使用的 SQL 插入子句。可能的值包括:

+   `None`: 使用标准 SQL `INSERT` 子句(每行一个)。

+   `'multi'`:在单个`INSERT`子句中传递多个值。它使用一种*特殊*的 SQL 语法,不受所有后端支持。这通常为像*Presto*和*Redshift*这样的分析数据库提供了更好的性能,但是如果表包含许多列,则传统 SQL 后端的性能较差。有关更多信息,请参阅 SQLAlchemy [文档](https://docs.sqlalchemy.org/en/latest/core/dml.html#sqlalchemy.sql.expression.Insert.values.params.*args)。

+   具有签名`(pd_table, conn, keys, data_iter)`的可调用函数:这可以用于基于特定后端方言功能实现更高性能的插入方法。

使用 PostgreSQL [COPY clause](https://www.postgresql.org/docs/current/sql-copy.html) 的可调用示例:

```py
# Alternative to_sql() *method* for DBs that support COPY FROM
import csv
from io import StringIO

def psql_insert_copy(table, conn, keys, data_iter):
  """
 Execute SQL statement inserting data

 Parameters
 ----------
 table : pandas.io.sql.SQLTable
 conn : sqlalchemy.engine.Engine or sqlalchemy.engine.Connection
 keys : list of str
 Column names
 data_iter : Iterable that iterates the values to be inserted
 """
    # gets a DBAPI connection that can provide a cursor
    dbapi_conn = conn.connection
    with dbapi_conn.cursor() as cur:
        s_buf = StringIO()
        writer = csv.writer(s_buf)
        writer.writerows(data_iter)
        s_buf.seek(0)

        columns = ', '.join(['"{}"'.format(k) for k in keys])
        if table.schema:
            table_name = '{}.{}'.format(table.schema, table.name)
        else:
            table_name = table.name

        sql = 'COPY {} ({}) FROM STDIN WITH CSV'.format(
            table_name, columns)
        cur.copy_expert(sql=sql, file=s_buf) 

读取表格

read_sql_table() 将读取给定表名的数据库表,可选地读取列的子集。

注意

要使用read_sql_table(),您必须安装 ADBC 驱动程序或 SQLAlchemy 可选依赖项。

In [650]: pd.read_sql_table("data", engine)
Out[650]: 
 index  id       Date Col_1  Col_2  Col_3
0      0  26 2010-10-18     X  27.50   True
1      1  42 2010-10-19     Y -12.50  False
2      2  63 2010-10-20     Z   5.73   True 

注意

ADBC 驱动程序将数据库类型直接映射回箭头类型。对于其他驱动程序,请注意 pandas 通过查询输出推断列 dtypes,而不是通过在物理数据库模式中查找数据类型。例如,假设 userid 是表中的整数列。那么,直观地,select userid ... 将返回整数值系列,而 select cast(userid as text) ... 将返回对象值(str)系列。因此,如果查询输出为空,则所有结果列将以对象值返回(因为它们是最一般的)。如果您预见到您的查询有时会生成空结果,则可能希望事后明确地进行类型转换以确保 dtype 完整性。

您还可以指定列的名称作为DataFrame索引,并指定要读取的列的子集。

In [651]: pd.read_sql_table("data", engine, index_col="id")
Out[651]: 
 index       Date Col_1  Col_2  Col_3
id 
26      0 2010-10-18     X  27.50   True
42      1 2010-10-19     Y -12.50  False
63      2 2010-10-20     Z   5.73   True

In [652]: pd.read_sql_table("data", engine, columns=["Col_1", "Col_2"])
Out[652]: 
 Col_1  Col_2
0     X  27.50
1     Y -12.50
2     Z   5.73 

您还可以明确强制将列解析为日期:

In [653]: pd.read_sql_table("data", engine, parse_dates=["Date"])
Out[653]: 
 index  id       Date Col_1  Col_2  Col_3
0      0  26 2010-10-18     X  27.50   True
1      1  42 2010-10-19     Y -12.50  False
2      2  63 2010-10-20     Z   5.73   True 

如果需要,您可以明确指定格式字符串,或者传递给pandas.to_datetime()的参数字典:

pd.read_sql_table("data", engine, parse_dates={"Date": "%Y-%m-%d"})
pd.read_sql_table(
    "data",
    engine,
    parse_dates={"Date": {"format": "%Y-%m-%d %H:%M:%S"}},
) 

您可以使用has_table()检查表是否存在

模式支持

通过read_sql_table()to_sql() 函数中的schema关键字支持从不同模式读取和写入。但请注意,这取决于数据库的类型(sqlite 没有模式)。例如:

df.to_sql(name="table", con=engine, schema="other_schema")
pd.read_sql_table("table", engine, schema="other_schema") 

查询

您可以在read_sql_query()函数中使用原始 SQL 进行查询。在这种情况下,您必须使用适合您的数据库的 SQL 变体。在使用 SQLAlchemy 时,您还可以传递与数据库无关的 SQLAlchemy 表达语言构造。

In [654]: pd.read_sql_query("SELECT * FROM data", engine)
Out[654]: 
 index  id                        Date Col_1  Col_2  Col_3
0      0  26  2010-10-18 00:00:00.000000     X  27.50      1
1      1  42  2010-10-19 00:00:00.000000     Y -12.50      0
2      2  63  2010-10-20 00:00:00.000000     Z   5.73      1 

当然,您可以指定一个更“复杂”的查询。

In [655]: pd.read_sql_query("SELECT id, Col_1, Col_2 FROM data WHERE id = 42;", engine)
Out[655]: 
 id Col_1  Col_2
0  42     Y  -12.5 

read_sql_query()函数支持chunksize参数。指定这个参数将返回查询结果的块的迭代器:

In [656]: df = pd.DataFrame(np.random.randn(20, 3), columns=list("abc"))

In [657]: df.to_sql(name="data_chunks", con=engine, index=False)
Out[657]: 20 
In [658]: for chunk in pd.read_sql_query("SELECT * FROM data_chunks", engine, chunksize=5):
 .....:    print(chunk)
 .....: 
 a         b         c
0 -0.395347 -0.822726 -0.363777
1  1.676124 -0.908102 -1.391346
2 -1.094269  0.278380  1.205899
3  1.503443  0.932171 -0.709459
4 -0.645944 -1.351389  0.132023
 a         b         c
0  0.210427  0.192202  0.661949
1  1.690629 -1.046044  0.618697
2 -0.013863  1.314289  1.951611
3 -1.485026  0.304662  1.194757
4 -0.446717  0.528496 -0.657575
 a         b         c
0 -0.876654  0.336252  0.172668
1  0.337684 -0.411202 -0.828394
2 -0.244413  1.094948  0.087183
3  1.125934 -1.480095  1.205944
4 -0.451849  0.452214 -2.208192
 a         b         c
0 -2.061019  0.044184 -0.017118
1  1.248959 -0.675595 -1.908296
2 -0.125934  1.491974  0.648726
3  0.391214  0.438609  1.634248
4  1.208707 -1.535740  1.620399 

引擎连接示例

要连接到 SQLAlchemy,您可以使用create_engine()函数从数据库 URI 创建一个引擎对象。您只需要为每个要连接的数据库创建一次引擎。

from sqlalchemy import create_engine

engine = create_engine("postgresql://scott:tiger@localhost:5432/mydatabase")

engine = create_engine("mysql+mysqldb://scott:tiger@localhost/foo")

engine = create_engine("oracle://scott:[[email protected]](/cdn-cgi/l/email-protection):1521/sidname")

engine = create_engine("mssql+pyodbc://mydsn")

# sqlite://<nohostname>/<path>
# where <path> is relative:
engine = create_engine("sqlite:///foo.db")

# or absolute, starting with a slash:
engine = create_engine("sqlite:////absolute/path/to/foo.db") 

有关更多信息,请参阅 SQLAlchemy 文档中的示例

高级 SQLAlchemy 查询

您可以使用 SQLAlchemy 构造描述您的查询。

使用sqlalchemy.text()以后端中立的方式指定查询参数

In [659]: import sqlalchemy as sa

In [660]: pd.read_sql(
 .....:    sa.text("SELECT * FROM data where Col_1=:col1"), engine, params={"col1": "X"}
 .....: )
 .....: 
Out[660]: 
 index  id                        Date Col_1  Col_2  Col_3
0      0  26  2010-10-18 00:00:00.000000     X   27.5      1 

如果您有数据库的 SQLAlchemy 描述,您可以使用 SQLAlchemy 表达式来表达 where 条件。

In [661]: metadata = sa.MetaData()

In [662]: data_table = sa.Table(
 .....:    "data",
 .....:    metadata,
 .....:    sa.Column("index", sa.Integer),
 .....:    sa.Column("Date", sa.DateTime),
 .....:    sa.Column("Col_1", sa.String),
 .....:    sa.Column("Col_2", sa.Float),
 .....:    sa.Column("Col_3", sa.Boolean),
 .....: )
 .....: 

In [663]: pd.read_sql(sa.select(data_table).where(data_table.c.Col_3 is True), engine)
Out[663]: 
Empty DataFrame
Columns: [index, Date, Col_1, Col_2, Col_3]
Index: [] 

您可以使用sqlalchemy.bindparam()将 SQLAlchemy 表达式与传递给read_sql()的参数结合使用

In [664]: import datetime as dt

In [665]: expr = sa.select(data_table).where(data_table.c.Date > sa.bindparam("date"))

In [666]: pd.read_sql(expr, engine, params={"date": dt.datetime(2010, 10, 18)})
Out[666]: 
 index       Date Col_1  Col_2  Col_3
0      1 2010-10-19     Y -12.50  False
1      2 2010-10-20     Z   5.73   True 

Sqlite 回退

支持使用不使用 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_gbqDataFrame.to_gbq,这将调用pandas-gbq中的相应函数。

完整文档可以在这里找到。

Stata 格式

写入 stata 格式

DataFrame.to_stata()方法将 DataFrame 写入.dta 文件。该文件的格式版本始终为 115(Stata 12)。

In [667]: df = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [668]: df.to_stata("stata.dta") 

Stata数据文件具有有限的数据类型支持;只能在.dta文件中存储 244 个或更少字符的字符串,int8int16int32float32float64。此外,Stata保留某些值来表示缺失数据。导出一个超出特定数据类型在 Stata 中允许范围的非缺失值将重新定义变量为下一个更大的大小。例如,在 Stata 中,int8值限制在-127 和 100 之间,因此具有大于 100 的值的变量将触发转换为int16。浮点数据类型中的nan值存储为基本缺失数据类型(Stata中的.)。

注意

不可能导出整数数据类型的缺失数据值。

Stata writer 优雅地处理其他数据类型,包括 int64booluint8uint16uint32,将其转换为能够表示数据的最小支持类型。例如,类型为 uint8 的数据将被转换为 int8,如果所有值都小于 100(Stata 中非缺失 int8 数据的上限),或者,如果值超出此范围,则将变量转换为 int16

警告

如果 int64 值大于 2**53,从 int64 转换为 float64 可能会导致精度损失。

警告

StataWriterDataFrame.to_stata() 仅支持包含最多 244 个字符的固定宽度字符串,这是由版本 115 dta 文件格式所强加的限制。尝试写入 Stata dta 文件的字符串超过 244 个字符将引发 ValueError。 ### 从 Stata 格式读取

顶级函数 read_stata 将读取一个 dta 文件,并返回一个 DataFrame 或一个 pandas.api.typing.StataReader,后者可用于逐步读取文件。

In [669]: pd.read_stata("stata.dta")
Out[669]: 
 index         A         B
0      0 -0.165614  0.490482
1      1 -0.637829  0.067091
2      2 -0.242577  1.348038
3      3  0.647699 -0.644937
4      4  0.625771  0.918376
5      5  0.401781 -1.488919
6      6 -0.981845 -0.046882
7      7 -0.306796  0.877025
8      8 -0.336606  0.624747
9      9 -1.582600  0.806340 

指定 chunksize 将产生一个 pandas.api.typing.StataReader 实例,该实例可用于一次从文件中读取 chunksize 行。StataReader 对象可用作迭代器。

In [670]: with pd.read_stata("stata.dta", chunksize=3) as reader:
 .....:    for df in reader:
 .....:        print(df.shape)
 .....: 
(3, 3)
(3, 3)
(3, 3)
(1, 3) 

要获得更精细的控制,使用 iterator=True 并在每次调用 read() 时指定 chunksize

In [671]: with pd.read_stata("stata.dta", iterator=True) as reader:
 .....:    chunk1 = reader.read(5)
 .....:    chunk2 = reader.read(5)
 .....: 

当前 index 被检索为列。

参数 convert_categoricals 表示是否应读取值标签并使用它们来创建一个 Categorical 变量。值标签也可以通过函数 value_labels 获取,但需要先调用 read()

参数 convert_missing 表示是否应保留 Stata 中的缺失值表示。如果为 False(默认值),缺失值将表示为 np.nan。如果为 True,缺失值将使用 StataMissingValue 对象表示,包含缺失值的列将具有 object 数据类型。

read_stata()StataReader 支持 .dta 格式 113-115(Stata 10-12)、117(Stata 13)和 118(Stata 14)。

设置 preserve_dtypes=False 将转换为标准的 pandas 数据类型:所有整数类型为 int64,浮点数据为 float64。默认情况下,导入时会保留 Stata 数据类型。

所有 StataReader 对象,无论是由 read_stata() (使用 iterator=Truechunksize 时) 创建的还是手动实例化的,都必须用作上下文管理器(例如 with 语句)。虽然 close() 方法可用,但其使用不受支持。它不是公共 API 的一部分,将在未来的某个时候被删除而不发出警告。

分类数据

Categorical数据可以导出为带有值标签的Stata数据文件。导出的数据包括基础类别代码作为整数数据值和类别作为值标签。Stata没有Categorical的明确等价物,当导出时,关于变量是否有序的信息会丢失。

警告

Stata仅支持字符串值标签,因此在导出数据时将类别转换为字符串。导出具有非字符串类别的Categorical变量会产生警告,并且如果类别的str表示不唯一,则可能导致信息丢失。

标记数据同样可以使用关键字参数convert_categoricals(默认为True)从Stata数据文件中导入为Categorical变量。关键字参数order_categoricals(默认为True)确定导入的Categorical变量是否有序。

注意

在导入分类数据时,Stata数据文件中的变量值不会保留,因为Categorical变量始终使用介于-1n-1之间的整数数据类型,其中n是类别数量。如果需要保留Stata数据文件中的原始值,则可以通过设置convert_categoricals=False来导入原始数据(但不包括变量标签)。由于原始Stata数据值与导入的 Categorical 变量的类别代码之间存在简单映射:缺失值被分配代码-1,最小原始值被分配0,第二小的被分配1,以此类推,直到最大原始值被分配代码n-1

注意

Stata支持部分标记的系列。这些系列对于某些但不是所有数据值都有值标签。导入部分标记的系列将生成一个Categorical,对于具有标签的值,使用字符串类别,对于没有标签的值,使用数值类别。 ### 写入 stata 格式

方法DataFrame.to_stata()将 DataFrame 写入.dta 文件。此文件的格式版本始终为 115(Stata 12)。

In [667]: df = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [668]: df.to_stata("stata.dta") 

Stata数据文件具有有限的数据类型支持;只有长度为 244 或更少字符的字符串、int8int16int32float32float64可以存储在.dta文件中。此外,Stata保留某些值以表示缺失数据。将非缺失值导出为 Stata 特定数据类型的允许范围之外的值会将变量重新类型为更大的大小。例如,在 Stata 中,int8值限制在-127 到 100 之间,因此值超过 100 的变量将转换为int16。浮点数据类型中的nan值存储为基本缺失数据类型(Stata中的.)。

注意

无法为整数数据类型导出缺失数据值。

Stata写入器优雅地处理其他数据类型,包括int64booluint8uint16uint32,通过将其转换为可以表示数据的最小支持类型。例如,类型为uint8的数据将在所有值小于 100(Stata中非缺失int8数据的上限)时转换为int8,或者如果值超出此范围,则将变量转换为int16

警告

int64转换为float64可能会导致精度损失,如果int64值大于 2**53。

警告

StataWriterDataFrame.to_stata()仅支持包含最多 244 个字符的固定宽度字符串,这是 115 版本 dta 文件格式所施加的限制。尝试写入超过 244 个字符的字符串的Stata dta 文件会引发ValueError

从 Stata 格式读取

顶层函数read_stata将读取一个 dta 文件,并返回一个DataFrame或可用于逐步读取文件的pandas.api.typing.StataReader

In [669]: pd.read_stata("stata.dta")
Out[669]: 
 index         A         B
0      0 -0.165614  0.490482
1      1 -0.637829  0.067091
2      2 -0.242577  1.348038
3      3  0.647699 -0.644937
4      4  0.625771  0.918376
5      5  0.401781 -1.488919
6      6 -0.981845 -0.046882
7      7 -0.306796  0.877025
8      8 -0.336606  0.624747
9      9 -1.582600  0.806340 

指定chunksize会产生一个pandas.api.typing.StataReader实例,可以一次从文件中读取chunksize行。StataReader对象可以用作迭代器。

In [670]: with pd.read_stata("stata.dta", chunksize=3) as reader:
 .....:    for df in reader:
 .....:        print(df.shape)
 .....: 
(3, 3)
(3, 3)
(3, 3)
(1, 3) 

要进行更精细的控制,请使用iterator=True,并在每次调用read()时指定chunksize

In [671]: with pd.read_stata("stata.dta", iterator=True) as reader:
 .....:    chunk1 = reader.read(5)
 .....:    chunk2 = reader.read(5)
 .....: 

目前index被检索为一列。

参数convert_categoricals指示是否应读取值标签并使用它们创建Categorical变量。值标签也可以通过函数value_labels检索,该函数在使用前需要调用read()

参数convert_missing指示是否应保留 Stata 中的缺失值表示。如果为False(默认值),则缺失值表示为np.nan。如果为True,则使用StataMissingValue对象表示缺失值,并包含缺失值的列将具有object数据类型。

注意

read_stata()StataReader支持.dta 格式 113-115(Stata 10-12)、117(Stata 13)和 118(Stata 14)。

注意

设置preserve_dtypes=False将升级为标准的 pandas 数据类型:所有整数类型为int64,浮点数据为float64。默认情况下,在导入时保留 Stata 数据类型。

注意

所有StataReader对象,无论是由read_stata()(在使用iterator=Truechunksize时)创建还是手动实例化,都必须用作上下文管理器(例如with语句)。虽然close()方法可用,但其使用不受支持。它不是公共 API 的一部分,并将在未来的版本中被删除而没有警告。

分类数据

Categorical数据可以导出到Stata数据文件作为值标签数据。导出的数据包括基础类别代码作为整数数据值和类别作为值标签。Stata没有Categorical的明确等价物,并且在导出时丢失了关于变量是否有序的信息。

警告

Stata仅支持字符串值标签,因此在导出数据时,对类别调用str。导出具有非字符串类别的Categorical变量会产生警告,并且如果类别的str表示不唯一,可能会导致信息丢失。

类似地,可以使用关键字参数convert_categoricals(默认为True)将带标签的数据从Stata数据文件导入为Categorical变量。关键字参数order_categoricals(默认为True)确定导入的Categorical变量是否有序。

注意

在导入分类数据时,Stata数据文件中变量的值不会被保留,因为Categorical变量始终使用介于-1n-1之间的整数数据类型,其中n是类别数。如果需要Stata数据文件中的原始值,可以通过设置convert_categoricals=False来导入原始数据(但不包括变量标签)。原始值可以与导入的分类数据匹配,因为原始Stata数据值与导入的Categorical变量的类别代码之间存在简单的映射:缺失值分配代码-1,最小的原始值分配0,第二小的分配1,依此类推,直到最大的原始值分配代码n-1

注意

Stata支持部分带标签的系列。这些系列对一些但不是所有数据值有值标签。导入部分带标签的系列将产生一个Categorical,对于已标记的值使用字符串类别,对于没有标签的值使用数值类别。#### 分类数据

Categorical数据可以导出到Stata数据文件作为值标签数据。导出的数据包括基础类别代码作为整数数据值和类别作为值标签。Stata没有Categorical的明确等价物,并且在导出时丢失了关于变量是否有序的信息。

警告

Stata仅支持字符串值标签,因此在导出数据时,对类别调用str。导出具有非字符串类别的Categorical变量会产生警告,并且如果类别的str表示不唯一,可能会导致信息丢失。

类似地,可以使用关键字参数convert_categoricals(默认为True)将带标签的数据从Stata数据文件导入为Categorical变量。关键字参数order_categoricals(默认为True)确定导入的Categorical变量是否有序。

注意

在导入分类数据时,Stata 数据文件中变量的值不会被保留,因为 Categorical 变量始终使用介于 -1n-1 之间的整数数据类型,其中 n 是类别数。如果需要原始 Stata 数据文件中的原始值,可以通过设置 convert_categoricals=False 来导入原始数据(但不包括变量标签)。原始值可以与导入的分类数据匹配,因为原始 Stata 数据值与导入的 Categorical 变量的类别代码之间存在简单的映射:缺失值被分配代码 -1,最小的原始值被分配 0,第二小的被分配 1,依此类推,直到最大的原始值被分配代码 n-1

注意

Stata 支持部分标记的系列。这些系列对一些但不是所有数据值有值标签。导入部分标记的系列将产生一个 Categorical,对于已标记的值使用字符串类别,对于没有标签的值使用数值类别。

SAS 格式

顶层函数 read_sas() 可以读取(但不能写入)SAS XPORT(.xpt)和 SAS7BDAT(.sas7bdat)格式文件。

SAS 文件仅包含两种值类型:ASCII 文本和浮点值(通常为 8 字节,但有时会被截断)。对于 xport 文件,不会自动将类型转换为整数、日期或分类。对于 SAS7BDAT 文件,格式代码可能允许日期变量自动转换为日期。默认情况下会读取整个文件并返回为 DataFrame

指定 chunksize 或使用 iterator=True 来获取读取文件的读取器对象(XportReaderSAS7BDATReader)。读取器对象还具有包含有关文件及其变量的其他信息的属性。

读取一个 SAS7BDAT 文件:

df = pd.read_sas("sas_data.sas7bdat") 

获取一个迭代器,并每次读取 XPORT 文件的 100,000 行:

def do_something(chunk):
    pass

with pd.read_sas("sas_xport.xpt", chunk=100000) as rdr:
    for chunk in rdr:
        do_something(chunk) 

Xport 文件格式的 规范 可从 SAS 网站获取。

SAS7BDAT 格式没有官方文档。

SPSS 格式

顶层函数 read_spss() 可以读取(但不能写入)SPSS SAV(.sav)和 ZSAV(.zsav)格式文件。

SPSS 文件包含列名。默认情况下会读取整个文件,将分类列转换为 pd.Categorical,并返回包含所有列的 DataFrame

指定 usecols 参数以获取列的子集。指定 convert_categoricals=False 以避免将分类列转换为 pd.Categorical

读取一个 SPSS 文件:

df = pd.read_spss("spss_data.sav") 

从 SPSS 文件中提取包含在 usecols 中的列的子集,并避免将分类列转换为 pd.Categorical

df = pd.read_spss(
    "spss_data.sav",
    usecols=["foo", "bar"],
    convert_categoricals=False,
) 

有关 SAV 和 ZSAV 文件格式的更多信息,请参阅此处

其他文件格式

pandas 本身仅支持与其表格数据模型清晰映射的有限一组文件格式的 IO。为了将其他文件格式读取和写入 pandas,我们建议使用来自更广泛社区的这些包。

netCDF

xarray 提供了受到 pandas DataFrame 启发的数据结构,用于处理多维数据集,重点放在 netCDF 文件格式上,并且可以轻松地转换到 pandas 和从 pandas 转换。

netCDF

xarray 提供了受到 pandas DataFrame 启发的数据结构,用于处理多维数据集,重点放在 netCDF 文件格式上,并且可以轻松地转换到 pandas 和从 pandas 转换。

性能考虑

这是对各种 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 
posted @ 2024-06-26 10:35  绝不原创的飞龙  阅读(5)  评论(0编辑  收藏  举报