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

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

格式类型

数据说明

读者

作家

文本

CSV

读取csv

到_csv

文本

固定宽度文本文件

读_fwf

文本

JSON

读取json

to_json

文本

超文本标记语言

读取html

to_html

文本

乳胶

Styler.to_latex

文本

XML

读取XML

to_xml

文本

本地剪贴板

读取剪贴板

到剪贴板

二进制

微软Excel

读取Excel

脱颖而出

二进制

开放文档

读取Excel

二进制

HDF5 格式

读取hdf

to_hdf

二进制

羽毛格式

读羽毛

羽毛

二进制

实木复合地板格式

读镶木地板

镶木地板

二进制

兽人格式

读兽人

to_orc

二进制

斯塔塔

读stata

to_stata

二进制

SAS

读_sas

二进制

统计软件

读spss

二进制

Python Pickle 格式

读取pickle

to_pickle

SQL

SQL

读sql

到_sql

SQL

谷歌大查询

读GBQ

到_GBQ

以下是其中一些 IO 方法的非正式性能比较。

笔记

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

CSV 和文本文件#

读取文本文件(又名平面文件)的主力函数是 read_csv().请参阅食谱了解一些高级策略。

解析选项#

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

基本的

filepath_or_buffer各种

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

sep str,默认为','for read_csv(), \tforread_table()

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

分隔符str,默认None

sep 的替代参数名称。

delim_whitespace布尔值,默认 False

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

列和索引位置和名称#

header int 或 int 列表,默认'infer'

用作列名称的行号以及数据的开头。默认行为是推断列名称:如果未传递名称,则行为header=0与从文件的第一行推断列名称相同,如果显式传递列名称,则行为与 相同 header=None。显式传递header=0以能够替换现有名称。

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

名称类似数组,默认None

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

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

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

笔记

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

默认值None指示 pandas 进行猜测。如果列标题行中的字段数等于数据文件正文中的字段数,则使用默认索引。如果较大,则将第一列用作索引,以便正文中的剩余字段数等于标头中的字段数。

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

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

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

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

元素顺序被忽略,因此与 相同。要从保留元素顺序的 DataFrame 实例化,请使用按顺序排列的列或 按 顺序排列的列。usecols=[0, 1][1, 0]datapd.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'}strobjectna_values

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

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

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

dtype_backends 仍处于实验阶段。

2.0版本中的新增功能。

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

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

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

转换器字典,默认None

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

true_values列表,默认None

考虑的值为True

false_values列表,默认值None

考虑的值为False

跳过初始空间布尔值,默认False

跳过分隔符后的空格。

跳过行列表或整数,默认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 int,默认0

文件底部要跳过的行数(engine='c' 不支持)。

nrows整数,默认None

要读取的文件行数。对于读取大文件很有用。

low_memory布尔值,默认值True

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

memory_map布尔值,默认 False

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

NA 和缺失数据处理#

na_values标量、str、类列表或字典,默认值None

识别为 NA/NaN 的其他字符串。如果 dict 通过,则特定的每列 NA 值。有关默认情况下解释为 NaN 的值的列表,请参阅下面的na 值 const 。

keep_default_na布尔值,默认值True

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

  • 如果指定keep_default_naTrue和, 则附加到用于解析的默认 NaN 值。na_valuesna_values

  • 如果keep_default_na未指定Truena_values,则仅使用默认的 NaN 值进行解析。

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

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

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

na_filter布尔值,默认True

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

详细布尔值,默认False

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

Skip_blank_lines布尔值,默认值True

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

日期时间处理#

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

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

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

  • If -> 将第 1、3 列解析为日期并调用结果“foo”。{'foo': [1, 3]}

笔记

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

infer_datetime_format布尔值,默认False

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

自 version 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替代方案,或读入 as object,然后 to_datetime()根据需要应用。

date_format列的 str 或 dict -> 格式,默认None

如果与 一起使用parse_dates,将根据此格式解析日期。对于更复杂的内容,请阅读 asobjectto_datetime()根据需要应用。

2.0.0 版本中的新增内容。

dayfirst布尔值,默认False

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

cache_dates布尔值,默认 True

如果为 True,则使用唯一的已转换日期的缓存来应用日期时间转换。解析重复的日期字符串时,尤其是具有时区偏移量的字符串时,可能会产生显着的加速。

迭代#

迭代器布尔值,默认False

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

chunksize int,默认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' 之一,其他键值对转发到, , , 或。例如,可以传递以下内容以加快压缩速度并创建可重现的 gzip 存档: .'zip''gzip''bz2''zstd'zipfile.ZipFilegzip.GzipFilebz2.BZ2Filezstandard.ZstdDecompressorcompression={'method': 'gzip', 'compresslevel': 1, 'mtime': 1}

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

str,默认None

千位分隔符。

十进制str,默认'.'

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

float_ precision字符串,默认 None

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

行终止符str(长度1),默认None

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

