IO 工具(文本、CSV、HDF5 等)#
pandas 的 I/O API 是一系列顶级 reader
函数,例如通过 pandas.read_csv()
访问,通常返回一个 pandas 对象。相应的 writer
函数是对象方法,例如通过 DataFrame.to_csv()
访问。下方表格列出了可用的 readers
和 writers
。
格式类型 |
数据描述 |
读取器 |
写入器 |
---|---|---|---|
文本 |
|||
文本 |
固定宽度文本文件 |
不适用 |
|
文本 |
|||
文本 |
|||
文本 |
不适用 |
||
文本 |
|||
文本 |
本地剪贴板 |
||
二进制 |
|||
二进制 |
不适用 |
||
二进制 |
|||
二进制 |
|||
二进制 |
|||
二进制 |
|||
二进制 |
|||
二进制 |
不适用 |
||
二进制 |
不适用 |
||
二进制 |
|||
SQL |
|||
SQL |
Google BigQuery;:ref:read_gbq<io.bigquery>;:ref:to_gbq<io.bigquery> |
此处是一些 IO 方法的非正式性能比较。
注意
对于使用 StringIO
类的示例,请确保在 Python 3 中使用 from io import StringIO
导入它。
CSV 和文本文件#
读取文本文件(也称为平面文件)的主力函数是 read_csv()
。请参阅使用指南了解一些高级策略。
解析选项#
read_csv()
接受以下常用参数
基本#
- filepath_or_buffer各种类型
文件路径(可以是
str
,pathlib.Path
或py:py._path.local.LocalPath
类型)、URL(包括 http、ftp 和 S3 位置),或任何具有read()
方法的对象(例如已打开的文件或StringIO
)。- sepstr,对于
read_csv()
默认为','
,对于read_table()
默认为\t
要使用的分隔符。如果 sep 为
None
,C 引擎无法自动检测分隔符,但 Python 解析引擎可以,这意味着将使用 Python 引擎并通过 Python 内置的嗅探器工具csv.Sniffer
自动检测分隔符。此外,长度超过 1 个字符且不同于'\s+'
的分隔符将被解释为正则表达式,并强制使用 Python 解析引擎。请注意,正则表达式分隔符容易忽略带引号的数据。正则表达式示例:'\\r\\t'
。- delimiterstr,默认为
None
sep 的替代参数名称。
- delim_whitespace布尔值,默认为 False
指定是否使用空白字符(例如
' '
或'\t'
)作为分隔符。等同于设置sep='\s+'
。如果此选项设置为True
,则不应为delimiter
参数传递任何值。
列和索引的位置与名称#
- headerint 或 int 列表,默认为
'infer'
用作列名和数据起始行的行号。默认行为是推断列名:如果未传递名称,则行为与
header=0
相同,并从文件的第一行推断列名;如果显式传递列名,则行为与header=None
相同。显式传递header=0
可以替换现有名称。header 可以是指定列上 MultiIndex 行位置的 int 列表,例如
[0,1,3]
。未指定的中间行将被跳过(例如此示例中的第 2 行被跳过)。请注意,如果skip_blank_lines=True
,此参数将忽略注释行和空行,因此 header=0 表示数据的第一个有效行,而不是文件的第一行。- namesarray-like,默认为
None
要使用的列名列表。如果文件不包含标题行,则应显式传递
header=None
。此列表中的重复项是不允许的。- index_colint、str、int/str 序列或 False,可选,默认为
None
用作
DataFrame
行标签的列,可以指定为字符串名称或列索引。如果给定了 int/str 序列,则使用 MultiIndex。注意
index_col=False
可用于强制 pandas 不 将第一列用作索引,例如当文件格式错误,每行末尾有分隔符时。默认值
None
会指示 pandas 进行猜测。如果列标题行的字段数量与数据文件主体中的字段数量相等,则使用默认索引。如果前者更大,则将前几列用作索引,以使主体中剩余的字段数量等于标题中的字段数量。标头后的第一行用于确定列数,这些列将进入索引。如果后续行的列少于第一行,它们将用
NaN
填充。可以通过
usecols
避免这种情况。这确保按原样获取列,并忽略尾部数据。- usecolslist-like 或 callable,默认为
None
返回列的子集。如果是 list-like,所有元素必须是位置索引(即文档列的整数索引)或字符串,这些字符串对应于用户在
names
中提供的列名或从文档标题行推断的列名。如果给定了names
,则文档标题行将被忽略。例如,一个有效的 list-likeusecols
参数可以是[0, 1, 2]
或['foo', 'bar', 'baz']
。元素的顺序被忽略,因此
usecols=[0, 1]
和[1, 0]
是一样的。要从data
实例化一个保留元素顺序的 DataFrame,可以使用pd.read_csv(data, usecols=['foo', 'bar'])[['foo', 'bar']]
来获取['foo', 'bar']
顺序的列,或者使用pd.read_csv(data, usecols=['foo', 'bar'])[['bar', 'foo']]
来获取['bar', 'foo']
顺序的列。如果是 callable,将针对列名评估该 callable 函数,返回函数评估为 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'}
。使用str
或object
并结合合适的na_values
设置来保留 dtype 并防止解释。如果指定了 converters,将优先应用它们而不是 dtype 转换。1.5.0 版本新增: 增加了对 defaultdict 的支持。可以指定一个 defaultdict 作为输入,其中默认值确定未显式列出的列的 dtype。
- dtype_backend{“numpy_nullable”, “pyarrow”},默认为 NumPy 支持的 DataFrames
要使用的 dtype_backend,例如 DataFrame 是否应使用 NumPy 数组,当设置为“numpy_nullable”时,对所有具有可空实现的 dtype 使用可空 dtype,如果设置为“pyarrow”,则对所有 dtype 使用 pyarrow。
dtype_backends 仍然是实验性的。
2.0 版本新增。
- engine{
'c'
,'python'
,'pyarrow'
} 要使用的解析引擎。C 和 pyarrow 引擎更快,而 python 引擎目前功能更全面。目前只有 pyarrow 引擎支持多线程。
1.4.0 版本新增: “pyarrow”引擎作为实验性引擎被添加,并且使用此引擎时,某些功能可能不受支持或无法正常工作。
- convertersdict,默认为
None
用于转换特定列中值的函数字典。键可以是整数或列标签。
- true_valueslist,默认为
None
视为
True
的值。- false_valueslist,默认为
None
视为
False
的值。- skipinitialspace布尔值,默认为
False
跳过分隔符后的空格。
- skiprowslist-like 或 integer,默认为
None
要跳过的行号(0-indexed)或在文件开头跳过的行数(int)。
如果是 callable,将针对行索引评估该 callable 函数,如果行应跳过则返回 True,否则返回 False。
In [6]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3" In [7]: pd.read_csv(StringIO(data)) Out[7]: col1 col2 col3 0 a b 1 1 a b 2 2 c d 3 In [8]: pd.read_csv(StringIO(data), skiprows=lambda x: x % 2 != 0) Out[8]: col1 col2 col3 0 a b 2
- skipfooterint,默认为
0
在文件底部跳过的行数(使用 engine=’c’ 时不支持)。
- nrowsint,默认为
None
要读取的行数。对于读取大型文件的部分内容很有用。
- low_memory布尔值,默认为
True
在内部以块(chunk)方式处理文件,从而在解析时降低内存使用,但可能导致混合类型推断。为确保没有混合类型,可以设置为
False
,或使用dtype
参数指定类型。请注意,无论如何整个文件都会被读取到一个DataFrame
中,要按块返回数据,请使用chunksize
或iterator
参数。(仅对 C 解析器有效)- memory_map布尔值,默认为 False
如果为
filepath_or_buffer
提供了文件路径,则将文件对象直接映射到内存并直接从内存访问数据。使用此选项可以提高性能,因为它不再有 I/O 开销。
NA 和缺失数据处理#
- na_valuesscalar, str, list-like 或 dict,默认为
None
额外识别为 NA/NaN 的字符串。如果传递 dict,则指定每列的 NA 值。有关默认解释为 NaN 的值列表,请参阅下方的 na values const。
- keep_default_na布尔值,默认为
True
解析数据时是否包含默认的 NaN 值。根据是否传入
na_values
,行为如下如果
keep_default_na
为True
且指定了na_values
,则na_values
将被附加到用于解析的默认 NaN 值列表。如果
keep_default_na
为True
且未指定na_values
,则仅使用默认的 NaN 值进行解析。如果
keep_default_na
为False
且指定了na_values
,则仅使用na_values
中指定的 NaN 值进行解析。如果
keep_default_na
为False
且未指定na_values
,则没有字符串将被解析为 NaN。
请注意,如果
na_filter
被设置为False
,则keep_default_na
和na_values
参数将被忽略。- na_filter布尔值,默认为
True
检测缺失值标记(空字符串和 na_values 的值)。在不含任何 NA 的数据中,传递
na_filter=False
可以提高读取大型文件的性能。- verbose布尔值,默认为
False
指示在非数字列中放置的 NA 值数量。
- skip_blank_lines布尔值,默认为
True
如果为
True
,则跳过空行,而不是将其解释为 NaN 值。
日期时间处理#
- parse_dates布尔值、int 或 name 列表、列表的列表或 dict,默认为
False
。 如果为
True
-> 尝试解析索引。如果为
[1, 2, 3]
-> 尝试将列 1、2、3 分别解析为单独的日期列。如果为
[[1, 3]]
-> 合并列 1 和 3,并解析为一个日期列。如果为
{'foo': [1, 3]}
-> 将列 1、3 解析为日期,并将结果命名为 ‘foo’。
注意
iso8601 格式的日期存在快速路径。
- infer_datetime_format布尔值,默认为
False
如果为
True
并且某列启用了 parse_dates,则尝试推断日期时间格式以加快处理速度。自 2.0.0 版本弃用: 此参数的严格版本现在是默认设置,传递它没有任何效果。
- keep_date_col布尔值,默认为
False
如果为
True
并且 parse_dates 指定合并多个列,则保留原始列。- date_parserfunction,默认为
None
用于将一系列字符串列转换为 datetime 实例数组的函数。默认使用
dateutil.parser.parser
进行转换。pandas 将尝试以三种不同方式调用 date_parser,如果发生异常则尝试下一种方式:1) 将一个或多个数组(由 parse_dates 定义)作为参数传递;2) 将由 parse_dates 定义的列中的字符串值(按行)连接成一个数组并传递;以及 3) 对每一行调用一次 date_parser,使用一个或多个字符串(对应于由 parse_dates 定义的列)作为参数。自 2.0.0 版本弃用: 请改用
date_format
,或者以object
类型读取,然后根据需要应用to_datetime()
。- date_formatstr 或 column -> format 的 dict,默认为
None
如果与
parse_dates
一起使用,将按照此格式解析日期。对于更复杂的格式,请以object
类型读取,然后根据需要应用to_datetime()
。2.0.0 版本新增。
- dayfirst布尔值,默认为
False
DD/MM 格式日期,国际和欧洲格式。
- cache_dates布尔值,默认为 True
如果为 True,使用唯一、已转换日期的缓存来应用日期时间转换。在解析重复日期字符串(特别是带有时区偏移的字符串)时,可能会显著提高速度。
迭代#
- iterator布尔值,默认为
False
返回
TextFileReader
对象,用于迭代或使用get_chunk()
获取数据块。- chunksizeint,默认为
None
返回
TextFileReader
对象用于迭代。请参阅下方的迭代和分块。
引用、压缩和文件格式#
- compression{
'infer'
,'gzip'
,'bz2'
,'zip'
,'xz'
,'zstd'
,None
,dict
},默认为'infer'
用于对磁盘数据进行即时解压缩。如果为 ‘infer’,则如果
filepath_or_buffer
是以 ‘.gz’、‘.bz2’、‘.zip’、‘.xz’、‘.zst’ 结尾的路径,则分别使用 gzip、bz2、zip、xz 或 zstandard 进行解压缩,否则不进行解压缩。如果使用 ‘zip’,ZIP 文件必须仅包含一个要读取的数据文件。设置为None
则不进行解压缩。也可以是一个 dict,其中键'method'
设置为 {'zip'
,'gzip'
,'bz2'
,'zstd'
} 之一,其他键值对将转发给zipfile.ZipFile
、gzip.GzipFile
、bz2.BZ2File
或zstandard.ZstdDecompressor
。例如,可以传递以下内容以实现更快的压缩并创建可复现的 gzip 归档:compression={'method': 'gzip', 'compresslevel': 1, 'mtime': 1}
。1.2.0 版本更改: 之前版本将 ‘gzip’ 的 dict 条目转发给
gzip.open
。- thousandsstr,默认为
None
千位分隔符。
- decimalstr,默认为
'.'
识别为小数点的字符。例如,对于欧洲数据,使用
','
。- float_precisionstring,默认为 None
指定 C 引擎用于浮点值的转换器。选项包括用于普通转换器的
None
、用于高精度转换器的high
以及用于往返转换器的round_trip
。- lineterminatorstr(长度 1),默认为
None
用于分隔文件行的字符。仅对 C 解析器有效。
- quotecharstr(长度 1)
用于表示带引号项开头和结尾的字符。带引号的项可以包含分隔符,并且分隔符将被忽略。
- quotingint 或
csv.QUOTE_*
实例,默认为0
根据
csv.QUOTE_*
常量控制字段引用行为。使用QUOTE_MINIMAL
(0)、QUOTE_ALL
(1)、QUOTE_NONNUMERIC
(2) 或QUOTE_NONE
(3) 中的一个。- doublequote布尔值,默认为
True
当指定了
quotechar
且quoting
不是QUOTE_NONE
时,指示是否将字段内部两个连续的quotechar
元素解释为单个quotechar
元素。- escapecharstr(长度 1),默认为
None
当 quoting 为
QUOTE_NONE
时,用于转义分隔符的单字符字符串。- commentstr,默认为
None
指示行的其余部分不应被解析。如果在行首找到,则该行将被完全忽略。此参数必须是单个字符。与空行(只要
skip_blank_lines=True
)一样,完全注释的行会被参数header
忽略,但不会被skiprows
忽略。例如,如果comment='#'
,使用header=0
解析 ‘#empty\na,b,c\n1,2,3’ 将导致 ‘a,b,c’ 被视为标题。- encodingstr,默认为
None
读/写 UTF 时使用的编码(例如
'utf-8'
)。Python 标准编码列表。- dialectstr 或
csv.Dialect
实例,默认为None
如果提供,此参数将覆盖以下参数的值(默认或非默认):
delimiter
,doublequote
,escapechar
,skipinitialspace
,quotechar
和quoting
。如果需要覆盖值,将发出 ParserWarning。更多详细信息请参阅csv.Dialect
文档。
错误处理#
- on_bad_lines(‘error’, ‘warn’, ‘skip’),默认为 ‘error’
指定遇到格式错误行(字段过多的行)时的处理方式。允许的值为
‘error’,在遇到格式错误行时引发 ParserError。
‘warn’(警告),遇到无效行时打印警告并跳过该行。
‘skip’(跳过),遇到无效行时跳过而不引发错误或警告。
在版本 1.3.0 中新增。
指定列数据类型#
您可以为整个 DataFrame
或单个列指定数据类型。
In [9]: import numpy as np
In [10]: data = "a,b,c,d\n1,2,3,4\n5,6,7,8\n9,10,11"
In [11]: print(data)
a,b,c,d
1,2,3,4
5,6,7,8
9,10,11
In [12]: df = pd.read_csv(StringIO(data), dtype=object)
In [13]: df
Out[13]:
a b c d
0 1 2 3 4
1 5 6 7 8
2 9 10 11 NaN
In [14]: df["a"][0]
Out[14]: '1'
In [15]: df = pd.read_csv(StringIO(data), dtype={"b": object, "c": np.float64, "d": "Int64"})
In [16]: df.dtypes
Out[16]:
a int64
b object
c float64
d Int64
dtype: object
幸运的是,pandas 提供了不止一种方法来确保您的列只包含一个 dtype
。如果您不熟悉这些概念,可以查看此处了解更多关于 dtypes 的信息,以及此处了解更多关于 pandas 中 object
转换的信息。
例如,您可以使用 read_csv()
的 converters
参数。
In [17]: data = "col_1\n1\n2\n'A'\n4.22"
In [18]: df = pd.read_csv(StringIO(data), converters={"col_1": str})
In [19]: df
Out[19]:
col_1
0 1
1 2
2 'A'
3 4.22
In [20]: df["col_1"].apply(type).value_counts()
Out[20]:
col_1
<class 'str'> 4
Name: count, dtype: int64
或者在读入数据后使用 to_numeric()
函数强制转换 dtypes,
In [21]: df2 = pd.read_csv(StringIO(data))
In [22]: df2["col_1"] = pd.to_numeric(df2["col_1"], errors="coerce")
In [23]: df2
Out[23]:
col_1
0 1.00
1 2.00
2 NaN
3 4.22
In [24]: df2["col_1"].apply(type).value_counts()
Out[24]:
col_1
<class 'float'> 4
Name: count, dtype: int64
这会将所有有效的解析转换为浮点数,将无效的解析保留为 NaN
。
最终,如何处理包含混合 dtypes 的列取决于您的具体需求。在上面的例子中,如果您想将数据异常值设为 NaN
,那么 to_numeric()
可能是最好的选择。然而,如果您希望所有数据都被强制转换(无论类型如何),那么使用 read_csv()
的 converters
参数无疑值得一试。
注意
在某些情况下,读取包含混合 dtypes 的异常数据会导致数据集不一致。如果您依赖 pandas 推断列的 dtypes,解析引擎会推断数据不同块的 dtypes,而不是一次性推断整个数据集的。因此,您的列可能会包含混合 dtypes。例如,
In [25]: col_1 = list(range(500000)) + ["a", "b"] + list(range(500000))
In [26]: df = pd.DataFrame({"col_1": col_1})
In [27]: df.to_csv("foo.csv")
In [28]: mixed_df = pd.read_csv("foo.csv")
In [29]: mixed_df["col_1"].apply(type).value_counts()
Out[29]:
col_1
<class 'int'> 737858
<class 'str'> 262144
Name: count, dtype: int64
In [30]: mixed_df["col_1"].dtype
Out[30]: dtype('O')
将导致 mixed_df
中的列在某些块中包含 int
dtype,而在其他块中包含 str
,这是由于读入的数据包含混合 dtypes。需要注意的是,整个列将被标记为 object
dtype,这种 dtype 用于包含混合 dtypes 的列。
设置 dtype_backend="numpy_nullable"
将使每个列都具有可空 dtype。
In [31]: data = """a,b,c,d,e,f,g,h,i,j
....: 1,2.5,True,a,,,,,12-31-2019,
....: 3,4.5,False,b,6,7.5,True,a,12-31-2019,
....: """
....:
In [32]: df = pd.read_csv(StringIO(data), dtype_backend="numpy_nullable", parse_dates=["i"])
In [33]: df
Out[33]:
a b c d e f g h i j
0 1 2.5 True a <NA> <NA> <NA> <NA> 2019-12-31 <NA>
1 3 4.5 False b 6 7.5 True a 2019-12-31 <NA>
In [34]: df.dtypes
Out[34]:
a Int64
b Float64
c boolean
d string[python]
e Int64
f Float64
g boolean
h string[python]
i datetime64[ns]
j Int64
dtype: object
指定 categorical dtype#
通过指定 dtype='category'
或 dtype=CategoricalDtype(categories, ordered)
,可以直接解析 Categorical
列。
In [35]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3"
In [36]: pd.read_csv(StringIO(data))
Out[36]:
col1 col2 col3
0 a b 1
1 a b 2
2 c d 3
In [37]: pd.read_csv(StringIO(data)).dtypes
Out[37]:
col1 object
col2 object
col3 int64
dtype: object
In [38]: pd.read_csv(StringIO(data), dtype="category").dtypes
Out[38]:
col1 category
col2 category
col3 category
dtype: object
单个列可以使用字典指定的方式解析为 Categorical
。
In [39]: pd.read_csv(StringIO(data), dtype={"col1": "category"}).dtypes
Out[39]:
col1 category
col2 object
col3 int64
dtype: object
指定 dtype='category'
将得到一个无序的 Categorical
,其 categories
是数据中观察到的唯一值。为了更好地控制分类和顺序,请提前创建一个 CategoricalDtype
,并将其作为该列的 dtype
传递。
In [40]: from pandas.api.types import CategoricalDtype
In [41]: dtype = CategoricalDtype(["d", "c", "b", "a"], ordered=True)
In [42]: pd.read_csv(StringIO(data), dtype={"col1": dtype}).dtypes
Out[42]:
col1 category
col2 object
col3 int64
dtype: object
当使用 dtype=CategoricalDtype
时,dtype.categories
之外的“意外”值将被视为缺失值。
In [43]: dtype = CategoricalDtype(["a", "b", "d"]) # No 'c'
In [44]: pd.read_csv(StringIO(data), dtype={"col1": dtype}).col1
Out[44]:
0 a
1 a
2 NaN
Name: col1, dtype: category
Categories (3, object): ['a', 'b', 'd']
这与 Categorical.set_categories()
的行为一致。
注意
使用 dtype='category'
时,生成的分类将始终解析为字符串(object dtype)。如果分类是数字,可以使用 to_numeric()
函数或适当的其他转换器(如 to_datetime()
)进行转换。
当 dtype
是具有同质 categories
(全部是数字、全部是日期时间等)的 CategoricalDtype
时,转换会自动完成。
In [45]: df = pd.read_csv(StringIO(data), dtype="category")
In [46]: df.dtypes
Out[46]:
col1 category
col2 category
col3 category
dtype: object
In [47]: df["col3"]
Out[47]:
0 1
1 2
2 3
Name: col3, dtype: category
Categories (3, object): ['1', '2', '3']
In [48]: new_categories = pd.to_numeric(df["col3"].cat.categories)
In [49]: df["col3"] = df["col3"].cat.rename_categories(new_categories)
In [50]: df["col3"]
Out[50]:
0 1
1 2
2 3
Name: col3, dtype: category
Categories (3, int64): [1, 2, 3]
列的命名和使用#
处理列名#
文件可能有也可能没有标题行。pandas 假定第一行应作为列名。
In [51]: data = "a,b,c\n1,2,3\n4,5,6\n7,8,9"
In [52]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9
In [53]: pd.read_csv(StringIO(data))
Out[53]:
a b c
0 1 2 3
1 4 5 6
2 7 8 9
通过结合使用 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.2”...
筛选列(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”列。
处理 Unicode 数据#
对于编码的 unicode 数据,应使用 encoding
参数,这将使字节串在结果中解码为 unicode。
In [86]: from io import BytesIO
In [87]: data = b"word,length\n" b"Tr\xc3\xa4umen,7\n" b"Gr\xc3\xbc\xc3\x9fe,5"
In [88]: data = data.decode("utf8").encode("latin-1")
In [89]: df = pd.read_csv(BytesIO(data), encoding="latin-1")
In [90]: df
Out[90]:
word length
0 Träumen 7
1 Grüße 5
In [91]: df["word"][1]
Out[91]: 'Grüße'
某些将所有字符编码为多个字节的格式(例如 UTF-16)如果不指定编码将无法正确解析。Python 标准编码完整列表。
索引列和尾部分隔符#
如果文件的数据列数比列名数多一列,则第一列将用作 DataFrame
的行名。
In [92]: data = "a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"
In [93]: pd.read_csv(StringIO(data))
Out[93]:
a b c
4 apple bat 5.7
8 orange cow 10.0
In [94]: data = "index,a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"
In [95]: pd.read_csv(StringIO(data), index_col=0)
Out[95]:
a b c
index
4 apple bat 5.7
8 orange cow 10.0
通常,您可以使用 index_col
选项来实现此行为。
在某些特殊情况下,文件在每行数据末尾带有分隔符,这可能会使解析器混淆。要显式禁用索引列推断并丢弃最后一列,请传递 index_col=False
。
In [96]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"
In [97]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,
In [98]: pd.read_csv(StringIO(data))
Out[98]:
a b c
4 apple bat NaN
8 orange cow NaN
In [99]: pd.read_csv(StringIO(data), index_col=False)
Out[99]:
a b c
0 4 apple bat
1 8 orange cow
如果使用 usecols
选项解析数据子集,则 index_col
的指定是基于该子集,而不是原始数据。
In [100]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"
In [101]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,
In [102]: pd.read_csv(StringIO(data), usecols=["b", "c"])
Out[102]:
b c
4 bat NaN
8 cow NaN
In [103]: pd.read_csv(StringIO(data), usecols=["b", "c"], index_col=0)
Out[103]:
b c
4 bat NaN
8 cow NaN
日期处理#
指定日期列#
为了更好地处理日期时间数据,read_csv()
使用关键字参数 parse_dates
和 date_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
注意
如果列或索引包含无法解析的日期,则整个列或索引将保持不变,作为 object 数据类型返回。对于非标准日期时间解析,请在 pd.read_csv
之后使用 to_datetime()
。
注意
read_csv 具有一个用于解析 iso8601 格式日期时间字符串的快速路径,例如 “2000-01-01T00:01:02+00:00” 和类似的变体。如果您能将数据中的日期时间存储为这种格式,加载时间将显著加快,已观察到约 20 倍的速度提升。
自版本 2.2.0 起已弃用: 在 read_csv 内部组合日期列已弃用。请改在相关结果列上使用 pd.to_datetime
。
日期解析函数#
最后,解析器允许您指定自定义的 date_format
。从性能角度考虑,您应按以下顺序尝试这些日期解析方法:
如果您知道格式,请使用
date_format
,例如:date_format="%d/%m/%Y"
或date_format={column_name: "%d/%m/%Y"}
。如果不同列有不同的格式,或者想向
to_datetime
传递任何额外选项(例如utc
),那么您应该将数据读取为object
dtype,然后再使用to_datetime
。
解析带有混合时区的 CSV#
pandas 原生不支持表示包含混合时区的列或索引。如果您的 CSV 文件包含具有混合时区的列,即使使用 parse_dates
,默认结果仍将是包含字符串的 object-dtype 列。要将混合时区值解析为 datetime 列,请将其读取为 object
dtype,然后调用带有 utc=True
参数的 to_datetime()
。
In [120]: content = """\
.....: a
.....: 2000-01-01T00:00:00+05:00
.....: 2000-01-01T00:00:00+06:00"""
.....:
In [121]: df = pd.read_csv(StringIO(content))
In [122]: df["a"] = pd.to_datetime(df["a"], utc=True)
In [123]: df["a"]
Out[123]:
0 1999-12-31 19:00:00+00:00
1 1999-12-31 18:00:00+00:00
Name: a, dtype: datetime64[ns, UTC]
推断日期时间格式#
以下是一些可以被推断的日期时间字符串示例(都代表 2011 年 12 月 30 日 00:00:00)
“20111230”
“2011/12/30”
“20111230 00:00:00”
“12/30/2011 00:00:00”
“30/Dec/2011 00:00:00”
“30/December/2011 00:00:00”
请注意,格式推断对 dayfirst
很敏感。当 dayfirst=True
时,它会将“01/12/2011”推断为 12 月 1 日。当 dayfirst=False
(默认)时,它会将“01/12/2011”推断为 1 月 12 日。
如果您尝试解析一列日期字符串,pandas 将尝试从第一个非 NaN 元素推断格式,然后使用该格式解析列的其余部分。如果 pandas 未能推断格式(例如,如果您的第一个字符串是 '01 December US/Pacific 2000'
),则会引发警告,并且每行将由 dateutil.parser.parse
单独解析。解析日期最安全的方法是显式设置 format=
。
In [124]: df = pd.read_csv(
.....: "foo.csv",
.....: index_col=0,
.....: parse_dates=True,
.....: )
.....:
In [125]: df
Out[125]:
A B C
date
2009-01-01 a 1 2
2009-01-02 b 3 4
2009-01-03 c 4 5
如果同一列中包含混合的日期时间格式,可以传递 format='mixed'
。
In [126]: data = StringIO("date\n12 Jan 2000\n2000-01-13\n")
In [127]: df = pd.read_csv(data)
In [128]: df['date'] = pd.to_datetime(df['date'], format='mixed')
In [129]: df
Out[129]:
date
0 2000-01-12
1 2000-01-13
或者,如果您的日期时间格式都是 ISO8601 格式(可能格式略有不同)
In [130]: data = StringIO("date\n2020-01-01\n2020-01-01 03:00\n")
In [131]: df = pd.read_csv(data)
In [132]: df['date'] = pd.to_datetime(df['date'], format='ISO8601')
In [133]: df
Out[133]:
date
0 2020-01-01 00:00:00
1 2020-01-01 03:00:00
国际日期格式#
虽然美国的日期格式通常是 MM/DD/YYYY,但许多国际格式使用 DD/MM/YYYY。为了方便,提供了 dayfirst
关键字。
In [134]: data = "date,value,cat\n1/6/2000,5,a\n2/6/2000,10,b\n3/6/2000,15,c"
In [135]: print(data)
date,value,cat
1/6/2000,5,a
2/6/2000,10,b
3/6/2000,15,c
In [136]: with open("tmp.csv", "w") as fh:
.....: fh.write(data)
.....:
In [137]: pd.read_csv("tmp.csv", parse_dates=[0])
Out[137]:
date value cat
0 2000-01-06 5 a
1 2000-02-06 10 b
2 2000-03-06 15 c
In [138]: pd.read_csv("tmp.csv", dayfirst=True, parse_dates=[0])
Out[138]:
date value cat
0 2000-06-01 5 a
1 2000-06-02 10 b
2 2000-06-03 15 c
将 CSV 写入二进制文件对象#
在版本 1.2.0 中新增。
df.to_csv(..., mode="wb")
允许将 CSV 写入以二进制模式打开的文件对象。在大多数情况下,无需指定 mode
,因为 Pandas 会自动检测文件对象是以文本模式还是二进制模式打开的。
In [139]: import io
In [140]: data = pd.DataFrame([0, 1, 2])
In [141]: buffer = io.BytesIO()
In [142]: data.to_csv(buffer, encoding="utf-8", compression="gzip")
指定浮点数转换方法#
可以指定参数 float_precision
,以便在使用 C 引擎解析时使用特定的浮点转换器。选项包括普通转换器、高精度转换器和往返转换器(保证在写入文件后能够往返转换值)。例如,
In [143]: val = "0.3066101993807095471566981359501369297504425048828125"
In [144]: data = "a,b,c\n1,2,{0}".format(val)
In [145]: abs(
.....: pd.read_csv(
.....: StringIO(data),
.....: engine="c",
.....: float_precision=None,
.....: )["c"][0] - float(val)
.....: )
.....:
Out[145]: 5.551115123125783e-17
In [146]: abs(
.....: pd.read_csv(
.....: StringIO(data),
.....: engine="c",
.....: float_precision="high",
.....: )["c"][0] - float(val)
.....: )
.....:
Out[146]: 5.551115123125783e-17
In [147]: abs(
.....: pd.read_csv(StringIO(data), engine="c", float_precision="round_trip")["c"][0]
.....: - float(val)
.....: )
.....:
Out[147]: 0.0
千位分隔符#
对于写入时带有千位分隔符的大数字,您可以将 thousands
关键字设置为长度为 1 的字符串,以便正确解析整数。
默认情况下,带有千位分隔符的数字将被解析为字符串。
In [148]: data = (
.....: "ID|level|category\n"
.....: "Patient1|123,000|x\n"
.....: "Patient2|23,000|y\n"
.....: "Patient3|1,234,018|z"
.....: )
.....:
In [149]: with open("tmp.csv", "w") as fh:
.....: fh.write(data)
.....:
In [150]: df = pd.read_csv("tmp.csv", sep="|")
In [151]: df
Out[151]:
ID level category
0 Patient1 123,000 x
1 Patient2 23,000 y
2 Patient3 1,234,018 z
In [152]: df.level.dtype
Out[152]: dtype('O')
thousands
关键字允许正确解析整数。
In [153]: df = pd.read_csv("tmp.csv", sep="|", thousands=",")
In [154]: df
Out[154]:
ID level category
0 Patient1 123000 x
1 Patient2 23000 y
2 Patient3 1234018 z
In [155]: df.level.dtype
Out[155]: dtype('int64')
NA 值#
要控制哪些值被解析为缺失值(用 NaN
表示),请在 na_values
中指定一个字符串。如果您指定字符串列表,则列表中的所有值都将被视为缺失值。如果您指定一个数字(浮点数如 5.0
或整数如 5
),相应的等价值也将表示缺失值(在这种情况下,实际上会将 [5.0, 5]
识别为 NaN
)。
要完全覆盖默认识别为缺失值的值,请指定 keep_default_na=False
。
让我们考虑一些例子:
pd.read_csv("path_to_file.csv", na_values=[5])
在上面的例子中,除了默认值外,5
和 5.0
都将被识别为 NaN
。字符串将首先被解释为数值 5
,然后被解释为 NaN
。
pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=[""])
上面,只有空字段会被识别为 NaN
。
pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=["NA", "0"])
上面,字符串形式的 NA
和 0
都被识别为 NaN
。
pd.read_csv("path_to_file.csv", na_values=["Nope"])
除了字符串 "Nope"
外,默认值也会被识别为 NaN
。
无穷大#
类似于 inf
的值将被解析为 np.inf
(正无穷大),而 -inf
将被解析为 -np.inf
(负无穷大)。这些解析不区分大小写,这意味着 Inf
也将被解析为 np.inf
。
布尔值#
常见的 True
、False
、TRUE
和 FALSE
值都将被识别为布尔值。有时您可能希望将其他值识别为布尔值。为此,请使用 true_values
和 false_values
选项,如下所示:
In [156]: data = "a,b,c\n1,Yes,2\n3,No,4"
In [157]: print(data)
a,b,c
1,Yes,2
3,No,4
In [158]: pd.read_csv(StringIO(data))
Out[158]:
a b c
0 1 Yes 2
1 3 No 4
In [159]: pd.read_csv(StringIO(data), true_values=["Yes"], false_values=["No"])
Out[159]:
a b c
0 1 True 2
1 3 False 4
处理“无效”行#
某些文件可能包含格式错误的行,字段数过少或过多。字段数过少的行将在尾部字段填充 NA 值。默认情况下,字段数过多的行将引发错误。
In [160]: data = "a,b,c\n1,2,3\n4,5,6,7\n8,9,10"
In [161]: pd.read_csv(StringIO(data))
---------------------------------------------------------------------------
ParserError Traceback (most recent call last)
Cell In[161], line 1
----> 1 pd.read_csv(StringIO(data))
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1026, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
1013 kwds_defaults = _refine_defaults_read(
1014 dialect,
1015 delimiter,
(...)
1022 dtype_backend=dtype_backend,
1023 )
1024 kwds.update(kwds_defaults)
-> 1026 return _read(filepath_or_buffer, kwds)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:626, in _read(filepath_or_buffer, kwds)
623 return parser
625 with parser:
--> 626 return parser.read(nrows)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1923, in TextFileReader.read(self, nrows)
1916 nrows = validate_integer("nrows", nrows)
1917 try:
1918 # error: "ParserBase" has no attribute "read"
1919 (
1920 index,
1921 columns,
1922 col_dict,
-> 1923 ) = self._engine.read( # type: ignore[attr-defined]
1924 nrows
1925 )
1926 except Exception:
1927 self.close()
File ~/work/pandas/pandas/pandas/io/parsers/c_parser_wrapper.py:234, in CParserWrapper.read(self, nrows)
232 try:
233 if self.low_memory:
--> 234 chunks = self._reader.read_low_memory(nrows)
235 # destructive to chunks
236 data = _concatenate_chunks(chunks)
File parsers.pyx:838, in pandas._libs.parsers.TextReader.read_low_memory()
File parsers.pyx:905, in pandas._libs.parsers.TextReader._read_rows()
File parsers.pyx:874, in pandas._libs.parsers.TextReader._tokenize_rows()
File parsers.pyx:891, in pandas._libs.parsers.TextReader._check_tokenize_status()
File parsers.pyx:2061, in pandas._libs.parsers.raise_parser_error()
ParserError: Error tokenizing data. C error: Expected 3 fields in line 3, saw 4
您可以选择跳过无效行。
In [162]: data = "a,b,c\n1,2,3\n4,5,6,7\n8,9,10"
In [163]: pd.read_csv(StringIO(data), on_bad_lines="skip")
Out[163]:
a b c
0 1 2 3
1 8 9 10
在版本 1.4.0 中新增。
或者,如果 engine="python"
,可以传递一个可调用函数来处理无效行。无效行将是一个由 sep
分割而成的字符串列表。
In [164]: external_list = []
In [165]: def bad_lines_func(line):
.....: external_list.append(line)
.....: return line[-3:]
.....:
In [166]: external_list
Out[166]: []
注意
可调用函数仅处理字段数过多的行。由其他错误导致的无效行将被静默跳过。
In [167]: bad_lines_func = lambda line: print(line)
In [168]: data = 'name,type\nname a,a is of type a\nname b,"b\" is of type b"'
In [169]: data
Out[169]: 'name,type\nname a,a is of type a\nname b,"b" is of type b"'
In [170]: pd.read_csv(StringIO(data), on_bad_lines=bad_lines_func, engine="python")
Out[170]:
name type
0 name a a is of type a
在此情况下,该行未被处理,因为这里的“无效行”是由转义字符引起的。
您还可以使用 usecols
参数来排除某些行中出现而其他行中没有的额外列数据。
In [171]: pd.read_csv(StringIO(data), usecols=[0, 1, 2])
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[171], line 1
----> 1 pd.read_csv(StringIO(data), usecols=[0, 1, 2])
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1026, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
1013 kwds_defaults = _refine_defaults_read(
1014 dialect,
1015 delimiter,
(...)
1022 dtype_backend=dtype_backend,
1023 )
1024 kwds.update(kwds_defaults)
-> 1026 return _read(filepath_or_buffer, kwds)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:620, in _read(filepath_or_buffer, kwds)
617 _validate_names(kwds.get("names", None))
619 # Create the parser.
--> 620 parser = TextFileReader(filepath_or_buffer, **kwds)
622 if chunksize or iterator:
623 return parser
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1620, in TextFileReader.__init__(self, f, engine, **kwds)
1617 self.options["has_index_names"] = kwds["has_index_names"]
1619 self.handles: IOHandles | None = None
-> 1620 self._engine = self._make_engine(f, self.engine)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1898, in TextFileReader._make_engine(self, f, engine)
1895 raise ValueError(msg)
1897 try:
-> 1898 return mapping[engine](f, **self.options)
1899 except Exception:
1900 if self.handles is not None:
File ~/work/pandas/pandas/pandas/io/parsers/c_parser_wrapper.py:155, in CParserWrapper.__init__(self, src, **kwds)
152 # error: Cannot determine type of 'names'
153 if len(self.names) < len(usecols): # type: ignore[has-type]
154 # error: Cannot determine type of 'names'
--> 155 self._validate_usecols_names(
156 usecols,
157 self.names, # type: ignore[has-type]
158 )
160 # error: Cannot determine type of 'names'
161 self._validate_parse_dates_presence(self.names) # type: ignore[has-type]
File ~/work/pandas/pandas/pandas/io/parsers/base_parser.py:979, in ParserBase._validate_usecols_names(self, usecols, names)
977 missing = [c for c in usecols if c not in names]
978 if len(missing) > 0:
--> 979 raise ValueError(
980 f"Usecols do not match columns, columns expected but not found: "
981 f"{missing}"
982 )
984 return usecols
ValueError: Usecols do not match columns, columns expected but not found: [0, 1, 2]
如果您想保留所有数据,包括字段数过多的行,您可以指定足够的 names
数量。这确保了字段数不足的行会填充 NaN
。
In [172]: pd.read_csv(StringIO(data), names=['a', 'b', 'c', 'd'])
Out[172]:
a b c d
0 name type NaN NaN
1 name a a is of type a NaN NaN
2 name b b is of type b" NaN NaN
文件格式/方言 (Dialect)#
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
解析器会尽一切努力“做正确的事”,使其不至于脆弱。类型推断是件大事。如果一个列可以在不改变内容的情况下被强制转换为 integer dtype,解析器就会这样做。任何非数字列都会像 pandas 的其他对象一样以 object dtype 形式出现。
引用和转义字符#
嵌入字段中的引号(和其他转义字符)可以通过多种方式处理。一种方法是使用反斜杠;要正确解析此类数据,您应该传递 escapechar
选项。
In [184]: data = 'a,b\n"hello, \\"Bob\\", nice to see you",5'
In [185]: print(data)
a,b
"hello, \"Bob\", nice to see you",5
In [186]: pd.read_csv(StringIO(data), escapechar="\\")
Out[186]:
a b
0 hello, "Bob", nice to see you 5
固定宽度列的文件#
虽然 read_csv()
读取分隔数据,但 read_fwf()
函数用于处理具有已知固定列宽度的数据文件。read_fwf
的函数参数与 read_csv
大致相同,但增加了两个额外的参数,并且 delimiter
参数的用法不同。
colspecs
: 一个包含对(元组)的列表,以半开区间(即 [起始,结束[)形式给出每行固定宽度字段的范围。字符串值“infer”可用于指示解析器尝试从数据的前 100 行检测列规范。如果未指定,默认行为是推断。widths
: 一个字段宽度列表,如果间隔是连续的,可以用它代替 'colspecs'。delimiter
: 在固定宽度文件中被视为填充字符的字符。如果字段的填充字符不是空格(例如 '~'),可以使用此参数指定。
考虑一个典型的固定宽度数据文件:
In [187]: data1 = (
.....: "id8141 360.242940 149.910199 11950.7\n"
.....: "id1594 444.953632 166.985655 11788.4\n"
.....: "id1849 364.136849 183.628767 11806.2\n"
.....: "id1230 413.836124 184.375703 11916.8\n"
.....: "id1948 502.953953 173.237159 12468.3"
.....: )
.....:
In [188]: with open("bar.csv", "w") as f:
.....: f.write(data1)
.....:
为了将此文件解析为 DataFrame
,我们只需将列规范和文件名一起提供给 read_fwf
函数即可。
# Column specifications are a list of half-intervals
In [189]: colspecs = [(0, 6), (8, 20), (21, 33), (34, 43)]
In [190]: df = pd.read_fwf("bar.csv", colspecs=colspecs, header=None, index_col=0)
In [191]: df
Out[191]:
1 2 3
0
id8141 360.242940 149.910199 11950.7
id1594 444.953632 166.985655 11788.4
id1849 364.136849 183.628767 11806.2
id1230 413.836124 184.375703 11916.8
id1948 502.953953 173.237159 12468.3
注意,当指定了参数 header=None
时,解析器会自动选择列名 X.<列号>。或者,您可以只为连续的列提供列宽。
# Widths are a list of integers
In [192]: widths = [6, 14, 13, 10]
In [193]: df = pd.read_fwf("bar.csv", widths=widths, header=None)
In [194]: df
Out[194]:
0 1 2 3
0 id8141 360.242940 149.910199 11950.7
1 id1594 444.953632 166.985655 11788.4
2 id1849 364.136849 183.628767 11806.2
3 id1230 413.836124 184.375703 11916.8
4 id1948 502.953953 173.237159 12468.3
解析器会处理列周围的额外空白字符,因此文件中列之间有额外的分隔符也没关系。
默认情况下,read_fwf
会尝试通过使用文件的前 100 行来推断文件的 colspecs
。只有当列对齐并且通过提供的 delimiter
(默认分隔符是空白字符)正确分隔时,它才能这样做。
In [195]: df = pd.read_fwf("bar.csv", header=None, index_col=0)
In [196]: df
Out[196]:
1 2 3
0
id8141 360.242940 149.910199 11950.7
id1594 444.953632 166.985655 11788.4
id1849 364.136849 183.628767 11806.2
id1230 413.836124 184.375703 11916.8
id1948 502.953953 173.237159 12468.3
read_fwf
支持 dtype
参数,用于指定解析列的类型与推断类型不同。
In [197]: pd.read_fwf("bar.csv", header=None, index_col=0).dtypes
Out[197]:
1 float64
2 float64
3 float64
dtype: object
In [198]: pd.read_fwf("bar.csv", header=None, dtype={2: "object"}).dtypes
Out[198]:
0 object
1 float64
2 object
3 float64
dtype: object
索引#
带有“隐式”索引列的文件#
考虑一个文件,其头部的条目比数据列的数量少一个。
In [199]: data = "A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5"
In [200]: print(data)
A,B,C
20090101,a,1,2
20090102,b,3,4
20090103,c,4,5
In [201]: with open("foo.csv", "w") as f:
.....: f.write(data)
.....:
在这种特殊情况下,read_csv
假定第一列将用作 DataFrame
的索引。
In [202]: pd.read_csv("foo.csv")
Out[202]:
A B C
20090101 a 1 2
20090102 b 3 4
20090103 c 4 5
请注意,日期没有自动解析。在这种情况下,您需要像之前那样做。
In [203]: df = pd.read_csv("foo.csv", parse_dates=True)
In [204]: df.index
Out[204]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', freq=None)
读取带有 MultiIndex
的索引#
假设您有由两列索引的数据。
In [205]: data = 'year,indiv,zit,xit\n1977,"A",1.2,.6\n1977,"B",1.5,.5'
In [206]: print(data)
year,indiv,zit,xit
1977,"A",1.2,.6
1977,"B",1.5,.5
In [207]: with open("mindex_ex.csv", mode="w") as f:
.....: f.write(data)
.....:
read_csv
的 index_col
参数可以接受列号列表,将多列转换为返回对象的索引的 MultiIndex
。
In [208]: df = pd.read_csv("mindex_ex.csv", index_col=[0, 1])
In [209]: df
Out[209]:
zit xit
year indiv
1977 A 1.2 0.6
B 1.5 0.5
In [210]: df.loc[1977]
Out[210]:
zit xit
indiv
A 1.2 0.6
B 1.5 0.5
读取带有 MultiIndex
的列#
通过为 header
参数指定行位置列表,您可以为列读取 MultiIndex
。指定非连续的行将跳过中间的行。
In [211]: mi_idx = pd.MultiIndex.from_arrays([[1, 2, 3, 4], list("abcd")], names=list("ab"))
In [212]: mi_col = pd.MultiIndex.from_arrays([[1, 2], list("ab")], names=list("cd"))
In [213]: df = pd.DataFrame(np.ones((4, 2)), index=mi_idx, columns=mi_col)
In [214]: df.to_csv("mi.csv")
In [215]: print(open("mi.csv").read())
c,,1,2
d,,a,b
a,b,,
1,a,1.0,1.0
2,b,1.0,1.0
3,c,1.0,1.0
4,d,1.0,1.0
In [216]: pd.read_csv("mi.csv", header=[0, 1, 2, 3], index_col=[0, 1])
Out[216]:
c 1 2
d a b
a Unnamed: 2_level_2 Unnamed: 3_level_2
1 1.0 1.0
2 b 1.0 1.0
3 c 1.0 1.0
4 d 1.0 1.0
read_csv
也能够解释更常见的多列索引格式。
In [217]: data = ",a,a,a,b,c,c\n,q,r,s,t,u,v\none,1,2,3,4,5,6\ntwo,7,8,9,10,11,12"
In [218]: print(data)
,a,a,a,b,c,c
,q,r,s,t,u,v
one,1,2,3,4,5,6
two,7,8,9,10,11,12
In [219]: with open("mi2.csv", "w") as fh:
.....: fh.write(data)
.....:
In [220]: pd.read_csv("mi2.csv", header=[0, 1], index_col=0)
Out[220]:
a b c
q r s t u v
one 1 2 3 4 5 6
two 7 8 9 10 11 12
注意
如果未指定 index_col
(例如,您没有索引,或使用 df.to_csv(..., index=False)
写入),则列索引上的任何 names
将会丢失。
自动“嗅探”分隔符#
read_csv
能够推断带分隔符(不一定是逗号分隔)的文件,因为 pandas 使用 csv 模块的 csv.Sniffer
类。为此,您必须指定 sep=None
。
In [221]: df = pd.DataFrame(np.random.randn(10, 4))
In [222]: df.to_csv("tmp2.csv", sep=":", index=False)
In [223]: pd.read_csv("tmp2.csv", sep=None, engine="python")
Out[223]:
0 1 2 3
0 0.469112 -0.282863 -1.509059 -1.135632
1 1.212112 -0.173215 0.119209 -1.044236
2 -0.861849 -2.104569 -0.494929 1.071804
3 0.721555 -0.706771 -1.039575 0.271860
4 -0.424972 0.567020 0.276232 -1.087401
5 -0.673690 0.113648 -1.478427 0.524988
6 0.404705 0.577046 -1.715002 -1.039268
7 -0.370647 -1.157892 -1.344312 0.844885
8 1.075770 -0.109050 1.643563 -1.469388
9 0.357021 -0.674600 -1.776904 -0.968914
读取多个文件以创建单个 DataFrame#
按块迭代文件#
假设您希望懒惰地迭代一个(可能非常大的)文件,而不是将整个文件读入内存,例如以下情况:
In [224]: df = pd.DataFrame(np.random.randn(10, 4))
In [225]: df.to_csv("tmp.csv", index=False)
In [226]: table = pd.read_csv("tmp.csv")
In [227]: table
Out[227]:
0 1 2 3
0 -1.294524 0.413738 0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2 0.895717 0.805244 -1.206412 2.565646
3 1.431256 1.340309 -1.170299 -0.226169
4 0.410835 0.813850 0.132003 -0.827317
5 -0.076467 -1.187678 1.130127 -1.436737
6 -1.413681 1.607920 1.024180 0.569605
7 0.875906 -2.211372 0.974466 -2.006747
8 -0.410001 -0.078638 0.545952 -1.219217
9 -1.226825 0.769804 -1.281247 -0.727707
通过为 read_csv
指定 chunksize
,返回值将是一个类型为 TextFileReader
的可迭代对象。
In [228]: with pd.read_csv("tmp.csv", chunksize=4) as reader:
.....: print(reader)
.....: for chunk in reader:
.....: print(chunk)
.....:
<pandas.io.parsers.readers.TextFileReader object at 0x7fe8deebc0d0>
0 1 2 3
0 -1.294524 0.413738 0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2 0.895717 0.805244 -1.206412 2.565646
3 1.431256 1.340309 -1.170299 -0.226169
0 1 2 3
4 0.410835 0.813850 0.132003 -0.827317
5 -0.076467 -1.187678 1.130127 -1.436737
6 -1.413681 1.607920 1.024180 0.569605
7 0.875906 -2.211372 0.974466 -2.006747
0 1 2 3
8 -0.410001 -0.078638 0.545952 -1.219217
9 -1.226825 0.769804 -1.281247 -0.727707
1.2 版本中已更改: 迭代文件时,read_csv/json/sas
返回一个上下文管理器。
指定 iterator=True
也将返回 TextFileReader
对象。
In [229]: with pd.read_csv("tmp.csv", iterator=True) as reader:
.....: print(reader.get_chunk(5))
.....:
0 1 2 3
0 -1.294524 0.413738 0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2 0.895717 0.805244 -1.206412 2.565646
3 1.431256 1.340309 -1.170299 -0.226169
4 0.410835 0.813850 0.132003 -0.827317
指定解析引擎#
Pandas 目前支持三种引擎:C 引擎、Python 引擎和一个实验性的 pyarrow 引擎(需要安装 pyarrow
包)。一般来说,pyarrow 引擎在较大工作负载下速度最快,在大多数其他工作负载下速度与 C 引擎相当。Python 引擎在大多数工作负载下往往比 pyarrow 和 C 引擎慢。然而,pyarrow 引擎远不如 C 引擎稳健,并且与 Python 引擎相比,它缺少一些特性。
如果可能,pandas 使用 C 解析器(指定为 engine='c'
),但如果指定了 C 不支持的选项,它可能会回退到 Python。
目前,C 和 pyarrow 引擎不支持的选项包括:
除了单个字符之外的
sep
(例如正则表达式分隔符)skipfooter
sep=None
和delim_whitespace=False
指定上述任何选项都将产生 ParserWarning
,除非使用 engine='python'
显式选择 Python 引擎。
pyarrow 引擎不支持且不在上述列表中的选项包括:
float_precision
chunksize
comment
nrows
thousands
memory_map
dialect
on_bad_lines
delim_whitespace
quoting
lineterminator
converters
decimal
iterator
dayfirst
infer_datetime_format
verbose
skipinitialspace
low_memory
使用 engine='pyarrow'
指定这些选项将引发 ValueError
。
读取/写入远程文件#
您可以将 URL 传递给 pandas 的许多 IO 函数来读取或写入远程文件 - 以下示例展示了读取 CSV 文件:
df = pd.read_csv("https://download.bls.gov/pub/time.series/cu/cu.item", sep="\t")
在版本 1.3.0 中新增。
可以通过将头部键值映射字典传递给 storage_options
关键字参数来随 HTTP(s) 请求发送自定义头部,如下所示:
headers = {"User-Agent": "pandas"}
df = pd.read_csv(
"https://download.bls.gov/pub/time.series/cu/cu.item",
sep="\t",
storage_options=headers
)
所有非本地文件或非 HTTP(s) 的 URL,如果安装了 fsspec 及其各种文件系统实现(包括 Amazon S3、Google Cloud、SSH、FTP、webHDFS…),都将由其处理。其中一些实现需要安装额外的包,例如 S3 URL 需要 s3fs 库。
df = pd.read_json("s3://pandas-test/adatafile.json")
在处理远程存储系统时,您可能需要通过环境变量或特殊位置的配置文件进行额外的配置。例如,要访问您的 S3 存储桶中的数据,您需要按照 S3Fs 文档中列出的几种方式之一来定义凭据。对于其他几个存储后端也是如此,您应该点击 fsimpl1 的链接查看内置于 fsspec
的实现,以及 fsimpl2 的链接查看未包含在主 fsspec
发行版中的实现。
您还可以直接将参数传递给后端驱动程序。由于 fsspec
不使用 AWS_S3_HOST
环境变量,我们可以直接定义一个包含 endpoint_url 的字典,并将该对象传递给 storage option 参数:
storage_options = {"client_kwargs": {"endpoint_url": "http://127.0.0.1:5555"}}}
df = pd.read_json("s3://pandas-test/test-1", storage_options=storage_options)
更多示例配置和文档可以在 S3Fs 文档中找到。
如果您没有 S3 凭据,您仍然可以通过指定匿名连接来访问公共数据,例如:
在版本 1.2.0 中新增。
pd.read_csv(
"s3://ncei-wcsd-archive/data/processed/SH1305/18kHz/SaKe2013"
"-D20130523-T080854_to_SaKe2013-D20130523-T085643.csv",
storage_options={"anon": True},
)
fsspec
还允许使用复杂的 URL,用于访问压缩归档中的数据、文件的本地缓存等。要对上述示例进行本地缓存,您可以修改调用为:
pd.read_csv(
"simplecache::s3://ncei-wcsd-archive/data/processed/SH1305/18kHz/"
"SaKe2013-D20130523-T080854_to_SaKe2013-D20130523-T085643.csv",
storage_options={"s3": {"anon": True}},
)
其中我们指定“anon”参数是针对实现的“s3”部分,而不是缓存实现。请注意,这只会在会话期间缓存到临时目录,但您也可以指定一个永久存储。
写出数据#
写入 CSV 格式#
Series
和 DataFrame
对象有一个实例方法 to_csv
,它允许将对象的内容存储为逗号分隔值文件。该函数接受多个参数。只有第一个是必需的。
path_or_buf
: 写入文件的字符串路径或文件对象。如果是文件对象,必须以newline=''
打开。sep
: 输出文件的字段分隔符 (默认 “,”)na_rep
: 缺失值的字符串表示 (默认 ‘’)。float_format
: 浮点数的格式字符串。columns
: 要写入的列 (默认 None)。header
: 是否写出列名 (默认 True)。index
: 是否写出行(索引)名称 (默认 True)。index_label
: 如果需要,指定索引列的列标签。如果为 None (默认),并且header
和index
为 True,则使用索引名称。(如果DataFrame
使用 MultiIndex,应提供序列。)mode
: Python 写入模式,默认为 ‘w’。encoding
: 对于 Python 3 之前的版本,如果内容不是 ASCII,则表示要使用的编码的字符串。lineterminator
: 表示行尾的字符序列 (默认os.linesep
)。quoting
: 设置引用规则,如 csv 模块中所示 (默认 csv.QUOTE_MINIMAL)。请注意,如果您设置了float_format
,则浮点数将转换为字符串,并且 csv.QUOTE_NONNUMERIC 将把它们视为非数字。quotechar
: 用于引用字段的字符 (默认 ‘”’)。doublequote
: 控制字段中quotechar
的引用 (默认 True)。escapechar
: 在适当时候用于转义sep
和quotechar
的字符 (默认 None)。chunksize
: 每次写入的行数。date_format
: datetime 对象的格式字符串。
写入格式化字符串#
DataFrame
对象有一个实例方法 to_string
,它允许控制对象的字符串表示形式。所有参数都是可选的:
buf
默认 None,例如 StringIO 对象。columns
默认 None,要写入哪些列。col_space
默认 None,每列的最小宽度。na_rep
默认NaN
,NA 值的表示形式。formatters
默认 None,一个(按列划分的)函数字典,每个函数接受一个参数并返回格式化字符串。float_format
默认 None,一个函数,接受一个(浮点数)参数并返回格式化字符串;应用于DataFrame
中的浮点数。sparsify
默认 True,对于具有分层索引的DataFrame
,设置为 False 将在每行打印每个 MultiIndex 键。index_names
默认 True,将打印索引的名称。index
默认 True,将打印索引(即行标签)。header
默认 True,将打印列标签。justify
默认left
,将列标题左对齐或右对齐打印。
Series
对象也有一个 to_string
方法,但只有 buf
、na_rep
、float_format
参数。还有一个 length
参数,如果设置为 True
,将额外输出 Series 的长度。
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
列表格式 [{column -> value}; … ]
index
字典格式 {index -> {column -> value}}
columns
字典格式 {column -> {index -> value}}
values
仅值数组
table
遵循 JSON Table Schema
date_format
: 字符串,日期转换类型,'epoch' 表示时间戳,'iso' 表示 ISO8601。double_precision
: 编码浮点值时使用的十进制位数,默认为 10。force_ascii
: 强制编码字符串为 ASCII,默认为 True。date_unit
: 编码时使用的时间单位,控制时间戳和 ISO8601 的精度。可以是 ‘s’、‘ms’、‘us’ 或 ‘ns’,分别表示秒、毫秒、微秒和纳秒。默认为 ‘ms’。default_handler
: 如果对象无法转换为适合 JSON 的格式,则调用的处理器。接受一个参数,即要转换的对象,并返回一个可序列化的对象。lines
: 如果是records
方向,则每行写一个 JSON 记录。mode
: 字符串,写入路径时的写入模式。‘w’ 表示写入,‘a’ 表示追加。默认为 ‘w’。
注意,NaN
、NaT
和 None
将被转换为 null
,而 datetime
对象将根据 date_format
和 date_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 文件/字符串的格式有多种不同的选项。考虑以下 DataFrame
和 Series
:
In [233]: dfjo = pd.DataFrame(
.....: dict(A=range(1, 4), B=range(4, 7), C=range(7, 10)),
.....: columns=list("ABC"),
.....: index=list("xyz"),
.....: )
.....:
In [234]: dfjo
Out[234]:
A B C
x 1 4 7
y 2 5 8
z 3 6 9
In [235]: sjo = pd.Series(dict(x=15, y=16, z=17), name="D")
In [236]: sjo
Out[236]:
x 15
y 16
z 17
Name: D, dtype: int64
列方向(DataFrame
的默认方向)将数据序列化为嵌套的 JSON 对象,其中列标签作为主索引。
In [237]: dfjo.to_json(orient="columns")
Out[237]: '{"A":{"x":1,"y":2,"z":3},"B":{"x":4,"y":5,"z":6},"C":{"x":7,"y":8,"z":9}}'
# Not available for Series
索引方向(Series
的默认方向)类似于列方向,但现在索引标签是主要的。
In [238]: dfjo.to_json(orient="index")
Out[238]: '{"x":{"A":1,"B":4,"C":7},"y":{"A":2,"B":5,"C":8},"z":{"A":3,"B":6,"C":9}}'
In [239]: sjo.to_json(orient="index")
Out[239]: '{"x":15,"y":16,"z":17}'
记录方向 将数据序列化为列 -> 值记录的 JSON 数组,不包含索引标签。这对于将 DataFrame
数据传递给绘图库非常有用,例如 JavaScript 库 d3.js
。
In [240]: dfjo.to_json(orient="records")
Out[240]: '[{"A":1,"B":4,"C":7},{"A":2,"B":5,"C":8},{"A":3,"B":6,"C":9}]'
In [241]: sjo.to_json(orient="records")
Out[241]: '[15,16,17]'
值方向 是一个精简选项,仅序列化为值的嵌套 JSON 数组,不包含列和索引标签。
In [242]: dfjo.to_json(orient="values")
Out[242]: '[[1,4,7],[2,5,8],[3,6,9]]'
# Not available for Series
分割方向 序列化为一个 JSON 对象,包含值、索引和列的独立条目。对于 Series
,也包含名称。
In [243]: dfjo.to_json(orient="split")
Out[243]: '{"columns":["A","B","C"],"index":["x","y","z"],"data":[[1,4,7],[2,5,8],[3,6,9]]}'
In [244]: sjo.to_json(orient="split")
Out[244]: '{"name":"D","index":["x","y","z"],"data":[15,16,17]}'
表格方向 序列化为 JSON Table Schema,从而可以保留元数据,包括但不限于 dtypes 和索引名称。
注意
任何编码为 JSON 对象的方向选项在往返序列化期间都不会保留索引和列标签的顺序。如果您希望保留标签顺序,请使用 split
选项,因为它使用有序容器。
日期处理#
写入 ISO 日期格式
In [245]: dfd = pd.DataFrame(np.random.randn(5, 2), columns=list("AB"))
In [246]: dfd["date"] = pd.Timestamp("20130101")
In [247]: dfd = dfd.sort_index(axis=1, ascending=False)
In [248]: json = dfd.to_json(date_format="iso")
In [249]: json
Out[249]: '{"date":{"0":"2013-01-01T00:00:00.000","1":"2013-01-01T00:00:00.000","2":"2013-01-01T00:00:00.000","3":"2013-01-01T00:00:00.000","4":"2013-01-01T00:00:00.000"},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'
写入 ISO 日期格式,带微秒
In [250]: json = dfd.to_json(date_format="iso", date_unit="us")
In [251]: json
Out[251]: '{"date":{"0":"2013-01-01T00:00:00.000000","1":"2013-01-01T00:00:00.000000","2":"2013-01-01T00:00:00.000000","3":"2013-01-01T00:00:00.000000","4":"2013-01-01T00:00:00.000000"},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'
Epoch 时间戳,以秒为单位
In [252]: json = dfd.to_json(date_format="epoch", date_unit="s")
In [253]: json
Out[253]: '{"date":{"0":1,"1":1,"2":1,"3":1,"4":1},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'
写入文件,带日期索引和日期列
In [254]: dfj2 = dfj.copy()
In [255]: dfj2["date"] = pd.Timestamp("20130101")
In [256]: dfj2["ints"] = list(range(5))
In [257]: dfj2["bools"] = True
In [258]: dfj2.index = pd.date_range("20130101", periods=5)
In [259]: dfj2.to_json("test.json")
In [260]: with open("test.json") as fh:
.....: print(fh.read())
.....:
{"A":{"1356998400000":-0.1213062281,"1357084800000":0.6957746499,"1357171200000":0.9597255933,"1357257600000":-0.6199759194,"1357344000000":-0.7323393705},"B":{"1356998400000":-0.0978826728,"1357084800000":0.3417343559,"1357171200000":-1.1103361029,"1357257600000":0.1497483186,"1357344000000":0.6877383895},"date":{"1356998400000":1356,"1357084800000":1356,"1357171200000":1356,"1357257600000":1356,"1357344000000":1356},"ints":{"1356998400000":0,"1357084800000":1,"1357171200000":2,"1357257600000":3,"1357344000000":4},"bools":{"1356998400000":true,"1357084800000":true,"1357171200000":true,"1357257600000":true,"1357344000000":true}}
回退行为#
如果 JSON 序列化程序无法直接处理容器内容,它将按以下方式回退:
如果 dtype 不受支持(例如
np.complex_
),则如果提供了default_handler
,将为每个值调用它,否则将引发异常。如果对象不受支持,它将尝试以下操作:
检查对象是否定义了
toDict
方法并调用它。toDict
方法应返回一个dict
,然后将其进行 JSON 序列化。如果提供了
default_handler
,则调用它。通过遍历其内容将对象转换为
dict
。然而,这通常会因OverflowError
而失败或产生意外结果。
通常,处理不受支持的对象或 dtypes 的最佳方法是提供一个 default_handler
。例如:
>>> DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json() # raises
RuntimeError: Unhandled numpy dtype 15
可以通过指定一个简单的 default_handler
来处理:
In [261]: pd.DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json(default_handler=str)
Out[261]: '{"0":{"0":"(1+0j)","1":"(2+0j)","2":"(1+2j)"}}'
读取 JSON#
将 JSON 字符串读取到 pandas 对象可以接受多个参数。如果未提供 typ
或为 None
,解析器将尝试解析为 DataFrame
。要显式强制解析为 Series
,请传递 typ=series
。
filepath_or_buffer
: 一个 **有效** 的 JSON 字符串或文件句柄/StringIO。该字符串可以是 URL。有效的 URL scheme 包括 http、ftp、S3 和 file。对于 file URL,需要主机名。例如,本地文件可以是 file://localhost/path/to/table.json。typ
: 要恢复的对象类型 (series 或 frame),默认为 ‘frame’。orient
:- Series
默认是
index
允许的值是 {
split
,records
,index
}
- DataFrame
默认是
columns
允许的值是 {
split
,records
,index
,columns
,values
,table
}
JSON 字符串的格式:
split
字典格式 {index -> [index]; columns -> [columns]; data -> [values]}
records
列表格式 [{column -> value} …]
index
字典格式 {index -> {column -> value}}
columns
字典格式 {column -> {index -> value}}
values
仅值数组
table
遵循 JSON Table Schema
dtype
: 如果为 True,推断 dtypes;如果是列到 dtype 的字典,则使用这些 dtype;如果为False
,则完全不推断 dtypes。默认为 True,仅应用于数据。convert_axes
: 布尔值,尝试将轴转换为适当的 dtypes,默认为True
。convert_dates
: 要解析日期的列列表;如果为True
,则尝试解析日期格式的列。默认为True
。keep_default_dates
: 布尔值,默认为True
。如果解析日期,则解析默认的日期格式列。precise_float
: 布尔值,默认为False
。设置为 True 时,解码字符串为双精度值时使用精度更高的 (strtod) 函数。默认值 (False
) 是使用快速但不精确的内置功能。date_unit
: 字符串,如果转换日期,要检测的时间戳单位。默认为 None。默认情况下将检测时间戳精度,如果不需要,则传递 ‘s’、‘ms’、‘us’ 或 ‘ns’ 之一,以分别强制时间戳精度为秒、毫秒、微秒或纳秒。lines
: 将文件按每行一个 json 对象读取。encoding
: 用于解码 py3 字节的编码。chunksize
: 当与lines=True
结合使用时,返回一个pandas.api.typing.JsonReader
,它每次迭代读取chunksize
行。engine
: 可以是 `"ujson"`(内置的 JSON 解析器)、或 `"pyarrow"`(调用 pyarrow 的pyarrow.json.read_json
)。`"pyarrow"` 仅在lines=True
时可用。
如果 JSON 无法解析,解析器将引发 ValueError/TypeError/AssertionError
中的一个。
如果在编码 JSON 时使用了非默认的 orient
,请务必在此处传递相同的选项,以便解码产生合理的结果。请参阅方向选项了解概览。
数据转换#
默认的 convert_axes=True
、dtype=True
和 convert_dates=True
将尝试将轴和所有数据解析为适当的类型,包括日期。如果您需要覆盖特定的 dtypes,请将字典传递给 dtype
。只有在需要保留轴中的字符串数字(例如 ‘1’、‘2’)时,才应将 convert_axes
设置为 False
。
注意
如果 convert_dates=True
并且数据和/或列标签看起来像“日期”,大型整数值可能会转换为日期。确切的阈值取决于指定的 date_unit
。“日期格式”意味着列标签符合以下条件之一:
以
'_at'
结尾以
'_time'
结尾以
'timestamp'
开头是
'modified'
是
'date'
警告
读取 JSON 数据时,自动强制转换为 dtypes 有一些特性:
索引可以以与序列化不同的顺序重建,也就是说,不保证返回的顺序与序列化之前相同。
原本为
float
数据的列,如果可以安全转换(例如,一个值为1.
的列),将被转换为integer
。bool 列在重建时将转换为
integer
。
因此,有时您可能希望通过 dtype
关键字参数指定特定的 dtypes。
从 JSON 字符串读取
In [262]: from io import StringIO
In [263]: pd.read_json(StringIO(json))
Out[263]:
date B A
0 1 0.403310 0.176444
1 1 0.301624 -0.154951
2 1 -1.369849 -2.179861
3 1 1.462696 -0.954208
4 1 -0.826591 -1.743161
从文件读取
In [264]: pd.read_json("test.json")
Out[264]:
A B date ints bools
2013-01-01 -0.121306 -0.097883 1356 0 True
2013-01-02 0.695775 0.341734 1356 1 True
2013-01-03 0.959726 -1.110336 1356 2 True
2013-01-04 -0.619976 0.149748 1356 3 True
2013-01-05 -0.732339 0.687738 1356 4 True
不转换任何数据(但仍转换轴和日期)
In [265]: pd.read_json("test.json", dtype=object).dtypes
Out[265]:
A object
B object
date object
ints object
bools object
dtype: object
指定用于转换的 dtypes
In [266]: pd.read_json("test.json", dtype={"A": "float32", "bools": "int8"}).dtypes
Out[266]:
A float32
B float64
date int64
ints int64
bools int8
dtype: object
保留字符串索引
In [267]: from io import StringIO
In [268]: si = pd.DataFrame(
.....: np.zeros((4, 4)), columns=list(range(4)), index=[str(i) for i in range(4)]
.....: )
.....:
In [269]: si
Out[269]:
0 1 2 3
0 0.0 0.0 0.0 0.0
1 0.0 0.0 0.0 0.0
2 0.0 0.0 0.0 0.0
3 0.0 0.0 0.0 0.0
In [270]: si.index
Out[270]: Index(['0', '1', '2', '3'], dtype='object')
In [271]: si.columns
Out[271]: Index([0, 1, 2, 3], dtype='int64')
In [272]: json = si.to_json()
In [273]: sij = pd.read_json(StringIO(json), convert_axes=False)
In [274]: sij
Out[274]:
0 1 2 3
0 0 0 0 0
1 0 0 0 0
2 0 0 0 0
3 0 0 0 0
In [275]: sij.index
Out[275]: Index(['0', '1', '2', '3'], dtype='object')
In [276]: sij.columns
Out[276]: Index(['0', '1', '2', '3'], dtype='object')
以纳秒写入的日期需要以纳秒读取回来
In [277]: from io import StringIO
In [278]: json = dfj2.to_json(date_unit="ns")
# Try to parse timestamps as milliseconds -> Won't Work
In [279]: dfju = pd.read_json(StringIO(json), date_unit="ms")
In [280]: dfju
Out[280]:
A B date ints bools
1356998400000000000 -0.121306 -0.097883 1356998400 0 True
1357084800000000000 0.695775 0.341734 1356998400 1 True
1357171200000000000 0.959726 -1.110336 1356998400 2 True
1357257600000000000 -0.619976 0.149748 1356998400 3 True
1357344000000000000 -0.732339 0.687738 1356998400 4 True
# Let pandas detect the correct precision
In [281]: dfju = pd.read_json(StringIO(json))
In [282]: dfju
Out[282]:
A B date ints bools
2013-01-01 -0.121306 -0.097883 2013-01-01 0 True
2013-01-02 0.695775 0.341734 2013-01-01 1 True
2013-01-03 0.959726 -1.110336 2013-01-01 2 True
2013-01-04 -0.619976 0.149748 2013-01-01 3 True
2013-01-05 -0.732339 0.687738 2013-01-01 4 True
# Or specify that all timestamps are in nanoseconds
In [283]: dfju = pd.read_json(StringIO(json), date_unit="ns")
In [284]: dfju
Out[284]:
A B date ints bools
2013-01-01 -0.121306 -0.097883 1356998400 0 True
2013-01-02 0.695775 0.341734 1356998400 1 True
2013-01-03 0.959726 -1.110336 1356998400 2 True
2013-01-04 -0.619976 0.149748 1356998400 3 True
2013-01-05 -0.732339 0.687738 1356998400 4 True
通过设置 dtype_backend
参数,您可以控制结果 DataFrame 使用的默认 dtypes。
In [285]: data = (
.....: '{"a":{"0":1,"1":3},"b":{"0":2.5,"1":4.5},"c":{"0":true,"1":false},"d":{"0":"a","1":"b"},'
.....: '"e":{"0":null,"1":6.0},"f":{"0":null,"1":7.5},"g":{"0":null,"1":true},"h":{"0":null,"1":"a"},'
.....: '"i":{"0":"12-31-2019","1":"12-31-2019"},"j":{"0":null,"1":null}}'
.....: )
.....:
In [286]: df = pd.read_json(StringIO(data), dtype_backend="pyarrow")
In [287]: df
Out[287]:
a b c d e f g h i j
0 1 2.5 True a <NA> <NA> <NA> <NA> 12-31-2019 None
1 3 4.5 False b 6 7.5 True a 12-31-2019 None
In [288]: df.dtypes
Out[288]:
a int64[pyarrow]
b double[pyarrow]
c bool[pyarrow]
d string[pyarrow]
e int64[pyarrow]
f double[pyarrow]
g bool[pyarrow]
h string[pyarrow]
i string[pyarrow]
j null[pyarrow]
dtype: object
规范化#
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)中很常见。
对于行分隔的 JSON 文件,pandas 还可以返回一个迭代器,它一次读取 chunksize
行。这对于处理大型文件或从流中读取非常有用。
In [295]: from io import StringIO
In [296]: jsonl = """
.....: {"a": 1, "b": 2}
.....: {"a": 3, "b": 4}
.....: """
.....:
In [297]: df = pd.read_json(StringIO(jsonl), lines=True)
In [298]: df
Out[298]:
a b
0 1 2
1 3 4
In [299]: df.to_json(orient="records", lines=True)
Out[299]: '{"a":1,"b":2}\n{"a":3,"b":4}\n'
# reader is an iterator that returns ``chunksize`` lines each iteration
In [300]: with pd.read_json(StringIO(jsonl), lines=True, chunksize=1) as reader:
.....: reader
.....: for chunk in reader:
.....: print(chunk)
.....:
Empty DataFrame
Columns: []
Index: []
a b
0 1 2
a b
1 3 4
还可以通过指定 engine="pyarrow"
使用 pyarrow 读取器读取行分隔的 JSON。
In [301]: from io import BytesIO
In [302]: df = pd.read_json(BytesIO(jsonl.encode()), lines=True, engine="pyarrow")
In [303]: df
Out[303]:
a b
0 1 2
1 3 4
2.0.0 版本新增。
表格 schema#
Table Schema 是一个用于将表格数据集描述为 JSON 对象的规范。JSON 包含字段名称、类型和其他属性的信息。您可以使用 table
方向构建一个包含两个字段 schema
和 data
的 JSON 字符串。
In [304]: df = pd.DataFrame(
.....: {
.....: "A": [1, 2, 3],
.....: "B": ["a", "b", "c"],
.....: "C": pd.date_range("2016-01-01", freq="d", periods=3),
.....: },
.....: index=pd.Index(range(3), name="idx"),
.....: )
.....:
In [305]: df
Out[305]:
A B C
idx
0 1 a 2016-01-01
1 2 b 2016-01-02
2 3 c 2016-01-03
In [306]: df.to_json(orient="table", date_format="iso")
Out[306]: '{"schema":{"fields":[{"name":"idx","type":"integer"},{"name":"A","type":"integer"},{"name":"B","type":"string"},{"name":"C","type":"datetime"}],"primaryKey":["idx"],"pandas_version":"1.4.0"},"data":[{"idx":0,"A":1,"B":"a","C":"2016-01-01T00:00:00.000"},{"idx":1,"A":2,"B":"b","C":"2016-01-02T00:00:00.000"},{"idx":2,"A":3,"B":"c","C":"2016-01-03T00:00:00.000"}]}'
schema
字段包含 fields
键,该键本身包含一个列名到类型对的列表,包括 Index
或 MultiIndex
(类型列表见下文)。如果(Multi)索引是唯一的,schema
字段还包含 primaryKey
字段。
第二个字段 data
包含使用 records
方向序列化的数据。索引包含在内,并且所有日期时间都按照 Table Schema 规范的要求以 ISO 8601 格式化。
Table Schema 规范中描述了支持的完整类型列表。下表显示了 pandas 类型到 Table Schema 类型的映射:
pandas 类型 |
Table Schema 类型 |
---|---|
int64 |
integer (整数) |
float64 |
number (数值) |
bool |
boolean (布尔值) |
datetime64[ns] |
datetime (日期时间) |
timedelta64[ns] |
duration (持续时间) |
categorical |
any (任意类型) |
object |
str (字符串) |
关于生成的表格 schema 的几点注意事项:
schema
对象包含一个pandas_version
字段。这包含 schema 的 pandas 方言版本,每次修订都会增加。所有日期在序列化时都转换为 UTC。即使是时区不敏感的值,也被视为 UTC,偏移量为 0。
In [307]: from pandas.io.json import build_table_schema In [308]: s = pd.Series(pd.date_range("2016", periods=4)) In [309]: build_table_schema(s) Out[309]: {'fields': [{'name': 'index', 'type': 'integer'}, {'name': 'values', 'type': 'datetime'}], 'primaryKey': ['index'], 'pandas_version': '1.4.0'}
带有(序列化前的)时区的日期时间,包含一个额外的字段
tz
,以及时区名称(例如'US/Central'
)。In [310]: s_tz = pd.Series(pd.date_range("2016", periods=12, tz="US/Central")) In [311]: build_table_schema(s_tz) Out[311]: {'fields': [{'name': 'index', 'type': 'integer'}, {'name': 'values', 'type': 'datetime', 'tz': 'US/Central'}], 'primaryKey': ['index'], 'pandas_version': '1.4.0'}
Periods 在序列化之前转换为时间戳,因此具有转换为 UTC 的相同行为。此外,periods 将包含一个额外的字段
freq
,以及 period 的频率,例如'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'}
默认命名大致遵循以下规则
对于 Series,使用
object.name
。如果为空,则名称为values
。对于
DataFrames
,使用列名的字符串化版本。对于
Index
(不是MultiIndex
),使用index.name
,如果为空则回退到index
。对于
MultiIndex
,使用mi.names
。如果任何级别没有名称,则使用level_<i>
。
read_json
也接受参数 orient='table'
。这允许以可往返的方式保留元数据,例如 dtypes 和索引名称。
In [320]: df = pd.DataFrame(
.....: {
.....: "foo": [1, 2, 3, 4],
.....: "bar": ["a", "b", "c", "d"],
.....: "baz": pd.date_range("2018-01-01", freq="d", periods=4),
.....: "qux": pd.Categorical(["a", "b", "c", "c"]),
.....: },
.....: index=pd.Index(range(4), name="idx"),
.....: )
.....:
In [321]: df
Out[321]:
foo bar baz qux
idx
0 1 a 2018-01-01 a
1 2 b 2018-01-02 b
2 3 c 2018-01-03 c
3 4 d 2018-01-04 c
In [322]: df.dtypes
Out[322]:
foo int64
bar object
baz datetime64[ns]
qux category
dtype: object
In [323]: df.to_json("test.json", orient="table")
In [324]: new_df = pd.read_json("test.json", orient="table")
In [325]: new_df
Out[325]:
foo bar baz qux
idx
0 1 a 2018-01-01 a
1 2 b 2018-01-02 b
2 3 c 2018-01-03 c
3 4 d 2018-01-04 c
In [326]: new_df.dtypes
Out[326]:
foo int64
bar object
baz datetime64[ns]
qux category
dtype: object
请注意,将字面字符串“index”用作 Index
的名称不可往返,MultiIndex
中以 'level_'
开头的任何名称也一样。DataFrame.to_json()
默认使用这些名称来表示缺失值,后续读取无法区分意图。
In [327]: df.index.name = "index"
In [328]: df.to_json("test.json", orient="table")
In [329]: new_df = pd.read_json("test.json", orient="table")
In [330]: print(new_df.index.name)
None
当与用户定义的 ExtensionArray
一起使用 orient='table'
时,生成的模式将在相应的 fields
元素中包含一个额外的 extDtype
键。这个额外的键不是标准键,但它支持扩展类型的 JSON 往返(例如 read_json(df.to_json(orient="table"), orient="table")
)。
extDtype
键携带扩展的名称,如果您已正确注册 ExtensionDtype
,pandas 将使用该名称在注册表中查找并重新将序列化数据转换为您的自定义 dtype。
HTML#
读取 HTML 内容#
警告
我们**强烈建议**您阅读下面的 HTML 表格解析陷阱,了解有关 BeautifulSoup4/html5lib/lxml 解析器的问题。
顶层函数 read_html()
可以接受 HTML 字符串/文件/URL,并将 HTML 表格解析为 pandas DataFrames
列表。让我们看几个例子。
注意
read_html
返回一个 DataFrame
对象 list
,即使 HTML 内容中只包含一个表格。
读取不带选项的 URL
In [320]: url = "https://www.fdic.gov/resources/resolutions/bank-failures/failed-bank-list"
In [321]: pd.read_html(url)
Out[321]:
[ Bank NameBank CityCity StateSt ... Acquiring InstitutionAI Closing DateClosing FundFund
0 Almena State Bank Almena KS ... Equity Bank October 23, 2020 10538
1 First City Bank of Florida Fort Walton Beach FL ... United Fidelity Bank, fsb October 16, 2020 10537
2 The First State Bank Barboursville WV ... MVB Bank, Inc. April 3, 2020 10536
3 Ericson State Bank Ericson NE ... Farmers and Merchants Bank February 14, 2020 10535
4 City National Bank of New Jersey Newark NJ ... Industrial Bank November 1, 2019 10534
.. ... ... ... ... ... ... ...
558 Superior Bank, FSB Hinsdale IL ... Superior Federal, FSB July 27, 2001 6004
559 Malta National Bank Malta OH ... North Valley Bank May 3, 2001 4648
560 First Alliance Bank & Trust Co. Manchester NH ... Southern New Hampshire Bank & Trust February 2, 2001 4647
561 National State Bank of Metropolis Metropolis IL ... Banterra Bank of Marion December 14, 2000 4646
562 Bank of Honolulu Honolulu HI ... Bank of the Orient October 13, 2000 4645
[563 rows x 7 columns]]
注意
上述 URL 中的数据每周一都会更改,因此上面的结果数据可能会略有不同。
读取 URL 并随 HTTP 请求传递头部信息
In [322]: url = 'https://www.sump.org/notes/request/' # HTTP request reflector
In [323]: pd.read_html(url)
Out[323]:
[ 0 1
0 Remote Socket: 51.15.105.256:51760
1 Protocol Version: HTTP/1.1
2 Request Method: GET
3 Request URI: /notes/request/
4 Request Query: NaN,
0 Accept-Encoding: identity
1 Host: www.sump.org
2 User-Agent: Python-urllib/3.8
3 Connection: close]
In [324]: headers = {
In [325]: 'User-Agent':'Mozilla Firefox v14.0',
In [326]: 'Accept':'application/json',
In [327]: 'Connection':'keep-alive',
In [328]: 'Auth':'Bearer 2*/f3+fe68df*4'
In [329]: }
In [340]: pd.read_html(url, storage_options=headers)
Out[340]:
[ 0 1
0 Remote Socket: 51.15.105.256:51760
1 Protocol Version: HTTP/1.1
2 Request Method: GET
3 Request URI: /notes/request/
4 Request Query: NaN,
0 User-Agent: Mozilla Firefox v14.0
1 AcceptEncoding: gzip, deflate, br
2 Accept: application/json
3 Connection: keep-alive
4 Auth: Bearer 2*/f3+fe68df*4]
注意
我们在上面看到,我们传递的头部信息反映在 HTTP 请求中。
读取上述 URL 文件中的内容,并将其作为字符串传递给 read_html
In [331]: html_str = """
.....: <table>
.....: <tr>
.....: <th>A</th>
.....: <th colspan="1">B</th>
.....: <th rowspan="1">C</th>
.....: </tr>
.....: <tr>
.....: <td>a</td>
.....: <td>b</td>
.....: <td>c</td>
.....: </tr>
.....: </table>
.....: """
.....:
In [332]: with open("tmp.html", "w") as f:
.....: f.write(html_str)
.....:
In [333]: df = pd.read_html("tmp.html")
In [334]: df[0]
Out[334]:
A B C
0 a b c
如果您愿意,甚至可以传入一个 StringIO
实例
In [335]: dfs = pd.read_html(StringIO(html_str))
In [336]: dfs[0]
Out[336]:
A B C
0 a b c
注意
以下示例未由 IPython 评估器运行,因为有太多网络访问函数会减慢文档构建速度。如果您发现错误或无法运行的示例,请随时在 pandas GitHub issues page 上报告。
读取 URL 并匹配包含特定文本的表格
match = "Metcalf Bank"
df_list = pd.read_html(url, match=match)
指定头部行(默认情况下,使用位于 <thead>
中的 <th>
或 <td>
元素来形成列索引,如果 <thead>
中包含多行,则创建一个 MultiIndex);如果指定,则从数据中减去已解析的头部元素(<th>
元素)来获取头部行。
dfs = pd.read_html(url, header=0)
指定索引列
dfs = pd.read_html(url, index_col=0)
指定跳过的行数
dfs = pd.read_html(url, skiprows=0)
使用列表指定要跳过的行数(range
也适用)
dfs = pd.read_html(url, skiprows=range(2))
指定 HTML 属性
dfs1 = pd.read_html(url, attrs={"id": "table"})
dfs2 = pd.read_html(url, attrs={"class": "sortable"})
print(np.array_equal(dfs1[0], dfs2[0])) # Should be True
指定应转换为 NaN 的值
dfs = pd.read_html(url, na_values=["No Acquirer"])
指定是否保留默认的 NaN 值集合
dfs = pd.read_html(url, keep_default_na=False)
为列指定转换器。这对于带有前导零的数字文本数据很有用。默认情况下,数字列会被转换为数字类型,前导零会丢失。为了避免这种情况,我们可以将这些列转换为字符串。
url_mcc = "https://en.wikipedia.org/wiki/Mobile_country_code?oldid=899173761"
dfs = pd.read_html(
url_mcc,
match="Telekom Albania",
header=0,
converters={"MNC": str},
)
使用上述方法的一些组合
dfs = pd.read_html(url, match="Metcalf Bank", index_col=0)
读取 pandas to_html
输出(会损失一些浮点精度)
df = pd.DataFrame(np.random.randn(2, 2))
s = df.to_html(float_format="{0:.40g}".format)
dfin = pd.read_html(s, index_col=0)
如果 `lxml` 后端是你提供的唯一解析器,它将在解析失败时引发错误。如果你只有一个解析器,可以直接提供一个字符串,但如果函数期望一个字符串序列,传入一个包含一个字符串的列表被认为是良好的实践。你可以使用
dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor=["lxml"])
或者你可以不使用列表,直接传递 flavor='lxml'
dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor="lxml")
然而,如果你安装了 bs4 和 html5lib 并传入 None
或 ['lxml', 'bs4']
,解析很可能会成功。请注意,*一旦解析成功,函数将立即返回*。
dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor=["lxml", "bs4"])
使用 extract_links="all"
可以从单元格中提取链接以及文本。
In [337]: html_table = """
.....: <table>
.....: <tr>
.....: <th>GitHub</th>
.....: </tr>
.....: <tr>
.....: <td><a href="https://github.com/pandas-dev/pandas">pandas</a></td>
.....: </tr>
.....: </table>
.....: """
.....:
In [338]: df = pd.read_html(
.....: StringIO(html_table),
.....: extract_links="all"
.....: )[0]
.....:
In [339]: df
Out[339]:
(GitHub, None)
0 (pandas, https://github.com/pandas-dev/pandas)
In [340]: df[("GitHub", None)]
Out[340]:
0 (pandas, https://github.com/pandas-dev/pandas)
Name: (GitHub, None), dtype: object
In [341]: df[("GitHub", None)].str[1]
Out[341]:
0 https://github.com/pandas-dev/pandas
Name: (GitHub, None), dtype: object
1.5.0 版本新增。
写入 HTML 文件#
DataFrame
对象有一个实例方法 to_html
,它将 DataFrame
的内容渲染为 HTML 表格。函数参数与上面描述的 to_string
方法中的参数相同。
注意
出于简洁考虑,此处并未展示 DataFrame.to_html
的所有可能选项。请参阅 DataFrame.to_html()
以获取完整的选项集。
注意
在支持 HTML 渲染的环境(例如 Jupyter Notebook)中,display(HTML(...))`
会将原始 HTML 渲染到环境中。
In [342]: from IPython.display import display, HTML
In [343]: df = pd.DataFrame(np.random.randn(2, 2))
In [344]: df
Out[344]:
0 1
0 -0.345352 1.314232
1 0.690579 0.995761
In [345]: html = df.to_html()
In [346]: print(html) # raw html
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>-0.345352</td>
<td>1.314232</td>
</tr>
<tr>
<th>1</th>
<td>0.690579</td>
<td>0.995761</td>
</tr>
</tbody>
</table>
In [347]: display(HTML(html))
<IPython.core.display.HTML object>
columns
参数将限制显示的列
In [348]: html = df.to_html(columns=[0])
In [349]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>-0.345352</td>
</tr>
<tr>
<th>1</th>
<td>0.690579</td>
</tr>
</tbody>
</table>
In [350]: display(HTML(html))
<IPython.core.display.HTML object>
float_format
接受一个 Python 可调用对象,用于控制浮点值的精度
In [351]: html = df.to_html(float_format="{0:.10f}".format)
In [352]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>-0.3453521949</td>
<td>1.3142323796</td>
</tr>
<tr>
<th>1</th>
<td>0.6905793352</td>
<td>0.9957609037</td>
</tr>
</tbody>
</table>
In [353]: display(HTML(html))
<IPython.core.display.HTML object>
bold_rows
默认会将行标签加粗,但你可以关闭此功能
In [354]: html = df.to_html(bold_rows=False)
In [355]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>-0.345352</td>
<td>1.314232</td>
</tr>
<tr>
<td>1</td>
<td>0.690579</td>
<td>0.995761</td>
</tr>
</tbody>
</table>
In [356]: display(HTML(html))
<IPython.core.display.HTML object>
classes
参数允许为生成的 HTML 表格指定 CSS 类。请注意,这些类将*附加*到现有的 'dataframe'
类之后。
In [357]: print(df.to_html(classes=["awesome_table_class", "even_more_awesome_class"]))
<table border="1" class="dataframe awesome_table_class even_more_awesome_class">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>-0.345352</td>
<td>1.314232</td>
</tr>
<tr>
<th>1</th>
<td>0.690579</td>
<td>0.995761</td>
</tr>
</tbody>
</table>
render_links
参数允许为包含 URL 的单元格添加超链接。
In [358]: url_df = pd.DataFrame(
.....: {
.....: "name": ["Python", "pandas"],
.....: "url": ["https://pythonlang.cn/", "https://pandas.ac.cn"],
.....: }
.....: )
.....:
In [359]: html = url_df.to_html(render_links=True)
In [360]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>name</th>
<th>url</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>Python</td>
<td><a href="https://pythonlang.cn/" target="_blank">https://pythonlang.cn/</a></td>
</tr>
<tr>
<th>1</th>
<td>pandas</td>
<td><a href="https://pandas.ac.cn" target="_blank">https://pandas.ac.cn</a></td>
</tr>
</tbody>
</table>
In [361]: display(HTML(html))
<IPython.core.display.HTML object>
最后,escape
参数允许你控制结果 HTML 中是否转义“<”、“>”和“&”字符(默认值为 True
)。因此,要获取不含转义字符的 HTML,请传递 escape=False
In [362]: df = pd.DataFrame({"a": list("&<>"), "b": np.random.randn(3)})
已转义
In [363]: html = df.to_html()
In [364]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>&</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 [365]: display(HTML(html))
<IPython.core.display.HTML object>
未转义
In [366]: html = df.to_html(escape=False)
In [367]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>&</td>
<td>2.396780</td>
</tr>
<tr>
<th>1</th>
<td><</td>
<td>0.014871</td>
</tr>
<tr>
<th>2</th>
<td>></td>
<td>3.357427</td>
</tr>
</tbody>
</table>
In [368]: display(HTML(html))
<IPython.core.display.HTML object>
注意
某些浏览器可能不会显示前两个 HTML 表格渲染之间的差异。
HTML 表格解析陷阱#
在顶层 pandas io 函数 read_html
中用于解析 HTML 表格的库存在一些版本问题。
**`lxml` 的问题****lxml**
优点
缺点
**lxml** *不*对其解析结果做任何保证,*除非*给定的是 **严格有效的标记**。
鉴于上述情况,我们选择允许用户使用 **lxml** 后端,但如果 **lxml** 解析失败,**此后端将使用** **html5lib**。
因此,*强烈建议*您同时安装 **BeautifulSoup4** 和 **html5lib**,这样即使 **lxml** 失败,您仍然能获得有效结果(前提是其他部分也是有效的)。
**使用** **lxml** **作为后端的** **BeautifulSoup4** **的问题**
由于 **BeautifulSoup4** 本质上只是解析器后端的一个包装器,上述问题在这里也适用。
**使用** **html5lib** **作为后端的** **BeautifulSoup4** **的问题**
优点
**html5lib** 比 **lxml** 宽松得多,因此以一种更合理的方式处理*现实中的标记*,而不是仅仅,例如,不通知你就丢弃一个元素。
**html5lib** *自动从无效标记中生成有效的 HTML5 标记*。这对于解析 HTML 表格非常重要,因为它保证了有效文档。然而,这并不意味着它是“正确”的,因为修复标记的过程没有唯一的定义。
**html5lib** 是纯 Python 库,除了自身安装外不需要额外的构建步骤。
缺点
使用 **html5lib** 最大的缺点是它慢得像糖浆。然而,考虑到网络上的许多表格并不大,解析算法的运行时并不重要。瓶颈更有可能出现在通过网络从 URL 读取原始文本的过程,即 IO(输入-输出)。对于非常大的表格,情况可能并非如此。
LaTeX#
在版本 1.3.0 中新增。
目前没有从 LaTeX 读取的方法,只有输出方法。
写入 LaTeX 文件#
注意
DataFrame
*和* Styler
对象目前都有一个 to_latex
方法。由于前者在条件样式方面具有更大的灵活性,并且后者将来可能会弃用,我们建议使用 Styler.to_latex() 方法而非 DataFrame.to_latex()。
查看 Styler.to_latex 的文档,其中提供了条件样式的示例并解释了其关键字参数的作用。
对于简单的应用,以下模式就足够了。
In [369]: df = pd.DataFrame([[1, 2], [3, 4]], index=["a", "b"], columns=["c", "d"])
In [370]: print(df.style.to_latex())
\begin{tabular}{lrr}
& c & d \\
a & 1 & 2 \\
b & 3 & 4 \\
\end{tabular}
在输出前格式化值,可以链式调用 Styler.format 方法。
In [371]: print(df.style.format("€ {}").to_latex())
\begin{tabular}{lrr}
& c & d \\
a & € 1 & € 2 \\
b & € 3 & € 4 \\
\end{tabular}
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://w3schools.org.cn/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”的内容作为 StringIO
或 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 存储桶中读取 XML,例如提供生物医学和生命科学期刊的 NIH NCBI PMC 文章数据集
In [388]: df = pd.read_xml(
.....: "s3://pmc-oa-opendata/oa_comm/xml/all/PMC1236943.xml",
.....: xpath=".//journal-meta",
.....: )
.....:
In [389]: df
Out[389]:
journal-id journal-title issn publisher
0 Cardiovasc Ultrasound Cardiovascular Ultrasound 1476-7120 NaN
使用 lxml 作为默认 parser
,您可以访问功能齐全的 XML 库,该库扩展了 Python 的 ElementTree API。一个强大的工具是使用更具表达力的 XPath 有选择地或有条件地查询节点的能力。
In [390]: df = pd.read_xml(file_path, xpath="//book[year=2005]")
In [391]: df
Out[391]:
category title author year price
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00
1 children Harry Potter J K. Rowling 2005 29.99
指定只解析元素或只解析属性
In [392]: df = pd.read_xml(file_path, elems_only=True)
In [393]: df
Out[393]:
title author year price
0 Everyday Italian Giada De Laurentiis 2005 30.00
1 Harry Potter J K. Rowling 2005 29.99
2 Learning XML Erik T. Ray 2003 39.95
In [394]: df = pd.read_xml(file_path, attrs_only=True)
In [395]: df
Out[395]:
category
0 cooking
1 children
2 web
XML 文档可以包含带有前缀的命名空间和不带前缀的默认命名空间,两者都使用特殊属性 xmlns
表示。为了在命名空间上下文中按节点解析,xpath
必须引用前缀。
例如,下面的 XML 包含一个带有前缀 doc
和 URI https://example.com
的命名空间。为了解析 doc:row
节点,必须使用 namespaces
。
In [396]: xml = """<?xml version='1.0' encoding='utf-8'?>
.....: <doc:data xmlns:doc="https://example.com">
.....: <doc:row>
.....: <doc:shape>square</doc:shape>
.....: <doc:degrees>360</doc:degrees>
.....: <doc:sides>4.0</doc:sides>
.....: </doc:row>
.....: <doc:row>
.....: <doc:shape>circle</doc:shape>
.....: <doc:degrees>360</doc:degrees>
.....: <doc:sides/>
.....: </doc:row>
.....: <doc:row>
.....: <doc:shape>triangle</doc:shape>
.....: <doc:degrees>180</doc:degrees>
.....: <doc:sides>3.0</doc:sides>
.....: </doc:row>
.....: </doc:data>"""
.....:
In [397]: df = pd.read_xml(StringIO(xml),
.....: xpath="//doc:row",
.....: namespaces={"doc": "https://example.com"})
.....:
In [398]: df
Out[398]:
shape degrees sides
0 square 360 4.0
1 circle 360 NaN
2 triangle 180 3.0
类似地,XML 文档可以包含不带前缀的默认命名空间。如果未能分配临时前缀,将不会返回任何节点并引发 ValueError
。但是将*任意*临时名称分配给正确的 URI 就可以按节点进行解析。
In [399]: xml = """<?xml version='1.0' encoding='utf-8'?>
.....: <data xmlns="https://example.com">
.....: <row>
.....: <shape>square</shape>
.....: <degrees>360</degrees>
.....: <sides>4.0</sides>
.....: </row>
.....: <row>
.....: <shape>circle</shape>
.....: <degrees>360</degrees>
.....: <sides/>
.....: </row>
.....: <row>
.....: <shape>triangle</shape>
.....: <degrees>180</degrees>
.....: <sides>3.0</sides>
.....: </row>
.....: </data>"""
.....:
In [400]: df = pd.read_xml(StringIO(xml),
.....: xpath="//pandas:row",
.....: namespaces={"pandas": "https://example.com"})
.....:
In [401]: df
Out[401]:
shape degrees sides
0 square 360 4.0
1 circle 360 NaN
2 triangle 180 3.0
但是,如果 XPath 不引用默认的节点名称,例如 /*
,则不需要 namespaces
。
注意
由于 xpath
标识了要解析内容的父级,因此只解析直接后代,包括子节点或当前属性。因此,read_xml
不会解析孙子节点或其他后代的文本,也不会解析任何后代的属性。要检索较低级别的内容,请调整 xpath 到较低级别。例如,
In [402]: xml = """
.....: <data>
.....: <row>
.....: <shape sides="4">square</shape>
.....: <degrees>360</degrees>
.....: </row>
.....: <row>
.....: <shape sides="0">circle</shape>
.....: <degrees>360</degrees>
.....: </row>
.....: <row>
.....: <shape sides="3">triangle</shape>
.....: <degrees>180</degrees>
.....: </row>
.....: </data>"""
.....:
In [403]: df = pd.read_xml(StringIO(xml), xpath="./row")
In [404]: df
Out[404]:
shape degrees
0 square 360
1 circle 360
2 triangle 180
显示 shape
元素上的属性 sides
未按预期解析,因为该属性位于 row
元素的子级上,而不是 row
元素本身。换句话说,sides
属性是 row
元素的孙子级别后代。然而,xpath
目标是 row
元素,它只包含其子级和属性。
使用 lxml 作为解析器,您可以使用 XSLT 脚本展平嵌套的 XML 文档,该脚本也可以是字符串/文件/URL 类型。背景知识:XSLT 是一种特殊用途的语言,编写在一个特殊的 XML 文件中,可以使用 XSLT 处理器将原始 XML 文档转换为其他 XML、HTML,甚至文本(CSV、JSON 等)。
例如,考虑芝加哥“L”交通的这种稍微嵌套的结构,其中站和乘坐元素将数据封装在各自的部分中。使用下面的 XSLT,lxml
可以将原始嵌套文档转换为更扁平的输出(如下所示用于演示),以便更容易解析到 DataFrame
中。
In [405]: xml = """<?xml version='1.0' encoding='utf-8'?>
.....: <response>
.....: <row>
.....: <station id="40850" name="Library"/>
.....: <month>2020-09-01T00:00:00</month>
.....: <rides>
.....: <avg_weekday_rides>864.2</avg_weekday_rides>
.....: <avg_saturday_rides>534</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
.....: </rides>
.....: </row>
.....: <row>
.....: <station id="41700" name="Washington/Wabash"/>
.....: <month>2020-09-01T00:00:00</month>
.....: <rides>
.....: <avg_weekday_rides>2707.4</avg_weekday_rides>
.....: <avg_saturday_rides>1909.8</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
.....: </rides>
.....: </row>
.....: <row>
.....: <station id="40380" name="Clark/Lake"/>
.....: <month>2020-09-01T00:00:00</month>
.....: <rides>
.....: <avg_weekday_rides>2949.6</avg_weekday_rides>
.....: <avg_saturday_rides>1657</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
.....: </rides>
.....: </row>
.....: </response>"""
.....:
In [406]: xsl = """<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
.....: <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
.....: <xsl:strip-space elements="*"/>
.....: <xsl:template match="/response">
.....: <xsl:copy>
.....: <xsl:apply-templates select="row"/>
.....: </xsl:copy>
.....: </xsl:template>
.....: <xsl:template match="row">
.....: <xsl:copy>
.....: <station_id><xsl:value-of select="station/@id"/></station_id>
.....: <station_name><xsl:value-of select="station/@name"/></station_name>
.....: <xsl:copy-of select="month|rides/*"/>
.....: </xsl:copy>
.....: </xsl:template>
.....: </xsl:stylesheet>"""
.....:
In [407]: output = """<?xml version='1.0' encoding='utf-8'?>
.....: <response>
.....: <row>
.....: <station_id>40850</station_id>
.....: <station_name>Library</station_name>
.....: <month>2020-09-01T00:00:00</month>
.....: <avg_weekday_rides>864.2</avg_weekday_rides>
.....: <avg_saturday_rides>534</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
.....: </row>
.....: <row>
.....: <station_id>41700</station_id>
.....: <station_name>Washington/Wabash</station_name>
.....: <month>2020-09-01T00:00:00</month>
.....: <avg_weekday_rides>2707.4</avg_weekday_rides>
.....: <avg_saturday_rides>1909.8</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
.....: </row>
.....: <row>
.....: <station_id>40380</station_id>
.....: <station_name>Clark/Lake</station_name>
.....: <month>2020-09-01T00:00:00</month>
.....: <avg_weekday_rides>2949.6</avg_weekday_rides>
.....: <avg_saturday_rides>1657</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
.....: </row>
.....: </response>"""
.....:
In [408]: df = pd.read_xml(StringIO(xml), stylesheet=xsl)
In [409]: df
Out[409]:
station_id station_name ... avg_saturday_rides avg_sunday_holiday_rides
0 40850 Library ... 534.0 417.2
1 41700 Washington/Wabash ... 1909.8 1438.6
2 40380 Clark/Lake ... 1657.0 1453.8
[3 rows x 6 columns]
对于范围从几百兆字节到几千兆字节的超大 XML 文件,pandas.read_xml()
支持使用 lxml 的 iterparse 和 etree 的 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 语法规则的标记文档。请注意,除非遵循 XHTML 规范,否则 HTML 不是 XML 文档。但是,其他流行的标记类型,包括 KML、XAML、RSS、MusicML、MathML 都是符合规范的 XML schema。基于上述原因,如果您的应用程序在进行 pandas 操作之前构建 XML,请使用适当的 DOM 库(如
etree
和lxml
)来构建所需文档,而不是通过字符串连接或正则表达式调整。务必记住 XML 是一个带有标记规则的*特殊*文本文件。对于超大 XML 文件(几百兆字节到几千兆字节),XPath 和 XSLT 可能成为内存密集型操作。请确保有足够的可用 RAM 来读写大型 XML 文件(大约是文本大小的 5 倍)。
由于 XSLT 是一种编程语言,请谨慎使用,因为此类脚本可能在您的环境中构成安全风险,并且可能运行大量或无限的递归操作。在完全运行之前,务必在小片段上测试脚本。
etree
解析器支持read_xml
和to_xml
的所有功能,但复杂 XPath 和任何 XSLT 除外。尽管功能有限,etree
仍然是一个可靠且有能力的解析器和树构建器。对于大型文件,其性能可能会在一定程度上落后于lxml
,但在中小型文件上相对不明显。
Excel 文件#
read_excel()
方法可以使用 openpyxl
Python 模块读取 Excel 2007+ (.xlsx
) 文件。Excel 2003 (.xls
) 文件可以使用 xlrd
读取。二进制 Excel (.xlsb
) 文件可以使用 pyxlsb
读取。所有格式都可以使用 calamine 引擎读取。to_excel()
实例方法用于将 DataFrame
保存到 Excel。通常,其语义与处理 csv 数据类似。有关一些高级策略,请参阅 cookbook。
注意
当 engine=None
时,将使用以下逻辑来确定引擎
如果
path_or_buffer
是 OpenDocument 格式(.odf、.ods、.odt),则将使用 odf。否则,如果
path_or_buffer
是 xls 格式,则将使用xlrd
。否则,如果
path_or_buffer
是 xlsb 格式,则将使用pyxlsb
。否则,将使用
openpyxl
。
读取 Excel 文件#
在最基本的用例中,read_excel
接受 Excel 文件的路径,以及指示要解析哪个工作表的 sheet_name
。
使用 engine_kwargs
参数时,pandas 会将这些参数传递给引擎。为此,了解 pandas 内部使用哪个函数很重要。
对于 openpyxl 引擎,pandas 使用
openpyxl.load_workbook()
读取 (.xlsx
) 和 (.xlsm
) 文件。对于 xlrd 引擎,pandas 使用
xlrd.open_workbook()
读取 (.xls
) 文件。对于 pyxlsb 引擎,pandas 使用
pyxlsb.open_workbook()
读取 (.xlsb
) 文件。对于 odf 引擎,pandas 使用
odf.opendocument.load()
读取 (.ods
) 文件。对于 calamine 引擎,pandas 使用
python_calamine.load_workbook()
读取 (.xlsx
)、(.xlsm
)、(.xls
)、(.xlsb
)、(.ods
) 文件。
# Returns a DataFrame
pd.read_excel("path_to_file.xls", sheet_name="Sheet1")
ExcelFile
类#
为了方便处理同一文件中的多个工作表,可以使用 ExcelFile
类来封装文件,并将其传递给 read_excel
。由于文件只读入内存一次,读取多个工作表会带来性能优势。
xlsx = pd.ExcelFile("path_to_file.xls")
df = pd.read_excel(xlsx, "Sheet1")
ExcelFile
类也可以用作上下文管理器。
with pd.ExcelFile("path_to_file.xls") as xls:
df1 = pd.read_excel(xls, "Sheet1")
df2 = pd.read_excel(xls, "Sheet2")
sheet_names
属性将生成文件中工作表名称的列表。
ExcelFile
的主要用例是使用不同的参数解析多个工作表
data = {}
# For when Sheet1's format differs from Sheet2
with pd.ExcelFile("path_to_file.xls") as xls:
data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=1)
请注意,如果所有工作表使用相同的解析参数,只需将工作表名称列表传递给 read_excel
即可,性能不会损失。
# using the ExcelFile class
data = {}
with pd.ExcelFile("path_to_file.xls") as xls:
data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=None, na_values=["NA"])
# equivalent using the read_excel function
data = pd.read_excel(
"path_to_file.xls", ["Sheet1", "Sheet2"], index_col=None, na_values=["NA"]
)
ExcelFile
也可以将 xlrd.book.Book
对象作为参数调用。这允许用户控制 Excel 文件的读取方式。例如,可以通过调用 xlrd.open_workbook()
并设置 on_demand=True
按需加载工作表。
import xlrd
xlrd_book = xlrd.open_workbook("path_to_file.xls", on_demand=True)
with pd.ExcelFile(xlrd_book) as xls:
df1 = pd.read_excel(xls, "Sheet1")
df2 = pd.read_excel(xls, "Sheet2")
指定工作表#
注意
第二个参数是 sheet_name
,不要与 ExcelFile.sheet_names
混淆。
注意
ExcelFile 的属性 sheet_names
提供对工作表列表的访问。
参数
sheet_name
允许指定要读取的工作表或工作表。sheet_name
的默认值为 0,表示读取第一个工作表传递一个字符串来引用工作簿中特定工作表的名称。
传递一个整数来引用工作表的索引。索引遵循 Python 约定,从 0 开始。
传递一个由字符串或整数组成的列表,以返回指定工作表的字典。
传递
None
以返回所有可用工作表的字典。
# Returns a DataFrame
pd.read_excel("path_to_file.xls", "Sheet1", index_col=None, na_values=["NA"])
使用工作表索引
# Returns a DataFrame
pd.read_excel("path_to_file.xls", 0, index_col=None, na_values=["NA"])
使用所有默认值
# Returns a DataFrame
pd.read_excel("path_to_file.xls")
使用 None 获取所有工作表
# Returns a dictionary of DataFrames
pd.read_excel("path_to_file.xls", sheet_name=None)
使用列表获取多个工作表
# Returns the 1st and 4th sheet, as a dictionary of DataFrames.
pd.read_excel("path_to_file.xls", sheet_name=["Sheet1", 3])
read_excel
可以读取多个工作表,通过将 sheet_name
设置为工作表名称列表、工作表位置列表,或设置为 None
读取所有工作表。可以使用整数或字符串分别通过工作表索引或工作表名称指定工作表。
读取 MultiIndex
#
read_excel
可以读取 MultiIndex
索引,方法是将列列表传递给 index_col
;也可以读取 MultiIndex
列,方法是将行列表传递给 header
。如果 index
或 columns
具有序列化的级别名称,通过指定构成这些级别的行/列,这些名称也将被读取。
例如,读取不带名称的 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_col
和 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
中指定的列中的缺失值将被向前填充,以便在 merged_cells=True
的情况下与 to_excel
进行往返。要避免向前填充缺失值,请在读取数据后使用 set_index
,而不是 index_col
。
解析特定列#
用户经常会在 Excel 中插入列用于临时计算,而您可能不希望读取这些列。read_excel
函数有一个 usecols
参数,允许您指定要解析的列的子集。
您可以将由逗号分隔的 Excel 列和范围集合指定为字符串
pd.read_excel("path_to_file.xls", "Sheet1", usecols="A,C:E")
如果 usecols
是一个整数列表,则假定它们是要解析的文件列索引。
pd.read_excel("path_to_file.xls", "Sheet1", usecols=[0, 2, 3])
元素的顺序被忽略,所以 usecols=[0, 1]
和 [1, 0]
是相同的。
如果 usecols
是一个字符串列表,则假定每个字符串对应于用户在 names
参数中提供的列名,或从文档标题行推断出的列名。这些字符串定义了将解析哪些列。
pd.read_excel("path_to_file.xls", "Sheet1", usecols=["foo", "bar"])
元素的顺序被忽略,所以 usecols=['baz', 'joe']
和 ['joe', 'baz']
是相同的。
如果 usecols
是一个可调用对象,则将针对列名评估该可调用函数,返回可调用函数评估结果为 True
的列名。
pd.read_excel("path_to_file.xls", "Sheet1", usecols=lambda x: x.isalpha())
解析日期#
读取 Excel 文件时,日期时间类值通常会自动转换为适当的数据类型(dtype)。但如果您的某一列包含 看起来 像日期(但实际上在 Excel 中并未格式化为日期)的字符串,您可以使用 parse_dates
参数将这些字符串解析为日期时间。
pd.read_excel("path_to_file.xls", "Sheet1", parse_dates=["date_strings"])
单元格转换器#
可以通过 converters
选项转换 Excel 单元格的内容。例如,将一列转换为布尔类型。
pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyBools": bool})
此选项处理缺失值,并将转换器中的异常视为缺失数据。转换是逐个单元格应用的,而不是应用于整个列,因此不能保证数组的数据类型(dtype)。例如,包含缺失值的整数列无法转换为整数数据类型的数组,因为 NaN 严格来说是浮点数。您可以手动掩码缺失数据以恢复整数数据类型。
def cfun(x):
return int(x) if x else -1
pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyInts": cfun})
数据类型(dtype)规范#
作为转换器的替代方案,可以使用 dtype
参数指定整个列的类型,该参数接受一个将列名映射到类型的字典。要解释数据而不进行类型推断,请使用 str
或 object
类型。
pd.read_excel("path_to_file.xls", dtype={"MyInts": "int64", "MyText": str})
写入 Excel 文件#
将 Excel 文件写入磁盘#
要将 DataFrame
对象写入 Excel 文件的一个工作表,您可以使用 to_excel
实例方法。其参数与上面描述的 to_csv
基本相同,第一个参数是 Excel 文件名,可选的第二个参数是要写入 DataFrame
的工作表名称。例如:
df.to_excel("path_to_file.xlsx", sheet_name="Sheet1")
扩展名为 .xlsx
的文件将使用 xlsxwriter
(如果可用)或 openpyxl
写入。
DataFrame
将以模仿 REPL 输出的方式写入。index_label
将放置在第二行而不是第一行。您可以通过将 to_excel()
中的 merge_cells
选项设置为 False
来将其放置在第一行。
df.to_excel("path_to_file.xlsx", index_label="label", merge_cells=False)
为了将单独的 DataFrames
写入单个 Excel 文件中的不同工作表,可以传递一个 ExcelWriter
。
with pd.ExcelWriter("path_to_file.xlsx") as writer:
df1.to_excel(writer, sheet_name="Sheet1")
df2.to_excel(writer, sheet_name="Sheet2")
使用 engine_kwargs
参数时,pandas 会将这些参数传递给引擎。为此,了解 pandas 内部使用哪个函数很重要。
对于 openpyxl 引擎,pandas 使用
openpyxl.Workbook()
创建新工作表,使用openpyxl.load_workbook()
向现有工作表追加数据。openpyxl 引擎写入 (.xlsx
) 和 (.xlsm
) 文件。对于 xlsxwriter 引擎,pandas 使用
xlsxwriter.Workbook()
写入 (.xlsx
) 文件。对于 odf 引擎,pandas 使用
odf.opendocument.OpenDocumentSpreadsheet()
写入 (.ods
) 文件。
将 Excel 文件写入内存#
pandas 支持使用 ExcelWriter
将 Excel 文件写入缓冲区类对象,例如 StringIO
或 BytesIO
。
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 写入器:
engine
关键字参数文件名扩展名(通过配置选项中指定的默认值)
默认情况下,pandas 对 .xlsx
文件使用 XlsxWriter,对 .xlsm
文件使用 openpyxl。如果您安装了多个引擎,可以通过设置配置选项 io.excel.xlsx.writer
和 io.excel.xls.writer
来设置默认引擎。如果 Xlsxwriter 不可用,pandas 将对 .xlsx
文件回退使用 openpyxl。
要指定要使用的写入器,您可以将 engine 关键字参数传递给 to_excel
和 ExcelWriter
。内置的引擎有:
openpyxl
:需要 2.4 或更高版本xlsxwriter
# By setting the 'engine' in the DataFrame 'to_excel()' methods.
df.to_excel("path_to_file.xlsx", sheet_name="Sheet1", engine="xlsxwriter")
# By setting the 'engine' in the ExcelWriter constructor.
writer = pd.ExcelWriter("path_to_file.xlsx", engine="xlsxwriter")
# Or via pandas configuration.
from pandas import options # noqa: E402
options.io.excel.xlsx.writer = "xlsxwriter"
df.to_excel("path_to_file.xlsx", sheet_name="Sheet1")
样式和格式#
可以使用 DataFrame
的 to_excel
方法上的以下参数修改从 pandas 创建的 Excel 工作表的外观和感觉。
float_format
:浮点数的格式字符串(默认为None
)。freeze_panes
:一个包含两个整数的元组,表示要冻结的最底部行和最右边列。每个参数都是基于 1 的,所以 (1, 1) 将冻结第一行和第一列(默认为None
)。
使用 Xlsxwriter 引擎提供了许多选项来控制使用 to_excel
方法创建的 Excel 工作表的格式。在 Xlsxwriter 文档中可以找到很好的示例,地址如下:https://xlsxwriter.readthedocs.io/working_with_pandas.html
OpenDocument 表格#
Excel 文件的 I/O 方法也支持使用 odfpy 模块读取和写入 OpenDocument 表格。读取和写入 OpenDocument 表格的语义和功能与使用 engine='odf'
对Excel 文件的操作相匹配。可选依赖项“odfpy”需要安装。
read_excel()
方法可以读取 OpenDocument 表格。
# Returns a DataFrame
pd.read_excel("path_to_file.ods", engine="odf")
类似地,to_excel()
方法可以写入 OpenDocument 表格。
# Writes DataFrame to a .ods file
df.to_excel("path_to_file.ods", engine="odf")
二进制 Excel (.xlsb) 文件#
read_excel()
方法还可以使用 pyxlsb
模块读取二进制 Excel 文件。读取二进制 Excel 文件的语义和功能与使用 engine='pyxlsb'
对Excel 文件的操作基本匹配。pyxlsb
无法识别文件中的日期时间类型,而是返回浮点数(如果您需要识别日期时间类型,可以使用calamine)。
# Returns a DataFrame
pd.read_excel("path_to_file.xlsb", engine="pyxlsb")
注意
当前 pandas 只支持 读取 二进制 Excel 文件。写入功能尚未实现。
Calamine(Excel 和 ODS 文件)#
read_excel()
方法可以使用 python-calamine
模块读取 Excel 文件(.xlsx
、.xlsm
、.xls
、.xlsb
)和 OpenDocument 表格(.ods
)。该模块是 Rust 库 calamine 的绑定,在大多数情况下比其他引擎更快。可选依赖项“python-calamine”需要安装。
# Returns a DataFrame
pd.read_excel("path_to_file.xlsb", engine="calamine")
剪贴板#
获取数据的一种便捷方法是使用 read_clipboard()
方法,该方法获取剪贴板缓冲区的内容并将其传递给 read_csv
方法。例如,您可以将以下文本复制到剪贴板(在许多操作系统上按 CTRL+C):
A B C
x 1 4 p
y 2 5 q
z 3 6 r
然后通过调用以下命令将数据直接导入到 DataFrame
:
>>> clipdf = pd.read_clipboard()
>>> clipdf
A B C
x 1 4 p
y 2 5 q
z 3 6 r
to_clipboard
方法可用于将 DataFrame
的内容写入剪贴板。之后,您可以将剪贴板内容粘贴到其他应用程序中(在许多操作系统上按 CTRL+V)。这里我们演示将 DataFrame
写入剪贴板并将其读回。
>>> df = pd.DataFrame(
... {"A": [1, 2, 3], "B": [4, 5, 6], "C": ["p", "q", "r"]}, index=["x", "y", "z"]
... )
>>> df
A B C
x 1 4 p
y 2 5 q
z 3 6 r
>>> df.to_clipboard()
>>> pd.read_clipboard()
A B C
x 1 4 p
y 2 5 q
z 3 6 r
我们可以看到,我们成功取回了之前写入剪贴板的相同内容。
注意
在 Linux 上使用这些方法,您可能需要安装 xclip 或 xsel(以及 PyQt5、PyQt4 或 qtpy)。
Pickling#
所有 pandas 对象都配备了 to_pickle
方法,这些方法使用 Python 的 cPickle
模块,以 pickle 格式将数据结构保存到磁盘。
In [436]: df
Out[436]:
c1 a
c2 b d
lvl1 lvl2
a c 1 5
d 2 6
b c 3 7
d 4 8
In [437]: df.to_pickle("foo.pkl")
pandas
命名空间中的 read_pickle
函数可用于从文件加载任何 pickled pandas 对象(或任何其他 pickled 对象)。
In [438]: pd.read_pickle("foo.pkl")
Out[438]:
c1 a
c2 b d
lvl1 lvl2
a c 1 5
d 2 6
b c 3 7
d 4 8
警告
read_pickle()
仅保证向后兼容几个小版本。
压缩的 pickle 文件#
read_pickle()
、DataFrame.to_pickle()
和 Series.to_pickle()
可以读写压缩的 pickle 文件。支持 gzip
、bz2
、xz
、zstd
的压缩类型进行读写。zip
文件格式只支持读取,并且必须只包含一个数据文件才能读取。
压缩类型可以是显式参数,也可以从文件扩展名推断。如果设置为“infer”,则如果文件名分别以 .gz
、.bz2
、.zip
、.xz
或 .zst
结尾,则使用 gzip
、bz2
、zip
、xz
或 zstd
。
压缩参数也可以是一个 dict
,以便向压缩协议传递选项。它必须包含一个 'method'
键,其值设置为压缩协议的名称,该名称必须是 {'zip'
、'gzip'
、'bz2'
、'xz'
、'zstd'
} 之一。所有其他键值对都会传递给底层压缩库。
In [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})
msgpack#
pandas 对 msgpack
的支持已在 1.0.0 版本中移除。建议改用pickle。
另外,您还可以使用 Arrow IPC 序列化格式进行 pandas 对象的在线传输。有关 pyarrow 的文档,请参阅此处。
HDF5 (PyTables)#
HDFStore
是一个类似字典的对象,它使用优秀的 PyTables 库,以高性能的 HDF5 格式读写 pandas 数据。请参阅cookbook 了解一些高级策略。
警告
pandas 使用 PyTables 读写 HDF5 文件,这允许使用 pickle 序列化对象数据类型(object-dtype)的数据。从不可信来源加载 pickled 数据可能不安全。
In [454]: store = pd.HDFStore("store.h5")
In [455]: print(store)
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
可以像向字典添加键值对一样将对象写入文件。
In [456]: index = pd.date_range("1/1/2000", periods=8)
In [457]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])
In [458]: df = pd.DataFrame(np.random.randn(8, 3), index=index, columns=["A", "B", "C"])
# store.put('s', s) is an equivalent method
In [459]: store["s"] = s
In [460]: store["df"] = df
In [461]: store
Out[461]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
在当前或后续的 Python 会话中,您可以检索存储的对象。
# store.get('df') is an equivalent method
In [462]: store["df"]
Out[462]:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
# dotted (attribute) access provides get as well
In [463]: store.df
Out[463]:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
删除由键指定的对象。
# store.remove('df') is an equivalent method
In [464]: del store["df"]
In [465]: store
Out[465]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
关闭存储以及使用上下文管理器。
In [466]: store.close()
In [467]: store
Out[467]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
In [468]: store.is_open
Out[468]: False
# Working with, and automatically closing the store using a context manager
In [469]: with pd.HDFStore("store.h5") as store:
.....: store.keys()
.....:
读/写 API#
HDFStore
支持顶层 API,使用 read_hdf
读取和 to_hdf
写入,类似于 read_csv
和 to_csv
的工作方式。
In [470]: df_tl = pd.DataFrame({"A": list(range(5)), "B": list(range(5))})
In [471]: df_tl.to_hdf("store_tl.h5", key="table", append=True)
In [472]: pd.read_hdf("store_tl.h5", "table", where=["index>2"])
Out[472]:
A B
3 3 3
4 4 4
HDFStore
默认不会删除所有缺失值的行。可以通过设置 dropna=True
来更改此行为。
In [473]: df_with_missing = pd.DataFrame(
.....: {
.....: "col1": [0, np.nan, 2],
.....: "col2": [1, np.nan, np.nan],
.....: }
.....: )
.....:
In [474]: df_with_missing
Out[474]:
col1 col2
0 0.0 1.0
1 NaN NaN
2 2.0 NaN
In [475]: df_with_missing.to_hdf("file.h5", key="df_with_missing", format="table", mode="w")
In [476]: pd.read_hdf("file.h5", "df_with_missing")
Out[476]:
col1 col2
0 0.0 1.0
1 NaN NaN
2 2.0 NaN
In [477]: df_with_missing.to_hdf(
.....: "file.h5", key="df_with_missing", format="table", mode="w", dropna=True
.....: )
.....:
In [478]: pd.read_hdf("file.h5", "df_with_missing")
Out[478]:
col1 col2
0 0.0 1.0
2 2.0 NaN
固定格式#
上面的示例展示了使用 put
进行存储,它将 HDF5 以固定的数组格式写入 PyTables
,称为 fixed
格式。这类存储一旦写入就**不可**追加(尽管您可以直接删除并重写)。它们也**不可查询**;必须完整地检索。它们也不支持具有非唯一列名的数据框。fixed
格式的存储提供了非常快的写入速度和略快的读取速度,比 table
存储更快。在使用 put
或 to_hdf
时,或者通过指定 format='fixed'
或 format='f'
时,默认指定此格式。
警告
如果您尝试使用 where
进行检索,fixed
格式将引发 TypeError
。
In [479]: pd.DataFrame(np.random.randn(10, 2)).to_hdf("test_fixed.h5", key="df")
In [480]: pd.read_hdf("test_fixed.h5", "df", where="index>5")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[480], line 1
----> 1 pd.read_hdf("test_fixed.h5", "df", where="index>5")
File ~/work/pandas/pandas/pandas/io/pytables.py:452, in read_hdf(path_or_buf, key, mode, errors, where, start, stop, columns, iterator, chunksize, **kwargs)
447 raise ValueError(
448 "key must be provided when HDF5 "
449 "file contains multiple datasets."
450 )
451 key = candidate_only_group._v_pathname
--> 452 return store.select(
453 key,
454 where=where,
455 start=start,
456 stop=stop,
457 columns=columns,
458 iterator=iterator,
459 chunksize=chunksize,
460 auto_close=auto_close,
461 )
462 except (ValueError, TypeError, LookupError):
463 if not isinstance(path_or_buf, HDFStore):
464 # if there is an error, close the store if we opened it.
File ~/work/pandas/pandas/pandas/io/pytables.py:906, in HDFStore.select(self, key, where, start, stop, columns, iterator, chunksize, auto_close)
892 # create the iterator
893 it = TableIterator(
894 self,
895 s,
(...)
903 auto_close=auto_close,
904 )
--> 906 return it.get_result()
File ~/work/pandas/pandas/pandas/io/pytables.py:2029, in TableIterator.get_result(self, coordinates)
2026 where = self.where
2028 # directly return the result
-> 2029 results = self.func(self.start, self.stop, where)
2030 self.close()
2031 return results
File ~/work/pandas/pandas/pandas/io/pytables.py:890, in HDFStore.select.<locals>.func(_start, _stop, _where)
889 def func(_start, _stop, _where):
--> 890 return s.read(start=_start, stop=_stop, where=_where, columns=columns)
File ~/work/pandas/pandas/pandas/io/pytables.py:3278, in BlockManagerFixed.read(self, where, columns, start, stop)
3270 def read(
3271 self,
3272 where=None,
(...)
3276 ) -> DataFrame:
3277 # start, stop applied to rows, so 0th axis only
-> 3278 self.validate_read(columns, where)
3279 select_axis = self.obj_type()._get_block_manager_axis(0)
3281 axes = []
File ~/work/pandas/pandas/pandas/io/pytables.py:2922, in GenericFixed.validate_read(self, columns, where)
2917 raise TypeError(
2918 "cannot pass a column specification when reading "
2919 "a Fixed format store. this store must be selected in its entirety"
2920 )
2921 if where is not None:
-> 2922 raise TypeError(
2923 "cannot pass a where specification when reading "
2924 "from a Fixed format store. this store must be selected in its entirety"
2925 )
TypeError: cannot pass a where specification when reading from a Fixed format store. this store must be selected in its entirety
表格格式#
HDFStore
支持磁盘上的另一种 PyTables
格式,即 table
格式。从概念上讲,table
的形状与 DataFrame 非常相似,具有行和列。table
可以在同一会话或其他会话中追加数据。此外,还支持删除和查询操作。通过向 append
、put
或 to_hdf
指定 format='table'
或 format='t'
来指定此格式。
也可以将此格式设置为选项,例如 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'
注意
您还可以通过向 put
操作传递 format='table'
或 format='t'
来创建 table
。
分层键#
存储的键可以指定为字符串。它们可以是分层的路径名格式(例如 foo/bar/bah
),这将生成子存储的层级结构(在 PyTables 术语中称为 Groups
)。键可以不带开头的“/”指定,并且**始终**是绝对路径(例如,“foo”指代“/foo”)。删除操作可以删除子存储及其**以下**的所有内容,因此请务必*小心*。
In [489]: store.put("foo/bar/bah", df)
In [490]: store.append("food/orange", df)
In [491]: store.append("food/apple", df)
In [492]: store
Out[492]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
# a list of keys are returned
In [493]: store.keys()
Out[493]: ['/df', '/food/apple', '/food/orange', '/foo/bar/bah']
# remove all nodes under this level
In [494]: store.remove("food")
In [495]: store
Out[495]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
您可以使用 walk
方法遍历组层级结构,该方法将为每个组键及其内容的相对键生成一个元组。
In [496]: for (path, subgroups, subkeys) in store.walk():
.....: for subgroup in subgroups:
.....: print("GROUP: {}/{}".format(path, subgroup))
.....: for subkey in subkeys:
.....: key = "/".join([path, subkey])
.....: print("KEY: {}".format(key))
.....: print(store.get(key))
.....:
GROUP: /foo
KEY: /df
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
GROUP: /foo/bar
KEY: /foo/bar/bah
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
警告
对于存储在根节点下的项,不能像上面描述的那样通过点(属性)访问来检索分层键。
In [497]: store.foo.bar.bah
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[497], line 1
----> 1 store.foo.bar.bah
File ~/work/pandas/pandas/pandas/io/pytables.py:613, in HDFStore.__getattr__(self, name)
611 """allow attribute access to get stores"""
612 try:
--> 613 return self.get(name)
614 except (KeyError, ClosedFileError):
615 pass
File ~/work/pandas/pandas/pandas/io/pytables.py:813, in HDFStore.get(self, key)
811 if group is None:
812 raise KeyError(f"No object named {key} in the file")
--> 813 return self._read_group(group)
File ~/work/pandas/pandas/pandas/io/pytables.py:1878, in HDFStore._read_group(self, group)
1877 def _read_group(self, group: Node):
-> 1878 s = self._create_storer(group)
1879 s.infer_axes()
1880 return s.read()
File ~/work/pandas/pandas/pandas/io/pytables.py:1752, in HDFStore._create_storer(self, group, format, value, encoding, errors)
1750 tt = "generic_table"
1751 else:
-> 1752 raise TypeError(
1753 "cannot create a storer if the object is not existing "
1754 "nor a value are passed"
1755 )
1756 else:
1757 if isinstance(value, Series):
TypeError: cannot create a storer if the object is not existing nor a value are passed
# you can directly access the actual PyTables node but using the root node
In [498]: store.root.foo.bar.bah
Out[498]:
/foo/bar/bah (Group) ''
children := ['axis0' (Array), 'axis1' (Array), 'block0_items' (Array), 'block0_values' (Array)]
相反,请使用显式的基于字符串的键。
In [499]: store["foo/bar/bah"]
Out[499]:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
存储类型#
在表中存储混合类型#
支持存储混合数据类型(mixed-dtype)的数据。字符串以固定宽度存储,使用追加列的最大长度。随后尝试追加更长字符串将引发 ValueError
。
将 min_itemsize={`values`: size}
作为参数传递给 append 将为字符串列设置更大的最小长度。目前支持存储 floats, strings, ints, bools, datetime64
。对于字符串列,将 nan_rep = 'nan'
传递给 append 将更改磁盘上的默认 nan 表示(该表示会转换回 np.nan
),默认为 nan
。
In [500]: df_mixed = pd.DataFrame(
.....: {
.....: "A": np.random.randn(8),
.....: "B": np.random.randn(8),
.....: "C": np.array(np.random.randn(8), dtype="float32"),
.....: "string": "string",
.....: "int": 1,
.....: "bool": True,
.....: "datetime64": pd.Timestamp("20010102"),
.....: },
.....: index=list(range(8)),
.....: )
.....:
In [501]: df_mixed.loc[df_mixed.index[3:5], ["A", "B", "string", "datetime64"]] = np.nan
In [502]: store.append("df_mixed", df_mixed, min_itemsize={"values": 50})
In [503]: df_mixed1 = store.select("df_mixed")
In [504]: df_mixed1
Out[504]:
A B C ... int bool datetime64
0 0.013747 -1.166078 -1.292080 ... 1 True 1970-01-01 00:00:00.978393600
1 -0.712009 0.247572 1.526911 ... 1 True 1970-01-01 00:00:00.978393600
2 -0.645096 1.687406 0.288504 ... 1 True 1970-01-01 00:00:00.978393600
3 NaN NaN 0.097771 ... 1 True NaT
4 NaN NaN 1.536408 ... 1 True NaT
5 -0.023202 0.043702 0.926790 ... 1 True 1970-01-01 00:00:00.978393600
6 2.359782 0.088224 -0.676448 ... 1 True 1970-01-01 00:00:00.978393600
7 -0.143428 -0.813360 -0.179724 ... 1 True 1970-01-01 00:00:00.978393600
[8 rows x 7 columns]
In [505]: df_mixed1.dtypes.value_counts()
Out[505]:
float64 2
float32 1
object 1
int64 1
bool 1
datetime64[ns] 1
Name: count, dtype: int64
# we have provided a minimum string column size
In [506]: store.root.df_mixed.table
Out[506]:
/df_mixed/table (Table(8,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Float64Col(shape=(2,), dflt=0.0, pos=1),
"values_block_1": Float32Col(shape=(1,), dflt=0.0, pos=2),
"values_block_2": StringCol(itemsize=50, shape=(1,), dflt=b'', pos=3),
"values_block_3": Int64Col(shape=(1,), dflt=0, pos=4),
"values_block_4": BoolCol(shape=(1,), dflt=False, pos=5),
"values_block_5": Int64Col(shape=(1,), dflt=0, pos=6)}
byteorder := 'little'
chunkshape := (689,)
autoindex := True
colindexes := {
"index": Index(6, mediumshuffle, zlib(1)).is_csi=False}
存储 MultiIndex DataFrames#
将 MultiIndex DataFrames
存储为表与从同质索引 DataFrames
存储/选择非常相似。
In [507]: index = pd.MultiIndex(
.....: levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
.....: codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
.....: names=["foo", "bar"],
.....: )
.....:
In [508]: df_mi = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])
In [509]: df_mi
Out[509]:
A B C
foo bar
foo one -1.303456 -0.642994 -0.649456
two 1.012694 0.414147 1.950460
three 1.094544 -0.802899 -0.583343
bar one 0.410395 0.618321 0.560398
two 1.434027 -0.033270 0.343197
baz two -1.646063 -0.695847 -0.429156
three -0.244688 -1.428229 -0.138691
qux one 1.866184 -1.446617 0.036660
two -1.660522 0.929553 -1.298649
three 3.565769 0.682402 1.041927
In [510]: store.append("df_mi", df_mi)
In [511]: store.select("df_mi")
Out[511]:
A B C
foo bar
foo one -1.303456 -0.642994 -0.649456
two 1.012694 0.414147 1.950460
three 1.094544 -0.802899 -0.583343
bar one 0.410395 0.618321 0.560398
two 1.434027 -0.033270 0.343197
baz two -1.646063 -0.695847 -0.429156
three -0.244688 -1.428229 -0.138691
qux one 1.866184 -1.446617 0.036660
two -1.660522 0.929553 -1.298649
three 3.565769 0.682402 1.041927
# the levels are automatically included as data columns
In [512]: store.select("df_mi", "foo=bar")
Out[512]:
A B C
foo bar
bar one 0.410395 0.618321 0.560398
two 1.434027 -0.033270 0.343197
注意
关键字 index
是保留的,不能用作层级名称。
查询#
查询表#
select
和 delete
操作有一个可选的条件可以指定,以便只选择/删除数据的一个子集。这使得您可以拥有非常大的磁盘表,并只检索部分数据。
查询在底层使用 Term
类指定,作为一个布尔表达式。
index
和columns
是受支持的DataFrames
索引器。如果指定了
data_columns
,它们可以用作额外的索引器。MultiIndex 中的层级名称,如果未提供,默认名称为
level_0
、level_1
等。
有效的比较运算符有:
=, ==, !=, >, >=, <, <=
有效的布尔表达式通过以下方式组合:
|
:或&
:与(
和)
:用于分组
这些规则类似于 pandas 中用于索引的布尔表达式。
注意
=
将自动展开为比较运算符==
。~
是非运算符,但只能在非常有限的情况下使用。如果传递表达式的列表/元组,它们将通过
&
组合。
以下是有效的表达式:
'index >= date'
"columns = ['A', 'D']"
"columns in ['A', 'D']"
'columns = A'
'columns == A'
"~(columns = ['A', 'B'])"
'index > df.index[3] & string = "bar"'
'(index > df.index[3] & index <= df.index[6]) | string = "bar"'
"ts >= Timestamp('2012-02-01')"
"major_axis>=20130101"
indexers
在子表达式的左侧:
columns
、major_axis
、ts
子表达式的右侧(比较运算符之后)可以是:
将被评估的函数,例如
Timestamp('2012-02-01')
字符串,例如
"bar"
日期类型,例如
20130101
,或者"20130101"
列表,例如
"['A', 'B']"
在本地命名空间中定义的变量,例如
date
注意
不建议通过将字符串插值到查询表达式中来将其传递给查询。只需将目标字符串分配给一个变量,并在表达式中使用该变量即可。例如,请执行此操作
string = "HolyMoly'"
store.select("df", "index == string")
而不是这样
string = "HolyMoly'"
store.select('df', f'index == {string}')
后者不会工作,并将引发一个 SyntaxError
。请注意,在 string
变量中,有一个单引号后跟一个双引号。
如果您必须进行插值,请使用 '%r'
格式说明符
store.select("df", "index == %r" % string)
这将引用 string
。
以下是一些示例
In [513]: dfq = pd.DataFrame(
.....: np.random.randn(10, 4),
.....: columns=list("ABCD"),
.....: index=pd.date_range("20130101", periods=10),
.....: )
.....:
In [514]: store.append("dfq", dfq, format="table", data_columns=True)
使用布尔表达式,并进行内联函数评估。
In [515]: store.select("dfq", "index>pd.Timestamp('20130104') & columns=['A', 'B']")
Out[515]:
A B
2013-01-05 -0.830545 -0.457071
2013-01-06 0.431186 1.049421
2013-01-07 0.617509 -0.811230
2013-01-08 0.947422 -0.671233
2013-01-09 -0.183798 -1.211230
2013-01-10 0.361428 0.887304
使用内联列引用。
In [516]: store.select("dfq", where="A>0 or C>0")
Out[516]:
A B C D
2013-01-02 0.658179 0.362814 -0.917897 0.010165
2013-01-03 0.905122 1.848731 -1.184241 0.932053
2013-01-05 -0.830545 -0.457071 1.565581 1.148032
2013-01-06 0.431186 1.049421 0.383309 0.595013
2013-01-07 0.617509 -0.811230 -2.088563 -1.393500
2013-01-08 0.947422 -0.671233 -0.847097 -1.187785
2013-01-10 0.361428 0.887304 0.266457 -0.399641
columns
关键字可用于选择要返回的列列表,这相当于传递一个'columns=list_of_columns_to_filter'
In [517]: store.select("df", "columns=['A', 'B']")
Out[517]:
A B
2000-01-01 0.858644 -0.851236
2000-01-02 -0.080372 -1.268121
2000-01-03 0.816983 1.965656
2000-01-04 0.712795 -0.062433
2000-01-05 -0.298721 -1.988045
2000-01-06 1.103675 1.382242
2000-01-07 -0.729161 -0.142928
2000-01-08 -1.005977 0.465222
start
和 stop
参数可以指定来限制总搜索空间。这些参数以表中的总行数表示。
注意
select
如果查询表达式包含未知变量引用,将引发ValueError
。通常这意味着您尝试选择的列不是data_column。
select
如果查询表达式无效,将引发SyntaxError
。
查询 timedelta64[ns]#
您可以使用 timedelta64[ns]
类型进行存储和查询。术语可以按以下格式指定: <float>(<unit>)
,其中 float 可以带符号(和小数),unit 可以是时间差的 D,s,ms,us,ns
。以下是一个示例
In [518]: from datetime import timedelta
In [519]: dftd = pd.DataFrame(
.....: {
.....: "A": pd.Timestamp("20130101"),
.....: "B": [
.....: pd.Timestamp("20130101") + timedelta(days=i, seconds=10)
.....: for i in range(10)
.....: ],
.....: }
.....: )
.....:
In [520]: dftd["C"] = dftd["A"] - dftd["B"]
In [521]: dftd
Out[521]:
A B C
0 2013-01-01 2013-01-01 00:00:10 -1 days +23:59:50
1 2013-01-01 2013-01-02 00:00:10 -2 days +23:59:50
2 2013-01-01 2013-01-03 00:00:10 -3 days +23:59:50
3 2013-01-01 2013-01-04 00:00:10 -4 days +23:59:50
4 2013-01-01 2013-01-05 00:00:10 -5 days +23:59:50
5 2013-01-01 2013-01-06 00:00:10 -6 days +23:59:50
6 2013-01-01 2013-01-07 00:00:10 -7 days +23:59:50
7 2013-01-01 2013-01-08 00:00:10 -8 days +23:59:50
8 2013-01-01 2013-01-09 00:00:10 -9 days +23:59:50
9 2013-01-01 2013-01-10 00:00:10 -10 days +23:59:50
In [522]: store.append("dftd", dftd, data_columns=True)
In [523]: store.select("dftd", "C<'-3.5D'")
Out[523]:
A B C
4 1970-01-01 00:00:01.356998400 2013-01-05 00:00:10 -5 days +23:59:50
5 1970-01-01 00:00:01.356998400 2013-01-06 00:00:10 -6 days +23:59:50
6 1970-01-01 00:00:01.356998400 2013-01-07 00:00:10 -7 days +23:59:50
7 1970-01-01 00:00:01.356998400 2013-01-08 00:00:10 -8 days +23:59:50
8 1970-01-01 00:00:01.356998400 2013-01-09 00:00:10 -9 days +23:59:50
9 1970-01-01 00:00:01.356998400 2013-01-10 00:00:10 -10 days +23:59:50
查询 MultiIndex#
通过使用层级的名称,可以从 MultiIndex
中进行选择。
In [524]: df_mi.index.names
Out[524]: FrozenList(['foo', 'bar'])
In [525]: store.select("df_mi", "foo=baz and bar=two")
Out[525]:
A B C
foo bar
baz two -1.646063 -0.695847 -0.429156
如果 MultiIndex
的层级名称为 None
,则这些层级会通过关键字 level_n
自动提供,其中 n
是您想要从中选择的 MultiIndex
的层级。
In [526]: index = pd.MultiIndex(
.....: levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
.....: codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
.....: )
.....:
In [527]: df_mi_2 = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])
In [528]: df_mi_2
Out[528]:
A B C
foo one -0.219582 1.186860 -1.437189
two 0.053768 1.872644 -1.469813
three -0.564201 0.876341 0.407749
bar one -0.232583 0.179812 0.922152
two -1.820952 -0.641360 2.133239
baz two -0.941248 -0.136307 -1.271305
three -0.099774 -0.061438 -0.845172
qux one 0.465793 0.756995 -0.541690
two -0.802241 0.877657 -2.553831
three 0.094899 -2.319519 0.293601
In [529]: store.append("df_mi_2", df_mi_2)
# the levels are automatically included as data columns with keyword level_n
In [530]: store.select("df_mi_2", "level_0=foo and level_1=two")
Out[530]:
A B C
foo two 0.053768 1.872644 -1.469813
索引#
在数据已存在于表中(在 append/put
操作之后)时,您可以使用 create_table_index
为表创建/修改索引。强烈建议创建表索引。当您使用 select
并将索引维度作为 where
条件时,这将大大加快您的查询速度。
注意
索引会在可索引列和您指定的任何数据列上自动创建。通过向 append
传递 index=False
,可以关闭此行为。
# we have automagically already created an index (in the first section)
In [531]: i = store.root.df.table.cols.index.index
In [532]: i.optlevel, i.kind
Out[532]: (6, 'medium')
# change an index by passing new parameters
In [533]: store.create_table_index("df", optlevel=9, kind="full")
In [534]: i = store.root.df.table.cols.index.index
In [535]: i.optlevel, i.kind
Out[535]: (9, 'full')
通常,当向存储区附加大量数据时,最好关闭每次附加时的索引创建,然后在末尾重新创建。
In [536]: df_1 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))
In [537]: df_2 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))
In [538]: st = pd.HDFStore("appends.h5", mode="w")
In [539]: st.append("df", df_1, data_columns=["B"], index=False)
In [540]: st.append("df", df_2, data_columns=["B"], index=False)
In [541]: st.get_storer("df").table
Out[541]:
/df/table (Table(20,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
"B": Float64Col(shape=(), dflt=0.0, pos=2)}
byteorder := 'little'
chunkshape := (2730,)
然后在附加完成后创建索引。
In [542]: st.create_table_index("df", columns=["B"], optlevel=9, kind="full")
In [543]: st.get_storer("df").table
Out[543]:
/df/table (Table(20,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
"B": Float64Col(shape=(), dflt=0.0, pos=2)}
byteorder := 'little'
chunkshape := (2730,)
autoindex := True
colindexes := {
"B": Index(9, fullshuffle, zlib(1)).is_csi=True}
In [544]: st.close()
有关如何在现有存储上创建完全排序索引 (CSI),请参阅 此处。
通过数据列查询#
您可以指定(和索引)您希望能够执行查询的某些列(除了您总是可以查询的 indexable
列)。例如,假设您想在磁盘上执行此常见操作,并仅返回与此查询匹配的帧。您可以指定 data_columns = True
来强制所有列成为 data_columns
。
In [545]: df_dc = df.copy()
In [546]: df_dc["string"] = "foo"
In [547]: df_dc.loc[df_dc.index[4:6], "string"] = np.nan
In [548]: df_dc.loc[df_dc.index[7:9], "string"] = "bar"
In [549]: df_dc["string2"] = "cool"
In [550]: df_dc.loc[df_dc.index[1:3], ["B", "C"]] = 1.0
In [551]: df_dc
Out[551]:
A B C string string2
2000-01-01 0.858644 -0.851236 1.058006 foo cool
2000-01-02 -0.080372 1.000000 1.000000 foo cool
2000-01-03 0.816983 1.000000 1.000000 foo cool
2000-01-04 0.712795 -0.062433 0.736755 foo cool
2000-01-05 -0.298721 -1.988045 1.475308 NaN cool
2000-01-06 1.103675 1.382242 -0.650762 NaN cool
2000-01-07 -0.729161 -0.142928 -1.063038 foo cool
2000-01-08 -1.005977 0.465222 -0.094517 bar cool
# on-disk operations
In [552]: store.append("df_dc", df_dc, data_columns=["B", "C", "string", "string2"])
In [553]: store.select("df_dc", where="B > 0")
Out[553]:
A B C string string2
2000-01-02 -0.080372 1.000000 1.000000 foo cool
2000-01-03 0.816983 1.000000 1.000000 foo cool
2000-01-06 1.103675 1.382242 -0.650762 NaN cool
2000-01-08 -1.005977 0.465222 -0.094517 bar cool
# getting creative
In [554]: store.select("df_dc", "B > 0 & C > 0 & string == foo")
Out[554]:
A B C string string2
2000-01-02 -0.080372 1.0 1.0 foo cool
2000-01-03 0.816983 1.0 1.0 foo cool
# this is in-memory version of this type of selection
In [555]: df_dc[(df_dc.B > 0) & (df_dc.C > 0) & (df_dc.string == "foo")]
Out[555]:
A B C string string2
2000-01-02 -0.080372 1.0 1.0 foo cool
2000-01-03 0.816983 1.0 1.0 foo cool
# we have automagically created this index and the B/C/string/string2
# columns are stored separately as ``PyTables`` columns
In [556]: store.root.df_dc.table
Out[556]:
/df_dc/table (Table(8,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
"B": Float64Col(shape=(), dflt=0.0, pos=2),
"C": Float64Col(shape=(), dflt=0.0, pos=3),
"string": StringCol(itemsize=3, shape=(), dflt=b'', pos=4),
"string2": StringCol(itemsize=4, shape=(), dflt=b'', pos=5)}
byteorder := 'little'
chunkshape := (1680,)
autoindex := True
colindexes := {
"index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"B": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"C": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"string": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"string2": Index(6, mediumshuffle, zlib(1)).is_csi=False}
将许多列转换为 data columns
会带来一些性能损失,因此由用户来指定这些列。此外,在第一次 append/put 操作之后,您无法更改数据列(或可索引列)(当然,您可以简单地读取数据并创建新表!)。
迭代器#
您可以向 select
和 select_as_multiple
传递 iterator=True
或 chunksize=number_in_a_chunk
以返回结果的迭代器。默认情况下,每次返回 50,000 行数据块。
In [557]: for df in store.select("df", chunksize=3):
.....: print(df)
.....:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
A B C
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
A B C
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
注意
您也可以将迭代器与 read_hdf
一起使用,它会在迭代完成后打开然后自动关闭存储。
for df in pd.read_hdf("store.h5", "df", chunksize=3):
print(df)
请注意,chunksize 关键字适用于源行。因此,如果您正在进行查询,chunksize 将细分表中的总行数并应用查询,返回一个可能大小不等的块上的迭代器。
这里有一个生成查询并使用它来创建等大小返回块的方案。
In [558]: dfeq = pd.DataFrame({"number": np.arange(1, 11)})
In [559]: dfeq
Out[559]:
number
0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10
In [560]: store.append("dfeq", dfeq, data_columns=["number"])
In [561]: def chunks(l, n):
.....: return [l[i: i + n] for i in range(0, len(l), n)]
.....:
In [562]: evens = [2, 4, 6, 8, 10]
In [563]: coordinates = store.select_as_coordinates("dfeq", "number=evens")
In [564]: for c in chunks(coordinates, 2):
.....: print(store.select("dfeq", where=c))
.....:
number
1 2
3 4
number
5 6
7 8
number
9 10
高级查询#
选择单个列#
要检索单个可索引列或数据列,请使用方法 select_column
。例如,这将使您能够非常快速地获取索引。它们返回一个由行号索引的结果 Series
。它们目前不接受 where
选择器。
In [565]: store.select_column("df_dc", "index")
Out[565]:
0 2000-01-01
1 2000-01-02
2 2000-01-03
3 2000-01-04
4 2000-01-05
5 2000-01-06
6 2000-01-07
7 2000-01-08
Name: index, dtype: datetime64[ns]
In [566]: store.select_column("df_dc", "string")
Out[566]:
0 foo
1 foo
2 foo
3 foo
4 NaN
5 NaN
6 foo
7 bar
Name: string, dtype: object
选择坐标#
有时您想获取查询的坐标(也称为索引位置)。这将返回一个结果位置的 Index
。这些坐标也可以传递给后续的 where
操作。
In [567]: df_coord = pd.DataFrame(
.....: np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
.....: )
.....:
In [568]: store.append("df_coord", df_coord)
In [569]: c = store.select_as_coordinates("df_coord", "index > 20020101")
In [570]: c
Out[570]:
Index([732, 733, 734, 735, 736, 737, 738, 739, 740, 741,
...
990, 991, 992, 993, 994, 995, 996, 997, 998, 999],
dtype='int64', length=268)
In [571]: store.select("df_coord", where=c)
Out[571]:
0 1
2002-01-02 0.007717 1.168386
2002-01-03 0.759328 -0.638934
2002-01-04 -1.154018 -0.324071
2002-01-05 -0.804551 -1.280593
2002-01-06 -0.047208 1.260503
... ... ...
2002-09-22 -1.139583 0.344316
2002-09-23 -0.760643 -1.306704
2002-09-24 0.059018 1.775482
2002-09-25 1.242255 -0.055457
2002-09-26 0.410317 2.194489
[268 rows x 2 columns]
使用 where mask 进行选择#
有时您的查询可能涉及创建要选择的行列表。通常,此 mask
将是索引操作产生的结果 index
。此示例选择日期时间索引中月份为 5 的行。
In [572]: df_mask = pd.DataFrame(
.....: np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
.....: )
.....:
In [573]: store.append("df_mask", df_mask)
In [574]: c = store.select_column("df_mask", "index")
In [575]: where = c[pd.DatetimeIndex(c).month == 5].index
In [576]: store.select("df_mask", where=where)
Out[576]:
0 1
2000-05-01 1.479511 0.516433
2000-05-02 -0.334984 -1.493537
2000-05-03 0.900321 0.049695
2000-05-04 0.614266 -1.077151
2000-05-05 0.233881 0.493246
... ... ...
2002-05-27 0.294122 0.457407
2002-05-28 -1.102535 1.215650
2002-05-29 -0.432911 0.753606
2002-05-30 -1.105212 2.311877
2002-05-31 2.567296 2.610691
[93 rows x 2 columns]
Storer 对象#
如果您想检查存储的对象,请通过 get_storer
进行检索。您可以编程方式使用此方法,例如获取对象中的行数。
In [577]: store.get_storer("df_dc").nrows
Out[577]: 8
多表查询#
方法 append_to_multiple
和 select_as_multiple
可以同时执行从多个表中附加/选择。其思想是拥有一张表(称为选择器表),您可以在其中索引大部分/所有列并执行查询。其他表是数据表,其索引与选择器表的索引匹配。然后,您可以在选择器表上执行非常快速的查询,同时获得大量数据。此方法类似于拥有一个非常宽的表,但可以实现更高效的查询。
方法 append_to_multiple
根据 d
(一个将表名映射到您想要在该表中包含的‘列’列表的字典)将给定的单个 DataFrame 分割成多个表。如果使用 None
代替列表,则该表将包含给定 DataFrame 中剩余的未指定列。参数 selector
定义哪个表是选择器表(您可以从中进行查询)。参数 dropna
将从输入的 DataFrame
中删除行,以确保表同步。这意味着如果正在写入的某个表的某一行全部为 np.nan
,则该行将从所有表中删除。
如果 dropna
为 False,则用户负责同步表。请记住,完全 np.Nan
的行不会写入 HDFStore,因此如果您选择调用 dropna=False
,某些表可能比其他表拥有更多行,因此 select_as_multiple
可能无法工作或可能返回意外结果。
In [578]: df_mt = pd.DataFrame(
.....: np.random.randn(8, 6),
.....: index=pd.date_range("1/1/2000", periods=8),
.....: columns=["A", "B", "C", "D", "E", "F"],
.....: )
.....:
In [579]: df_mt["foo"] = "bar"
In [580]: df_mt.loc[df_mt.index[1], ("A", "B")] = np.nan
# you can also create the tables individually
In [581]: store.append_to_multiple(
.....: {"df1_mt": ["A", "B"], "df2_mt": None}, df_mt, selector="df1_mt"
.....: )
.....:
In [582]: store
Out[582]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
# individual tables were created
In [583]: store.select("df1_mt")
Out[583]:
A B
2000-01-01 0.162291 -0.430489
2000-01-02 NaN NaN
2000-01-03 0.429207 -1.099274
2000-01-04 1.869081 -1.466039
2000-01-05 0.092130 -1.726280
2000-01-06 0.266901 -0.036854
2000-01-07 -0.517871 -0.990317
2000-01-08 -0.231342 0.557402
In [584]: store.select("df2_mt")
Out[584]:
C D E F foo
2000-01-01 -2.502042 0.668149 0.460708 1.834518 bar
2000-01-02 0.130441 -0.608465 0.439872 0.506364 bar
2000-01-03 -1.069546 1.236277 0.116634 -1.772519 bar
2000-01-04 0.137462 0.313939 0.748471 -0.943009 bar
2000-01-05 0.836517 2.049798 0.562167 0.189952 bar
2000-01-06 1.112750 -0.151596 1.503311 0.939470 bar
2000-01-07 -0.294348 0.335844 -0.794159 1.495614 bar
2000-01-08 0.860312 -0.538674 -0.541986 -1.759606 bar
# as a multiple
In [585]: store.select_as_multiple(
.....: ["df1_mt", "df2_mt"],
.....: where=["A>0", "B>0"],
.....: selector="df1_mt",
.....: )
.....:
Out[585]:
Empty DataFrame
Columns: [A, B, C, D, E, F, foo]
Index: []
从表中删除#
您可以通过指定 where
来有选择地从表中删除数据。在删除行时,重要的是要了解 PyTables
通过擦除行,然后移动后续数据来删除行。因此,删除操作根据数据的方向可能是一个非常昂贵的操作。为了获得最佳性能,将您要删除的维度放在 indexables
的第一个是值得的。
数据在磁盘上按照 indexables
的顺序排列。以下是一个简单的用例。您存储面板类型数据,其中日期在 major_axis
中,ID 在 minor_axis
中。然后数据会像这样交错排列
- 日期_1
ID_1
ID_2
.
ID_n
- 日期_2
ID_1
.
ID_n
显然,对 major_axis
的删除操作会相当快,因为会删除一个块,然后移动后续数据。另一方面,对 minor_axis
的删除操作将非常昂贵。在这种情况下,使用 where
条件来选择除缺失数据之外的所有数据来重写表几乎肯定会更快。
注意事项和警告#
压缩#
PyTables
允许存储的数据被压缩。这适用于所有类型的存储,而不仅仅是表。使用两个参数来控制压缩:complevel
和complib
。
complevel
指定是否压缩数据以及压缩强度。complevel=0
和complevel=None
禁用压缩,0<complevel<10
启用压缩。complib
指定要使用的压缩库。如果没有指定,则使用默认库zlib
。压缩库通常会优化以获得良好的压缩率或速度,结果取决于数据的类型。选择哪种类型的压缩取决于您的具体需求和数据。支持的压缩库列表zlib:默认的压缩库。经典的压缩库,压缩率高但速度较慢。
lzo:快速压缩和解压缩。
bzip2:良好的压缩率。
blosc:快速压缩和解压缩。
支持其他 blosc 压缩器
blosc:blosclz 这是
blosc
的默认压缩器blosc:lz4:一种紧凑、非常流行且快速的压缩器。
blosc:lz4hc:LZ4 的调整版本,以牺牲速度为代价提供更好的压缩率。
blosc:snappy:一种在许多地方使用的流行压缩器。
blosc:zlib:经典压缩器;比前面的压缩器稍慢,但能获得更好的压缩率。
blosc:zstd:一种非常均衡的编解码器;它在上述压缩器中提供了最佳压缩率,并且速度合理。
如果
complib
被定义为列出的库之外的内容,将引发ValueError
异常。
注意
如果使用 complib
选项指定的库在您的平台上缺失,压缩将直接默认使用 zlib
。
启用文件中所有对象的压缩
store_compressed = pd.HDFStore(
"store_compressed.h5", complevel=9, complib="blosc:blosclz"
)
或者在未启用压缩的存储中进行即时压缩(这仅适用于表)
store.append("df", df, complib="zlib", complevel=5)
ptrepack#
PyTables
在表写入后进行压缩可以提供更好的写入性能,而不是在一开始就启用压缩。您可以使用提供的PyTables
工具ptrepack
。此外,ptrepack
可以在写入完成后更改压缩级别。
ptrepack --chunkshape=auto --propindexes --complevel=9 --complib=blosc in.h5 out.h5
此外,ptrepack in.h5 out.h5
将重新打包文件,以便您能够重新使用先前删除的空间。另外,也可以直接删除文件并重新写入,或使用copy
方法。
注意事项#
警告
HDFStore
在写入时不保证线程安全。底层的PyTables
仅支持并发读取(通过线程或进程)。如果您需要同时进行读写,则需要在单个进程中的单个线程中序列化这些操作。否则您的数据会损坏。有关更多信息,请参见(GH 2397)。
如果您使用锁来管理多个进程之间的写入访问,您可能需要在释放写入锁之前使用
fsync()
。为了方便起见,您可以使用store.flush(fsync=True)
为您完成此操作。一旦创建了
table
,列(DataFrame)就固定了;只能附加完全相同的列请注意,时区(例如,
pytz.timezone('US/Eastern')
)在不同时区版本之间不一定相等。因此,如果在 HDFStore 中使用某个时区库版本将数据本地化到特定时区,而使用另一个版本更新该数据,则由于这些时区被认为不相等,数据将被转换为 UTC。要么使用相同版本的时区库,要么使用更新的时区定义与tz_convert
一起使用。
警告
PyTables
如果列名不能用作属性选择器,将显示NaturalNameWarning
。自然标识符只包含字母、数字和下划线,且不能以数字开头。其他标识符不能在where
子句中使用,通常不是一个好主意。
数据类型#
HDFStore
会将对象 dtype 映射到底层的PyTables
dtype。这意味着以下类型已知可以正常工作
类型 |
表示缺失值 |
---|---|
浮点型 : |
|
整型 : |
|
boolean (布尔值) |
|
|
|
|
|
分类 : 参见下面的章节 |
|
对象 : |
|
unicode
列不受支持,并且将会失败。
分类数据#
您可以将包含 category
dtypes 的数据写入 HDFStore
中。查询的工作方式与对象数组相同。但是,category
dtyped 的数据以更高效的方式存储。
In [586]: dfcat = pd.DataFrame(
.....: {"A": pd.Series(list("aabbcdba")).astype("category"), "B": np.random.randn(8)}
.....: )
.....:
In [587]: dfcat
Out[587]:
A B
0 a -1.520478
1 a -1.069391
2 b -0.551981
3 b 0.452407
4 c 0.409257
5 d 0.301911
6 b -0.640843
7 a -2.253022
In [588]: dfcat.dtypes
Out[588]:
A category
B float64
dtype: object
In [589]: cstore = pd.HDFStore("cats.h5", mode="w")
In [590]: cstore.append("dfcat", dfcat, format="table", data_columns=["A"])
In [591]: result = cstore.select("dfcat", where="A in ['b', 'c']")
In [592]: result
Out[592]:
A B
2 b -0.551981
3 b 0.452407
4 c 0.409257
6 b -0.640843
In [593]: result.dtypes
Out[593]:
A category
B float64
dtype: object
字符串列#
min_itemsize
HDFStore
的底层实现使用固定的列宽度 (itemsize) 来存储字符串列。字符串列的 itemsize 是根据HDFStore
接收到的该列数据的最大长度计算得出的,在第一次附加时。随后的附加操作可能会为某个列引入比该列能够容纳的更大的字符串,这将引发异常(否则可能会发生这些列的静默截断,导致信息丢失)。将来我们可能会放宽此限制,允许用户指定的截断发生。
在第一次创建表时传递 min_itemsize
以预先指定特定字符串列的最小长度。min_itemsize
可以是一个整数,或一个将列名映射到整数的字典。您可以将 values
作为键,以允许所有可索引列或数据列拥有此 min_itemsize。
传递一个 min_itemsize
字典将导致所有传递的列自动创建为数据列。
注意
如果您未传递任何 data_columns
,则 min_itemsize
将是传递的任何字符串的最大长度
In [594]: dfs = pd.DataFrame({"A": "foo", "B": "bar"}, index=list(range(5)))
In [595]: dfs
Out[595]:
A B
0 foo bar
1 foo bar
2 foo bar
3 foo bar
4 foo bar
# A and B have a size of 30
In [596]: store.append("dfs", dfs, min_itemsize=30)
In [597]: store.get_storer("dfs").table
Out[597]:
/dfs/table (Table(5,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": StringCol(itemsize=30, shape=(2,), dflt=b'', pos=1)}
byteorder := 'little'
chunkshape := (963,)
autoindex := True
colindexes := {
"index": Index(6, mediumshuffle, zlib(1)).is_csi=False}
# A is created as a data_column with a size of 30
# B is size is calculated
In [598]: store.append("dfs2", dfs, min_itemsize={"A": 30})
In [599]: store.get_storer("dfs2").table
Out[599]:
/dfs2/table (Table(5,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": StringCol(itemsize=3, shape=(1,), dflt=b'', pos=1),
"A": StringCol(itemsize=30, shape=(), dflt=b'', pos=2)}
byteorder := 'little'
chunkshape := (1598,)
autoindex := True
colindexes := {
"index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"A": Index(6, mediumshuffle, zlib(1)).is_csi=False}
nan_rep
字符串列将使用 nan_rep
字符串表示形式序列化 np.nan
(缺失值)。这默认为字符串值 nan
。您可能会无意中将实际的 nan
值转换为缺失值。
In [600]: dfss = pd.DataFrame({"A": ["foo", "bar", "nan"]})
In [601]: dfss
Out[601]:
A
0 foo
1 bar
2 nan
In [602]: store.append("dfss", dfss)
In [603]: store.select("dfss")
Out[603]:
A
0 foo
1 bar
2 NaN
# here you need to specify a different nan rep
In [604]: store.append("dfss2", dfss, nan_rep="_nan_")
In [605]: store.select("dfss2")
Out[605]:
A
0 foo
1 bar
2 nan
性能#
tables
格式与fixed
存储相比,写入性能会受到一定影响。优点是可以附加/删除和查询(可能非常大量的数据)。与常规存储相比,写入时间通常更长。查询时间可以非常快,尤其是在索引轴上。您可以向
append
传递chunksize=<int>
,指定写入块大小(默认为 50000)。这将显著降低写入时的内存使用量。您可以向第一次
append
传递expectedrows=<int>
,以设置PyTables
将预期的总行数。这将优化读写性能。重复的行可以写入表中,但在选择时会被过滤掉(选择最后出现的项;因此,表在主轴和副轴对上是唯一的)
如果您尝试存储将由 PyTables 序列化(而不是作为固有类型存储)的类型,将引发
PerformanceWarning
。有关更多信息和一些解决方案,请参见 此处。
Feather#
Feather 提供数据帧的二进制列式序列化。它旨在提高数据帧的读写效率,并使数据在数据分析语言之间共享变得容易。
Feather 旨在忠实地序列化和反序列化 DataFrames,支持所有 pandas 数据类型 (dtypes),包括分类和带时区的日期时间等扩展数据类型。
一些注意事项
该格式不会为
DataFrame
写入Index
或MultiIndex
,如果提供了非默认索引,将引发错误。您可以使用.reset_index()
来存储索引,或使用.reset_index(drop=True)
来忽略它。不支持重复的列名和非字符串列名
不支持 object dtype 列中的实际 Python 对象。尝试序列化时,将引发有用的错误消息。
请参阅 完整文档。
In [606]: df = pd.DataFrame(
.....: {
.....: "a": list("abc"),
.....: "b": list(range(1, 4)),
.....: "c": np.arange(3, 6).astype("u1"),
.....: "d": np.arange(4.0, 7.0, dtype="float64"),
.....: "e": [True, False, True],
.....: "f": pd.Categorical(list("abc")),
.....: "g": pd.date_range("20130101", periods=3),
.....: "h": pd.date_range("20130101", periods=3, tz="US/Eastern"),
.....: "i": pd.date_range("20130101", periods=3, freq="ns"),
.....: }
.....: )
.....:
In [607]: df
Out[607]:
a b c ... g h i
0 a 1 3 ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1 b 2 4 ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2 c 3 5 ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002
[3 rows x 9 columns]
In [608]: df.dtypes
Out[608]:
a object
b int64
c uint8
d float64
e bool
f category
g datetime64[ns]
h datetime64[ns, US/Eastern]
i datetime64[ns]
dtype: object
写入 feather 文件。
In [609]: df.to_feather("example.feather")
从 feather 文件读取。
In [610]: result = pd.read_feather("example.feather")
In [611]: result
Out[611]:
a b c ... g h i
0 a 1 3 ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1 b 2 4 ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2 c 3 5 ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002
[3 rows x 9 columns]
# we preserve dtypes
In [612]: result.dtypes
Out[612]:
a object
b int64
c uint8
d float64
e bool
f category
g datetime64[ns]
h datetime64[ns, US/Eastern]
i datetime64[ns]
dtype: object
Parquet#
Apache Parquet提供数据帧的分区二进制列式序列化。它旨在提高数据帧的读写效率,并使数据在数据分析语言之间共享变得容易。Parquet 可以使用各种压缩技术来尽可能地减小文件大小,同时仍保持良好的读取性能。
Parquet 旨在忠实地序列化和反序列化 DataFrame
,支持所有 pandas 数据类型 (dtypes),包括带时区的日期时间等扩展数据类型。
一些注意事项。
不支持重复的列名和非字符串列名。
pyarrow
引擎总是将索引写入输出,但fastparquet
只写入非默认索引。这个额外的列可能会给非 pandas 消费者带来问题,因为他们没有预料到它。无论底层引擎是什么,您都可以使用index
参数强制包含或省略索引。如果指定了索引级别名称,则必须是字符串。
在
pyarrow
引擎中,非字符串类型的分类数据类型可以序列化为 parquet,但反序列化时将变为其原始数据类型。pyarrow
引擎保留了字符串类型分类数据类型的ordered
标志。fastparquet
不保留ordered
标志。不支持的类型包括
Interval
和实际的 Python 对象类型。尝试序列化时,将引发有用的错误消息。pyarrow >= 0.16.0 支持Period
类型。The
pyarrow
引擎保留扩展数据类型,例如可空整数和字符串数据类型(需要 pyarrow >= 0.16.0,并且需要扩展类型实现所需的协议,参见扩展类型文档)。
您可以指定一个 engine
来指示序列化。它可以是 pyarrow
、fastparquet
或 auto
之一。如果未指定引擎,则会检查 pd.options.io.parquet.engine
选项;如果此选项也是 auto
,则尝试使用 pyarrow
,如果失败则回退到 fastparquet
。
请参阅 pyarrow 和 fastparquet 的文档。
注意
这些引擎非常相似,应该能够读写几乎相同的 parquet 格式文件。pyarrow>=8.0.0
支持 timedelta 数据,fastparquet>=0.1.4
支持感知时区的日期时间。这些库的不同之处在于其底层依赖项不同(fastparquet
使用numba
,而pyarrow
使用 C 库)。
In [613]: df = pd.DataFrame(
.....: {
.....: "a": list("abc"),
.....: "b": list(range(1, 4)),
.....: "c": np.arange(3, 6).astype("u1"),
.....: "d": np.arange(4.0, 7.0, dtype="float64"),
.....: "e": [True, False, True],
.....: "f": pd.date_range("20130101", periods=3),
.....: "g": pd.date_range("20130101", periods=3, tz="US/Eastern"),
.....: "h": pd.Categorical(list("abc")),
.....: "i": pd.Categorical(list("abc"), ordered=True),
.....: }
.....: )
.....:
In [614]: df
Out[614]:
a b c d e f g h i
0 a 1 3 4.0 True 2013-01-01 2013-01-01 00:00:00-05:00 a a
1 b 2 4 5.0 False 2013-01-02 2013-01-02 00:00:00-05:00 b b
2 c 3 5 6.0 True 2013-01-03 2013-01-03 00:00:00-05:00 c c
In [615]: df.dtypes
Out[615]:
a object
b int64
c uint8
d float64
e bool
f datetime64[ns]
g datetime64[ns, US/Eastern]
h category
i category
dtype: object
写入 parquet 文件。
In [616]: df.to_parquet("example_pa.parquet", engine="pyarrow")
In [617]: df.to_parquet("example_fp.parquet", engine="fastparquet")
从 parquet 文件读取。
In [618]: result = pd.read_parquet("example_fp.parquet", engine="fastparquet")
In [619]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow")
In [620]: result.dtypes
Out[620]:
a object
b int64
c uint8
d float64
e bool
f datetime64[ns]
g datetime64[ns, US/Eastern]
h category
i category
dtype: object
通过设置 dtype_backend
参数,您可以控制结果 DataFrame 使用的默认 dtypes。
In [621]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow", dtype_backend="pyarrow")
In [622]: result.dtypes
Out[622]:
a string[pyarrow]
b int64[pyarrow]
c uint8[pyarrow]
d double[pyarrow]
e bool[pyarrow]
f timestamp[ns][pyarrow]
g timestamp[ns, tz=US/Eastern][pyarrow]
h dictionary<values=string, indices=int32, order...
i dictionary<values=string, indices=int32, order...
dtype: object
注意
请注意,fastparquet
不支持此功能。
仅读取 parquet 文件的特定列。
In [623]: result = pd.read_parquet(
.....: "example_fp.parquet",
.....: engine="fastparquet",
.....: columns=["a", "b"],
.....: )
.....:
In [624]: result = pd.read_parquet(
.....: "example_pa.parquet",
.....: engine="pyarrow",
.....: columns=["a", "b"],
.....: )
.....:
In [625]: result.dtypes
Out[625]:
a object
b int64
dtype: object
处理索引#
将 DataFrame
序列化为 parquet 时,可能会将隐式索引作为输出文件中的一列或多列包含在内。因此,这段代码
In [626]: df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
In [627]: df.to_parquet("test.parquet", engine="pyarrow")
如果您使用 pyarrow
进行序列化,将创建一个包含三列的 parquet 文件:a
、b
和__index_level_0__
。如果您使用 fastparquet
,则索引 可能写入也可能不写入 到文件中。
这个意想不到的额外列会导致某些数据库(如 Amazon Redshift)拒绝该文件,因为目标表中不存在该列。
如果您想在写入时省略 DataFrame 的索引,请将 index=False
传递给 to_parquet()
In [628]: df.to_parquet("test.parquet", index=False)
这将创建一个仅包含两个预期列(a
和b
)的 parquet 文件。如果您的DataFrame
有自定义索引,当您将此文件加载到DataFrame
中时,将无法恢复该索引。
传递 index=True
将总是写入索引,即使这不是底层引擎的默认行为。
Parquet 文件分区#
Parquet 支持根据一列或多列的值对数据进行分区。
In [629]: df = pd.DataFrame({"a": [0, 0, 1, 1], "b": [0, 1, 0, 1]})
In [630]: df.to_parquet(path="test", engine="pyarrow", partition_cols=["a"], compression=None)
The path
指定数据将保存到的父目录。partition_cols
是用于对数据集进行分区的列名。列按给定的顺序进行分区。分区拆分由分区列中的唯一值决定。上面的示例创建了一个分区数据集,可能看起来像
test
├── a=0
│ ├── 0bac803e32dc42ae83fddfd029cbdebc.parquet
│ └── ...
└── a=1
├── e6ab24a4f45147b49b54a662f0c412a3.parquet
└── ...
ORC#
类似于 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 特性。
目前,将 DataFrame 转换为 ORC 文件时,日期时间列中的时区不会被保留。
In [631]: df = pd.DataFrame(
.....: {
.....: "a": list("abc"),
.....: "b": list(range(1, 4)),
.....: "c": np.arange(4.0, 7.0, dtype="float64"),
.....: "d": [True, False, True],
.....: "e": pd.date_range("20130101", periods=3),
.....: }
.....: )
.....:
In [632]: df
Out[632]:
a b c d e
0 a 1 4.0 True 2013-01-01
1 b 2 5.0 False 2013-01-02
2 c 3 6.0 True 2013-01-03
In [633]: df.dtypes
Out[633]:
a object
b int64
c float64
d bool
e datetime64[ns]
dtype: object
写入 orc 文件。
In [634]: df.to_orc("example_pa.orc", engine="pyarrow")
从 orc 文件读取。
In [635]: result = pd.read_orc("example_pa.orc")
In [636]: result.dtypes
Out[636]:
a object
b int64
c float64
d bool
e datetime64[ns]
dtype: object
仅读取 orc 文件的特定列。
In [637]: result = pd.read_orc(
.....: "example_pa.orc",
.....: columns=["a", "b"],
.....: )
.....:
In [638]: result.dtypes
Out[638]:
a object
b int64
dtype: object
SQL 查询#
The pandas.io.sql
模块提供了一系列查询包装器,旨在促进数据检索并减少对特定数据库 API 的依赖。
如果可用,用户可能首先考虑使用 Apache Arrow ADBC 驱动程序。这些驱动程序应该提供最佳性能、null 处理和类型检测。
2.2.0 版新增:添加了对 ADBC 驱动程序的原生支持
有关 ADBC 驱动程序及其开发状态的完整列表,请参阅ADBC 驱动程序实现状态文档。
如果 ADBC 驱动程序不可用或可能缺少功能,用户应选择与数据库驱动程序库一起安装 SQLAlchemy。此类驱动程序的示例包括用于 PostgreSQL 的 psycopg2 或用于 MySQL 的 pymysql。SQLite 默认包含在 Python 的标准库中。您可以在 SQLAlchemy 文档中找到每个 SQL 方言支持的驱动程序概述。
如果未安装 SQLAlchemy,您可以使用 sqlite3.Connection
来代替 SQLAlchemy 引擎、连接或 URI 字符串。
另请参阅一些cookbook 示例以了解一些高级策略。
主要函数有
|
将 SQL 数据库表读取到 DataFrame 中。 |
|
将 SQL 查询读取到 DataFrame 中。 |
|
将 SQL 查询或数据库表读取到 DataFrame 中。 |
|
将存储在 DataFrame 中的记录写入 SQL 数据库。 |
注意
函数 read_sql()
是 read_sql_table()
和 read_sql_query()
的便利包装器(为了向后兼容),它将根据提供的输入(数据库表名或 SQL 查询)委托给特定的函数。如果表名包含特殊字符,则无需引用。
在以下示例中,我们使用 SQlite SQL 数据库引擎。您可以使用临时 SQLite 数据库,其中数据存储在“内存”中。
要使用 ADBC 驱动程序连接,您需要使用包管理器安装 adbc_driver_sqlite
。安装后,您可以使用 ADBC 驱动程序提供的 DBAPI 接口连接到数据库。
import adbc_driver_sqlite.dbapi as sqlite_dbapi
# Create the connection
with sqlite_dbapi.connect("sqlite:///:memory:") as conn:
df = pd.read_sql_table("data", conn)
要通过 SQLAlchemy 连接,请使用 create_engine()
函数从数据库 URI 创建引擎对象。您只需为您要连接的每个数据库创建一次引擎。有关 create_engine()
和 URI 格式的更多信息,请参阅下面的示例和 SQLAlchemy 文档
In [639]: from sqlalchemy import create_engine
# Create your engine.
In [640]: engine = create_engine("sqlite:///:memory:")
如果您想管理自己的连接,可以改为传递其中一个。以下示例使用 Python 上下文管理器打开到数据库的连接,该管理器在块完成后自动关闭连接。有关如何处理数据库连接的解释,请参阅 SQLAlchemy 文档
with engine.connect() as conn, conn.begin():
data = pd.read_sql_table("data", conn)
警告
打开到数据库的连接后,您也有责任关闭它。未关闭连接的副作用可能包括锁定数据库或其他破坏性行为。
写入 DataFrame#
假设以下数据位于 DataFrame
data
中,我们可以使用 to_sql()
将其插入数据库。
id |
Date |
Col_1 |
Col_2 |
Col_3 |
---|---|---|---|---|
26 |
2012-10-18 |
X |
25.7 |
True |
42 |
2012-10-19 |
Y |
-12.4 |
False |
63 |
2012-10-20 |
Z |
5.73 |
True |
In [641]: import datetime
In [642]: c = ["id", "Date", "Col_1", "Col_2", "Col_3"]
In [643]: d = [
.....: (26, datetime.datetime(2010, 10, 18), "X", 27.5, True),
.....: (42, datetime.datetime(2010, 10, 19), "Y", -12.5, False),
.....: (63, datetime.datetime(2010, 10, 20), "Z", 5.73, True),
.....: ]
.....:
In [644]: data = pd.DataFrame(d, columns=c)
In [645]: data
Out[645]:
id Date Col_1 Col_2 Col_3
0 26 2010-10-18 X 27.50 True
1 42 2010-10-19 Y -12.50 False
2 63 2010-10-20 Z 5.73 True
In [646]: data.to_sql("data", con=engine)
Out[646]: 3
对于某些数据库,写入大型 DataFrame 可能会因超出数据包大小限制而导致错误。这可以通过在调用 to_sql
时设置 chunksize
参数来避免。例如,以下代码以每次 1000 行的批量方式将 data
写入数据库
In [647]: data.to_sql("data_chunked", con=engine, chunksize=1000)
Out[647]: 3
SQL 数据类型#
确保 SQL 数据库之间数据类型管理的一致性具有挑战性。并非每个 SQL 数据库都提供相同的类型,即使它们提供,给定类型的实现方式也可能有所不同,从而对如何保留类型产生微妙的影响。
为了最大限度地保留数据库类型,建议用户尽可能使用 ADBC 驱动程序。Arrow 类型系统提供了比传统 pandas/NumPy 类型系统更接近数据库类型的更广泛的类型数组。例如,请注意此列表(非详尽)中不同数据库和 pandas 后端可用的类型
numpy/pandas |
arrow |
postgres |
sqlite |
---|---|---|---|
int16/Int16 |
int16 |
SMALLINT |
INTEGER |
int32/Int32 |
int32 |
INTEGER |
INTEGER |
int64/Int64 |
int64 |
BIGINT |
INTEGER |
float32 |
float32 |
REAL |
REAL |
float64 |
float64 |
DOUBLE PRECISION |
REAL |
object |
string |
TEXT |
TEXT |
bool |
|
BOOLEAN |
|
datetime64[ns] |
timestamp(us) |
TIMESTAMP |
|
datetime64[ns,tz] |
timestamp(us,tz) |
TIMESTAMPTZ |
|
date32 |
DATE |
||
month_day_nano_interval |
INTERVAL |
||
二进制 |
BINARY |
BLOB |
|
decimal128 |
DECIMAL [1] |
||
list |
ARRAY [1] |
||
struct |
|
脚注
如果您有兴趣在整个 DataFrame 生命周期中尽可能保留数据库类型,建议用户利用 read_sql()
的 dtype_backend="pyarrow"
参数
# for roundtripping
with pg_dbapi.connect(uri) as conn:
df2 = pd.read_sql("pandas_table", conn, dtype_backend="pyarrow")
这将防止您的数据被转换为传统的 pandas/NumPy 类型系统,该系统通常以无法往返的方式转换 SQL 类型。
如果 ADBC 驱动程序不可用,to_sql()
将尝试根据数据的 dtype 将您的数据映射到适当的 SQL 数据类型。当您的列的 dtype 为 object
时,pandas 将尝试推断数据类型。
您始终可以通过使用 dtype
参数指定任何列所需的 SQL 类型来覆盖默认类型。此参数需要一个将列名映射到 SQLAlchemy 类型的字典(对于 sqlite3 回退模式,则为字符串)。例如,指定使用 sqlalchemy String
类型而不是字符串列的默认 Text
类型
In [648]: from sqlalchemy.types import String
In [649]: data.to_sql("data_dtype", con=engine, dtype={"Col_1": String})
Out[649]: 3
注意
由于不同数据库风格对 timedelta 的支持有限,类型为 timedelta64
的列将以纳秒整数值写入数据库,并会引发警告。唯一的例外是使用 ADBC PostgreSQL 驱动程序时,此时 timedelta 将作为 INTERVAL
写入数据库
注意
类型为 category
的列将被转换为密集表示形式,如您使用 np.asarray(categorical)
所得(例如,对于字符串类别,这将生成一个字符串数组)。因此,读回数据库表 不会 生成分类变量。
日期时间数据类型#
使用 ADBC 或 SQLAlchemy,to_sql()
能够写入时区朴素或时区感知日期时间数据。但是,存储在数据库中的最终数据取决于所使用的数据库系统对日期时间数据支持的数据类型。
下表列出了一些常见数据库支持的日期时间数据类型。其他数据库方言可能对日期时间数据有不同的数据类型。
数据库 |
SQL日期时间类型 |
时区支持 |
---|---|---|
SQLite |
|
否 |
MySQL |
|
否 |
PostgreSQL |
|
是 |
当将时区感知数据写入不支持时区的数据库时,数据将被写为相对于时区的本地时间中的时区朴素时间戳。
read_sql_table()
也能够读取时区感知或朴素日期时间数据。读取 TIMESTAMP WITH TIME ZONE
类型时,pandas 会将数据转换为 UTC。
插入方法#
参数 method
控制使用的 SQL 插入子句。可能的值有
None
:使用标准 SQLINSERT
子句(每行一个)。'multi'
:在单个INSERT
子句中传递多个值。它使用一种并非所有后端都支持的特殊 SQL 语法。这通常能为 Presto 和 Redshift 等分析型数据库提供更好的性能,但如果表中包含许多列,则会降低传统 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 驱动程序将数据库类型直接映射回 arrow 类型。对于其他驱动程序,请注意 pandas 从查询输出中推断列 dtype,而不是通过查找物理数据库架构中的数据类型。例如,假设 userid
是表中的整数列。那么,从直观上看,select userid ...
将返回整数值序列,而 select cast(userid as text) ...
将返回对象值(str)序列。因此,如果查询输出为空,则所有结果列都将作为对象值返回(因为它们是最通用的)。如果您预见到您的查询有时会生成空结果,您可能希望之后明确进行类型转换以确保 dtype 的完整性。
您还可以指定列名作为 DataFrame
索引,并指定要读取的列的子集。
In [651]: pd.read_sql_table("data", engine, index_col="id")
Out[651]:
index Date Col_1 Col_2 Col_3
id
26 0 2010-10-18 X 27.50 True
42 1 2010-10-19 Y -12.50 False
63 2 2010-10-20 Z 5.73 True
In [652]: pd.read_sql_table("data", engine, columns=["Col_1", "Col_2"])
Out[652]:
Col_1 Col_2
0 X 27.50
1 Y -12.50
2 Z 5.73
您还可以明确强制将列解析为日期
In [653]: pd.read_sql_table("data", engine, parse_dates=["Date"])
Out[653]:
index id Date Col_1 Col_2 Col_3
0 0 26 2010-10-18 X 27.50 True
1 1 42 2010-10-19 Y -12.50 False
2 2 63 2010-10-20 Z 5.73 True
如果需要,您可以明确指定格式字符串,或传递给 pandas.to_datetime()
的参数字典
pd.read_sql_table("data", engine, parse_dates={"Date": "%Y-%m-%d"})
pd.read_sql_table(
"data",
engine,
parse_dates={"Date": {"format": "%Y-%m-%d %H:%M:%S"}},
)
您可以使用 has_table()
检查表是否存在
模式支持#
通过 read_sql_table()
和 to_sql()
函数中的 schema
关键字,支持从不同模式读取和写入。但请注意,这取决于数据库风格(sqlite 没有模式)。例如
df.to_sql(name="table", con=engine, schema="other_schema")
pd.read_sql_table("table", engine, schema="other_schema")
查询#
您可以在 read_sql_query()
函数中使用原始 SQL 进行查询。在这种情况下,您必须使用适合您的数据库的 SQL 变体。使用 SQLAlchemy 时,您还可以传递 SQLAlchemy 表达式语言构造,这些构造是与数据库无关的。
In [654]: pd.read_sql_query("SELECT * FROM data", engine)
Out[654]:
index id Date Col_1 Col_2 Col_3
0 0 26 2010-10-18 00:00:00.000000 X 27.50 1
1 1 42 2010-10-19 00:00:00.000000 Y -12.50 0
2 2 63 2010-10-20 00:00:00.000000 Z 5.73 1
当然,您可以指定更“复杂”的查询。
In [655]: pd.read_sql_query("SELECT id, Col_1, Col_2 FROM data WHERE id = 42;", engine)
Out[655]:
id Col_1 Col_2
0 42 Y -12.5
函数 read_sql_query()
支持 chunksize
参数。指定此参数将返回查询结果块的迭代器
In [656]: df = pd.DataFrame(np.random.randn(20, 3), columns=list("abc"))
In [657]: df.to_sql(name="data_chunks", con=engine, index=False)
Out[657]: 20
In [658]: for chunk in pd.read_sql_query("SELECT * FROM data_chunks", engine, chunksize=5):
.....: print(chunk)
.....:
a b c
0 -0.395347 -0.822726 -0.363777
1 1.676124 -0.908102 -1.391346
2 -1.094269 0.278380 1.205899
3 1.503443 0.932171 -0.709459
4 -0.645944 -1.351389 0.132023
a b c
0 0.210427 0.192202 0.661949
1 1.690629 -1.046044 0.618697
2 -0.013863 1.314289 1.951611
3 -1.485026 0.304662 1.194757
4 -0.446717 0.528496 -0.657575
a b c
0 -0.876654 0.336252 0.172668
1 0.337684 -0.411202 -0.828394
2 -0.244413 1.094948 0.087183
3 1.125934 -1.480095 1.205944
4 -0.451849 0.452214 -2.208192
a b c
0 -2.061019 0.044184 -0.017118
1 1.248959 -0.675595 -1.908296
2 -0.125934 1.491974 0.648726
3 0.391214 0.438609 1.634248
4 1.208707 -1.535740 1.620399
引擎连接示例#
要使用 SQLAlchemy 连接,请使用 create_engine()
函数从数据库 URI 创建引擎对象。您只需要为要连接的每个数据库创建一次引擎。
from sqlalchemy import create_engine
engine = create_engine("postgresql://scott:tiger@localhost:5432/mydatabase")
engine = create_engine("mysql+mysqldb://scott:tiger@localhost/foo")
engine = create_engine("oracle://scott:[email protected]:1521/sidname")
engine = create_engine("mssql+pyodbc://mydsn")
# sqlite://<nohostname>/<path>
# where <path> is relative:
engine = create_engine("sqlite:///foo.db")
# or absolute, starting with a slash:
engine = create_engine("sqlite:////absolute/path/to/foo.db")
更多信息请参阅 SQLAlchemy 文档中的示例
高级 SQLAlchemy 查询#
您可以使用 SQLAlchemy 构造来描述您的查询。
使用 sqlalchemy.text()
以与后端无关的方式指定查询参数
In [659]: import sqlalchemy as sa
In [660]: pd.read_sql(
.....: sa.text("SELECT * FROM data where Col_1=:col1"), engine, params={"col1": "X"}
.....: )
.....:
Out[660]:
index id Date Col_1 Col_2 Col_3
0 0 26 2010-10-18 00:00:00.000000 X 27.5 1
如果您有数据库的 SQLAlchemy 描述,您可以使用 SQLAlchemy 表达式表示 where 条件
In [661]: metadata = sa.MetaData()
In [662]: data_table = sa.Table(
.....: "data",
.....: metadata,
.....: sa.Column("index", sa.Integer),
.....: sa.Column("Date", sa.DateTime),
.....: sa.Column("Col_1", sa.String),
.....: sa.Column("Col_2", sa.Float),
.....: sa.Column("Col_3", sa.Boolean),
.....: )
.....:
In [663]: pd.read_sql(sa.select(data_table).where(data_table.c.Col_3 is True), engine)
Out[663]:
Empty DataFrame
Columns: [index, Date, Col_1, Col_2, Col_3]
Index: []
您可以使用 sqlalchemy.bindparam()
将 SQLAlchemy 表达式与传递给 read_sql()
的参数结合起来
In [664]: import datetime as dt
In [665]: expr = sa.select(data_table).where(data_table.c.Date > sa.bindparam("date"))
In [666]: pd.read_sql(expr, engine, params={"date": dt.datetime(2010, 10, 18)})
Out[666]:
index Date Col_1 Col_2 Col_3
0 1 2010-10-19 Y -12.50 False
1 2 2010-10-20 Z 5.73 True
Sqlite 回退#
在不使用 SQLAlchemy 的情况下支持使用 sqlite。此模式需要遵循 Python DB-API 的 Python 数据库适配器。
您可以像这样创建连接
import sqlite3
con = sqlite3.connect(":memory:")
然后发出以下查询
data.to_sql("data", con)
pd.read_sql_query("SELECT * FROM data", con)
Google BigQuery#
pandas-gbq
包提供了从 Google BigQuery 读取/写入的功能。
pandas 与此外部包集成。如果安装了 pandas-gbq
,您可以使用 pandas 方法 pd.read_gbq
和 DataFrame.to_gbq
,它们将调用来自 pandas-gbq
的相应函数。
完整文档可在此处找到此处。
Stata 格式#
写入 stata 格式#
方法 DataFrame.to_stata()
会将 DataFrame 写入 .dta 文件。此文件的格式版本始终为 115 (Stata 12)。
In [667]: df = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))
In [668]: df.to_stata("stata.dta")
Stata 数据文件的数据类型支持有限;只有 244 个或更少字符的字符串、int8
、int16
、int32
、float32
和 float64
可以存储在 .dta
文件中。此外,Stata 保留某些值来表示缺失数据。导出某个数据类型在 Stata 中超出允许范围的非缺失值将把变量重新类型化为下一个更大的大小。例如,int8
值在 Stata 中被限制在 -127 到 100 之间,因此值大于 100 的变量将触发转换为 int16
。浮点数据类型中的 nan
值存储为基本缺失数据类型(在 Stata 中为 .
)。
注意
无法导出整数数据类型的缺失数据值。
Stata 写入器可以优雅地处理其他数据类型,包括 int64
、bool
、uint8
、uint16
、uint32
,通过转换为可以表示数据的最小支持类型。例如,如果所有值都小于 100(Stata 中非缺失 int8
数据的上限),类型为 uint8
的数据将被转换为 int8
,或者,如果值超出此范围,变量将被转换为 int16
。
警告
如果 int64
值大于 2**53,从 int64
转换为 float64
可能会导致精度损失。
警告
StataWriter
和 DataFrame.to_stata()
仅支持包含最多 244 个字符的定长字符串,这是版本 115 dta 文件格式施加的限制。尝试写入包含超过 244 个字符的字符串的 Stata dta 文件会引发 ValueError
。
从 Stata 格式读取#
顶级函数 read_stata
将读取 dta 文件并返回 DataFrame
或 pandas.api.typing.StataReader
,后者可用于以增量方式读取文件。
In [669]: pd.read_stata("stata.dta")
Out[669]:
index A B
0 0 -0.165614 0.490482
1 1 -0.637829 0.067091
2 2 -0.242577 1.348038
3 3 0.647699 -0.644937
4 4 0.625771 0.918376
5 5 0.401781 -1.488919
6 6 -0.981845 -0.046882
7 7 -0.306796 0.877025
8 8 -0.336606 0.624747
9 9 -1.582600 0.806340
指定 chunksize
会生成一个 pandas.api.typing.StataReader
实例,该实例可用于每次从文件中读取 chunksize
行。 StataReader
对象可用作迭代器。
In [670]: with pd.read_stata("stata.dta", chunksize=3) as reader:
.....: for df in reader:
.....: print(df.shape)
.....:
(3, 3)
(3, 3)
(3, 3)
(1, 3)
为了进行更精细的控制,请使用 iterator=True
,并在每次调用 read()
时指定 chunksize
。
In [671]: with pd.read_stata("stata.dta", iterator=True) as reader:
.....: chunk1 = reader.read(5)
.....: chunk2 = reader.read(5)
.....:
目前,index
作为列检索。
参数 convert_categoricals
指示是否应读取值标签并将其用于创建 Categorical
变量。值标签也可以通过函数 value_labels
检索,该函数在使用前需要调用 read()
。
参数 convert_missing
指示是否应保留 Stata 中的缺失值表示。如果为 False
(默认值),缺失值表示为 np.nan
。如果为 True
,缺失值使用 StataMissingValue
对象表示,并且包含缺失值的列将具有 object
数据类型。
注意
read_stata()
和 StataReader
支持 .dta 格式 113-115 (Stata 10-12)、117 (Stata 13) 和 118 (Stata 14)。
注意
设置 preserve_dtypes=False
将向上转换为标准 pandas 数据类型:所有整数类型的 int64
和浮点数据的 float64
。默认情况下,导入时保留 Stata 数据类型。
注意
所有 StataReader
对象,无论是由 read_stata()
创建(使用 iterator=True
或 chunksize
时)还是手动实例化,都必须用作上下文管理器(例如 with
语句)。虽然 close()
方法可用,但不支持其使用。它不是公共 API 的一部分,将来会在不通知的情况下移除。
分类数据#
Categorical
数据可以作为值标记数据导出到 Stata 数据文件。导出的数据包括底层类别代码作为整数数据值和类别作为值标签。Stata 没有明确等同于 Categorical
的类型,并且导出时丢失有关变量是否有序的信息。
警告
Stata 仅支持字符串值标签,因此导出数据时会在类别上调用 str
。导出具有非字符串类别的 Categorical
变量会产生警告,并且如果类别的 str
表示不唯一,则可能导致信息丢失。
类似地,可以使用关键字参数 convert_categoricals
(默认为 True
)将标记数据从 Stata 数据文件导入为 Categorical
变量。关键字参数 order_categoricals
(默认为 True
)决定导入的 Categorical
变量是否有序。
注意
导入分类数据时,Stata 数据文件中变量的值不予保留,因为 Categorical
变量总是使用介于 -1
和 n-1
之间的整数数据类型,其中 n
是类别数量。如果需要 Stata 数据文件中的原始值,可以通过设置 convert_categoricals=False
来导入原始数据(但不导入变量标签)。原始值可以与导入的分类数据匹配,因为原始 Stata 数据值与导入的分类变量的类别代码之间存在简单的映射:缺失值被分配代码 -1
,最小的原始值被分配 0
,次小的被分配 1
,以此类推,直到最大的原始值被分配代码 n-1
。
注意
Stata 支持部分标记的序列。这些序列对某些数据值有值标签,但不是全部。导入部分标记的序列将生成一个 Categorical
,其中标记的值具有字符串类别,没有标签的值具有数字类别。
SAS 格式#
顶级函数 read_sas()
可以读取(但不能写入)SAS XPORT (.xpt) 和 SAS7BDAT (.sas7bdat) 格式文件。
SAS 文件只包含两种值类型:ASCII 文本和浮点值(通常为 8 字节,但有时会截断)。对于 xport 文件,没有自动类型转换为整数、日期或分类变量。对于 SAS7BDAT 文件,格式代码可能允许日期变量自动转换为日期。默认情况下,读取整个文件并将其作为 DataFrame
返回。
指定 chunksize
或使用 iterator=True
获取读取器对象 (XportReader
或 SAS7BDATReader
) 以增量方式读取文件。读取器对象还具有包含文件及其变量附加信息的属性。
读取 SAS7BDAT 文件
df = pd.read_sas("sas_data.sas7bdat")
获取迭代器并以每次 100,000 行的方式读取 XPORT 文件
def do_something(chunk):
pass
with pd.read_sas("sas_xport.xpt", chunk=100000) as rdr:
for chunk in rdr:
do_something(chunk)
xport 文件格式的规范可从 SAS 网站获取。
SAS7BDAT 格式没有官方文档。
SPSS 格式#
顶级函数 read_spss()
可以读取(但不能写入)SPSS SAV (.sav) 和 ZSAV (.zsav) 格式文件。
SPSS 文件包含列名。默认情况下,读取整个文件,将分类列转换为 pd.Categorical
,并返回包含所有列的 DataFrame
。
指定 usecols
参数获取列的子集。指定 convert_categoricals=False
以避免将分类列转换为 pd.Categorical
。
读取 SPSS 文件
df = pd.read_spss("spss_data.sav")
从 SPSS 文件中提取 usecols
中包含的列的子集,并避免将分类列转换为 pd.Categorical
df = pd.read_spss(
"spss_data.sav",
usecols=["foo", "bar"],
convert_categoricals=False,
)
有关 SAV 和 ZSAV 文件格式的更多信息可在此处获取此处。
其他文件格式#
pandas 本身仅支持与少量能够与其表格数据模型清晰映射的文件格式进行 IO。对于将其他文件格式读取到 pandas 中以及从 pandas 中写入其他文件格式,我们推荐以下来自更广泛社区的包。
netCDF#
xarray 提供了受 pandas DataFrame
启发的用于处理多维数据集的数据结构,重点是 netCDF 文件格式以及与 pandas 的轻松转换。
性能注意事项#
这是一项使用 pandas 0.24.2 对各种 IO 方法进行的非正式比较。计时取决于机器,应忽略微小差异。
In [1]: sz = 1000000
In [2]: df = pd.DataFrame({'A': np.random.randn(sz), 'B': [1] * sz})
In [3]: df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000000 entries, 0 to 999999
Data columns (total 2 columns):
A 1000000 non-null float64
B 1000000 non-null int64
dtypes: float64(1), int64(1)
memory usage: 15.3 MB
以下测试函数将用于比较几种 IO 方法的性能
import numpy as np
import os
sz = 1000000
df = pd.DataFrame({"A": np.random.randn(sz), "B": [1] * sz})
sz = 1000000
np.random.seed(42)
df = pd.DataFrame({"A": np.random.randn(sz), "B": [1] * sz})
def test_sql_write(df):
if os.path.exists("test.sql"):
os.remove("test.sql")
sql_db = sqlite3.connect("test.sql")
df.to_sql(name="test_table", con=sql_db)
sql_db.close()
def test_sql_read():
sql_db = sqlite3.connect("test.sql")
pd.read_sql_query("select * from test_table", sql_db)
sql_db.close()
def test_hdf_fixed_write(df):
df.to_hdf("test_fixed.hdf", key="test", mode="w")
def test_hdf_fixed_read():
pd.read_hdf("test_fixed.hdf", "test")
def test_hdf_fixed_write_compress(df):
df.to_hdf("test_fixed_compress.hdf", key="test", mode="w", complib="blosc")
def test_hdf_fixed_read_compress():
pd.read_hdf("test_fixed_compress.hdf", "test")
def test_hdf_table_write(df):
df.to_hdf("test_table.hdf", key="test", mode="w", format="table")
def test_hdf_table_read():
pd.read_hdf("test_table.hdf", "test")
def test_hdf_table_write_compress(df):
df.to_hdf(
"test_table_compress.hdf", key="test", mode="w", complib="blosc", format="table"
)
def test_hdf_table_read_compress():
pd.read_hdf("test_table_compress.hdf", "test")
def test_csv_write(df):
df.to_csv("test.csv", mode="w")
def test_csv_read():
pd.read_csv("test.csv", index_col=0)
def test_feather_write(df):
df.to_feather("test.feather")
def test_feather_read():
pd.read_feather("test.feather")
def test_pickle_write(df):
df.to_pickle("test.pkl")
def test_pickle_read():
pd.read_pickle("test.pkl")
def test_pickle_write_compress(df):
df.to_pickle("test.pkl.compress", compression="xz")
def test_pickle_read_compress():
pd.read_pickle("test.pkl.compress", compression="xz")
def test_parquet_write(df):
df.to_parquet("test.parquet")
def test_parquet_read():
pd.read_parquet("test.parquet")
写入时,速度最快的前三个函数是 test_feather_write
、test_hdf_fixed_write
和 test_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_read
、test_pickle_read
和 test_hdf_fixed_read
。
In [14]: %timeit test_sql_read()
1.77 s ± 17.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [15]: %timeit test_hdf_fixed_read()
19.4 ms ± 436 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [16]: %timeit test_hdf_fixed_read_compress()
19.5 ms ± 222 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [17]: %timeit test_hdf_table_read()
38.6 ms ± 857 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [18]: %timeit test_hdf_table_read_compress()
38.8 ms ± 1.49 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [19]: %timeit test_csv_read()
452 ms ± 9.04 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [20]: %timeit test_feather_read()
12.4 ms ± 99.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [21]: %timeit test_pickle_read()
18.4 ms ± 191 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [22]: %timeit test_pickle_read_compress()
915 ms ± 7.48 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [23]: %timeit test_parquet_read()
24.4 ms ± 146 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
文件 test.pkl.compress
、test.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
注释和空行#
忽略行注释和空行#
如果指定了
comment
参数,则完全注释掉的行将被忽略。默认情况下,完全空白的行也会被忽略。如果
skip_blank_lines=False
,则read_csv
将不会忽略空行。警告
忽略的行可能会在涉及行号时产生歧义;参数
header
使用的是行索引(忽略注释行/空行),而skiprows
使用的是行号(包括注释行/空行)。如果同时指定了
header
和skiprows
,则header
将相对于skiprows
之后的位置。例如,注释#
有时文件中可能包含注释或元数据。
默认情况下,解析器会将注释包含在输出中。
我们可以使用
comment
关键字来抑制注释。