quotechar str(长度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

escapechar str(长度1),默认None

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

注释str,默认None

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

编码str,默认None

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

方言str 或csv.Dialect实例,默认None

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

错误处理#

on_bad_lines ('错误', '警告', '跳过'), 默认'错误'

指定遇到错误行(包含太多字段的行)时要执行的操作。允许的值为:

  • '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 的更多信息,并 了解object有关pandas 中转换的更多信息。

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

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

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

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

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

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

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

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

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

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

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

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

笔记

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

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

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

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

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

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

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

由于读入数据的混合数据类型,将导致列的某些块mixed_df包含数据类型,而其他块则包含数据类型。需要注意的是,整个列将用 a of标记,用于列具有混合数据类型。intstrdtypeobject

设置dtype_backend="numpy_nullable"将导致每列的数据类型可为空。

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

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

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

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

指定分类数据类型#

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

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

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

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

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

Categorical可以使用 dict 规范将各个列解析为:

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

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

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

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

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

使用 时dtype=CategoricalDtype,超出范围的“意外”值 dtype.categories将被视为缺失值。

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

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

这符合 的行为Categorical.set_categories()

笔记

使用dtype='category',结果类别将始终被解析为字符串(对象数据类型)。如果类别是数字,则可以使用该to_numeric()函数进行转换,或者根据情况使用其他转换器,例如to_datetime().

dtypeaCategoricalDtype具有同构categories(所有数字、所有日期时间等)时,转换会自动完成。

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

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

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

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

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

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

命名和使用列#

处理列名#

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

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

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

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

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

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

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

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

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

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

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

笔记

默认行为是推断列名:如果未传递任何名称,则行为与 相同header=0,并且从文件的第一个非空行推断列名,如果显式传递列名,则行为与 相同 header=None

重复名称解析#

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

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

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

不再有重复数据,因为重复列“X”、...、“X”变为“X”、“X.1”、...、“X.N”。

过滤列 ( usecols) #

usecols参数允许您使用列名称、位置号或可调用函数来选择文件中列的任何子集:

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

如果同时指定了header和,则将相对于 的结尾。例如:skiprowsheaderskiprows

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 数据,这将导致字节字符串在结果中被解码为 un​​icode:

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

笔记

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

笔记

read_csv 有一个 fast_path 用于解析 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,那么您应该将数据作为objectdtype 读入,然后使用to_datetime

解析具有混合时区的 CSV #

pandas 本身无法表示具有混合时区的列或索引。如果您的 CSV 文件包含混合时区的列,则默认结果将是带有字符串的 object-dtype 列,即使是parse_dates.要将混合时区值解析为日期时间列,请以objectdtype 读入,然后使用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”

  • “2011 年 12 月 30 日 00:00:00”

  • “2011 年 12 月 30 日 00:00:00”

  • “2011 年 12 月 30 日 00:00:00”

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

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

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。如果指定字符串列表,则其中的所有值都将被视为缺失值。如果您指定一个数字( a float、 like5.0或 an integerlike 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字符串首先会被解释为 numeric 5,然后解释为 a 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 版本中的新增功能。

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

In [164]: external_list = []

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

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

笔记

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

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

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

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

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

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

您还可以使用该usecols参数来消除出现在某些行中而不是其他行中的无关列数据:

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

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

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

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

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

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

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

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

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

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

方言#

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

假设您有未包含引号的数据:

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

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

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

我们可以使用以下方法来解决这个问题dialect

In [175]: import csv

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

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

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

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

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

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

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

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

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

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

解析器尽一切努力“做正确的事”而不是脆弱的。类型推断是一件非常重要的事情。如果可以将列强制为整数数据类型而不更改内容,则解析器将这样做。任何非数字列都将作为对象 dtype 与其余 pandas 对象一样通过。

引用和转义字符#

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

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

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

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

具有固定宽度列的文件#

read_csv()读取分隔数据时,该read_fwf()函数适用于具有已知且固定列宽的数据文件。函数参数 toread_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

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

默认情况下,将尝试使用文件的前 100 行来read_fwf推断文件。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)
   .....: 

的参数index_col可以read_csv采用列号列表将多个列转换MultiIndex为返回对象的索引:

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

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

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

读取带有# 的MultiIndex

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

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.Sniffer csv 模块的类。为此,您必须指定sep=None.

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

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

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

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

最好用于concat()合并多个文件。请参阅食谱示例。

逐块迭代文件#

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

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

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

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

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

通过指定chunksizeto read_csv,返回值将是类型的可迭代对象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 引擎相比,C 引擎缺乏一些功能。

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

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

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

  • skipfooter

  • sep=Nonedelim_whitespace=False

ParserWarning除非使用 显式选择 python 引擎,否则指定上述任何选项都会生成engine='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 的 URL 均由 fsspec(如果已安装)及其各种文件系统实现(包括 Amazon S3、Google Cloud、SSH、FTP、webHDFS...)处理。其中一些实现需要安装额外的软件包,例如 S3 URL 需要s3fs库:

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

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

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

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

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

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

1.2.0 版本中的新增内容。

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

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

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

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

写出数据#

写入 CSV 格式#

Series和对象DataFrame有一个实例方法to_csv,允许将对象的内容存储为逗号分隔值文件。该函数接受多个参数。仅需要第一个。

  • path_or_buf:要写入的文件或文件对象的字符串路径。如果文件对象必须用以下命令打开newline=''

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

  • na_rep:缺失值的字符串表示形式(默认“”)

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

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

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

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

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

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

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

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

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

  • quotechar:用于引用字段的字符(默认“”)

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

  • escapecharsep: 适当时用于转义的字符quotechar(默认无)

  • chunksize:一次写入的行数

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

写入格式化字符串#

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

  • buf默认 None,例如 StringIO 对象

  • columns默认 None,写入哪些列

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

  • na_repdefault NaN, NA值的表示

  • formatters默认 None,函数的字典(按列),每个函数采用单个参数并返回格式化字符串

  • float_format默认 None,该函数采用单个(浮点)参数并返回格式化字符串;应用于 中的浮动 DataFrame

  • sparsifyDataFrame默认为 True,对于具有分层索引的情况,设置为 False以打印每行的每个 MultiIndex 键。

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

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

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

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

Series对象还有一个to_string方法,但只有buf, na_rep,float_format参数。还有一个length参数,如果设置为True,将另外输出系列的长度。

JSON #

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

编写 JSON #

Series可以DataFrame转换为有效的 JSON 字符串。to_json 与可选参数一起使用:

  • path_or_buf:写入输出的路径名或缓冲区。在这种情况下,可能会None返回 JSON 字符串。

  • orient

    Series
    • 默认是index

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

    DataFrame
    • 默认是columns

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

    JSON 字符串的格式

    split

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

    records

    列表如 [{列 -> 值}, … , {列 -> 值}]

    index

    像 {index -> {column -> value}} 这样的字典

    columns

    像 {column -> {index -> value}} 这样的字典

    values

    只是值数组

    table

    遵守 JSON表架构

  • date_format:字符串,日期转换类型,'epoch' 为时间戳,'iso' 为 ISO8601。

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

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

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

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

  • lines:如果是recordsorient,则将每行的每条记录写为 json。

  • mode:字符串,写入路径时的写入器模式。 'w' 表示写入,'a' 表示追加。默认“w”

注意NaN's, NaT's 和None会被转换为nulldatetime对象,并且会根据date_formatdate_unit参数进行转换。

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

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

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

方向选项#

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

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

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

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

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

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

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

# Not available for Series

面向索引(默认Series)与面向列类似,但索引标签现在是主要的:

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

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

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

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

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

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

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

# Not available for Series

面向拆分序列化为包含值、索引和列的单独条目的 JSON 对象。名称还包括Series

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

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

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

笔记

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

日期处理#

以 ISO 日期格式写入:

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

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

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

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

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

以 ISO 日期格式写入,以微秒为单位:

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

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

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

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

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

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

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

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

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

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

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

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

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

回退行为#

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

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

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

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

    • default_handler如果提供了则调用。

    • dict通过遍历对象的内容将对象转换为 a 。然而,这通常会失败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 对象可以采用多个参数。解析器将尝试解析DataFrameif typis not displayed 或 is None。要显式强制Series解析,请传递typ=series

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

  • typ:要恢复的对象类型(系列或帧),默认“帧”

  • orient

    系列 :
    • 默认是index

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

    数据框
    • 默认是columns

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

    JSON 字符串的格式

    split

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

    records

    列表如 [{列 -> 值}, … , {列 -> 值}]

    index

    像 {index -> {column -> value}} 这样的字典

    columns

    像 {column -> {index -> value}} 这样的字典

    values

    只是值数组

    table

    遵守 JSON表架构

  • dtype:如果为 True,则推断 dtypes,如果将列的字典转换为 dtype,则使用这些,如果False,则根本不推断 dtypes,默认为 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行数的 a。

  • engine:要么是"ujson"内置的 JSON 解析器,要么"pyarrow"分派到 pyarrow 的pyarrow.json.read_json.仅"pyarrow"当以下情况下才可用lines=True

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

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

数据转换#

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

笔记

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

  • 它结束于'_at'

  • 它结束于'_time'

  • 它开始于'timestamp'

  • 这是'modified'

  • 这是'date'

警告

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

  • 索引可以按照与序列化不同的顺序重建,即不保证返回的顺序与序列化之前相同

  • 如果可以安全地完成,float将转换为数据列,例如一列integer1.

  • integerbool 列将在重建时转换为

因此,有时您可能希望通过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 能够读取和写入行分隔的 json 文件,这些文件在使用 Hadoop 或 Spark 的数据处理管道中很常见。

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

In [295]: from io import StringIO

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

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

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

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

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

还可以通过指定 来使用 pyarrow reader 读取行限制的 json engine="pyarrow"

In [301]: from io import BytesIO

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

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

2.0.0 版本中的新增内容。

表架构#

表架构是将表格数据集描述为 JSON 对象的规范。 JSON 包含有关字段名称、类型和其他属性的信息。您可以使用 orienttable构建包含两个字段schema和 的JSON 字符串data

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键,键本身包含列名到类型对的列表,包括Indexor MultiIndex (请参阅下面的类型列表)。如果(多)索引是唯一的,则该schema字段还包含一个字段。primaryKey

第二个字段data包含带有方向的序列化数据records 。索引已包含在内,并且任何日期时间均采用 ISO 8601 格式,符合表架构规范的要求。

表架构规范中描述了支持的类型的完整列表。下表显示了 pandas 类型的映射:

熊猫型

表架构类型

整型64

整数

浮动64

数字

布尔值

布尔值

日期时间64[ns]

约会时间

时间增量64[ns]

期间

绝对的

任何

目的

斯特

关于生成的表架构的一些注释:

  • 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,使用列名的字符串化版本

    • For Index(not MultiIndex),index.name使用,index如果为 None 则回退。

    • 对于MultiIndexmi.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

请注意,作为 an 名称的文字字符串“index”Index 不可往返,也不能以'level_'a 开头的 任何名称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'与 user-defined 一起使用时ExtensionArray,生成的架构将extDtype在相应 fields元素中包含一个附加键。这个额外的键不是标准的,但确实为扩展类型启用了 JSON 往返(例如)。read_json(df.to_json(orient="table"), orient="table")

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

HTML #

读取 HTML 内容#

警告

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

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

笔记

read_html返回一个list对象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>位于 a 中的元素 <thead>用于形成列索引,如果其中包含多行, <thead>则创建 MultiIndex);如果指定,则标题行取自数据减go已解析的标题元素(<th>elements)。

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)

读取 pandasto_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 函数中用于解析 HTML 表的库存在一些版本控制问题read_html

lxml的问题

  • 好处

    • lxml非常快。

    • lxml需要 Cython 正确安装。

  • 缺点

    • lxml对其解析结果不做任何保证, 除非给出严格有效的标记

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

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

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

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

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

  • 好处

    • html5lib比lxml宽松得多,因此以更明智的方式处理现实生活中的标记,而不仅仅是在不通知您的情况下删除元素。

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

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

  • 缺点

    • 使用html5lib的最大缺点是速度慢得像糖蜜。然而,请考虑以下事实:网络上的许多表都不够大,不足以影响解析算法的运行时间。瓶颈更有可能出现在通过网络从 URL 读取原始文本的过程中,即 IO(输入-输出)。对于非常大的表,这可能不正确。

乳胶

1.3.0 版本中的新增功能。

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

写入 LaTeX 文件#

笔记

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

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

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

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

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

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

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

XML #

读取 XML #

1.3.0 版本中的新增功能。

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

笔记

由于没有标准的 XML 结构,其中设计类型可以在很多方面有所不同,因此read_xml最适合扁平化、浅层的版本。如果 XML 文档嵌套很深,请使用该stylesheet功能将 XML 转换为更扁平的版本。

让我们看几个例子。

读取 XML 字符串:

In [372]: from io import StringIO

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

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

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

读取没有选项的 URL:

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

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

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

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

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

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

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

读入“books.xml”的内容作为StringIOor 的实例BytesIO并将其传递给read_xml

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

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

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

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

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

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

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

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

使用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 xmlns:doc="https://example.com">
   .....:   <doc:row>
   .....:     <doc:shape>square</doc:shape>
   .....:     <doc:degrees>360</doc:degrees>
   .....:     <doc:sides>4.0</doc:sides>
   .....:   </doc:row>
   .....:   <doc:row>
   .....:     <doc:shape>circle</doc:shape>
   .....:     <doc:degrees>360</doc:degrees>
   .....:     <doc:sides/>
   .....:   </doc:row>
   .....:   <doc:row>
   .....:     <doc:shape>triangle</doc:shape>
   .....:     <doc:degrees>180</doc:degrees>
   .....:     <doc:sides>3.0</doc:sides>
   .....:   </doc:row>
   .....: </doc:data>"""
   .....: 

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

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

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

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

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

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

但是,如果 XPath 不引用节点名称(例如 default、 )/*,则 namespaces不需要。

笔记

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

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

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

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

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

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

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

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

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

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

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

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

[3 rows x 6 columns]

对于数百兆字节到千兆字节的非常大的 XML 文件,pandas.read_xml() 支持使用lxml 的 iterparseetree 的 iterparse来解析如此大的文件 ,这些方法是内存高效的方法,用于迭代 XML 树并提取特定元素和属性。无需将整个树保存在内存中。

1.5.0 版本中的新增功能。

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

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

[3578765 rows x 3 columns]

编写 XML #

1.3.0 版本中的新增功能。

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

笔记

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

让我们看几个例子。

编写不带选项的 XML:

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

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

使用新的根和行名称编写 XML:

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

编写以属性为中心的 XML:

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

编写元素和属性的组合:

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

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

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

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

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

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

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

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

编写带有命名空间前缀的 XML:

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

编写一个没有声明或漂亮打印的 XML:

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

编写 XML 并使用样式表进行转换:

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

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

XML 最后注释#

  • 所有 XML 文档都遵循W3C 规范etree和解析器都lxml 无法解析任何格式不正确或遵循 XML 语法规则的标记文档。请注意 HTML 不是 XML 文档,除非它遵循 XHTML 规范。然而,其他流行的标记类型(包括 KML、XAML、RSS、MusicML、MathML)都是兼容的XML 架构

  • 由于上述原因,如果您的应用程序在 pandas 操作之前构建 XML,请使用适当的 DOM 库(例如etree和 )lxml来构建必要的文档,而不是通过字符串连接或正则表达式调整。永远记住 XML 是一种带有标记规则的特殊文本文件。

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

  • 由于 XSLT 是一种编程语言,因此请谨慎使用它,因为此类脚本可能会给您的环境带来安全风险,并且可能运行大型或无限的递归操作。在完全运行之前,始终在小片段上测试脚本。

  • etree解析器支持read_xmlto_xml复杂 XPath 和任何 XSLT 之外的所有功能。尽管功能有限,但 etree仍然是一个可靠且功能强大的解析器和树构建器。对于较大的文件,其性能可能会有所lxml下降,但对于中小型文件,其性能相对不明显。

Excel 文件#

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

笔记

当 时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)文件。

  • 对于炉甘石引擎,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属性将生成文件中工作表名称的列表。

an 的主要用例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_name0,表示读取第一张

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

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

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

  • 传递 aNone返回所有可用工作表的字典。

# 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_excelsheet_name通过设置为工作表名称列表、工作表位置列表或None读取所有工作表,可以读取多个工作表。工作表可以通过工作表索引或工作表名称分别使用整数或字符串来指定。

正在读MultiIndex#

read_excelMultiIndex可以通过将列列表传递给 来读取索引,index_col 也可以MultiIndex通过将行列表传递给 来读取列header。如果indexcolumns具有序列化的级别名称,也将通过指定构成级别的行/列来读入这些名称。

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

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

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

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

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

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

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

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

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

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

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

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_excelfor进行往返merged_cells=True。为了避免前向填充缺失值,请使用set_indexafter read the data 而不是 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 文件时,类似日期时间的值通常会自动转换为适当的数据类型。但是,如果您有一列 看起来像日期的字符串(但实际上并未格式化为 Excel 中的日期),则可以使用关键字parse_dates将这些字符串解析为日期时间:

pd.read_excel("path_to_file.xls", "Sheet1", parse_dates=["date_strings"])

细胞转换器#

可以通过该converters 选项转换 Excel 单元格的内容。例如,要将列转换为布尔值:

pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyBools": bool})

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

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


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

D类型规格#

作为转换器的替代方案,可以使用关键字指定整个列的类型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放置在第二行而不是第一行。您可以通过将merge_cells选项设置to_excel()为将其放置在第一行 False

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

为了DataFrames在单个 Excel 文件中写入单独的工作表,可以将ExcelWriter.

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

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

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

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

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

将 Excel 文件写入内存#

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

from io import BytesIO

bio = BytesIO()

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

# Save the workbook
writer.save()

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

笔记

engine是可选的,但推荐。设置引擎决定了生成的工作簿的版本。设置engine='xlrd'后将生成 Excel 2003 格式的工作簿 (xls)。使用'openpyxl''xlsxwriter'将生成 Excel 2007 格式的工作簿 (xlsx)。如果省略,将生成 Excel 2007 格式的工作簿。

Excel 编写引擎#

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

  1. 关键字engine参数

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

默认情况下,pandas 使用XlsxWriter for .xlsxopenpyxl for .xlsm。如果您安装了多个引擎,您可以通过设置配置选项 io.excel.xlsx.writer和 来设置默认引擎io.excel.xls.writer。 如果Xlsxwriter不可用,pandas 将依靠openpyxl来获取文件。.xlsx

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

  • openpyxl:需要2.4或更高版本

  • xlsxwriter

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

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

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

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

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

样式和格式#

可以使用DataFrameto_excel方法中的以下参数修改从 pandas 创建的 Excel 工作表的外观和风格。

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

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

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

OpenDocument 电子表格#

Excel 文件的 io 方法还支持使用odfpy模块读取和写入 OpenDocument 电子表格。读写 OpenDocument 电子表格的语义和功能使用 engine='odf'.需要安装可选依赖项“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()方法还可以使用该模块读取二进制 Excel 文件pyxlsb。读取二进制 Excel文件的语义和功能大部分与使用 engine='pyxlsb'.pyxlsb不识别文件中的日期时间类型,而是返回浮点数( 如果需要识别日期时间类型,可以使用炉甘石)。

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

笔记

目前pandas仅支持读取二进制Excel文件。写入没有实现。

炉甘石(Excel 和 ODS 文件)#

该方法可以使用该模块read_excel()读取 Excel 文件 ( .xlsx.xlsm.xls.xlsb) 和 OpenDocument 电子表格 ( ) 。该模块是 Rust 库calamine的绑定 ,在大多数情况下比其他引擎更快。需要安装可选依赖项“python-calamine”。.odspython-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然后通过调用将数据直接导入到a :

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

to_clipboard方法可用于将 a 的内容写入DataFrame剪贴板。接下来,您可以将剪贴板内容粘贴到其他应用程序中(在许多操作系统上为 CTRL-V)。这里我们演示将 a 写入 DataFrame剪贴板并读回。

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

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

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

笔记

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

酸洗#

所有 pandas 对象都配备了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")

read_pickle命名空间中的函数可pandas用于从文件加载任何 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

警告

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

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

警告

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

压缩的pickle文件#

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

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

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

默认是“推断”:

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

消息包#

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

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

HDF5 (PyTables) #

HDFStore是一个类似字典的对象,它使用优秀的PyTables库使用高性能 HDF5 格式读取和写入 pandas 。请参阅食谱 了解一些高级策略

警告

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

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

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

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

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

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

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

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

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

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

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

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

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

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

删除key指定的对象:

# 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

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

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 #

HDFStoreread_hdf支持用于读取和写入的顶级 API to_hdf,类似于 howread_csvto_csvwork。

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。当使用putorto_hdf或 byformat='fixed'或 时,默认指定此格式format='f'

警告

如果您尝试使用 a 进行检索,格式fixed将引发 a :TypeErrorwhere

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 格式。从概念上讲,a 的table形状非常像 DataFrame,具有行和列。 Atable可以附加到同一会话或其他会话中。另外还支持删除和查询类型的操作。该格式由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'

笔记

table您还可以通过将format='table'or传递format='t'给操作来创建put

分层键#

商店的密钥可以指定为字符串。这些可以采用分层路径名格式(例如foo/bar/bah),这将生成子存储的层次结构(或Groups用 PyTables 的说法)。键可以在没有前导“/”的情况下指定,并且始终是 绝对的(例如“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.

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

In [500]: df_mixed = pd.DataFrame(
   .....:     {
   .....:         "A": np.random.randn(8),
   .....:         "B": np.random.randn(8),
   .....:         "C": np.array(np.random.randn(8), dtype="float32"),
   .....:         "string": "string",
   .....:         "int": 1,
   .....:         "bool": True,
   .....:         "datetime64": pd.Timestamp("20010102"),
   .....:     },
   .....:     index=list(range(8)),
   .....: )
   .....: 

In [501]: df_mixed.loc[df_mixed.index[3:5], ["A", "B", "string", "datetime64"]] = np.nan

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

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

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

[8 rows x 7 columns]

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

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

存储多索引数据帧#

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

In [507]: index = pd.MultiIndex(
   .....:    levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
   .....:    codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
   .....:    names=["foo", "bar"],
   .....: )
   .....: 

In [508]: df_mi = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])

In [509]: df_mi
Out[509]: 
                  A         B         C
foo bar                                
foo one   -1.303456 -0.642994 -0.649456
    two    1.012694  0.414147  1.950460
    three  1.094544 -0.802899 -0.583343
bar one    0.410395  0.618321  0.560398
    two    1.434027 -0.033270  0.343197
baz two   -1.646063 -0.695847 -0.429156
    three -0.244688 -1.428229 -0.138691
qux one    1.866184 -1.446617  0.036660
    two   -1.660522  0.929553 -1.298649
    three  3.565769  0.682402  1.041927

In [510]: store.append("df_mi", df_mi)

In [511]: store.select("df_mi")
Out[511]: 
                  A         B         C
foo bar                                
foo one   -1.303456 -0.642994 -0.649456
    two    1.012694  0.414147  1.950460
    three  1.094544 -0.802899 -0.583343
bar one    0.410395  0.618321  0.560398
    two    1.434027 -0.033270  0.343197
baz two   -1.646063 -0.695847 -0.429156
    three -0.244688 -1.428229 -0.138691
qux one    1.866184 -1.446617  0.036660
    two   -1.660522  0.929553 -1.298649
    three  3.565769  0.682402  1.041927

# the levels are automatically included as data columns
In [512]: store.select("df_mi", "foo=bar")
Out[512]: 
                A         B         C
foo bar                              
bar one  0.410395  0.618321  0.560398
    two  1.434027 -0.033270  0.343197

笔记

index关键字是保留关键字,不能用作级别名称。

查询#

查询表#

select并且delete操作具有可选标准,可以指定该标准来仅选择/删除数据的子集。这允许拥有一个非常大的磁盘表并仅检索一部分数据。

查询是使用Term底层的类作为布尔表达式来指定的。

  • indexcolumns受支持的索引器DataFrames

  • 如果data_columns指定,它们可以用作附加索引器。

  • MultiIndex 中的级别名称,如果未提供,则使用默认名称level_0, , ... 。level_1

有效的比较运算符是:

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

有效的布尔表达式与:

  • |: 或者

  • &: 和

  • (): 用于分组

这些规则类似于 pandas 中使用布尔表达式进行索引的方式。

笔记

  • =将自动扩展为比较运算符==

  • ~是 not 运算符,但只能在非常有限的情况下使用

  • 如果传递了表达式列表/元组,它们将通过以下方式组合&

以下是有效的表达式:

  • 'index >= date'

  • "columns = ['A', 'D']"

  • "columns in ['A', 'D']"

  • 'columns = A'

  • 'columns == A'

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

  • 'index > df.index[3] & string = "bar"'

  • '(index > df.index[3] & index <= df.index[6]) | string = "bar"'

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

  • "major_axis>=20130101"

它们indexers位于子表达式的左侧:

columns, major_axis,ts

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

  • 将被评估的函数,例如Timestamp('2012-02-01')

  • 字符串,例如"bar"

  • 类似日期的,例如20130101, 或"20130101"

  • 列表,例如"['A', 'B']"

  • 在本地名称空间中定义的变量,例如date

笔记

不建议通过将字符串插入到查询表达式中来将字符串传递给查询。只需将感兴趣的字符串分配给变量并在表达式中使用该变量即可。例如,这样做

string = "HolyMoly'"
store.select("df", "index == string")

而不是这个

string = "HolyMoly'"
store.select('df', f'index == {string}')

后者不起作用并且会引发一个SyntaxError。请注意,变量中有一个单引号,后跟一个双引号string

如果必须插值,请使用'%r'格式说明符

store.select("df", "index == %r" % string)

这将引用string.

这里有些例子:

In [513]: dfq = pd.DataFrame(
   .....:     np.random.randn(10, 4),
   .....:     columns=list("ABCD"),
   .....:     index=pd.date_range("20130101", periods=10),
   .....: )
   .....: 

In [514]: store.append("dfq", dfq, format="table", data_columns=True)

使用布尔表达式和内联函数求值。

In [515]: store.select("dfq", "index>pd.Timestamp('20130104') & columns=['A', 'B']")
Out[515]: 
                   A         B
2013-01-05 -0.830545 -0.457071
2013-01-06  0.431186  1.049421
2013-01-07  0.617509 -0.811230
2013-01-08  0.947422 -0.671233
2013-01-09 -0.183798 -1.211230
2013-01-10  0.361428  0.887304

使用内联列引用。

In [516]: store.select("dfq", where="A>0 or C>0")
Out[516]: 
                   A         B         C         D
2013-01-02  0.658179  0.362814 -0.917897  0.010165
2013-01-03  0.905122  1.848731 -1.184241  0.932053
2013-01-05 -0.830545 -0.457071  1.565581  1.148032
2013-01-06  0.431186  1.049421  0.383309  0.595013
2013-01-07  0.617509 -0.811230 -2.088563 -1.393500
2013-01-08  0.947422 -0.671233 -0.847097 -1.187785
2013-01-10  0.361428  0.887304  0.266457 -0.399641

可以提供关键字columns来选择要返回的列列表,这相当于传递一个 'columns=list_of_columns_to_filter'

In [517]: store.select("df", "columns=['A', 'B']")
Out[517]: 
                   A         B
2000-01-01  0.858644 -0.851236
2000-01-02 -0.080372 -1.268121
2000-01-03  0.816983  1.965656
2000-01-04  0.712795 -0.062433
2000-01-05 -0.298721 -1.988045
2000-01-06  1.103675  1.382242
2000-01-07 -0.729161 -0.142928
2000-01-08 -1.005977  0.465222

start并且stop可以指定参数来限制总搜索空间。这些是表中的总行数。

笔记

selectValueError如果查询表达式具有未知的变量引用,则会引发 a 。通常这意味着您尝试选择不是data_column的列。

selectSyntaxError如果查询表达式无效,将引发 a 。

查询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可以通过使用级别名称来实现从 a 中进行选择。

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_nnMultiIndex

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

索引#

create_table_index 您可以在数据已存在于表中之后(在 andappend/put 操作之后)为表创建/修改索引。强烈鼓励创建表索引。当您使用select索引维度作为 时,这将大大加快您的查询速度where

笔记

索引是在可索引和您指定的任何数据列上自动创建的。可以通过传递index=False到 来关闭此行为 append

# we have automagically already created an index (in the first section)
In [531]: i = store.root.df.table.cols.index.index

In [532]: i.optlevel, i.kind
Out[532]: (6, 'medium')

# change an index by passing new parameters
In [533]: store.create_table_index("df", optlevel=9, kind="full")

In [534]: i = store.root.df.table.cols.index.index

In [535]: i.optlevel, i.kind
Out[535]: (9, 'full')

通常,当将大量数据附加到存储时,关闭每个附加的索引创建,然后在最后重新创建是很有用的。

In [536]: df_1 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [537]: df_2 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [538]: st = pd.HDFStore("appends.h5", mode="w")

In [539]: st.append("df", df_1, data_columns=["B"], index=False)

In [540]: st.append("df", df_2, data_columns=["B"], index=False)

In [541]: st.get_storer("df").table
Out[541]: 
/df/table (Table(20,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
  "B": Float64Col(shape=(), dflt=0.0, pos=2)}
  byteorder := 'little'
  chunkshape := (2730,)

然后在完成追加后创建索引。

In [542]: st.create_table_index("df", columns=["B"], optlevel=9, kind="full")

In [543]: st.get_storer("df").table
Out[543]: 
/df/table (Table(20,)) ''
  description := {
  "index": Int64Col(shape=(), dflt=0, pos=0),
  "values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
  "B": Float64Col(shape=(), dflt=0.0, pos=2)}
  byteorder := 'little'
  chunkshape := (2730,)
  autoindex := True
  colindexes := {
    "B": Index(9, fullshuffle, zlib(1)).is_csi=True}

In [544]: st.close()

请参阅此处,了解如何在现有存储上创建完全排序索引 (CSI)。

通过数据列查询#

您可以指定(并索引)某些您希望能够执行查询的列(除了indexable您始终可以查询的列)。例如,假设您想要在磁盘上执行此常见操作,并仅返回与此查询匹配的帧。您可以指定强制所有列为.data_columns = Truedata_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=True或传递chunksize=number_in_a_chunkselectselect_as_multiple来返回结果的迭代器。默认值是在一个块中返回 50,000 行。

In [557]: for df in store.select("df", chunksize=3):
   .....:     print(df)
   .....: 
                   A         B         C
2000-01-01  0.858644 -0.851236  1.058006
2000-01-02 -0.080372 -1.268121  1.561967
2000-01-03  0.816983  1.965656 -1.169408
                   A         B         C
2000-01-04  0.712795 -0.062433  0.736755
2000-01-05 -0.298721 -1.988045  1.475308
2000-01-06  1.103675  1.382242 -0.650762
                   A         B         C
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977  0.465222 -0.094517

笔记

您还可以使用read_hdf将打开存储的迭代器,然后在完成迭代时自动关闭存储。

for df in pd.read_hdf("store.h5", "df", chunksize=3):
    print(df)

请注意,chunksize 关键字适用于行。因此,如果您正在执行查询,那么 chunksize 将细分表中的总行和应用的查询,并返回可能大小不等的块上的迭代器。

以下是生成查询并使用它来创建相同大小的返回块的方法。

In [558]: dfeq = pd.DataFrame({"number": np.arange(1, 11)})

In [559]: dfeq
Out[559]: 
   number
0       1
1       2
2       3
3       4
4       5
5       6
6       7
7       8
8       9
9      10

In [560]: store.append("dfeq", dfeq, data_columns=["number"])

In [561]: def chunks(l, n):
   .....:     return [l[i: i + n] for i in range(0, len(l), n)]
   .....: 

In [562]: evens = [2, 4, 6, 8, 10]

In [563]: coordinates = store.select_as_coordinates("dfeq", "number=evens")

In [564]: for c in chunks(coordinates, 2):
   .....:     print(store.select("dfeq", where=c))
   .....: 
   number
1       2
3       4
   number
5       6
7       8
   number
9      10

高级查询#

选择单列#

要检索单个可索引列或数据列,请使用方法select_column。例如,这将使您能够非常快速地获取索引。它们返回Series结果,按行号索引。这些当前不接受where选择器。

In [565]: store.select_column("df_dc", "index")
Out[565]: 
0   2000-01-01
1   2000-01-02
2   2000-01-03
3   2000-01-04
4   2000-01-05
5   2000-01-06
6   2000-01-07
7   2000-01-08
Name: index, dtype: datetime64[ns]

In [566]: store.select_column("df_dc", "string")
Out[566]: 
0    foo
1    foo
2    foo
3    foo
4    NaN
5    NaN
6    foo
7    bar
Name: string, dtype: object
选择坐标#

有时您想要获取查询的坐标(也称为索引位置)。这将返回 Index结果位置之一。这些坐标也可以传递给后续 where操作。

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

In [568]: store.append("df_coord", df_coord)

In [569]: c = store.select_as_coordinates("df_coord", "index > 20020101")

In [570]: c
Out[570]: 
Index([732, 733, 734, 735, 736, 737, 738, 739, 740, 741,
       ...
       990, 991, 992, 993, 994, 995, 996, 997, 998, 999],
      dtype='int64', length=268)

In [571]: store.select("df_coord", where=c)
Out[571]: 
                   0         1
2002-01-02  0.007717  1.168386
2002-01-03  0.759328 -0.638934
2002-01-04 -1.154018 -0.324071
2002-01-05 -0.804551 -1.280593
2002-01-06 -0.047208  1.260503
...              ...       ...
2002-09-22 -1.139583  0.344316
2002-09-23 -0.760643 -1.306704
2002-09-24  0.059018  1.775482
2002-09-25  1.242255 -0.055457
2002-09-26  0.410317  2.194489

[268 rows x 2 columns]
使用 where 掩码进行选择#

有时,您的查询可能涉及创建要选择的行列表。通常这maskindex索引操作的结果。此示例选择 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_multiple可以 select_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_axisID 在 中minor_axis。然后数据将像这样交错:

  • 日期_1
    • id_1

    • id_2

    • id_n

  • 日期_2
    • id_1

    • id_n

应该清楚的是,删除操作将major_axis相当快,因为​​删除一个块,然后移动后续数据。另一方面,删除操作将minor_axis非常昂贵。在这种情况下,使用where选择除缺失数据之外的所有数据的a 来重写表几乎肯定会更快。

警告

请注意,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.h5copy

注意事项#

警告

HDFStore对于写入来说不是线程安全的。底层 PyTables仅支持并发读取(通过线程或进程)。如果需要同时进行读取和写入,则需要将这些操作序列化在单个进程的单个线程中。否则您将损坏您的数据。请参阅 ( GH 2397 ) 了解更多信息。

  • 如果您使用锁来管理多个进程之间的写访问,您可能需要fsync()在释放写锁之前使用。为了方便起见,您可以使用store.flush(fsync=True)它来为您执行此操作。

  • 一旦table创建了列(DataFrame)就固定了;只能附加完全相同的列

  • 请注意,时区(例如,pytz.timezone('US/Eastern'))在时区版本之间不一定相同。因此,如果使用一个版本的时区库将数据本地化到 HDFStore 中的特定时区,并且使用另一版本更新该数据,则数据将转换为 UTC,因为这些时区不被视为相等。使用相同版本的时区库或使用tz_convert更新的时区定义。

警告

PyTablesNaturalNameWarning如果列名不能用作属性选择器, 将显示 a 。自然标识符仅包含字母、数字和下划线,并且不能以数字开头。其他标识符不能在子句中使用where,通常是一个坏主意。

数据类型#

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

类型

代表缺失值

漂浮的 :float64, float32, float16

np.nan

整数 :int64, int32, int8, uint64,uint32, uint8

布尔值

datetime64[ns]

NaT

timedelta64[ns]

NaT

分类:请参阅下面的部分

目的 :strings

np.nan

unicode不支持列,并且会失败

分类数据#

您可以将包含category数据类型的数据写入HDFStore.查询的工作方式与对象数组相同。然而,categorydtyped 数据以更有效的方式存储。

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

字符串列#

最小项目大小

的底层实现HDFStore对字符串列使用固定的列宽(itemsize)。字符串列 itemsize 计算为传递到第一个追加中的HDFStore,的数据(针对该列)的最大长度。后续追加可能会为列引入一个大于该列可以容纳的字符串,将引发异常(否则您可能会静默截断这些列,从而导致信息丢失)。将来我们可能会放宽这一点并允许用户指定的截断发生。

将第一个表创建传递min_itemsize给先验指定特定字符串列的最小长度。 min_itemsize可以是一个整数,也可以是一个将列名映射到整数的字典。您可以values作为键传递,以允许所有可索引data_columns具有此 min_itemsize。

传递min_itemsize字典将导致所有传递的列自动创建为data_columns

笔记

如果您没有传递 any 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}

南代表

字符串列将np.nan使用字符串表示形式序列化 a (缺失值)nan_rep。默认为字符串值nan。您可能会无意中将实际nan值变成缺失值。

In [600]: dfss = pd.DataFrame({"A": ["foo", "bar", "nan"]})

In [601]: dfss
Out[601]: 
     A
0  foo
1  bar
2  nan

In [602]: store.append("dfss", dfss)

In [603]: store.select("dfss")
Out[603]: 
     A
0  foo
1  bar
2  NaN

# here you need to specify a different nan rep
In [604]: store.append("dfss2", dfss, nan_rep="_nan_")

In [605]: store.select("dfss2")
Out[605]: 
     A
0  foo
1  bar
2  nan

表现

  • tables与商店相比,格式会带来写入性能损失 fixed。好处是能够追加/删除和查询(可能是大量数据)。与常规存储相比,写入时间通常更长。查询时间可能非常快,尤其是在索引轴上。

  • 您可以传递chunksize=<int>append,指定写入块大小(默认为 50000)。这将显着降低写入时的内存使用量。

  • 您可以传递expectedrows=<int>给第一个, 以设置预期的append总行数。PyTables这将优化读/写性能。

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

  • PerformanceWarning如果您尝试存储将由 PyTables 腌制的类型(而不是存储为特有类型),则会引发A 。请参阅 此处 了解更多信息和一些解决方案。

羽毛

Feather 为数据帧提供二进制柱状序列化。它旨在提高数据帧的读写效率,并使跨数据分析语言共享数据变得容易。

Feather 旨在忠实地序列化和反序列化 DataFrame,支持所有 pandas 数据类型,包括扩展数据类型,例如带有 tz 的分类和日期时间。

几个注意事项:

  • 该格式不会为 写入Index, 或 ,如果提供非默认值,则会引发错误。您可以存储索引或忽略它。MultiIndexDataFrame.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

写入羽毛文件。

In [609]: df.to_feather("example.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

实木复合地板#

Apache Parquet为数据帧提供分区二进制列序列化。它旨在提高数据帧的读写效率,并使跨数据分析语言共享数据变得容易。 Parquet 可以使用多种压缩技术来尽可能缩小文件大小,同时仍保持良好的读取性能。

Parquet 旨在忠实地序列化和反序列化DataFrame,支持所有 pandas 数据类型,包括扩展数据类型,例如带有 tz 的日期时间。

几个警告。

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

  • 引擎pyarrow始终将索引写入输出,但fastparquet仅写入非默认索引。这个额外的列可能会给那些没有预料到的非 pandas 消费者带来问题。index无论底层引擎如何,您都可以使用参数强制包含或省略索引。

  • 索引级别名称(如果指定)必须是字符串。

  • pyarrow引擎中,非字符串类型的分类数据类型可以序列化为镶木地板,但将反序列化为其原始数据类型。

  • 引擎pyarrow保留ordered字符串类型的分类数据类型的标志。fastparquet不保留ordered标志。

  • 不支持的类型包括Interval实际的 Python 对象类型。这些将在尝试序列化时引发有用的错误消息。Periodpyarrow >= 0.16.0 支持类型。

  • 引擎pyarrow保留扩展数据类型,例如可为空的整数和字符串数据类型(需要 pyarrow >= 0.16.0,并且需要扩展类型来实现所需的协议,请参阅扩展类型文档)。

您可以指定 anengine来指导序列化。这可以是pyarrow、 或fastparquet、 或之一auto。如果未指定引擎,则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

写入镶木地板文件。

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

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

从镶木地板文件中读取。

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

处理索引#

将 a 序列化为DataFrameparquet 可能会将隐式索引包含为输出文件中的一列或多列。因此,这段代码:

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

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

如果您用于序列化,则会创建一个包含列的 parquet 文件pyarrowab__index_level_0__。如果您使用fastparquet,索引可能会也可能不会 写入文件。

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

如果您想在写入时省略数据帧的索引,请传递index=Falseto_parquet()

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

这将创建一个仅包含两个预期列的 parquet 文件,a并且b.如果您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
    └── ...

兽人#

parquet格式类似,ORC 格式是数据帧的二进制柱状序列化。它旨在提高读取数据帧的效率。 pandas 为 ORC 格式提供了读取器和写入器,read_orc()并且to_orc().这需要pyarrow库。

警告

  • 由于 pyarrow 出现一些问题,强烈建议使用conda 安装 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 驱动程序实现状态 文档。

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

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

另请参阅一些食谱示例以了解一些高级策略。

主要功能是:

read_sql_table(表名, con[, 架构, ...])

将 SQL 数据库表读入 DataFrame。

read_sql_query(sql, con[, index_col, ...])

将 SQL 查询读入 DataFrame。

read_sql(sql, con[, index_col, ...])

将 SQL 查询或数据库表读入 DataFrame。

DataFrame.to_sql(名称、con、*[、模式、...])

将存储在 DataFrame 中的记录写入 SQL 数据库。

笔记

该函数是and 的read_sql()便捷包装器 (并且为了向后兼容),并将根据提供的输入(数据库表名称或 sql 查询)委托给特定函数。如果表名有特殊字符,则不需要用引号引起来。read_sql_table()read_sql_query()

在以下示例中,我们使用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 创建引擎对象。您只需为要连接的每个数据库创建一次引擎。有关 URI 格式的更多信息create_engine(),请参阅下面的示例和 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)

警告

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

编写数据帧#

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

ID

日期

列_1

列_2

列_3

26

2012-10-18

X

25.7

真的

42

2012-10-19

-12.4

错误的

63

2012-10-20

Z

5.73

真的

In [641]: import datetime

In [642]: c = ["id", "Date", "Col_1", "Col_2", "Col_3"]

In [643]: d = [
   .....:     (26, datetime.datetime(2010, 10, 18), "X", 27.5, True),
   .....:     (42, datetime.datetime(2010, 10, 19), "Y", -12.5, False),
   .....:     (63, datetime.datetime(2010, 10, 20), "Z", 5.73, True),
   .....: ]
   .....: 

In [644]: data = pd.DataFrame(d, columns=c)

In [645]: data
Out[645]: 
   id       Date Col_1  Col_2  Col_3
0  26 2010-10-18     X  27.50   True
1  42 2010-10-19     Y -12.50  False
2  63 2010-10-20     Z   5.73   True

In [646]: data.to_sql("data", con=engine)
Out[646]: 3

对于某些数据库,写入大型 DataFrame 可能会由于超出数据包大小限制而导致错误。可以通过chunksize调用时设置参数来避免这种情况 to_sql。例如,以下内容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/熊猫

postgres

sqlite

整型16/整型16

整型16

小智

整数

int32/Int32

整型32

整数

整数

int64/Int64

整型64

BIGINT

整数

浮动32

浮动32

真实的

真实的

浮动64

浮动64

双精度

真实的

目的

细绳

文本

文本

布尔值

bool_

布尔值

日期时间64[ns]

时间戳(我们)

时间戳

日期时间64[ns,tz]

时间戳(我们,tz)

时间戳

日期32

日期

月日纳米间隔

间隔

二进制

二进制

BLOB

十进制128

小数[ 1 ]

列表

阵列[ 1 ]

结构体

复合型

[ 1 ]

脚注

如果您有兴趣在 DataFrame 的整个生命周期中尽可能保留数据库类型,则鼓励用户利用dtype_backend="pyarrow"以下论点:read_sql()

# for roundtripping
with pg_dbapi.connect(uri) as conn:
    df2 = pd.read_sql("pandas_table", conn, dtype_backend="pyarrow")

这将阻止您的数据转换为传统的 pandas/NumPy 类型系统,该系统通常以无法往返的方式转换 SQL 类型。

如果 ADBC 驱动程序不可用,to_sql() 将尝试根据数据的数据类型将数据映射到适当的 SQL 数据类型。当您有 dtype 列时object,pandas 将尝试推断数据类型。

您始终可以通过使用参数指定任何列的所需 SQL 类型来覆盖默认类型dtype。此参数需要一个将列名称映射到 SQLAlchemy 类型(或 sqlite3 后备模式的字符串)的字典。例如,指定使用 sqlalchemyString类型而不是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 驱动程序时,在这种情况下,时间增量将作为INTERVAL

笔记

dtype的列category将被转换为密集表示,就像您所得到的那样np.asarray(categorical)(例如,对于字符串类别,这给出了一个字符串数组)。因此,读回数据库表不会生成分类。

日期时间数据类型#

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

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

数据库

SQL 日期时间类型

时区支持

SQLite

TEXT

MySQL

TIMESTAMP或者DATETIME

PostgreSQL

TIMESTAMP或者TIMESTAMP WITH TIME ZONE

是的

将时区感知数据写入不支持时区的数据库时,数据将被写入时区原生时间戳,该时间戳是相对于时区的本地时间。

read_sql_table()还能够读取时区感知或天真的日期时间数据。当读取类型时,pandas 会将数据转换为 UTC。TIMESTAMP WITH TIME ZONE

插入方法#

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

  • None:使用标准 SQLINSERT子句(每行一个)。

  • 'multi':在单个INSERT子句中传递多个值。它使用并非所有后端都支持的特殊SQL 语法。这通常为PrestoRedshift等分析数据库提供更好的性能,但如果表包含许多列,则传统 SQL 后端的性能会较差。有关更多信息,请查看 SQLAlchemy文档

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

使用 PostgreSQL COPY 子句的可调用示例:

# Alternative to_sql() *method* for DBs that support COPY FROM
import csv
from io import StringIO

def psql_insert_copy(table, conn, keys, data_iter):
    """
    Execute SQL statement inserting data

    Parameters
    ----------
    table : pandas.io.sql.SQLTable
    conn : sqlalchemy.engine.Engine or sqlalchemy.engine.Connection
    keys : list of str
        Column names
    data_iter : Iterable that iterates the values to be inserted
    """
    # gets a DBAPI connection that can provide a cursor
    dbapi_conn = conn.connection
    with dbapi_conn.cursor() as cur:
        s_buf = StringIO()
        writer = csv.writer(s_buf)
        writer.writerows(data_iter)
        s_buf.seek(0)

        columns = ', '.join(['"{}"'.format(k) for k in keys])
        if table.schema:
            table_name = '{}.{}'.format(table.schema, table.name)
        else:
            table_name = table.name

        sql = 'COPY {} ({}) FROM STDIN WITH CSV'.format(
            table_name, columns)
        cur.copy_expert(sql=sql, file=s_buf)

读表#

read_sql_table()将读取给定表名和可选的要读取的列子集的数据库表。

笔记

为了使用read_sql_table(),您必须安装 ADBC 驱动程序或 SQLAlchemy 可选依赖项。

In [650]: pd.read_sql_table("data", engine)
Out[650]: 
   index  id       Date Col_1  Col_2  Col_3
0      0  26 2010-10-18     X  27.50   True
1      1  42 2010-10-19     Y -12.50  False
2      2  63 2010-10-20     Z   5.73   True

笔记

ADBC 驱动程序会将数据库类型直接映射回箭头类型。对于其他驱动程序,请注意 pandas 从查询输出推断列数据类型,而不是通过在物理数据库模式中查找数据类型。例如,假设userid 是表中的整数列。然后,直观地,将返回整数值系列,而将返回对象值(str)系列。因此,如果查询输出为空,则所有结果列都将作为对象值返回(因为它们是最通用的)。如果您预见查询有时会生成空结果,您可能需要随后显式进行类型转换以确保数据类型的完整性。select userid ...select cast(userid as text) ...

您还可以指定列的名称作为索引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()

架构支持#

通过and函数schema 中的关键字支持读取和写入不同的模式 。但请注意,这取决于数据库风格(sqlite 没有模式)。例如:read_sql_table()to_sql()

df.to_sql(name="table", con=engine, schema="other_schema")
pd.read_sql_table("table", engine, schema="other_schema")

查询#

您可以在函数中使用原始 SQL 进行查询read_sql_query()。在这种情况下,您必须使用适合您的数据库的 SQL 变体。使用 SQLAlchemy 时,您还可以传递与数据库无关的 SQLAlchemy 表达式语言构造。

In [654]: pd.read_sql_query("SELECT * FROM data", engine)
Out[654]: 
   index  id                        Date Col_1  Col_2  Col_3
0      0  26  2010-10-18 00:00:00.000000     X  27.50      1
1      1  42  2010-10-19 00:00:00.000000     Y -12.50      0
2      2  63  2010-10-20 00:00:00.000000     Z   5.73      1

当然,您可以指定更“复杂”的查询。

In [655]: pd.read_sql_query("SELECT id, Col_1, Col_2 FROM data WHERE id = 42;", engine)
Out[655]: 
   id Col_1  Col_2
0  42     Y  -12.5

read_sql_query()函数支持一个chunksize参数。指定此项将返回一个遍历查询结果块的迭代器:

In [656]: df = pd.DataFrame(np.random.randn(20, 3), columns=list("abc"))

In [657]: df.to_sql(name="data_chunks", con=engine, index=False)
Out[657]: 20
In [658]: for chunk in pd.read_sql_query("SELECT * FROM data_chunks", engine, chunksize=5):
   .....:     print(chunk)
   .....: 
          a         b         c
0 -0.395347 -0.822726 -0.363777
1  1.676124 -0.908102 -1.391346
2 -1.094269  0.278380  1.205899
3  1.503443  0.932171 -0.709459
4 -0.645944 -1.351389  0.132023
          a         b         c
0  0.210427  0.192202  0.661949
1  1.690629 -1.046044  0.618697
2 -0.013863  1.314289  1.951611
3 -1.485026  0.304662  1.194757
4 -0.446717  0.528496 -0.657575
          a         b         c
0 -0.876654  0.336252  0.172668
1  0.337684 -0.411202 -0.828394
2 -0.244413  1.094948  0.087183
3  1.125934 -1.480095  1.205944
4 -0.451849  0.452214 -2.208192
          a         b         c
0 -2.061019  0.044184 -0.017118
1  1.248959 -0.675595 -1.908296
2 -0.125934  1.491974  0.648726
3  0.391214  0.438609  1.634248
4  1.208707 -1.535740  1.620399

引擎连接示例#

要与 SQLAlchemy 连接,您可以使用该create_engine()函数从数据库 URI 创建引擎对象。您只需为要连接的每个数据库创建一次引擎。

from sqlalchemy import create_engine

engine = create_engine("postgresql://scott:tiger@localhost:5432/mydatabase")

engine = create_engine("mysql+mysqldb://scott:tiger@localhost/foo")

engine = create_engine("oracle://scott:[email protected]:1521/sidname")

engine = create_engine("mssql+pyodbc://mydsn")

# sqlite://<nohostname>/<path>
# where <path> is relative:
engine = create_engine("sqlite:///foo.db")

# or absolute, starting with a slash:
engine = create_engine("sqlite:////absolute/path/to/foo.db")

有关更多信息,请参阅 SQLAlchemy文档中的示例

高级 SQLAlchemy 查询#

您可以使用 SQLAlchemy 构造来描述您的查询。

用于sqlalchemy.text()以与后端无关的方式指定查询参数

In [659]: import sqlalchemy as sa

In [660]: pd.read_sql(
   .....:     sa.text("SELECT * FROM data where Col_1=:col1"), engine, params={"col1": "X"}
   .....: )
   .....: 
Out[660]: 
   index  id                        Date Col_1  Col_2  Col_3
0      0  26  2010-10-18 00:00:00.000000     X   27.5      1

如果您有数据库的 SQLAlchemy 描述,您可以使用 SQLAlchemy 表达式表达 where 条件

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

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

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

read_sql()您可以将 SQLAlchemy 表达式与传递给using 的参数结合起来sqlalchemy.bindparam()

In [664]: import datetime as dt

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

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

SQLite 后备#

支持在不使用 SQLAlchemy 的情况下使用 sqlite。此模式需要一个尊重Python DB-API 的Python 数据库适配器。

您可以像这样创建连接:

import sqlite3

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

然后发出以下查询:

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

谷歌大查询#

pandas-gbq包提供了从 Google BigQuery 读取/写入的功能。

pandas 与这个外部包集成。如果pandas-gbq安装了,您可以使用 pandas 方法pd.read_gbqDataFrame.to_gbq,它将调用 中的相应函数pandas-gbq

完整的文档可以在这里找到。

统计数据格式#

写入 stata 格式#

该方法DataFrame.to_stata()会将 DataFrame 写入 .dta 文件。该文件的格式版本始终为 115 (Stata 12)。

In [667]: df = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))

In [668]: df.to_stata("stata.dta")

Stata数据文件的数据类型支持有限;仅包含 244 个或更少字符的字符串 、int8int16int32float32 可以float64存储在.dta文件中。此外, Stata保留某些值来表示缺失的数据。导出超出 Stata 中特定数据类型允许范围的非缺失值会将变量重新键入为下一个更大的大小。例如,int8Stata 中的值被限制在 -127 到 100 之间,因此值大于 100 的变量将触发到int16.nan浮点数据类型中的值存储为基本缺失数据类型(.Stata中)。

笔记

无法导出整数数据类型的缺失数据值。

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

警告

如果值大于 2**53,则从int64到 的转换float64可能会导致精度损失。int64

警告

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

从 Stata 格式读取#

顶层函数read_stata将读取 dta 文件并返回 aDataFrame或 apandas.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

指定 achunksize会生成一个 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并指定 。chunksizeread()

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对象,无论是通过(使用orread_stata() 时)创建还是手动实例化,都必须用作上下文管理器(例如语句)。虽然该方法可用,但不支持其使用。它不是公共 API 的一部分,将来会在没有警告的情况下被删除。iterator=Truechunksizewithclose()

分类数据#

Categorical数据可以作为值标记数据导出到Stata数据文件。导出的数据由作为整数数据值的基础类别代码和作为值标签的类别组成。Stata没有与 a 等价的显式变量,并且有关变量是否Categorical有序的信息在导出时会丢失。

警告

Stata仅支持字符串值标签,因此str在导出数据时会在类别上调用。导出具有非字符串类别的变量会产生警告,并且如果类别的表示形式不唯一,Categorical可能会导致信息丢失。str

类似地,可以使用关键字参数(默认情况下)从Stata数据文件中将标记数据作为变量导入。关键字参数(默认情况下)确定导入的变量是否有序。Categoricalconvert_categoricalsTrueorder_categoricalsTrueCategorical

笔记

导入分类数据时, Stata数据文件中的变量值 不会保留,因为Categorical变量始终使用整数数据类型,其中和-1是类别数。如果需要Stata数据文件中的原始值,可以通过设置导入这些值,这将导入原始数据(但不导入变量标签)。原始值可以与导入的分类数据进行匹配,因为原始Stata数据值和导入的分类变量的类别代码之间存在简单的映射:缺失值分配为 code ,分配最小的原始值,第二小的值是依此类推 ,直到最大的原始值被分配给代码。n-1nconvert_categoricals=False-101n-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返回包含所有列的 a 。

指定usecols参数以获取列的子集。指定convert_categoricals=False 以避免将分类列转换为pd.Categorical.

读取 SPSS 文件:

df = pd.read_spss("spss_data.sav")

usecols从 SPSS 文件中提取包含的列子集,并避免将分类列转换为pd.Categorical

df = pd.read_spss(
    "spss_data.sav",
    usecols=["foo", "bar"],
    convert_categoricals=False,
)

有关 SAV 和 ZSAV 文件格式的更多信息,请参见此处

其他文件格式#

pandas 本身仅支持一组有限的文件格式的 IO,这些文件格式干净地映射到其表格数据模型。为了在 pandas 中读取和写入其他文件格式,我们推荐来自更广泛社区的这些软件包。

网络CDF #

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.parquet占用test.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