IO 工具 (文本, CSV, HDF5, …)#
pandas 的 I/O API 是一组顶层 reader 函数,可以像 pandas.read_csv() 一样访问,通常返回一个 pandas 对象。相应的 writer 函数是对象方法,可以像 DataFrame.to_csv() 一样访问。下表包含了可用的 reader 和 writer。
格式类型 |
数据描述 |
读取器 |
写入器 |
|---|---|---|---|
文本 |
|||
文本 |
固定宽度文本文件 |
NA |
|
文本 |
|||
文本 |
|||
文本 |
NA |
||
文本 |
|||
文本 |
本地剪贴板 |
||
binary |
|||
binary |
NA |
||
binary |
|||
binary |
|||
binary |
|||
binary |
|||
binary |
|||
binary |
|||
binary |
NA |
||
binary |
NA |
||
binary |
|||
SQL |
此处是其中一些 IO 方法的非正式性能比较。
注意
对于使用 StringIO 类的示例,请确保在 Python 3 中使用 from io import StringIO 来导入它。
CSV & 文本文件#
读取文本文件(也称为平面文件)的主力函数是 read_csv()。有关一些高级策略,请参阅 cookbook。
解析选项#
read_csv() 接受以下常用参数
基本
- filepath_or_buffer各种
可以是文件路径(
str,pathlib.Path)、URL(包括 http、ftp 和 S3 位置),或任何具有read()方法的对象(例如,打开的文件或StringIO)。- sepstr,默认为
read_csv()的',',为read_table()的'\t' 要使用的分隔符。如果 sep 为
None,C 引擎无法自动检测分隔符,但 Python 解析引擎可以。这意味着后者将被使用,并通过 Python 内置的嗅探器工具csv.Sniffer自动检测分隔符。此外,长度大于 1 且与'\s+'不同的分隔符将被解释为正则表达式,并强制使用 Python 解析引擎。请注意,正则表达式分隔符容易忽略带引号的数据。正则表达式示例:'\\r\\t'。- delimiterstr,默认为
None sep 的替代参数名称。
列和索引的位置及名称#
- headerint 或 int 列表,默认为
'infer' 用作列名和数据起点的行号。默认行为是推断列名:如果未传递任何名称,行为与
header=0相同,列名从文件的第一行推断;如果显式传递了列名,则行为与header=None相同。显式传递header=0以便替换现有名称。header 可以是指定列 MultiIndex 行位置的整数列表,例如
[0,1,3]。未指定的中间行将被跳过(例如,此示例中的 2 被跳过)。请注意,如果skip_blank_lines=True,此参数将忽略注释行和空行,因此 header=0 表示数据的第一行而不是文件的第一行。- names类数组,默认为
None 要使用的列名列表。如果文件不包含标题行,则应显式传递
header=None。此列表中的重复项是不允许的。- index_colint, str, int/str 序列,或 False,可选,默认为
None 用作
DataFrame行标签的列(或列),可以是字符串名称或列索引。如果给定整数/字符串序列,则使用 MultiIndex。注意
index_col=False可用于强制 pandas 不将第一列用作索引,例如,当您有一个带有每行末尾分隔符的格式错误的文件时。默认值
None指示 pandas 进行猜测。如果列标题行中的字段数等于数据文件正文中的字段数,则使用默认索引。如果较大,则使用前几列作为索引,以便正文中的剩余字段数等于标题中的字段数。标题后的第一行用于确定列数,这些列将进入索引。如果后续行的列数少于第一行,则用
NaN填充。这可以通过
usecols来避免。这可确保列按原样获取,并且尾部数据被忽略。- usecols类列表或可调用对象,默认为
None 返回列的子集。如果为类列表,所有元素都必须是位置性的(即文档列的整数索引)或与用户在
names中提供的列名或从文档标题行推断出的列名对应的字符串。如果给定了names,则不考虑文档标题行。例如,有效的类列表usecols参数将是[0, 1, 2]或['foo', 'bar', 'baz']。元素顺序被忽略,因此
usecols=[0, 1]与[1, 0]相同。要从data中实例化一个元素顺序保留的 DataFrame,请使用pd.read_csv(data, usecols=['foo', 'bar'])[['foo', 'bar']]对于['foo', 'bar']顺序的列,或pd.read_csv(data, usecols=['foo', 'bar'])[['bar', 'foo']]对于['bar', 'foo']顺序。如果可调用,该可调用函数将针对列名进行评估,并返回可调用函数评估为 True 的名称。
In [1]: import pandas as pd In [2]: from io import StringIO In [3]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3" In [4]: pd.read_csv(StringIO(data)) Out[4]: col1 col2 col3 0 a b 1 1 a b 2 2 c d 3 In [5]: pd.read_csv(StringIO(data), usecols=lambda x: x.upper() in ["COL1", "COL3"]) Out[5]: col1 col3 0 a 1 1 a 2 2 c 3
使用此参数可以大大加快解析速度并降低内存使用量(在使用 c 引擎时)。Python 引擎会先加载数据,然后再决定删除哪些列。
常规解析配置#
- dtype类型名称或列 -> 类型字典,默认为
None 数据或列的数据类型。例如:
{'a': np.float64, 'b': np.int32, 'c': 'Int64'}使用str或object以及适当的na_values设置来保留 dtype 而不解释。如果指定了转换器,它们将代替 dtype 转换。将 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 引擎支持。“pyarrow”引擎的某些功能不受支持或可能无法正常工作。
- converters字典,默认为
None 用于转换特定列中值的字典。键可以是整数或列标签。
- true_values列表,默认为
None 被视为
True的值。- false_values列表,默认为
None 被视为
False的值。- skipinitialspace布尔值,默认为
False 跳过分隔符后的空格。
- skiprows类列表或整数,默认为
None 要跳过的行号(从 0 开始)或要跳过的行数(整数),位于文件开头。
如果可调用,该可调用函数将针对行索引进行评估,如果应跳过该行则返回 True,否则返回 False。
In [6]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3" In [7]: pd.read_csv(StringIO(data)) Out[7]: col1 col2 col3 0 a b 1 1 a b 2 2 c d 3 In [8]: pd.read_csv(StringIO(data), skiprows=lambda x: x % 2 != 0) Out[8]: col1 col2 col3 0 a b 2
- skipfooterint,默认为
0 要跳过的文件底部的行数(使用 engine=’c’ 时不支持)。
- nrowsint,默认为
None 要读取的文件行数。对于读取大文件的部分很有用。
- low_memory布尔值,默认为
True 内部以块的形式处理文件,从而降低了解析时的内存使用量,但可能会导致类型推断混合。为确保没有混合类型,请将
False设置为False,或使用dtype参数指定类型。请注意,整个文件仍会读入一个DataFrame中,请使用chunksize或iterator参数以块的形式返回数据。(仅对 C 解析器有效)- memory_map布尔值,默认为 False
如果为
filepath_or_buffer提供了文件路径,则将文件对象直接映射到内存,并直接从中访问数据。使用此选项可以提高性能,因为不再存在 I/O 开销。
NA 和缺失值处理#
- na_values标量、字符串、类列表或字典,默认为
None 要识别为 NA/NaN 的其他字符串。如果传递了字典,则为特定于列的 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设置为False可以提高读取大型文件的性能。- verbose布尔值,默认为
False 指示在非数字列中放置的 NA 值的数量。
- skip_blank_lines布尔值,默认为
True 如果为
True,则跳过空行,而不是将其解释为 NaN 值。
日期时间处理#
- parse_dates布尔值或整数列表或名称列表或列表列表或字典,默认为
False。 如果为
True-> 尝试解析索引。如果为
[1, 2, 3]-> 尝试将列 1、2、3 中的每一个解析为单独的日期列。
注意
对于 iso8601 格式的日期,存在快速路径。
- date_format字符串或列 -> 格式字典,默认为
None 如果与
parse_dates一起使用,将根据此格式解析日期。对于更复杂的情况,请将其读取为object,然后根据需要应用to_datetime()。版本 2.0.0 中已添加。
- dayfirst布尔值,默认为
False DD/MM 格式日期,国际和欧洲格式。
- cache_dates布尔值,默认为 True
如果为 True,则使用唯一的、已转换日期的缓存来应用日期时间转换。在解析重复的日期字符串(尤其是带时区偏移的日期字符串)时,可能会显著加快速度。
迭代#
- iterator布尔值,默认为
False 返回
TextFileReader对象以进行迭代或使用get_chunk()获取块。- chunksizeint,默认为
None 返回
TextFileReader对象以进行迭代。参见下面的 迭代和分块。
引号、压缩和文件格式#
- compression{
'infer','gzip','bz2','zip','xz','zstd',None,dict}, 默认为'infer' 用于对磁盘上的数据进行即时解压缩。如果为“infer”,则如果
filepath_or_buffer是以‘.gz’、‘.bz2’、‘.zip’、‘.xz’、‘.zst’结尾的路径状对象,则使用 gzip、bz2、zip、xz 或 zstandard,否则不进行解压缩。如果使用‘zip’,则 ZIP 文件必须只包含一个要读取的数据文件。设置为None表示不解压缩。也可以是一个字典,其键'method'设置为 {'zip','gzip','bz2','zstd'} 中的一个,并且其他键值对将被转发给zipfile.ZipFile、gzip.GzipFile、bz2.BZ2File或zstandard.ZstdDecompressor。例如,可以传递以下内容以获得更快的压缩并创建可重复的 gzip 存档:compression={'method': 'gzip', 'compresslevel': 1, 'mtime': 1}。- thousands字符串,默认为
None 千位分隔符。
- decimal字符串,默认为
'.' 识别为小数点的字符。例如,对欧洲数据使用
','。- float_precision字符串,默认为 None
指定 C 引擎应使用哪个转换器处理浮点值。选项有
None用于普通转换器,high用于高精度转换器,round_trip用于往返转换器。- lineterminator字符串(长度为 1),默认为
None 用于将文件分割成行的字符。仅对 C 解析器有效。
- quotechar字符串(长度为 1)
用于标记带引号项的开始和结束的字符。带引号的项可以包含分隔符,它将被忽略。
- quoting整数或
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元素。- escapechar字符串(长度为 1),默认为
None 当引用为
QUOTE_NONE时,用于转义分隔符的单字符字符串。- comment字符串,默认为
None 指示行的剩余部分不应被解析。如果出现在行开头,该行将被完全忽略。此参数必须是单个字符。就像空行(只要
skip_blank_lines=True)一样,完全注释的行会被header参数忽略,但不会被skiprows参数忽略。例如,如果comment='#',使用header=0解析 ‘#empty\na,b,c\n1,2,3’ 将导致 ‘a,b,c’ 被视为标题。- encoding字符串,默认为
None 读取/写入 UTF 时要使用的编码(例如
'utf-8')。Python 标准编码列表。- dialect字符串或
csv.Dialect实例,默认为None 如果提供,此参数将覆盖以下参数的值(默认值或非默认值):
delimiter、doublequote、escapechar、skipinitialspace、quotechar和quoting。如果有必要覆盖值,将发出 ParserWarning。有关更多详细信息,请参阅csv.Dialect文档。
错误处理#
- on_bad_lines
‘error’,遇到错误行时引发 ParserError。
‘warn’,遇到错误行时打印警告并跳过该行。
‘skip’,跳过错误行,而不在遇到时引发或发出警告。
指定列数据类型#
您可以为整个 DataFrame 或单个列指定数据类型。
In [9]: import numpy as np
In [10]: data = "a,b,c,d\n1,2,3,4\n5,6,7,8\n9,10,11"
In [11]: print(data)
a,b,c,d
1,2,3,4
5,6,7,8
9,10,11
In [12]: df = pd.read_csv(StringIO(data), dtype=object)
In [13]: df
Out[13]:
a b c d
0 1 2 3 4
1 5 6 7 8
2 9 10 11 NaN
In [14]: df["a"][0]
Out[14]: '1'
In [15]: df = pd.read_csv(StringIO(data), dtype={"b": object, "c": np.float64, "d": "Int64"})
In [16]: df.dtypes
Out[16]:
a int64
b object
c float64
d Int64
dtype: object
幸运的是,pandas 提供了多种确保您的列只包含一种 dtype 的方法。如果您不熟悉这些概念,可以 在此处 了解更多关于 dtype 的信息,并 在此处 了解更多关于 pandas 中 object 转换的信息。
例如,您可以使用 read_csv() 的 converters 参数。
In [17]: data = "col_1\n1\n2\n'A'\n4.22"
In [18]: df = pd.read_csv(StringIO(data), converters={"col_1": str})
In [19]: df
Out[19]:
col_1
0 1
1 2
2 'A'
3 4.22
In [20]: df["col_1"].apply(type).value_counts()
Out[20]:
col_1
<class 'str'> 4
Name: count, dtype: int64
或者,您可以使用 to_numeric() 函数在读取数据后强制转换 dtype。
In [21]: df2 = pd.read_csv(StringIO(data))
In [22]: df2["col_1"] = pd.to_numeric(df2["col_1"], errors="coerce")
In [23]: df2
Out[23]:
col_1
0 1.00
1 2.00
2 NaN
3 4.22
In [24]: df2["col_1"].apply(type).value_counts()
Out[24]:
col_1
<class 'float'> 4
Name: count, dtype: int64
这将把所有有效解析转换为浮点数,将无效解析保留为 NaN。
最终,如何处理包含混合数据类型(dtypes)的列取决于您的具体需求。在上面的例子中,如果您想将数据异常值统一处理为 NaN,那么 to_numeric() 可能是您的最佳选择。但是,如果您希望无论什么类型的数据都强制转换,那么使用 read_csv() 的 converters 参数绝对值得尝试。
注意
在某些情况下,读取包含混合数据类型(dtypes)的列的异常数据会导致数据集不一致。如果您依赖 pandas 来推断您列的数据类型,解析引擎将对数据的不同块推断数据类型,而不是一次性推断整个数据集。因此,您可能会得到包含混合数据类型(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 数据类型,而其他块由于读取的数据混合数据类型(dtypes)而为 str。需要注意的是,整列将被标记为 object 数据类型,这是用于混合数据类型(dtypes)的列。
设置 dtype_backend="numpy_nullable" 将导致每列都具有可为空的数据类型。
In [31]: data = """a,b,c,d,e,f,g,h,i,j
....: 1,2.5,True,a,,,,,12-31-2019,
....: 3,4.5,False,b,6,7.5,True,a,12-31-2019,
....: """
....:
In [32]: df = pd.read_csv(StringIO(data), dtype_backend="numpy_nullable", parse_dates=["i"])
In [33]: df
Out[33]:
a b c d e f g h i j
0 1 2.5 True a <NA> <NA> <NA> <NA> 2019-12-31 <NA>
1 3 4.5 False b 6 7.5 True a 2019-12-31 <NA>
In [34]: df.dtypes
Out[34]:
a Int64
b Float64
c boolean
d string
e Int64
f Float64
g boolean
h string
i datetime64[us]
j Int64
dtype: object
指定分类数据类型#
Categorical 列可以直接通过指定 dtype='category' 或 dtype=CategoricalDtype(categories, ordered) 来解析。
In [35]: data = "col1,col2,col3\na,b,1\na,b,2\nc,d,3"
In [36]: pd.read_csv(StringIO(data))
Out[36]:
col1 col2 col3
0 a b 1
1 a b 2
2 c d 3
In [37]: pd.read_csv(StringIO(data)).dtypes
Out[37]:
col1 str
col2 str
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 str
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 str
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, str): ['a', 'b', 'd']
这与 Categorical.set_categories() 的行为相匹配。此行为已被弃用。在未来的版本中,出现不在指定类别中的非 NA 值将引发错误。
注意
使用 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, str): ['1', '2', '3']
In [48]: new_categories = pd.to_numeric(df["col3"].cat.categories)
In [49]: df["col3"] = df["col3"].cat.rename_categories(new_categories)
In [50]: df["col3"]
Out[50]:
0 1
1 2
2 3
Name: col3, dtype: category
Categories (3, int64): [1, 2, 3]
命名和使用列#
处理列名#
文件可能包含或不包含标题行。pandas 假定第一行将用作列名。
In [51]: data = "a,b,c\n1,2,3\n4,5,6\n7,8,9"
In [52]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9
In [53]: pd.read_csv(StringIO(data))
Out[53]:
a b c
0 1 2 3
1 4 5 6
2 7 8 9
通过指定 names 参数和 header 参数,您可以指定要使用的其他名称,以及是否丢弃标题行(如果有)。
In [54]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9
In [55]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=0)
Out[55]:
foo bar baz
0 1 2 3
1 4 5 6
2 7 8 9
In [56]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=None)
Out[56]:
foo bar baz
0 a b c
1 1 2 3
2 4 5 6
3 7 8 9
如果标题在第一行以外的某一行,请将行号传递给 header。这将跳过前面的行。
In [57]: data = "skip this skip it\na,b,c\n1,2,3\n4,5,6\n7,8,9"
In [58]: pd.read_csv(StringIO(data), header=1)
Out[58]:
a b c
0 1 2 3
1 4 5 6
2 7 8 9
注意
默认行为是推断列名:如果不传递名称,行为与 header=0 相同,列名将从文件的第一个非空行推断;如果显式传递列名,则行为与 header=None 相同。
重复名称解析#
如果文件或标题包含重复的名称,pandas 默认会区分它们以防止数据被覆盖。
In [59]: data = "a,b,a\n0,1,2\n3,4,5"
In [60]: pd.read_csv(StringIO(data))
Out[60]:
a b a.1
0 0 1 2
1 3 4 5
由于重复的列“X”,...,“X”变成了“X”,“X.1”,...,“X.N”,因此不再有重复数据。
过滤列(usecols)#
usecols 参数允许您使用列名、位置编号或可调用对象来选择文件中的任何列子集。
In [61]: data = "a,b,c,d\n1,2,3,foo\n4,5,6,bar\n7,8,9,baz"
In [62]: pd.read_csv(StringIO(data))
Out[62]:
a b c d
0 1 2 3 foo
1 4 5 6 bar
2 7 8 9 baz
In [63]: pd.read_csv(StringIO(data), usecols=["b", "d"])
Out[63]:
b d
0 2 foo
1 5 bar
2 8 baz
In [64]: pd.read_csv(StringIO(data), usecols=[0, 2, 3])
Out[64]:
a c d
0 1 3 foo
1 4 6 bar
2 7 9 baz
In [65]: pd.read_csv(StringIO(data), usecols=lambda x: x.upper() in ["A", "C"])
Out[65]:
a c
0 1 3
1 4 6
2 7 9
usecols 参数也可以用来指定最终结果中不使用的列。
In [66]: pd.read_csv(StringIO(data), usecols=lambda x: x not in ["a", "c"])
Out[66]:
b d
0 2 foo
1 5 bar
2 8 baz
在这种情况下,可调用对象指定我们从输出中排除“a”和“c”列。
处理 Unicode 数据#
encoding 参数应用于已编码的 Unicode 数据,这将导致字节字符串在结果中被解码为 Unicode。
In [86]: from io import BytesIO
In [87]: data = b"word,length\n" b"Tr\xc3\xa4umen,7\n" b"Gr\xc3\xbc\xc3\x9fe,5"
In [88]: data = data.decode("utf8").encode("latin-1")
In [89]: df = pd.read_csv(BytesIO(data), encoding="latin-1")
In [90]: df
Out[90]:
word length
0 Träumen 7
1 Grüße 5
In [91]: df["word"][1]
Out[91]: 'Grüße'
一些将所有字符编码为多字节的格式,如 UTF-16,如果没有指定编码,根本无法正确解析。 Python 标准编码列表。
索引列和尾随分隔符#
如果文件中的数据列比列名多一列,则第一列将用作 DataFrame 的行名。
In [92]: data = "a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"
In [93]: pd.read_csv(StringIO(data))
Out[93]:
a b c
4 apple bat 5.7
8 orange cow 10.0
In [94]: data = "index,a,b,c\n4,apple,bat,5.7\n8,orange,cow,10"
In [95]: pd.read_csv(StringIO(data), index_col=0)
Out[95]:
a b c
index
4 apple bat 5.7
8 orange cow 10.0
通常,您可以使用 index_col 选项来实现此行为。
在文件末尾有分隔符导致解析器混淆的一些异常情况。要显式禁用索引列推断并丢弃最后一列,请传递 index_col=False。
In [96]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"
In [97]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,
In [98]: pd.read_csv(StringIO(data))
Out[98]:
a b c
4 apple bat NaN
8 orange cow NaN
In [99]: pd.read_csv(StringIO(data), index_col=False)
Out[99]:
a b c
0 4 apple bat
1 8 orange cow
如果正在使用 usecols 选项解析数据子集,则 index_col 的指定是基于该子集,而不是原始数据。
In [100]: data = "a,b,c\n4,apple,bat,\n8,orange,cow,"
In [101]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,
In [102]: pd.read_csv(StringIO(data), usecols=["b", "c"])
Out[102]:
b c
4 bat NaN
8 cow NaN
In [103]: pd.read_csv(StringIO(data), usecols=["b", "c"], index_col=0)
Out[103]:
b c
4 bat NaN
8 cow NaN
日期处理#
指定日期列#
为了更好地处理日期时间数据,read_csv() 使用关键字参数 parse_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[us]', name='date', freq=None)
我们经常需要分别存储日期和时间数据,或者分别存储各种日期字段。parse_dates 关键字可用于指定要解析日期和/或时间的列。
注意
如果列或索引包含无法解析的日期,则整个列或索引将原样返回为 object 数据类型。对于非标准日期时间解析,请在 pd.read_csv 之后使用 to_datetime()。
注意
read_csv 对解析 iso8601 格式的日期时间字符串(例如,“2000-01-01T00:01:02+00:00”及其类似变体)有一个快速通道。如果您的数据可以安排以这种格式存储日期时间,加载时间将显著加快,已观察到约 20 倍的提升。
日期解析函数#
最后,解析器允许您指定自定义 date_format。性能方面,您应该按此顺序尝试这些日期解析方法:
如果您知道格式,请使用
date_format,例如:date_format="%d/%m/%Y"或date_format={column_name: "%d/%m/%Y"}。如果您为不同列使用不同格式,或想将任何额外选项(如
utc)传递给to_datetime,那么您应该将数据读取为object数据类型,然后使用to_datetime。
解析具有混合时区的 CSV#
pandas 无法原生表示具有混合时区的列或索引。如果您的 CSV 文件包含时区混合的列,即使使用了 parse_dates,默认结果也将是一个带有字符串的 object-dtype 列。要将混合时区值解析为日期时间列,请读取为 object 数据类型,然后调用 to_datetime() 并将 utc=True。
In [108]: content = """\
.....: a
.....: 2000-01-01T00:00:00+05:00
.....: 2000-01-01T00:00:00+06:00"""
.....:
In [109]: df = pd.read_csv(StringIO(content))
In [110]: df["a"] = pd.to_datetime(df["a"], utc=True)
In [111]: df["a"]
Out[111]:
0 1999-12-31 19:00:00+00:00
1 1999-12-31 18:00:00+00:00
Name: a, dtype: datetime64[us, 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 [112]: df = pd.read_csv(
.....: "foo.csv",
.....: index_col=0,
.....: parse_dates=True,
.....: )
.....:
In [113]: df
Out[113]:
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 [114]: data = StringIO("date\n12 Jan 2000\n2000-01-13\n")
In [115]: df = pd.read_csv(data)
In [116]: df['date'] = pd.to_datetime(df['date'], format='mixed')
In [117]: df
Out[117]:
date
0 2000-01-12
1 2000-01-13
或者,如果您的日期时间格式都是 ISO8601(可能格式不完全相同)
In [118]: data = StringIO("date\n2020-01-01\n2020-01-01 03:00\n")
In [119]: df = pd.read_csv(data)
In [120]: df['date'] = pd.to_datetime(df['date'], format='ISO8601')
In [121]: df
Out[121]:
date
0 2020-01-01 00:00:00
1 2020-01-01 03:00:00
国际日期格式#
美国日期格式倾向于 MM/DD/YYYY,而许多国际格式则使用 DD/MM/YYYY。为了方便起见,提供了一个 dayfirst 关键字。
In [122]: data = "date,value,cat\n1/6/2000,5,a\n2/6/2000,10,b\n3/6/2000,15,c"
In [123]: print(data)
date,value,cat
1/6/2000,5,a
2/6/2000,10,b
3/6/2000,15,c
In [124]: with open("tmp.csv", "w") as fh:
.....: fh.write(data)
.....:
In [125]: pd.read_csv("tmp.csv", parse_dates=[0])
Out[125]:
date value cat
0 2000-01-06 5 a
1 2000-02-06 10 b
2 2000-03-06 15 c
In [126]: pd.read_csv("tmp.csv", dayfirst=True, parse_dates=[0])
Out[126]:
date value cat
0 2000-06-01 5 a
1 2000-06-02 10 b
2 2000-06-03 15 c
将 CSV 写入二进制文件对象#
df.to_csv(..., mode="wb") 允许将 CSV 写入以二进制模式打开的文件对象。在大多数情况下,无需指定 mode,因为 pandas 会自动检测文件对象是以降文本还是二进制模式打开的。
In [127]: import io
In [128]: data = pd.DataFrame([0, 1, 2])
In [129]: buffer = io.BytesIO()
In [130]: data.to_csv(buffer, encoding="utf-8", compression="gzip")
指定浮点数转换方法#
可以指定 float_precision 参数,以便在 C 引擎解析时使用特定的浮点转换器。选项包括普通转换器、高精度转换器和往返转换器(保证在写入文件后往返值)。例如,
In [131]: val = "0.3066101993807095471566981359501369297504425048828125"
In [132]: data = "a,b,c\n1,2,{0}".format(val)
In [133]: abs(
.....: pd.read_csv(
.....: StringIO(data),
.....: engine="c",
.....: float_precision=None,
.....: )["c"][0] - float(val)
.....: )
.....:
Out[133]: np.float64(5.551115123125783e-17)
In [134]: abs(
.....: pd.read_csv(
.....: StringIO(data),
.....: engine="c",
.....: float_precision="high",
.....: )["c"][0] - float(val)
.....: )
.....:
Out[134]: np.float64(5.551115123125783e-17)
In [135]: abs(
.....: pd.read_csv(StringIO(data), engine="c", float_precision="round_trip")["c"][0]
.....: - float(val)
.....: )
.....:
Out[135]: np.float64(0.0)
千位分隔符#
对于已用千位分隔符写入的大数字,您可以将 thousands 关键字设置为长度为 1 的字符串,以便正确解析整数。
默认情况下,带有千位分隔符的数字将被解析为字符串。
In [136]: data = (
.....: "ID|level|category\n"
.....: "Patient1|123,000|x\n"
.....: "Patient2|23,000|y\n"
.....: "Patient3|1,234,018|z"
.....: )
.....:
In [137]: with open("tmp.csv", "w") as fh:
.....: fh.write(data)
.....:
In [138]: df = pd.read_csv("tmp.csv", sep="|")
In [139]: df
Out[139]:
ID level category
0 Patient1 123,000 x
1 Patient2 23,000 y
2 Patient3 1,234,018 z
In [140]: df.level.dtype
Out[140]: <StringDtype(na_value=nan)>
thousands 关键字允许正确解析整数。
In [141]: df = pd.read_csv("tmp.csv", sep="|", thousands=",")
In [142]: df
Out[142]:
ID level category
0 Patient1 123000 x
1 Patient2 23000 y
2 Patient3 1234018 z
In [143]: df.level.dtype
Out[143]: dtype('int64')
NA 值#
要控制哪些值被解析为缺失值(用 NaN 表示),请在 na_values 中指定一个字符串。如果您指定了一个字符串列表,那么列表中的所有值都将被视为缺失值。如果您指定了一个数字(一个 float,如 5.0 或一个 integer,如 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 [144]: data = "a,b,c\n1,Yes,2\n3,No,4"
In [145]: print(data)
a,b,c
1,Yes,2
3,No,4
In [146]: pd.read_csv(StringIO(data))
Out[146]:
a b c
0 1 Yes 2
1 3 No 4
In [147]: pd.read_csv(StringIO(data), true_values=["Yes"], false_values=["No"])
Out[147]:
a b c
0 1 True 2
1 3 False 4
处理“坏”行#
某些文件可能包含格式错误的行,字段过多或过少。字段过少的行将在尾随字段中填充 NA 值。默认情况下,字段过多的行将引发错误。
In [148]: data = "a,b,c\n1,2,3\n4,5,6,7\n8,9,10"
In [149]: pd.read_csv(StringIO(data))
---------------------------------------------------------------------------
ParserError Traceback (most recent call last)
Cell In[149], line 1
----> 1 pd.read_csv(StringIO(data))
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:873, 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, skip_blank_lines, parse_dates, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, low_memory, memory_map, float_precision, storage_options, dtype_backend)
861 kwds_defaults = _refine_defaults_read(
862 dialect,
863 delimiter,
(...) 869 dtype_backend=dtype_backend,
870 )
871 kwds.update(kwds_defaults)
--> 873 return _read(filepath_or_buffer, kwds)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:306, in _read(filepath_or_buffer, kwds)
303 return parser
305 with parser:
--> 306 return parser.read(nrows)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1947, in TextFileReader.read(self, nrows)
1940 nrows = validate_integer("nrows", nrows)
1941 try:
1942 # error: "ParserBase" has no attribute "read"
1943 (
1944 index,
1945 columns,
1946 col_dict,
-> 1947 ) = self._engine.read( # type: ignore[attr-defined]
1948 nrows
1949 )
1950 except Exception:
1951 self.close()
File ~/work/pandas/pandas/pandas/io/parsers/c_parser_wrapper.py:215, in CParserWrapper.read(self, nrows)
213 try:
214 if self.low_memory:
--> 215 chunks = self._reader.read_low_memory(nrows)
216 # destructive to chunks
217 data = _concatenate_chunks(chunks, self.names)
File ~/work/pandas/pandas/pandas/_libs/parsers.pyx:832, in pandas._libs.parsers.TextReader.read_low_memory()
File ~/work/pandas/pandas/pandas/_libs/parsers.pyx:897, in pandas._libs.parsers.TextReader._read_rows()
File ~/work/pandas/pandas/pandas/_libs/parsers.pyx:868, in pandas._libs.parsers.TextReader._tokenize_rows()
File ~/work/pandas/pandas/pandas/_libs/parsers.pyx:885, in pandas._libs.parsers.TextReader._check_tokenize_status()
File ~/work/pandas/pandas/pandas/_libs/parsers.pyx:2084, in pandas._libs.parsers.raise_parser_error()
ParserError: Error tokenizing data. C error: Expected 3 fields in line 3, saw 4
您可以选择跳过坏行。
In [150]: data = "a,b,c\n1,2,3\n4,5,6,7\n8,9,10"
In [151]: pd.read_csv(StringIO(data), on_bad_lines="skip")
Out[151]:
a b c
0 1 2 3
1 8 9 10
或者,如果 engine="python",则传递一个可调用函数来处理坏行。坏行将是一个由 sep 分割的字符串列表。
In [152]: external_list = []
In [153]: def bad_lines_func(line):
.....: external_list.append(line)
.....: return line[-3:]
.....:
In [154]: external_list
Out[154]: []
注意
可调用函数将只处理字段过多的行。由其他错误引起的坏行将被静默跳过。
In [155]: bad_lines_func = lambda line: print(line)
In [156]: data = 'name,type\nname a,a is of type a\nname b,"b\" is of type b"'
In [157]: data
Out[157]: 'name,type\nname a,a is of type a\nname b,"b" is of type b"'
In [158]: pd.read_csv(StringIO(data), on_bad_lines=bad_lines_func, engine="python")
Out[158]:
name type
0 name a a is of type a
在这种情况下,该行未被处理,因为“坏行”是由转义字符引起的。
您还可以使用 usecols 参数来消除某些行中存在而其他行中不存在的额外列数据。
In [159]: pd.read_csv(StringIO(data), usecols=[0, 1, 2])
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[159], line 1
----> 1 pd.read_csv(StringIO(data), usecols=[0, 1, 2])
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:873, 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, skip_blank_lines, parse_dates, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, low_memory, memory_map, float_precision, storage_options, dtype_backend)
861 kwds_defaults = _refine_defaults_read(
862 dialect,
863 delimiter,
(...) 869 dtype_backend=dtype_backend,
870 )
871 kwds.update(kwds_defaults)
--> 873 return _read(filepath_or_buffer, kwds)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:300, in _read(filepath_or_buffer, kwds)
297 _validate_names(kwds.get("names", None))
299 # Create the parser.
--> 300 parser = TextFileReader(filepath_or_buffer, **kwds)
302 if chunksize or iterator:
303 return parser
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1645, in TextFileReader.__init__(self, f, engine, **kwds)
1642 self.options["has_index_names"] = kwds["has_index_names"]
1644 self.handles: IOHandles | None = None
-> 1645 self._engine = self._make_engine(f, self.engine)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1922, in TextFileReader._make_engine(self, f, engine)
1919 raise ValueError(msg)
1921 try:
-> 1922 return mapping[engine](f, **self.options)
1923 except Exception:
1924 if self.handles is not None:
File ~/work/pandas/pandas/pandas/io/parsers/c_parser_wrapper.py:147, in CParserWrapper.__init__(self, src, **kwds)
140 self.names = [
141 n
142 for i, n in enumerate(self.names)
143 if (i in usecols or n in usecols)
144 ]
146 if len(self.names) < len(usecols):
--> 147 self._validate_usecols_names(
148 usecols,
149 self.names,
150 )
152 validate_parse_dates_presence(self.parse_dates, self.names)
153 self._set_noconvert_columns()
File ~/work/pandas/pandas/pandas/io/parsers/base_parser.py:662, in ParserBase._validate_usecols_names(self, usecols, names)
660 missing = [c for c in usecols if c not in names]
661 if len(missing) > 0:
--> 662 raise ValueError(
663 f"Usecols do not match columns, columns expected but not found: "
664 f"{missing}"
665 )
667 return usecols
ValueError: Usecols do not match columns, columns expected but not found: [0, 1, 2]
如果您想保留所有数据,包括字段过多的行,可以指定足够的 names。这确保了字段不足的行将用 NaN 填充。
In [160]: pd.read_csv(StringIO(data), names=['a', 'b', 'c', 'd'])
Out[160]:
a b c d
0 name type NaN NaN
1 name a a is of type a NaN NaN
2 name b b is of type b" NaN NaN
方言#
dialect 关键字在指定文件格式方面提供了更大的灵活性。默认情况下,它使用 Excel 方言,但您可以指定方言名称或 csv.Dialect 实例。
假设您有未引用的数据。
In [161]: data = "label1,label2,label3\n" 'index1,"a,c,e\n' "index2,b,d,f"
In [162]: print(data)
label1,label2,label3
index1,"a,c,e
index2,b,d,f
默认情况下,read_csv 使用 Excel 方言并将双引号视为引号字符,这会导致在找到结束双引号之前遇到换行符时失败。
我们可以通过使用 dialect 来解决这个问题。
In [163]: import csv
In [164]: dia = csv.excel()
In [165]: dia.quoting = csv.QUOTE_NONE
In [166]: pd.read_csv(StringIO(data), dialect=dia)
Out[166]:
label1 label2 label3
index1 "a c e
index2 b d f
所有方言选项都可以通过关键字参数单独指定。
In [167]: data = "a,b,c~1,2,3~4,5,6"
In [168]: pd.read_csv(StringIO(data), lineterminator="~")
Out[168]:
a b c
0 1 2 3
1 4 5 6
另一个常见的方言选项是 skipinitialspace,用于跳过分隔符后的任何空格。
In [169]: data = "a, b, c\n1, 2, 3\n4, 5, 6"
In [170]: print(data)
a, b, c
1, 2, 3
4, 5, 6
In [171]: pd.read_csv(StringIO(data), skipinitialspace=True)
Out[171]:
a b c
0 1 2 3
1 4 5 6
解析器会尽最大努力“做正确的事”并且不脆弱。类型推断是一个很大的问题。如果一列可以强制转换为整数数据类型而不更改其内容,则解析器将执行此操作。任何非数字列都将像 pandas 的其他对象一样,以 object 数据类型读取。
引用和转义字符#
嵌入字段中的引号(和其他转义字符)可以以多种方式处理。一种方法是使用反斜杠;要正确解析此数据,您应该传递 escapechar 选项。
In [172]: data = 'a,b\n"hello, \\"Bob\\", nice to see you",5'
In [173]: print(data)
a,b
"hello, \"Bob\", nice to see you",5
In [174]: pd.read_csv(StringIO(data), escapechar="\\")
Out[174]:
a b
0 hello, "Bob", nice to see you 5
固定宽度列的文件#
虽然 read_csv() 读取分隔数据,但 read_fwf() 函数处理具有已知和固定列宽的数据文件。read_fwf 的函数参数与 read_csv 大致相同,但有两个额外的参数,并且 delimiter 参数的使用方式有所不同。
colspecs:一对(元组)列表,给出每行固定宽度字段的范围,表示半开区间(即 [from, to[ )。字符串值 'infer' 可用于指示解析器尝试从数据的前 100 行检测列规范。默认行为(如果未指定)是推断。widths:如果区间是连续的,则可以使用字段宽度列表代替 'colspecs'。delimiter:要视为固定宽度文件中填充字符的字符。如果填充字符不是空格(例如,'~'),则可用于指定字段的填充字符。
考虑一个典型的固定宽度数据文件。
In [175]: 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 [176]: with open("bar.csv", "w") as f:
.....: f.write(data1)
.....:
为了将此文件解析为 DataFrame,我们只需将列规范与文件名一起提供给 read_fwf 函数。
# Column specifications are a list of half-intervals
In [177]: colspecs = [(0, 6), (8, 20), (21, 33), (34, 43)]
In [178]: df = pd.read_fwf("bar.csv", colspecs=colspecs, header=None, index_col=0)
In [179]: df
Out[179]:
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 [180]: widths = [6, 14, 13, 10]
In [181]: df = pd.read_fwf("bar.csv", widths=widths, header=None)
In [182]: df
Out[182]:
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 [183]: df = pd.read_fwf("bar.csv", header=None, index_col=0)
In [184]: df
Out[184]:
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 [185]: pd.read_fwf("bar.csv", header=None, index_col=0).dtypes
Out[185]:
1 float64
2 float64
3 float64
dtype: object
In [186]: pd.read_fwf("bar.csv", header=None, dtype={2: "object"}).dtypes
Out[186]:
0 str
1 float64
2 object
3 float64
dtype: object
索引#
具有“隐式”索引列的文件#
考虑一个文件中标题的条目比数据列的数量少一个的文件。
In [187]: data = "A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5"
In [188]: print(data)
A,B,C
20090101,a,1,2
20090102,b,3,4
20090103,c,4,5
In [189]: with open("foo.csv", "w") as f:
.....: f.write(data)
.....:
在这种特殊情况下,read_csv 假定第一列将用作 DataFrame 的索引。
In [190]: pd.read_csv("foo.csv")
Out[190]:
A B C
20090101 a 1 2
20090102 b 3 4
20090103 c 4 5
请注意,日期没有被自动解析。在这种情况下,您需要像以前一样进行。
In [191]: df = pd.read_csv("foo.csv", parse_dates=True)
In [192]: df.index
Out[192]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[us]', freq=None)
读取带有 MultiIndex 的索引#
假设您有按两列索引的数据。
In [193]: data = 'year,indiv,zit,xit\n1977,"A",1.2,.6\n1977,"B",1.5,.5'
In [194]: print(data)
year,indiv,zit,xit
1977,"A",1.2,.6
1977,"B",1.5,.5
In [195]: with open("mindex_ex.csv", mode="w") as f:
.....: f.write(data)
.....:
read_csv 的 index_col 参数可以接受一个列号列表,将多列转换为 MultiIndex 作为返回对象的索引。
In [196]: df = pd.read_csv("mindex_ex.csv", index_col=[0, 1])
In [197]: df
Out[197]:
zit xit
year indiv
1977 A 1.2 0.6
B 1.5 0.5
In [198]: df.loc[1977]
Out[198]:
zit xit
indiv
A 1.2 0.6
B 1.5 0.5
读取带有 MultiIndex 的列#
通过为 header 参数指定行位置列表,您可以读取 MultiIndex 作为列。指定非连续的行将跳过中间的行。
In [199]: mi_idx = pd.MultiIndex.from_arrays([[1, 2, 3, 4], list("abcd")], names=list("ab"))
In [200]: mi_col = pd.MultiIndex.from_arrays([[1, 2], list("ab")], names=list("cd"))
In [201]: df = pd.DataFrame(np.ones((4, 2)), index=mi_idx, columns=mi_col)
In [202]: df.to_csv("mi.csv")
In [203]: 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 [204]: pd.read_csv("mi.csv", header=[0, 1, 2, 3], index_col=[0, 1])
Out[204]:
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 [205]: 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 [206]: 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 [207]: with open("mi2.csv", "w") as fh:
.....: fh.write(data)
.....:
In [208]: pd.read_csv("mi2.csv", header=[0, 1], index_col=0)
Out[208]:
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 [209]: df = pd.DataFrame(np.random.randn(10, 4))
In [210]: df.to_csv("tmp2.csv", sep=":", index=False)
In [211]: pd.read_csv("tmp2.csv", sep=None, engine="python")
Out[211]:
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 [212]: df = pd.DataFrame(np.random.randn(10, 4))
In [213]: df.to_csv("tmp.csv", index=False)
In [214]: table = pd.read_csv("tmp.csv")
In [215]: table
Out[215]:
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
通过将 chunksize 指定给 read_csv() 作为上下文管理器,返回值将是一个 TextFileReader 类型的可迭代对象。
In [216]: 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 0x7fc1271fab50>
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
指定 iterator=True 也将返回 TextFileReader 对象。
In [217]: with pd.read_csv("tmp.csv", iterator=True) as reader:
.....: print(reader.get_chunk(5))
.....:
0 1 2 3
0 -1.294524 0.413738 0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2 0.895717 0.805244 -1.206412 2.565646
3 1.431256 1.340309 -1.170299 -0.226169
4 0.410835 0.813850 0.132003 -0.827317
指定解析器引擎#
pandas 目前支持三种引擎:C 引擎、Python 引擎和实验性的 pyarrow 引擎(需要 pyarrow 包)。通常,pyarrow 引擎在较大的工作负载上速度最快,在大多数其他工作负载上与 C 引擎的速度相当。Python 引擎在大多数工作负载上通常比 pyarrow 和 C 引擎慢。然而,pyarrow 引擎的健壮性远不如 C 引擎,而 C 引擎在一些功能上不如 Python 引擎。
如果可能,pandas 会使用 C 解析器(指定为 engine='c'),但如果指定了 C 不支持的选项,它可能会回退到 Python。
目前,C 和 pyarrow 引擎不支持的选项包括:
sep(除了单个字符,例如正则表达式分隔符)skipfooter
指定以上任何选项都会产生 ParserWarning,除非使用 engine='python' 显式选择 Python 引擎。
上面列表中未涵盖的、pyarrow 引擎不支持的选项包括:
float_precisionchunksizecommentnrowsthousandsmemory_mapdialecton_bad_linesquotinglineterminatorconvertersdecimaliteratordayfirstverboseskipinitialspacelow_memory
使用 engine='pyarrow' 指定这些选项将引发 ValueError。
读/写远程文件#
您可以通过 URL 将许多 pandas IO 函数用于读取或写入远程文件 - 以下示例展示了如何读取 CSV 文件
df = pd.read_csv("https://download.bls.gov/pub/time.series/cu/cu.item", sep="\t")
可以通过将键值映射的字典传递给 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 凭据,您仍然可以通过指定匿名连接来访问公共数据,例如:
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:如果内容为非 ASCII,则使用此字符串表示编码(适用于 Python 3 之前的版本)lineterminator:表示行结束的字符序列(默认为os.linesep)quoting:根据 csv 模块设置引用规则(默认为 csv.QUOTE_MINIMAL)。请注意,如果您设置了float_format,则浮点数将被转换为字符串,并且 csv.QUOTE_NONNUMERIC 会将它们视为非数字。quotechar:用于引用字段的字符(默认为 ‘”’)doublequote:控制字段中quotechar的引用(默认为 True)escapechar:在适当的时候用于转义sep和quotechar的字符(默认为 None)chunksize:一次写入的行数date_format:日期时间的格式字符串
写入格式化字符串#
DataFrame 对象有一个实例方法 to_string,它允许控制对象的字符串表示。所有参数都是可选的
buf默认为 None,例如 StringIO 对象columns默认为 None,要写入的列col_space默认为 None,每列的最小宽度。na_rep默认为NaN,NA 值的表示formatters默认为 None,一个字典(按列)的函数,每个函数接受一个参数并返回一个格式化字符串float_format默认为 None,一个函数,它接受一个(浮点数)参数并返回一个格式化字符串;将应用于DataFrame中的浮点数。sparsify默认为 True,对于具有分层索引的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:如果orient为records,则每条记录将按行写入为 json。mode:字符串,写入路径时的写入模式。‘w’ 表示写入,‘a’ 表示追加。默认为 ‘w’
注意:NaN、NaT 和 None 将被转换为 null,而 datetime 对象将根据 date_format 和 date_unit 参数进行转换。
In [218]: dfj = pd.DataFrame(np.random.randn(5, 2), columns=list("AB"))
In [219]: json = dfj.to_json()
In [220]: json
Out[220]: '{"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}}'
Orient 选项#
结果 JSON 文件/字符串的格式有多种选项。考虑以下 DataFrame 和 Series:
In [221]: dfjo = pd.DataFrame(
.....: dict(A=range(1, 4), B=range(4, 7), C=range(7, 10)),
.....: columns=list("ABC"),
.....: index=list("xyz"),
.....: )
.....:
In [222]: dfjo
Out[222]:
A B C
x 1 4 7
y 2 5 8
z 3 6 9
In [223]: sjo = pd.Series(dict(x=15, y=16, z=17), name="D")
In [224]: sjo
Out[224]:
x 15
y 16
z 17
Name: D, dtype: int64
列导向(DataFrame 的默认值)将数据序列化为嵌套的 JSON 对象,以列标签作为主索引
In [225]: dfjo.to_json(orient="columns")
Out[225]: '{"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 [226]: dfjo.to_json(orient="index")
Out[226]: '{"x":{"A":1,"B":4,"C":7},"y":{"A":2,"B":5,"C":8},"z":{"A":3,"B":6,"C":9}}'
In [227]: sjo.to_json(orient="index")
Out[227]: '{"x":15,"y":16,"z":17}'
记录导向将数据序列化为列 -> 值记录的 JSON 数组,不包含索引标签。这对于将 DataFrame 数据传递给绘图库很有用,例如 JavaScript 库 d3.js
In [228]: dfjo.to_json(orient="records")
Out[228]: '[{"A":1,"B":4,"C":7},{"A":2,"B":5,"C":8},{"A":3,"B":6,"C":9}]'
In [229]: sjo.to_json(orient="records")
Out[229]: '[15,16,17]'
值导向是一个基本选项,它序列化为纯值嵌套的 JSON 数组,不包含列和索引标签
In [230]: dfjo.to_json(orient="values")
Out[230]: '[[1,4,7],[2,5,8],[3,6,9]]'
# Not available for Series
拆分导向将数据序列化为包含值、索引和列的单独条目的 JSON 对象。对于 Series,名称也包括在内
In [231]: dfjo.to_json(orient="split")
Out[231]: '{"columns":["A","B","C"],"index":["x","y","z"],"data":[[1,4,7],[2,5,8],[3,6,9]]}'
In [232]: sjo.to_json(orient="split")
Out[232]: '{"name":"D","index":["x","y","z"],"data":[15,16,17]}'
表导向序列化为 JSON Table Schema,允许保留元数据,包括但不限于 dtype 和索引名称。
注意
任何编码为 JSON 对象的 orient 选项在往返序列化期间都不会保留索引和列标签的顺序。如果您希望保留标签顺序,请使用 split 选项,因为它使用有序容器。
日期处理#
以 ISO 日期格式写入
In [233]: dfd = pd.DataFrame(np.random.randn(5, 2), columns=list("AB"))
In [234]: dfd["date"] = pd.Timestamp("20130101")
In [235]: dfd = dfd.sort_index(axis=1, ascending=False)
In [236]: json = dfd.to_json(date_format="iso")
In [237]: json
Out[237]: '{"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 [238]: json = dfd.to_json(date_format="iso", date_unit="us")
In [239]: json
Out[239]: '{"date":{"0":"2013-01-01T00:00:00.000000","1":"2013-01-01T00:00:00.000000","2":"2013-01-01T00:00:00.000000","3":"2013-01-01T00:00:00.000000","4":"2013-01-01T00:00:00.000000"},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'
写入文件,带日期索引和日期列
In [240]: dfj2 = dfj.copy()
In [241]: dfj2["date"] = pd.Timestamp("20130101")
In [242]: dfj2["ints"] = list(range(5))
In [243]: dfj2["bools"] = True
In [244]: dfj2.index = pd.date_range("20130101", periods=5)
In [245]: dfj2.to_json("test.json", date_format="iso")
In [246]: with open("test.json") as fh:
.....: print(fh.read())
.....:
{"A":{"2013-01-01T00:00:00.000":-0.1213062281,"2013-01-02T00:00:00.000":0.6957746499,"2013-01-03T00:00:00.000":0.9597255933,"2013-01-04T00:00:00.000":-0.6199759194,"2013-01-05T00:00:00.000":-0.7323393705},"B":{"2013-01-01T00:00:00.000":-0.0978826728,"2013-01-02T00:00:00.000":0.3417343559,"2013-01-03T00:00:00.000":-1.1103361029,"2013-01-04T00:00:00.000":0.1497483186,"2013-01-05T00:00:00.000":0.6877383895},"date":{"2013-01-01T00:00:00.000":"2013-01-01T00:00:00.000","2013-01-02T00:00:00.000":"2013-01-01T00:00:00.000","2013-01-03T00:00:00.000":"2013-01-01T00:00:00.000","2013-01-04T00:00:00.000":"2013-01-01T00:00:00.000","2013-01-05T00:00:00.000":"2013-01-01T00:00:00.000"},"ints":{"2013-01-01T00:00:00.000":0,"2013-01-02T00:00:00.000":1,"2013-01-03T00:00:00.000":2,"2013-01-04T00:00:00.000":3,"2013-01-05T00:00:00.000":4},"bools":{"2013-01-01T00:00:00.000":true,"2013-01-02T00:00:00.000":true,"2013-01-03T00:00:00.000":true,"2013-01-04T00:00:00.000":true,"2013-01-05T00:00:00.000":true}}
回退行为#
如果 JSON 序列化器无法直接处理容器内容,它将按以下方式回退:
如果 dtype 不受支持(例如
np.complex_),则将调用提供的default_handler来处理每个值,否则将引发异常。如果对象不受支持,它将尝试以下操作:
检查对象是否定义了
toDict方法并调用它。toDict方法应返回一个dict,然后该dict将被 JSON 序列化。调用(如果提供了)
default_handler。通过遍历其内容将对象转换为
dict。但这样做通常会导致OverflowError或产生意外的结果。
总的来说,对于不受支持的对象或 dtype,最好的方法是提供一个 default_handler。例如:
>>> DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json() # raises
RuntimeError: Unhandled numpy dtype 15
可以通过指定一个简单的 default_handler 来处理
In [247]: pd.DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json(default_handler=str)
Out[247]: '{"0":{"0":"(1+0j)","1":"(2+0j)","2":"(1+2j)"}}'
读取 JSON#
将 JSON 字符串读入 pandas 对象可以接受多个参数。如果未提供 typ 或为 None,解析器将尝试解析 DataFrame。要显式强制解析为 Series,请传递 typ=series
filepath_or_buffer:一个**有效**的 JSON 字符串或文件句柄/StringIO。字符串可以是 URL。有效的 URL 方案包括 http、ftp、S3 和 file。对于文件 URL,需要主机名。例如,本地文件可以是 file :///path/to/table.jsontyp:要恢复的对象类型(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,则推断 dtype;如果为列到 dtype 的字典,则使用这些;如果为False,则根本不推断 dtype;默认为 True,仅应用于数据。convert_axes:布尔值,尝试将轴转换为正确的 dtype,默认为Trueconvert_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 之一。
如果使用非默认 orient 进行 JSON 编码,请务必在此处传递相同的选项,以便解码产生有意义的结果,有关概述,请参阅 Orient Options。
数据转换#
默认值 convert_axes=True、dtype=True 和 convert_dates=True 将尝试将轴和所有数据解析为适当的类型,包括日期。如果您需要覆盖特定 dtype,请将字典传递给 dtype。convert_axes 仅应设置为 False,如果您需要保留字符串类数字(例如 ‘1’、‘2’)作为轴。
注意
如果 convert_dates=True 并且数据和/或列标签看起来像“日期型”,则大型整数值可能会被转换为日期。确切的阈值取决于指定的 date_unit。“日期型”意味着列标签满足以下任一标准:
它以
'_at'结尾它以
'_time'结尾它以
'timestamp'开头它是
'modified'它是
'date'
警告
读取 JSON 数据时,自动转换为 dtype 有一些怪癖:
索引可以以与序列化不同的顺序重建,也就是说,返回的顺序不能保证与序列化之前相同。
如果可以安全地进行,则
float数据列将被转换为integer,例如1.列。布尔列在重建时将被转换为
integer。
因此,有时您可能需要通过 dtype 关键字参数指定特定 dtype。
从 JSON 字符串读取
In [248]: from io import StringIO
In [249]: pd.read_json(StringIO(json))
Out[249]:
date B A
0 2013-01-01 0.403310 0.176444
1 2013-01-01 0.301624 -0.154951
2 2013-01-01 -1.369849 -2.179861
3 2013-01-01 1.462696 -0.954208
4 2013-01-01 -0.826591 -1.743161
从文件读取
In [250]: pd.read_json("test.json")
Out[250]:
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
不转换任何数据(但仍转换轴和日期)
In [251]: pd.read_json("test.json", dtype=object).dtypes
Out[251]:
A object
B object
date datetime64[us]
ints object
bools object
dtype: object
为转换指定 dtype
In [252]: pd.read_json("test.json", dtype={"A": "float32", "bools": "int8"}).dtypes
Out[252]:
A float32
B float64
date datetime64[us]
ints int64
bools int8
dtype: object
保留字符串索引
In [253]: from io import StringIO
In [254]: si = pd.DataFrame(
.....: np.zeros((4, 4)), columns=list(range(4)), index=[str(i) for i in range(4)]
.....: )
.....:
In [255]: si
Out[255]:
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 [256]: si.index
Out[256]: Index(['0', '1', '2', '3'], dtype='str')
In [257]: si.columns
Out[257]: Index([0, 1, 2, 3], dtype='int64')
In [258]: json = si.to_json()
In [259]: sij = pd.read_json(StringIO(json), convert_axes=False)
In [260]: sij
Out[260]:
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 [261]: sij.index
Out[261]: Index(['0', '1', '2', '3'], dtype='str')
In [262]: sij.columns
Out[262]: Index(['0', '1', '2', '3'], dtype='str')
以纳秒为单位写入的日期需要以纳秒为单位读回
In [263]: from io import StringIO
In [264]: json = dfj2.to_json(date_format="iso", date_unit="ns")
# Try to parse timestamps as milliseconds -> Won't Work
In [265]: dfju = pd.read_json(StringIO(json), date_unit="ms")
In [266]: dfju
Out[266]:
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
# Let pandas detect the correct precision
In [267]: dfju = pd.read_json(StringIO(json))
In [268]: dfju
Out[268]:
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 [269]: dfju = pd.read_json(StringIO(json), date_unit="ns")
In [270]: dfju
Out[270]:
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
通过设置 dtype_backend 参数,您可以控制结果 DataFrame 的默认 dtype。
In [271]: 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 [272]: df = pd.read_json(StringIO(data), dtype_backend="pyarrow")
In [273]: df
Out[273]:
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 [274]: df.dtypes
Out[274]:
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 [275]: data = [
.....: {"id": 1, "name": {"first": "Coleen", "last": "Volk"}},
.....: {"name": {"given": "Mark", "family": "Regner"}},
.....: {"id": 2, "name": "Faye Raker"},
.....: ]
.....:
In [276]: pd.json_normalize(data)
Out[276]:
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 [277]: 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 [278]: pd.json_normalize(data, "county", ["state", "shortname", ["info", "governor"]])
Out[278]:
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 [279]: data = [
.....: {
.....: "CreatedBy": {"Name": "User001"},
.....: "Lookup": {
.....: "TextField": "Some text",
.....: "UserField": {"Id": "ID001", "Name": "Name001"},
.....: },
.....: "Image": {"a": "b"},
.....: }
.....: ]
.....:
In [280]: pd.json_normalize(data, max_level=1)
Out[280]:
CreatedBy.Name Lookup.TextField Lookup.UserField Image.a
0 User001 Some text {'Id': 'ID001', 'Name': 'Name001'} b
逐行分隔的 JSON#
pandas 能够读写在 Hadoop 或 Spark 的数据处理管道中常见的逐行分隔的 JSON 文件。
对于逐行分隔的 JSON 文件,pandas 还可以返回一个迭代器,该迭代器每次读取 chunksize 行。这对于大文件或从流中读取很有用。
In [281]: from io import StringIO
In [282]: jsonl = """
.....: {"a": 1, "b": 2}
.....: {"a": 3, "b": 4}
.....: """
.....:
In [283]: df = pd.read_json(StringIO(jsonl), lines=True)
In [284]: df
Out[284]:
a b
0 1 2
1 3 4
In [285]: df.to_json(orient="records", lines=True)
Out[285]: '{"a":1,"b":2}\n{"a":3,"b":4}\n'
# reader is an iterator that returns ``chunksize`` lines each iteration
In [286]: 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 [287]: from io import BytesIO
In [288]: df = pd.read_json(BytesIO(jsonl.encode()), lines=True, engine="pyarrow")
In [289]: df
Out[289]:
a b
0 1 2
1 3 4
版本 2.0.0 中已添加。
表模式#
Table Schema 是一个用于将表格数据集描述为 JSON 对象的规范。JSON 包含字段名称、类型和其他属性的信息。您可以使用 orient table 来构建一个包含两个字段 schema 和 data 的 JSON 字符串。
In [290]: 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 [291]: df
Out[291]:
A B C
idx
0 1 a 2016-01-01
1 2 b 2016-01-02
2 3 c 2016-01-03
In [292]: df.to_json(orient="table", date_format="iso")
Out[292]: '{"schema":{"fields":[{"name":"idx","type":"integer"},{"name":"A","type":"integer"},{"name":"B","type":"string","extDtype":"str"},{"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(请参阅下文了解支持的类型)。schema 字段还包含一个 primaryKey 字段(如果 (Multi)index 是唯一的)。
第二个字段 data 包含使用 records 格式序列化的数据。索引包含在内,任何日期时间都按照 Table Schema 规范的要求进行 ISO 8601 格式化。
支持的类型完整列表在 Table Schema 规范中进行了描述。下表显示了从 pandas 类型到 Table Schema 类型的映射
pandas 类型 |
Table Schema 类型 |
|---|---|
int64 |
整数 |
float64 |
number |
bool |
布尔值 |
datetime64[ns] |
datetime |
timedelta64[ns] |
duration |
categorical |
any |
对象 |
str |
关于生成的表模式的几点说明
schema对象包含一个pandas_version字段。它包含 pandas 模式方言的版本,每次修订都会递增。所有日期在序列化时都转换为 UTC。即使是时区无关的值,也被视为 UTC,偏移量为 0。
In [293]: from pandas.io.json import build_table_schema In [294]: s = pd.Series(pd.date_range("2016", periods=4)) In [295]: build_table_schema(s) Out[295]: {'fields': [{'name': 'index', 'type': 'integer'}, {'name': 'values', 'type': 'datetime'}], 'primaryKey': ['index'], 'pandas_version': '1.4.0'}
带时区的日期时间(序列化之前)包含一个额外的
tz字段,包含时区名称(例如'US/Central')。In [296]: s_tz = pd.Series(pd.date_range("2016", periods=12, tz="US/Central")) In [297]: build_table_schema(s_tz) Out[297]: {'fields': [{'name': 'index', 'type': 'integer'}, {'name': 'values', 'type': 'datetime', 'tz': 'US/Central'}], 'primaryKey': ['index'], 'pandas_version': '1.4.0'}
Period 会在序列化前转换为时间戳,因此具有转换为 UTC 的相同行为。此外,Period 会包含一个额外的
freq字段,包含 Period 的频率,例如'A-DEC'。In [298]: s_per = pd.Series(1, index=pd.period_range("2016", freq="Y-DEC", periods=4)) In [299]: build_table_schema(s_per) Out[299]: {'fields': [{'name': 'index', 'type': 'datetime', 'freq': 'YE-DEC'}, {'name': 'values', 'type': 'integer'}], 'primaryKey': ['index'], 'pandas_version': '1.4.0'}
Categoricals 使用
any类型和一个enum约束,列出了可能值的集合。此外,还包含一个ordered字段。In [300]: s_cat = pd.Series(pd.Categorical(["a", "b", "a"])) In [301]: build_table_schema(s_cat) Out[301]: {'fields': [{'name': 'index', 'type': 'integer'}, {'name': 'values', 'type': 'any', 'constraints': {'enum': ['a', 'b']}, 'ordered': False}], 'primaryKey': ['index'], 'pandas_version': '1.4.0'}
如果索引是唯一的,则包含一个包含标签数组的
primaryKey字段。In [302]: s_dupe = pd.Series([1, 2], index=[1, 1]) In [303]: build_table_schema(s_dupe) Out[303]: {'fields': [{'name': 'index', 'type': 'integer'}, {'name': 'values', 'type': 'integer'}], 'pandas_version': '1.4.0'}
对于 MultiIndexes,
primaryKey的行为相同,但在这种情况下,primaryKey是一个数组。In [304]: s_multi = pd.Series(1, index=pd.MultiIndex.from_product([("a", "b"), (0, 1)])) In [305]: build_table_schema(s_multi) Out[305]: {'fields': [{'name': 'level_0', 'type': 'string', 'extDtype': 'str'}, {'name': 'level_1', 'type': 'integer'}, {'name': 'values', 'type': 'integer'}], 'primaryKey': FrozenList(['level_0', 'level_1']), 'pandas_version': '1.4.0'}
默认命名大致遵循以下规则:
对于 Series,使用
object.name。如果该值为 None,则名称为values。对于
DataFrames,使用列名称的字符串化版本。对于
Index(而非MultiIndex),将使用index.name,如果为 None,则回退到index。对于
MultiIndex,将使用mi.names。如果任何级别没有名称,则使用level_<i>。
read_json 还接受 orient='table' 作为参数。这允许以可往返的方式保留元数据,如 dtypes 和索引名称。
In [306]: 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 [307]: df
Out[307]:
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 [308]: df.dtypes
Out[308]:
foo int64
bar str
baz datetime64[us]
qux category
dtype: object
In [309]: df.to_json("test.json", orient="table")
In [310]: new_df = pd.read_json("test.json", orient="table")
In [311]: new_df
Out[311]:
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 [312]: new_df.dtypes
Out[312]:
foo int64
bar str
baz datetime64[ns]
qux category
dtype: object
请注意,字符串“index”作为 Index 的名称是不可往返的,MultiIndex 中以 'level_' 开头的任何名称也是如此。这些在 DataFrame.to_json() 中用于指示缺失值,并且后续读取无法区分意图。
In [313]: df.index.name = "index"
In [314]: df.to_json("test.json", orient="table")
In [315]: new_df = pd.read_json("test.json", orient="table")
In [316]: print(new_df.index.name)
None
当使用 orient='table' 以及用户定义的 ExtensionArray 时,生成的模式将在相应的 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 对象列表,即使 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 的数据每周一更改,因此上面的结果数据可能略有不同。
使用 read_html 从文件读取 HTML 内容
In [317]: 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 [318]: with open("tmp.html", "w") as f:
.....: f.write(html_str)
.....:
In [319]: df = pd.read_html("tmp.html")
In [320]: df[0]
Out[320]:
A B C
0 a b c
如果需要,您甚至可以传递 StringIO 的实例
In [321]: dfs = pd.read_html(StringIO(html_str))
In [322]: dfs[0]
Out[322]:
A B C
0 a b c
注意
以下示例未由 IPython 评估器运行,因为存在大量网络访问函数会减慢文档构建速度。如果您发现错误或无法运行的示例,请随时在 pandas GitHub issues page 上报告。
读取 URL 并匹配包含特定文本的表格
match = "Metcalf Bank"
df_list = pd.read_html(url, match=match)
指定标题行(默认情况下,<th> 或 <td> 元素位于 <thead> 中用于形成列索引,如果 <thead> 包含多个行,则会创建 MultiIndex);如果指定,则标题行取自数据减去解析的标题元素(<th> 元素)。
dfs = pd.read_html(url, header=0)
指定索引列
dfs = pd.read_html(url, index_col=0)
指定要跳过的行数
dfs = pd.read_html(url, skiprows=0)
使用列表指定要跳过的行数(range 也可以)
dfs = pd.read_html(url, skiprows=range(2))
指定 HTML 属性
dfs1 = pd.read_html(url, attrs={"id": "table"})
dfs2 = pd.read_html(url, attrs={"class": "sortable"})
print(np.array_equal(dfs1[0], dfs2[0])) # Should be True
指定应转换为 NaN 的值
dfs = pd.read_html(url, na_values=["No Acquirer"])
指定是否保留默认的 NaN 值集
dfs = pd.read_html(url, keep_default_na=False)
为列指定转换器。这对于带有前导零的数字文本数据很有用。默认情况下,数值列会被转换为数字类型,前导零会丢失。要避免这种情况,我们可以将这些列转换为字符串。
url_mcc = "https://en.wikipedia.org/wiki/Mobile_country_code?oldid=899173761"
dfs = pd.read_html(
url_mcc,
match="Telekom Albania",
header=0,
converters={"MNC": str},
)
使用以上某些组合
dfs = pd.read_html(url, match="Metcalf Bank", index_col=0)
读取 pandas to_html 输出(浮点精度略有损失)
df = pd.DataFrame(np.random.randn(2, 2))
s = df.to_html(float_format="{0:.40g}".format)
dfin = pd.read_html(s, index_col=0)
如果 lxml 是您提供的唯一解析器,则在解析失败时它将引发错误。如果您只有一个解析器,您可以只提供一个字符串,但通常的做法是传递一个包含一个字符串的列表,例如,当函数期望字符串序列时。您可以使用
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 [323]: html_table = """
.....: <table>
.....: <tr>
.....: <th>GitHub</th>
.....: </tr>
.....: <tr>
.....: <td><a href="https://github.com/pandas-dev/pandas">pandas</a></td>
.....: </tr>
.....: </table>
.....: """
.....:
In [324]: df = pd.read_html(
.....: StringIO(html_table),
.....: extract_links="all"
.....: )[0]
.....:
In [325]: df
Out[325]:
(GitHub, None)
0 (pandas, https://github.com/pandas-dev/pandas)
In [326]: df[("GitHub", None)]
Out[326]:
0 (pandas, https://github.com/pandas-dev/pandas)
Name: (GitHub, None), dtype: object
In [327]: df[("GitHub", None)].str[1]
Out[327]:
0 https://github.com/pandas-dev/pandas
Name: (GitHub, None), dtype: object
写入 HTML 文件#
DataFrame 对象有一个实例方法 to_html,它将 DataFrame 的内容渲染为 HTML 表格。该方法的参数与上面描述的 to_string 方法中的参数相同。
注意
为了简洁起见,此处并未展示 DataFrame.to_html 的所有可能选项。请参阅 DataFrame.to_html() 以获取完整的选项集。
注意
在支持 HTML 渲染的环境(如 Jupyter Notebook)中,display(HTML(...))` 将把原始 HTML 渲染到环境中。
In [328]: from IPython.display import display, HTML
In [329]: df = pd.DataFrame(np.random.randn(2, 2))
In [330]: df
Out[330]:
0 1
0 -0.345352 1.314232
1 0.690579 0.995761
In [331]: html = df.to_html()
In [332]: 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 [333]: display(HTML(html))
<IPython.core.display.HTML object>
columns 参数将限制显示的列
In [334]: html = df.to_html(columns=[0])
In [335]: 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 [336]: display(HTML(html))
<IPython.core.display.HTML object>
float_format 接受一个 Python 可调用对象来控制浮点值的精度
In [337]: html = df.to_html(float_format="{0:.10f}".format)
In [338]: 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 [339]: display(HTML(html))
<IPython.core.display.HTML object>
bold_rows 默认会使行标签变粗,但您可以将其关闭
In [340]: html = df.to_html(bold_rows=False)
In [341]: 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 [342]: display(HTML(html))
<IPython.core.display.HTML object>
classes 参数提供了将 CSS 类赋予生成的 HTML 表格的能力。请注意,这些类会附加到现有的 'dataframe' 类上。
In [343]: 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 [344]: url_df = pd.DataFrame(
.....: {
.....: "name": ["Python", "pandas"],
.....: "url": ["https://pythonlang.cn/", "https://pandas.ac.cn"],
.....: }
.....: )
.....:
In [345]: html = url_df.to_html(render_links=True)
In [346]: 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 [347]: display(HTML(html))
<IPython.core.display.HTML object>
最后,escape 参数允许您控制是否转义生成的 HTML 中的“<”、“>”和“&”字符(默认值为 True)。因此,要获取没有转义字符的 HTML,请传递 escape=False。
In [348]: df = pd.DataFrame({"a": list("&<>"), "b": np.random.randn(3)})
转义
In [349]: html = df.to_html()
In [350]: 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 [351]: display(HTML(html))
<IPython.core.display.HTML object>
未转义
In [352]: html = df.to_html(escape=False)
In [353]: 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 [354]: display(HTML(html))
<IPython.core.display.HTML object>
注意
某些浏览器可能不会显示以上两个 HTML 表格渲染的差异。
HTML 表格解析的注意事项#
在 pandas io 的顶级函数 read_html 中使用的库存在一些版本问题。
与 lxml **的问题**
优点
缺点
使用 lxml **作为后端**的 BeautifulSoup4 **的问题**
由于 BeautifulSoup4 本质上只是一个解析器后端的包装器,因此上述问题也同样适用。
使用 html5lib **作为后端**的 BeautifulSoup4 **的问题**
优点
缺点
使用 html5lib **最大的缺点是它速度非常慢**。但请考虑,网络上的许多表格不够大,以至于解析算法的运行时间会很重要。更可能成为瓶颈的是从网络读取原始文本的过程,即 IO(输入-输出)。对于非常大的表格,情况可能并非如此。
LaTeX#
目前没有从 LaTeX 读取的方法,只有输出方法。
写入 LaTeX 文件#
注意
DataFrame **和** Styler 对象当前都有一个 to_latex 方法。我们建议使用 Styler.to_latex() 方法而不是 DataFrame.to_latex(),因为前者在条件样式方面更灵活,而后者将来可能被弃用。
请查阅 Styler.to_latex 的文档,其中提供了条件样式的示例,并解释了其关键字参数的操作。
对于简单应用,以下模式就足够了。
In [355]: df = pd.DataFrame([[1, 2], [3, 4]], index=["a", "b"], columns=["c", "d"])
In [356]: print(df.style.to_latex())
\begin{tabular}{lrr}
& c & d \\
a & 1 & 2 \\
b & 3 & 4 \\
\end{tabular}
要格式化输出值,请链接 Styler.format 方法。
In [357]: print(df.style.format("€ {}").to_latex())
\begin{tabular}{lrr}
& c & d \\
a & € 1 & € 2 \\
b & € 3 & € 4 \\
\end{tabular}
XML#
读取 XML#
顶层的 read_xml() 函数可以接受 XML 字符串/文件/URL,并将节点和属性解析为 pandas DataFrame。
注意
由于没有标准的 XML 结构,其设计类型可能差异很大,read_xml 最适合平坦、浅层的版本。如果 XML 文档嵌套很深,请使用 stylesheet 功能将 XML 转换为更平坦的版本。
让我们看几个例子。
读取 XML 字符串
In [358]: from io import StringIO
In [359]: 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 [360]: df = pd.read_xml(StringIO(xml))
In [361]: df
Out[361]:
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 [362]: df = pd.read_xml("https://w3schools.org.cn/xml/books.xml")
In [363]: df
Out[363]:
category title author year price cover
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00 NaN
1 children Harry Potter J K. Rowling 2005 29.99 NaN
2 web XQuery Kick Start Vaidyanathan Nagarajan 2003 49.99 NaN
3 web Learning XML Erik T. Ray 2003 39.95 paperback
读取“books.xml”文件的内容,并将其作为字符串传递给 read_xml
In [364]: file_path = "books.xml"
In [365]: with open(file_path, "w") as f:
.....: f.write(xml)
.....:
In [366]: with open(file_path, "r") as f:
.....: df = pd.read_xml(StringIO(f.read()))
.....:
In [367]: df
Out[367]:
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 [368]: with open(file_path, "r") as f:
.....: sio = StringIO(f.read())
.....:
In [369]: df = pd.read_xml(sio)
In [370]: df
Out[370]:
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 [371]: with open(file_path, "rb") as f:
.....: bio = BytesIO(f.read())
.....:
In [372]: df = pd.read_xml(bio)
In [373]: df
Out[373]:
category title author year price
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00
1 children Harry Potter J K. Rowling 2005 29.99
2 web Learning XML Erik T. Ray 2003 39.95
甚至可以从 AWS S3 存储桶读取 XML,例如 NIH NCBI PMC Article Datasets,提供生物医学和生命科学期刊
>>> df = pd.read_xml(
... "s3://pmc-oa-opendata/oa_comm/xml/all/PMC1236943.xml",
... xpath=".//journal-meta",
...)
>>> df
journal-id journal-title issn publisher
0 Cardiovasc Ultrasound Cardiovascular Ultrasound 1476-7120 NaN
使用 lxml 作为默认 parser,您可以访问功能齐全的 XML 库,该库扩展了 Python 的 ElementTree API。一个强大的工具是能够使用更具表现力的 XPath 选择性或条件性地查询节点。
In [374]: df = pd.read_xml(file_path, xpath="//book[year=2005]")
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
仅指定要解析的元素或属性
In [376]: df = pd.read_xml(file_path, elems_only=True)
In [377]: df
Out[377]:
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 [378]: df = pd.read_xml(file_path, attrs_only=True)
In [379]: df
Out[379]:
category
0 cooking
1 children
2 web
XML 文档可以具有带前缀的命名空间和不带前缀的默认命名空间,两者都用特殊属性 xmlns 表示。为了在命名空间上下文下按节点解析,xpath 必须引用一个前缀。
例如,下面的 XML 包含一个带前缀 doc 的命名空间,以及 URI https://example.com。为了解析 doc:row 节点,必须使用 namespaces。
In [380]: 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 [381]: df = pd.read_xml(StringIO(xml),
.....: xpath="//doc:row",
.....: namespaces={"doc": "https://example.com"})
.....:
In [382]: df
Out[382]:
shape degrees sides
0 square 360 4.0
1 circle 360 NaN
2 triangle 180 3.0
类似地,XML 文档可以有一个不带前缀的默认命名空间。如果不分配临时前缀,将不会返回任何节点并引发 ValueError。但是,为正确 URI 分配*任何*临时名称都允许按节点解析。
In [383]: 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 [384]: df = pd.read_xml(StringIO(xml),
.....: xpath="//pandas:row",
.....: namespaces={"pandas": "https://example.com"})
.....:
In [385]: df
Out[385]:
shape degrees sides
0 square 360 4.0
1 circle 360 NaN
2 triangle 180 3.0
但是,如果 XPath 不引用节点名称(如默认的 `/*`),则不需要 namespaces。
注意
由于 xpath 标识了要解析的内容的父节点,因此只解析直接后代(包括子节点或当前属性)。因此,read_xml 不会解析孙子节点或其他后代的文本,也不会解析任何后代的属性。要检索较低级别的内容,请调整 xpath 到较低级别。例如,
In [386]: 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 [387]: df = pd.read_xml(StringIO(xml), xpath="./row")
In [388]: df
Out[388]:
shape degrees
0 square 360
1 circle 360
2 triangle 180
显示了 shape 元素上的 sides 属性未按预期解析,因为该属性位于 row 元素的子节点而非 row 元素本身。换句话说,sides 属性是 row 元素的孙子级别后代。但是,xpath 目标是 row 元素,该元素仅覆盖其子节点和属性。
使用 lxml 作为解析器,您可以使用 XSLT 脚本来展平嵌套的 XML 文档,该脚本也可以是字符串/文件/URL 类型。作为背景,XSLT 是一种特殊用途语言,用特殊的 XML 文件编写,可以使用 XSLT 处理器将原始 XML 文档转换为其他 XML、HTML,甚至文本(CSV、JSON 等)。
例如,考虑下面芝加哥“L”线路的略微嵌套的结构,其中 station 和 rides 元素在其自己的部分中封装数据。使用下面的 XSLT,lxml 可以将原始嵌套文档转换为更平坦的输出(如下所示用于演示),以便于解析为 DataFrame。
In [389]: 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 [390]: 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 [391]: 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 [392]: df = pd.read_xml(StringIO(xml), stylesheet=StringIO(xsl))
In [393]: df
Out[393]:
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 树并提取特定元素和属性,而无需将整个树保存在内存中。
要使用此功能,您必须将物理 XML 文件路径传递给 read_xml,并使用 iterparse 参数。文件不应压缩或指向在线源,而应存储在本地磁盘上。此外,iterparse 应该是一个字典,其中键是文档中的重复节点(它们将成为行),值是作为重复节点后代(即子节点、孙子节点)的任何元素或属性的列表。由于此方法不使用 XPath,因此后代不必彼此之间共享相同的关系。下面显示了读取维基百科非常大的(12GB+)最新文章数据转储的示例。
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#
DataFrame 对象有一个实例方法 to_xml,它将 DataFrame 的内容渲染为 XML 文档。
注意
此方法不支持 XML 的特殊属性,包括 DTD、CData、XSD 模式、处理指令、注释等。仅支持根级别的命名空间。但是,stylesheet 允许在初始输出后进行设计更改。
让我们看几个例子。
不带任何选项写入 XML
In [394]: geom_df = pd.DataFrame(
.....: {
.....: "shape": ["square", "circle", "triangle"],
.....: "degrees": [360, 360, 180],
.....: "sides": [4, np.nan, 3],
.....: }
.....: )
.....:
In [395]: 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 [396]: 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 [397]: 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 [398]: 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 [399]: ext_geom_df = pd.DataFrame(
.....: {
.....: "type": ["polygon", "other", "polygon"],
.....: "shape": ["square", "circle", "triangle"],
.....: "degrees": [360, 360, 180],
.....: "sides": [4, np.nan, 3],
.....: }
.....: )
.....:
In [400]: pvt_df = ext_geom_df.pivot_table(index='shape',
.....: columns='type',
.....: values=['degrees', 'sides'],
.....: aggfunc='sum')
.....:
In [401]: pvt_df
Out[401]:
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 [402]: 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 [403]: 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 [404]: 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 [405]: 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 [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="/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 [407]: print(geom_df.to_xml(stylesheet=StringIO(xsl)))
<?xml version="1.0"?>
<geometry>
<object index="0" type="polygon">
<shape>square</shape>
<property>
<degrees>360</degrees>
<sides>4.0</sides>
</property>
</object>
<object index="1">
<shape>circle</shape>
<property>
<degrees>360</degrees>
<sides/>
</property>
</object>
<object index="2" type="polygon">
<shape>triangle</shape>
<property>
<degrees>180</degrees>
<sides>3.0</sides>
</property>
</object>
</geometry>
XML 最终说明#
所有 XML 文档均遵循 W3C 规范。
etree和lxml解析器都将无法解析任何格式不正确或不遵循 XML 语法规则的标记文档。请注意,HTML 不是 XML 文档,除非它遵循 XHTML 规范。但是,其他流行的标记类型,包括 KML、XAML、RSS、MusicML、MathML,都是符合规范的 XML 模式。因此,如果您的应用程序在 pandas 操作之前构建 XML,请使用适当的 DOM 库,如
etree和lxml来构建必要的文档,而不是通过字符串连接或正则表达式调整。始终记住 XML 是一个*特殊的*具有标记规则的文本文件。对于非常大的 XML 文件(数百兆字节到千兆字节),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 可以通过将列列表传递给 index_col 来读取 MultiIndex 索引,并通过将行列表传递给 header 来读取 MultiIndex 列。如果 index 或 columns 具有序列化的级别名称,则也会通过指定构成级别的行/列来读取它们。
例如,读取没有名称的 MultiIndex 索引
In [408]: df = pd.DataFrame(
.....: {"a": [1, 2, 3, 4], "b": [5, 6, 7, 8]},
.....: index=pd.MultiIndex.from_product([["a", "b"], ["c", "d"]]),
.....: )
.....:
In [409]: df.to_excel("path_to_file.xlsx")
In [410]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1])
In [411]: df
Out[411]:
a b
a c 1 5
d 2 6
b c 3 7
d 4 8
如果索引具有级别名称,它们也会被解析,使用相同的参数。
In [412]: df.index = df.index.set_names(["lvl1", "lvl2"])
In [413]: df.to_excel("path_to_file.xlsx")
In [414]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1])
In [415]: df
Out[415]:
a b
lvl1 lvl2
a c 1 5
d 2 6
b c 3 7
d 4 8
如果源文件同时具有 MultiIndex 索引和列,则应将指定每个的列表传递给 index_col 和 header。
In [416]: df.columns = pd.MultiIndex.from_product([["a"], ["b", "d"]], names=["c1", "c2"])
In [417]: df.to_excel("path_to_file.xlsx")
In [418]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1], header=[0, 1])
In [419]: df
Out[419]:
c1 a
c2 b d
lvl1 lvl2
a c 1 5
d 2 6
b c 3 7
d 4 8
在 index_col 中指定的列中的缺失值将被向前填充,以允许与 to_excel 进行 merged_cells=True 的可往返。要避免向前填充缺失值,请在读取数据后使用 set_index 而不是 index_col。
解析特定列#
用户经常会在 Excel 中插入列来执行临时计算,而您可能不想读取这些列。 read_excel 接受一个 usecols 关键字参数,允许您指定要解析的列子集。
您可以将逗号分隔的 Excel 列和范围集指定为字符串
pd.read_excel("path_to_file.xls", "Sheet1", usecols="A,C:E")
如果 usecols 是整数列表,则假定它是要解析的文件列索引。
pd.read_excel("path_to_file.xls", "Sheet1", usecols=[0, 2, 3])
元素的顺序无关紧要,因此 usecols=[0, 1] 与 [1, 0] 相同。
如果 usecols 是字符串列表,则假定每个字符串对应于用户在 names 中提供的列名,或者从文档标题行推断出的列名。这些字符串定义了要解析的列。
pd.read_excel("path_to_file.xls", "Sheet1", usecols=["foo", "bar"])
元素的顺序无关紧要,因此 usecols=['baz', 'joe'] 与 ['joe', 'baz'] 相同。
如果 usecols 是可调用对象,则该可调用对象将被应用于列名,并返回可调用对象评估为 True 的名称。
pd.read_excel("path_to_file.xls", "Sheet1", usecols=lambda x: x.isalpha())
解析日期#
日期时间类的值在读取Excel文件时通常会自动转换为适当的dtype。但是,如果您的字符串列*看起来*像日期(但在Excel中并未实际格式化为日期),则可以使用parse_dates关键字将这些字符串解析为日期时间。
pd.read_excel("path_to_file.xls", "Sheet1", parse_dates=["date_strings"])
单元格转换器#
可以通过converters选项转换Excel单元格的内容。例如,将一列转换为布尔值
pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyBools": bool})
此选项会处理缺失值,并将转换器中的异常视为缺失数据。转换是逐个单元格应用,而不是对整个列应用,因此不保证数组的dtype。例如,包含缺失值的整数列无法转换为具有整数dtype的数组,因为NaN严格来说是浮点数。您可以手动屏蔽缺失数据以恢复整数dtype。
def cfun(x):
return int(x) if x else -1
pd.read_excel("path_to_file.xls", "Sheet1", converters={"MyInts": cfun})
Dtype规范#
作为转换器的替代方法,可以使用dtype关键字指定整个列的类型,该关键字接受一个映射列名到类型的字典。要解释没有类型推断的数据,请使用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写入。
将以一种试图模仿REPL输出的方式写入DataFrame。 index_label将放在第二行而不是第一行。可以通过将to_excel()中的merge_cells选项设置为False来将其放在第一行。
df.to_excel("path_to_file.xlsx", index_label="label", merge_cells=False)
为了将单独的DataFrame写入单个Excel文件中的单独工作表,可以使用ExcelWriter。
with pd.ExcelWriter("path_to_file.xlsx") as writer:
df1.to_excel(writer, sheet_name="Sheet1")
df2.to_excel(writer, sheet_name="Sheet2")
使用 engine_kwargs 参数时,pandas 会将这些参数传递给引擎。为此,了解 pandas 内部使用的是哪个函数很重要。
对于openpyxl引擎,pandas使用
openpyxl.Workbook()创建新工作表,并使用openpyxl.load_workbook()将数据追加到现有工作表。openpyxl引擎写入(.xlsx)和(.xlsm)文件。对于xlsxwriter引擎,pandas使用
xlsxwriter.Workbook()写入(.xlsx)文件。对于odf引擎,pandas使用
odf.opendocument.OpenDocumentSpreadsheet()写入(.ods)文件。
将Excel文件写入内存#
pandas支持使用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将回退到openpyxl来处理.xlsx文件。
要指定要使用的写入器,可以将引擎关键字参数传递给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)。autofilter:一个布尔值,指示是否为所有列添加自动筛选器(默认False)。
注意
从pandas 3.0开始,默认情况下使用to_excel方法创建的电子表格不包含任何样式。希望在to_excel输出的工作表中加粗文本、添加边框样式等的用户,可以通过使用Styler.to_excel()来创建带样式的Excel文件。有关样式电子表格的文档,请参阅此处。
css = "border: 1px solid black; font-weight: bold;"
df.style.map_index(lambda x: css).map_index(lambda x: css, axis=1).to_excel("myfile.xlsx")
使用Xlsxwriter引擎提供了许多控制使用to_excel方法创建的Excel工作表格式的选项。可以在Xlsxwriter文档中找到极好的示例:https://xlsxwriter.readthedocs.io/working_with_pandas.html
OpenDocument电子表格#
用于Excel文件的IO方法也支持使用odfpy模块读取和写入OpenDocument电子表格。读取和写入OpenDocument电子表格的语义和功能与使用engine='odf'处理Excel文件的功能相匹配。可选依赖项“odfpy”需要被安装。
read_excel()方法可以读取OpenDocument电子表格
# Returns a DataFrame
pd.read_excel("path_to_file.ods", engine="odf")
同样,to_excel()方法可以写入OpenDocument电子表格
# Writes DataFrame to a .ods file
df.to_excel("path_to_file.ods", engine="odf")
二进制Excel(.xlsb)文件#
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 [420]: df
Out[420]:
c1 a
c2 b d
lvl1 lvl2
a c 1 5
d 2 6
b c 3 7
d 4 8
In [421]: df.to_pickle("foo.pkl")
pandas命名空间中的read_pickle函数可用于从文件加载任何已pickled的pandas对象(或任何其他已pickled的对象)。
In [422]: pd.read_pickle("foo.pkl")
Out[422]:
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 [423]: df = pd.DataFrame(
.....: {
.....: "A": np.random.randn(1000),
.....: "B": "foo",
.....: "C": pd.date_range("20130101", periods=1000, freq="s"),
.....: }
.....: )
.....:
In [424]: df
Out[424]:
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 [425]: df.to_pickle("data.pkl.compress", compression="gzip")
In [426]: rt = pd.read_pickle("data.pkl.compress", compression="gzip")
In [427]: rt
Out[427]:
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 [428]: df.to_pickle("data.pkl.xz", compression="infer")
In [429]: rt = pd.read_pickle("data.pkl.xz", compression="infer")
In [430]: rt
Out[430]:
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 [431]: df.to_pickle("data.pkl.gz")
In [432]: rt = pd.read_pickle("data.pkl.gz")
In [433]: rt
Out[433]:
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 [434]: df["A"].to_pickle("s1.pkl.bz2")
In [435]: rt = pd.read_pickle("s1.pkl.bz2")
In [436]: rt
Out[436]:
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 [437]: 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是一个类似字典的对象,它使用高性能的HDF5格式并通过优秀的PyTables库读写pandas。有关一些高级策略,请参阅cookbook。
警告
pandas使用PyTables来读写HDF5文件,这允许使用pickle序列化对象dtype数据。从不受信任的来源加载已pickled的数据可能不安全。
In [438]: store = pd.HDFStore("store.h5")
In [439]: print(store)
<class 'pandas.HDFStore'>
File path: store.h5
对象可以像向字典中添加键值对一样写入文件
In [440]: index = pd.date_range("1/1/2000", periods=8)
In [441]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])
In [442]: df = pd.DataFrame(np.random.randn(8, 3), index=index, columns=["A", "B", "C"])
# store.put('s', s) is an equivalent method
In [443]: store["s"] = s
In [444]: store["df"] = df
In [445]: store
Out[445]:
<class 'pandas.HDFStore'>
File path: store.h5
在当前或后续的Python会话中,您可以检索已存储的对象
# store.get('df') is an equivalent method
In [446]: store["df"]
Out[446]:
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 [447]: store.df
Out[447]:
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 [448]: del store["df"]
In [449]: store
Out[449]:
<class 'pandas.HDFStore'>
File path: store.h5
关闭Store并使用上下文管理器
In [450]: store.close()
In [451]: store
Out[451]:
<class 'pandas.HDFStore'>
File path: store.h5
In [452]: store.is_open
Out[452]: False
# Working with, and automatically closing the store using a context manager
In [453]: with pd.HDFStore("store.h5") as store:
.....: store.keys()
.....:
读/写API#
HDFStore支持使用read_hdf进行读取和to_hdf进行写入的顶级API,类似于read_csv和to_csv的工作方式。
In [454]: df_tl = pd.DataFrame({"A": list(range(5)), "B": list(range(5))})
In [455]: df_tl.to_hdf("store_tl.h5", key="table", append=True)
In [456]: pd.read_hdf("store_tl.h5", "table", where=["index>2"])
Out[456]:
A B
3 3 3
4 4 4
默认情况下,HDFStore不会删除所有值都缺失的行。通过设置dropna=True可以改变此行为。
In [457]: df_with_missing = pd.DataFrame(
.....: {
.....: "col1": [0, np.nan, 2],
.....: "col2": [1, np.nan, np.nan],
.....: }
.....: )
.....:
In [458]: df_with_missing
Out[458]:
col1 col2
0 0.0 1.0
1 NaN NaN
2 2.0 NaN
In [459]: df_with_missing.to_hdf("file.h5", key="df_with_missing", format="table", mode="w")
In [460]: pd.read_hdf("file.h5", "df_with_missing")
Out[460]:
col1 col2
0 0.0 1.0
1 NaN NaN
2 2.0 NaN
In [461]: df_with_missing.to_hdf(
.....: "file.h5", key="df_with_missing", format="table", mode="w", dropna=True
.....: )
.....:
In [462]: pd.read_hdf("file.h5", "df_with_missing")
Out[462]:
col1 col2
0 0.0 1.0
2 2.0 NaN
固定格式#
上面的示例显示了使用put进行存储,它将HDF5写入PyTables,采用一种称为fixed格式的固定数组格式。这些类型的存储一旦写入就*不能*追加(尽管您可以简单地删除它们并重新写入)。它们也*不能*被查询;它们必须被完整地检索。它们也不支持具有非唯一列名的DataFrames。fixed格式存储提供非常快速的写入和比table存储稍快的读取。当使用put或to_hdf时,此格式是默认指定的,或者通过format='fixed'或format='f'指定。
警告
如果尝试使用where进行检索,fixed格式将引发TypeError。
In [463]: pd.DataFrame(np.random.randn(10, 2)).to_hdf("test_fixed.h5", key="df")
In [464]: pd.read_hdf("test_fixed.h5", "df", where="index>5")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[464], line 1
----> 1 pd.read_hdf("test_fixed.h5", "df", where="index>5")
File ~/work/pandas/pandas/pandas/io/pytables.py:463, in read_hdf(path_or_buf, key, mode, errors, where, start, stop, columns, iterator, chunksize, **kwargs)
458 raise ValueError(
459 "key must be provided when HDF5 "
460 "file contains multiple datasets."
461 )
462 key = candidate_only_group._v_pathname
--> 463 return store.select(
464 key,
465 where=where,
466 start=start,
467 stop=stop,
468 columns=columns,
469 iterator=iterator,
470 chunksize=chunksize,
471 auto_close=auto_close,
472 )
473 except (ValueError, TypeError, LookupError):
474 if not isinstance(path_or_buf, HDFStore):
475 # if there is an error, close the store if we opened it.
File ~/work/pandas/pandas/pandas/io/pytables.py:936, in HDFStore.select(self, key, where, start, stop, columns, iterator, chunksize, auto_close)
922 # create the iterator
923 it = TableIterator(
924 self,
925 s,
(...) 933 auto_close=auto_close,
934 )
--> 936 return it.get_result()
File ~/work/pandas/pandas/pandas/io/pytables.py:2128, in TableIterator.get_result(self, coordinates)
2125 where = self.where
2127 # directly return the result
-> 2128 results = self.func(self.start, self.stop, where)
2129 self.close()
2130 return results
File ~/work/pandas/pandas/pandas/io/pytables.py:920, in HDFStore.select.<locals>.func(_start, _stop, _where)
919 def func(_start, _stop, _where):
--> 920 return s.read(start=_start, stop=_stop, where=_where, columns=columns)
File ~/work/pandas/pandas/pandas/io/pytables.py:3443, in BlockManagerFixed.read(self, where, columns, start, stop)
3435 def read(
3436 self,
3437 where=None,
(...) 3441 ) -> DataFrame:
3442 # start, stop applied to rows, so 0th axis only
-> 3443 self.validate_read(columns, where)
3444 select_axis = self.obj_type()._get_block_manager_axis(0)
3446 axes = []
File ~/work/pandas/pandas/pandas/io/pytables.py:3041, in GenericFixed.validate_read(self, columns, where)
3036 raise TypeError(
3037 "cannot pass a column specification when reading "
3038 "a Fixed format store. this store must be selected in its entirety"
3039 )
3040 if where is not None:
-> 3041 raise TypeError(
3042 "cannot pass a where specification when reading "
3043 "from a Fixed format store. this store must be selected in its entirety"
3044 )
TypeError: cannot pass a where specification when reading from a Fixed format store. this store must be selected in its entirety
表格式#
HDFStore支持另一种在磁盘上的PyTables格式,即table格式。概念上,一个table的形状非常类似于DataFrame,具有行和列。一个table可以在同一会话或其他会话中追加。此外,还支持删除和查询类型的操作。此格式通过将format='table'或format='t'传递给append或put或to_hdf来指定。
此格式也可以设置为选项pd.set_option('io.hdf.default_format','table'),以启用put/append/to_hdf默认以table格式存储。
In [465]: store = pd.HDFStore("store.h5")
In [466]: df1 = df[0:4]
In [467]: df2 = df[4:]
# append data (creates a table automatically)
In [468]: store.append("df", df1)
In [469]: store.append("df", df2)
In [470]: store
Out[470]:
<class 'pandas.HDFStore'>
File path: store.h5
# select the entire object
In [471]: store.select("df")
Out[471]:
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 [472]: store.root.df._v_attrs.pandas_type
Out[472]: np.str_('frame_table')
注意
您也可以通过将format='table'或format='t'传递给put操作来创建table。
分层键#
Store的键可以指定为字符串。它们可以是分层路径名格式(例如foo/bar/bah),这将生成一个子Store的层次结构(在PyTables术语中称为Groups)。键可以不带前导'/'指定,并且*总是*绝对的(例如,“foo”指的是“/foo”)。删除操作可以删除子Store*及其下方*的所有内容,所以请*小心*。
In [473]: store.put("foo/bar/bah", df)
In [474]: store.append("food/orange", df)
In [475]: store.append("food/apple", df)
In [476]: store
Out[476]:
<class 'pandas.HDFStore'>
File path: store.h5
# a list of keys are returned
In [477]: store.keys()
Out[477]: ['/df', '/food/apple', '/food/orange', '/foo/bar/bah']
# remove all nodes under this level
In [478]: store.remove("food")
In [479]: store
Out[479]:
<class 'pandas.HDFStore'>
File path: store.h5
您可以使用walk方法遍历组层次结构,该方法将为每个组键以及其内容的相对键生成一个元组。
In [480]: 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 [481]: store.foo.bar.bah
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[481], line 1
----> 1 store.foo.bar.bah
File ~/work/pandas/pandas/pandas/io/pytables.py:625, in HDFStore.__getattr__(self, name)
623 """allow attribute access to get stores"""
624 try:
--> 625 return self.get(name)
626 except (KeyError, ClosedFileError):
627 pass
File ~/work/pandas/pandas/pandas/io/pytables.py:837, in HDFStore.get(self, key)
835 if group is None:
836 raise KeyError(f"No object named {key} in the file")
--> 837 return self._read_group(group)
File ~/work/pandas/pandas/pandas/io/pytables.py:1977, in HDFStore._read_group(self, group)
1976 def _read_group(self, group: Node):
-> 1977 s = self._create_storer(group)
1978 s.infer_axes()
1979 return s.read()
File ~/work/pandas/pandas/pandas/io/pytables.py:1851, in HDFStore._create_storer(self, group, format, value, encoding, errors)
1849 tt = "generic_table"
1850 else:
-> 1851 raise TypeError(
1852 "cannot create a storer if the object is not existing "
1853 "nor a value are passed"
1854 )
1855 else:
1856 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 [482]: store.root.foo.bar.bah
Out[482]:
/foo/bar/bah (Group) np.str_('')
children := ['axis0' (Array), 'axis1' (Array), 'block0_items' (Array), 'block0_values' (Array)]
而是使用显式字符串键
In [483]: store["foo/bar/bah"]
Out[483]:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
存储类型#
在表中存储混合类型#
支持存储混合dtype数据。字符串以固定宽度存储,使用追加列的最大大小。后续尝试追加更长字符串将引发ValueError。
将min_itemsize={`values`: size}作为参数传递给append将为字符串列设置一个更大的最小值。目前支持存储floats, strings, ints, bools, datetime64。对于字符串列,将nan_rep = 'nan'传递给append将更改磁盘上的默认nan表示(转换为/从np.nan),此默认值为nan。
In [484]: 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 [485]: df_mixed.loc[df_mixed.index[3:5], ["A", "B", "string", "datetime64"]] = np.nan
In [486]: store.append("df_mixed", df_mixed, min_itemsize={"values": 50})
In [487]: df_mixed1 = store.select("df_mixed")
In [488]: df_mixed1
Out[488]:
A B C string int bool datetime64
0 0.013747 -1.166078 -1.292080 string 1 True 2001-01-02
1 -0.712009 0.247572 1.526911 string 1 True 2001-01-02
2 -0.645096 1.687406 0.288504 string 1 True 2001-01-02
3 NaN NaN 0.097771 NaN 1 True NaT
4 NaN NaN 1.536408 NaN 1 True NaT
5 -0.023202 0.043702 0.926790 string 1 True 2001-01-02
6 2.359782 0.088224 -0.676448 string 1 True 2001-01-02
7 -0.143428 -0.813360 -0.179724 string 1 True 2001-01-02
In [489]: df_mixed1.dtypes.value_counts()
Out[489]:
float64 2
float32 1
str 1
int64 1
bool 1
datetime64[us] 1
Name: count, dtype: int64
# we have provided a minimum string column size
In [490]: store.root.df_mixed.table
Out[490]:
/df_mixed/table (Table(np.int64(8),)) np.str_('')
description := {
"index": Int64Col(shape=(), dflt=np.int64(0), pos=0),
"values_block_0": Float64Col(shape=(np.int64(2),), dflt=np.float64(0.0), pos=1),
"values_block_1": Float32Col(shape=(np.int64(1),), dflt=np.float32(0.0), pos=2),
"values_block_2": StringCol(itemsize=50, shape=(np.int64(1),), dflt=np.bytes_(b''), pos=3),
"values_block_3": Int64Col(shape=(np.int64(1),), dflt=np.int64(0), pos=4),
"values_block_4": BoolCol(shape=(np.int64(1),), dflt=np.False_, pos=5),
"values_block_5": Int64Col(shape=(np.int64(1),), dflt=np.int64(0), pos=6)}
byteorder := 'little'
chunkshape := (np.int64(689),)
autoindex := True
colindexes := {
"index": Index(6, mediumshuffle, zlib(1)).is_csi=False}
存储MultiIndex DataFrames#
将MultiIndex DataFrames存储为表与存储/选择同质索引DataFrames非常相似。
In [491]: 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 [492]: df_mi = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])
In [493]: df_mi
Out[493]:
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 [494]: store.append("df_mi", df_mi)
In [495]: store.select("df_mi")
Out[495]:
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 [496]: store.select("df_mi", "foo=bar")
Out[496]:
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 [497]: dfq = pd.DataFrame(
.....: np.random.randn(10, 4),
.....: columns=list("ABCD"),
.....: index=pd.date_range("20130101", periods=10),
.....: )
.....:
In [498]: store.append("dfq", dfq, format="table", data_columns=True)
使用布尔表达式,并进行内联函数求值。
In [499]: store.select("dfq", "index>pd.Timestamp('20130104') & columns=['A', 'B']")
Out[499]:
Empty DataFrame
Columns: [A, B]
Index: []
使用内联列引用。
In [500]: store.select("dfq", where="A>0 or C>0")
Out[500]:
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 [501]: store.select("df", "columns=['A', 'B']")
Out[501]:
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 [502]: from datetime import timedelta
In [503]: dftd = pd.DataFrame(
.....: {
.....: "A": pd.Timestamp("20130101"),
.....: "B": [
.....: pd.Timestamp("20130101") + timedelta(days=i, seconds=10)
.....: for i in range(10)
.....: ],
.....: }
.....: )
.....:
In [504]: dftd["C"] = dftd["A"] - dftd["B"]
In [505]: dftd
Out[505]:
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 [506]: store.append("dftd", dftd, data_columns=True)
In [507]: store.select("dftd", "C<'-3.5D'")
Out[507]:
A B C
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
查询 MultiIndex#
可以通过使用层名称来从 MultiIndex 中进行选择。
In [508]: df_mi.index.names
Out[508]: FrozenList(['foo', 'bar'])
In [509]: store.select("df_mi", "foo=baz and bar=two")
Out[509]:
A B C
foo bar
baz two -1.646063 -0.695847 -0.429156
如果 MultiIndex 的层名称是 None,则可以通过 level_n 关键字自动提供这些层,其中 n 是您要从中选择的 MultiIndex 的层。
In [510]: 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 [511]: df_mi_2 = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])
In [512]: df_mi_2
Out[512]:
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 [513]: store.append("df_mi_2", df_mi_2)
# the levels are automatically included as data columns with keyword level_n
In [514]: store.select("df_mi_2", "level_0=foo and level_1=two")
Out[514]:
A B C
foo two 0.053768 1.872644 -1.469813
索引#
您可以使用 create_table_index 来创建/修改表的索引,前提是数据已经存在于表中(在 append/put 操作之后)。强烈建议创建表索引。这将在您使用 select 并将索引维度作为 where 子句时,大大加快您的查询速度。
注意
索引会自动在可索引列和您指定的任何数据列上创建。可以通过将 index=False 传递给 append 来关闭此行为。
# we have automagically already created an index (in the first section)
In [515]: i = store.root.df.table.cols.index.index
In [516]: i.optlevel, i.kind
Out[516]: (6, 'medium')
# change an index by passing new parameters
In [517]: store.create_table_index("df", optlevel=9, kind="full")
In [518]: i = store.root.df.table.cols.index.index
In [519]: i.optlevel, i.kind
Out[519]: (9, 'full')
在将大量数据追加到存储时,通常的做法是关闭每个追加操作的索引创建,然后在最后重新创建。
In [520]: df_1 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))
In [521]: df_2 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))
In [522]: st = pd.HDFStore("appends.h5", mode="w")
In [523]: st.append("df", df_1, data_columns=["B"], index=False)
In [524]: st.append("df", df_2, data_columns=["B"], index=False)
In [525]: st.get_storer("df").table
Out[525]:
/df/table (Table(np.int64(20),)) np.str_('')
description := {
"index": Int64Col(shape=(), dflt=np.int64(0), pos=0),
"values_block_0": Float64Col(shape=(np.int64(1),), dflt=np.float64(0.0), pos=1),
"B": Float64Col(shape=(), dflt=np.float64(0.0), pos=2)}
byteorder := 'little'
chunkshape := (np.int64(2730),)
然后,在追加完成后创建索引。
In [526]: st.create_table_index("df", columns=["B"], optlevel=9, kind="full")
In [527]: st.get_storer("df").table
Out[527]:
/df/table (Table(np.int64(20),)) np.str_('')
description := {
"index": Int64Col(shape=(), dflt=np.int64(0), pos=0),
"values_block_0": Float64Col(shape=(np.int64(1),), dflt=np.float64(0.0), pos=1),
"B": Float64Col(shape=(), dflt=np.float64(0.0), pos=2)}
byteorder := 'little'
chunkshape := (np.int64(2730),)
autoindex := True
colindexes := {
"B": Index(9, fullshuffle, zlib(1)).is_csi=True}
In [528]: st.close()
有关如何在现有存储上创建完全排序索引 (CSI) 的信息,请参见 此处。
通过数据列查询#
您可以指定(并索引)某些列,以便能够对它们进行查询(除了 indexable 列,这些列始终可以查询)。例如,假设您想在磁盘上执行此常见操作,并仅返回与此查询匹配的帧。您可以指定 data_columns = True 来强制所有列成为 data_columns。
In [529]: df_dc = df.copy()
In [530]: df_dc["string"] = "foo"
In [531]: df_dc.loc[df_dc.index[4:6], "string"] = np.nan
In [532]: df_dc.loc[df_dc.index[7:9], "string"] = "bar"
In [533]: df_dc["string2"] = "cool"
In [534]: df_dc.loc[df_dc.index[1:3], ["B", "C"]] = 1.0
In [535]: df_dc
Out[535]:
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 [536]: store.append("df_dc", df_dc, data_columns=["B", "C", "string", "string2"])
In [537]: store.select("df_dc", where="B > 0")
Out[537]:
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 [538]: store.select("df_dc", "B > 0 & C > 0 & string == foo")
Out[538]:
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 [539]: df_dc[(df_dc.B > 0) & (df_dc.C > 0) & (df_dc.string == "foo")]
Out[539]:
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 [540]: store.root.df_dc.table
Out[540]:
/df_dc/table (Table(np.int64(8),)) np.str_('')
description := {
"index": Int64Col(shape=(), dflt=np.int64(0), pos=0),
"values_block_0": Float64Col(shape=(np.int64(1),), dflt=np.float64(0.0), pos=1),
"B": Float64Col(shape=(), dflt=np.float64(0.0), pos=2),
"C": Float64Col(shape=(), dflt=np.float64(0.0), pos=3),
"string": StringCol(itemsize=3, shape=(), dflt=np.bytes_(b''), pos=4),
"string2": StringCol(itemsize=4, shape=(), dflt=np.bytes_(b''), pos=5)}
byteorder := 'little'
chunkshape := (np.int64(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 操作之后,您无法更改数据列(或可索引列)(当然,您可以简单地读取数据并创建一个新表!)。
迭代器#
您可以将 iterator=True 或 chunksize=number_in_a_chunk 传递给 select 和 select_as_multiple,以返回结果的迭代器。默认情况下,每次返回 50,000 行作为一个块。
In [541]: 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 [542]: dfeq = pd.DataFrame({"number": np.arange(1, 11)})
In [543]: dfeq
Out[543]:
number
0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10
In [544]: store.append("dfeq", dfeq, data_columns=["number"])
In [545]: def chunks(l, n):
.....: return [l[i: i + n] for i in range(0, len(l), n)]
.....:
In [546]: evens = [2, 4, 6, 8, 10]
In [547]: coordinates = store.select_as_coordinates("dfeq", "number=evens")
In [548]: 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 [549]: store.select_column("df_dc", "index")
Out[549]:
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[us]
In [550]: store.select_column("df_dc", "string")
Out[550]:
0 foo
1 foo
2 foo
3 foo
4 NaN
5 NaN
6 foo
7 bar
Name: string, dtype: str
选择坐标#
有时您想获取查询的坐标(也称为索引位置)。这将返回结果位置的 Index。这些坐标也可以传递给后续的 where 操作。
In [551]: df_coord = pd.DataFrame(
.....: np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
.....: )
.....:
In [552]: store.append("df_coord", df_coord)
In [553]: c = store.select_as_coordinates("df_coord", "index > 20020101")
In [554]: c
Out[554]: Index([], dtype='int64')
In [555]: store.select("df_coord", where=c)
Out[555]:
0 1
2000-01-01 -2.621377 -0.441682
2000-01-02 -0.455698 0.060380
2000-01-03 0.491397 0.730761
2000-01-04 -0.032668 0.779137
2000-01-05 -0.882215 2.788970
... ... ...
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
[1000 rows x 2 columns]
使用 where 掩码进行选择#
有时您的查询可能涉及创建要选择的行列表。通常,此 mask 将是索引操作的 index 结果。此示例选择 datetimeindex 中月份为 5 的项。
In [556]: df_mask = pd.DataFrame(
.....: np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
.....: )
.....:
In [557]: store.append("df_mask", df_mask)
In [558]: c = store.select_column("df_mask", "index")
In [559]: where = c[pd.DatetimeIndex(c).month == 5].index
In [560]: store.select("df_mask", where=where)
Out[560]:
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 [561]: store.get_storer("df_dc").nrows
Out[561]: np.int64(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 [562]: 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 [563]: df_mt["foo"] = "bar"
In [564]: df_mt.loc[df_mt.index[1], ("A", "B")] = np.nan
# you can also create the tables individually
In [565]: store.append_to_multiple(
.....: {"df1_mt": ["A", "B"], "df2_mt": None}, df_mt, selector="df1_mt"
.....: )
.....:
In [566]: store
Out[566]:
<class 'pandas.HDFStore'>
File path: store.h5
# individual tables were created
In [567]: store.select("df1_mt")
Out[567]:
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 [568]: store.select("df2_mt")
Out[568]:
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 [569]: store.select_as_multiple(
.....: ["df1_mt", "df2_mt"],
.....: where=["A>0", "B>0"],
.....: selector="df1_mt",
.....: )
.....:
Out[569]:
Empty DataFrame
Columns: [A, B, C, D, E, F, foo]
Index: []
从表中删除#
您可以通过指定 where 来选择性地从表中删除。在删除行时,理解 PyTables 是通过擦除行然后 **移动** 后续数据来删除行的,这一点很重要。因此,删除操作可能会非常耗时,具体取决于数据的方向。为了获得最佳性能,将您要删除的维度设置为 indexables 中的第一个是值得的。
数据是根据 indexables 的顺序(在磁盘上)排列的。这是一个简单的用例。您存储面板类型的数据,其中 dates 位于 major_axis 中,id 位于 minor_axis 中。数据然后交错排列,如下所示:
- date_1
id_1
id_2
.
id_n
- date_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)就是固定的;只能追加完全相同的列。请注意,时区(例如,
zoneinfo.ZoneInfo('US/Eastern'))在不同时区版本之间不一定相等。因此,如果数据在一个时区库版本中使用时区信息本地化到 HDFStore 中,并且该数据使用另一个版本更新,则数据将被转换为 UTC,因为这些时区不被视为相等。要么使用相同版本时区库,要么使用更新的时区定义通过tz_convert进行转换。
警告
PyTables 会在列名无法用作属性选择器时显示 NaturalNameWarning。自然标识符仅包含字母、数字和下划线,且不能以数字开头。其他标识符不能在 where 子句中使用,通常是不好的选择。
数据类型#
HDFStore 会将 object dtypes 映射到 PyTables 的底层 dtypes。这意味着以下类型已知可以工作:
类型 |
表示缺失值 |
|---|---|
浮点数: |
|
整数: |
|
布尔值 |
|
|
|
|
|
分类:请参阅下面的部分。 |
|
对象: |
|
unicode 列不受支持,并且**将会失败**。
分类数据#
您可以将包含 category dtypes 的数据写入 HDFStore。查询方式与对象数组相同。但是,category dtyped 的数据以更有效的方式存储。
In [570]: dfcat = pd.DataFrame(
.....: {"A": pd.Series(list("aabbcdba")).astype("category"), "B": np.random.randn(8)}
.....: )
.....:
In [571]: dfcat
Out[571]:
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 [572]: dfcat.dtypes
Out[572]:
A category
B float64
dtype: object
In [573]: cstore = pd.HDFStore("cats.h5", mode="w")
In [574]: cstore.append("dfcat", dfcat, format="table", data_columns=["A"])
In [575]: result = cstore.select("dfcat", where="A in ['b', 'c']")
In [576]: result
Out[576]:
A B
2 b -0.551981
3 b 0.452407
4 c 0.409257
6 b -0.640843
In [577]: result.dtypes
Out[577]:
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 [578]: dfs = pd.DataFrame({"A": "foo", "B": "bar"}, index=list(range(5)))
In [579]: dfs
Out[579]:
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 [580]: store.append("dfs", dfs, min_itemsize=30)
In [581]: store.get_storer("dfs").table
Out[581]:
/dfs/table (Table(np.int64(5),)) np.str_('')
description := {
"index": Int64Col(shape=(), dflt=np.int64(0), pos=0),
"values_block_0": StringCol(itemsize=30, shape=(np.int64(1),), dflt=np.bytes_(b''), pos=1),
"values_block_1": StringCol(itemsize=30, shape=(np.int64(1),), dflt=np.bytes_(b''), pos=2)}
byteorder := 'little'
chunkshape := (np.int64(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 [582]: store.append("dfs2", dfs, min_itemsize={"A": 30})
In [583]: store.get_storer("dfs2").table
Out[583]:
/dfs2/table (Table(np.int64(5),)) np.str_('')
description := {
"index": Int64Col(shape=(), dflt=np.int64(0), pos=0),
"values_block_0": StringCol(itemsize=3, shape=(np.int64(1),), dflt=np.bytes_(b''), pos=1),
"A": StringCol(itemsize=30, shape=(), dflt=np.bytes_(b''), pos=2)}
byteorder := 'little'
chunkshape := (np.int64(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 [584]: dfss = pd.DataFrame({"A": ["foo", "bar", "nan"]})
In [585]: dfss
Out[585]:
A
0 foo
1 bar
2 nan
In [586]: store.append("dfss", dfss)
In [587]: store.select("dfss")
Out[587]:
A
0 foo
1 bar
2 NaN
# here you need to specify a different nan rep
In [588]: store.append("dfss2", dfss, nan_rep="_nan_")
In [589]: store.select("dfss2")
Out[589]:
A
0 foo
1 bar
2 nan
性能#
tables格式与fixed存储相比,在写入性能上有所下降。优点是能够追加/删除和查询(可能非常大量的数据)。与常规存储相比,写入时间通常更长。查询时间可能非常快,尤其是在索引轴上。您可以将
chunksize=<int>传递给append,指定写入块的大小(默认值为 50000)。这将显著降低写入时的内存使用。您可以在第一次
append时传递expectedrows=<int>,以设置PyTables预计的总行数。这将优化读写性能。可以向表中写入重复的行,但在选择时会被过滤掉(选择最后出现的行;因此,表在 major、minor 对上是唯一的)。
如果您尝试存储将被 PyTables 序列化的类型(而不是作为固有类型存储),将会引发
PerformanceWarning。有关更多信息和一些解决方案,请参见 此处。
Feather#
Feather 提供数据帧的二进制列序列化。它旨在使数据帧的读写高效,并使跨数据分析语言共享数据变得容易。
Feather 旨在忠实地序列化和反序列化 DataFrames,支持所有 pandas dtypes,包括扩展 dtypes,如带时区的分类和日期时间。
几个注意事项
该格式**不会**为
DataFrame写入Index或MultiIndex,如果提供了非默认索引,则会引发错误。您可以.reset_index()来存储索引,或者.reset_index(drop=True)来忽略它。不支持重复的列名和非字符串的列名。
对象 dtypes 列中的实际 Python 对象不支持。这些在尝试序列化时会引发有用的错误消息。
请参阅 完整文档。
In [590]: import pytz
In [591]: 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=pytz.timezone("US/Eastern")),
.....: "i": pd.date_range("20130101", periods=3, freq="ns"),
.....: }
.....: )
.....:
In [592]: df
Out[592]:
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 [593]: df.dtypes
Out[593]:
a str
b int64
c uint8
d float64
e bool
f category
g datetime64[us]
h datetime64[us, US/Eastern]
i datetime64[ns]
dtype: object
写入 feather 文件。
In [594]: df.to_feather("example.feather")
从 feather 文件读取。
In [595]: result = pd.read_feather("example.feather")
In [596]: result
Out[596]:
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 [597]: result.dtypes
Out[597]:
a str
b int64
c uint8
d float64
e bool
f category
g datetime64[us]
h datetime64[us, US/Eastern]
i datetime64[ns]
dtype: object
Parquet#
Apache Parquet 提供数据帧的已分区二进制列序列化。它旨在使数据帧的读写高效,并使跨数据分析语言共享数据变得容易。Parquet 可以使用各种压缩技术来尽可能减小文件大小,同时保持良好的读取性能。
Parquet 旨在忠实地序列化和反序列化 DataFrame s,支持所有 pandas dtypes,包括带时区的日期时间等扩展 dtypes。
几个注意事项。
不支持重复的列名和非字符串的列名。
当 DataFrame 索引是非默认范围索引时,它将被写为单独的列(们)。这个额外的列可能会导致非 pandas 消费者(不期望它)出现问题。您可以通过
index参数强制包含或省略索引。索引层名称(如果指定)必须是字符串。
在
pyarrow引擎中,非字符串类型的分类 dtypes 可以序列化为 parquet,但会反序列化为其原始 dtype。pyarrow引擎支持Period和Intervaldtypes。fastparquet不支持这些。不支持的类型包括实际的 Python 对象类型。这些在尝试序列化时会引发有用的错误消息。
pyarrow引擎保留扩展数据类型,如可为空的整数和字符串数据类型(这也适用于外部扩展类型,需要扩展类型实现所需的协议,请参阅 扩展类型文档)。
您可以指定一个 engine 来控制序列化。它可以是 pyarrow、fastparquet 或 auto。如果未指定引擎,则检查 pd.options.io.parquet.engine 选项;如果该选项也为 auto,则在安装 pyarrow 时使用 pyarrow,否则回退到 fastparquet。
请参阅 pyarrow 和 fastparquet 的文档。
注意
这些引擎非常相似,在大多数情况下应读取/写入几乎相同的 parquet 格式文件。这些库的区别在于它们具有不同的底层依赖项(fastparquet 使用 numba,而 pyarrow 使用 c 库)。
In [598]: 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 [599]: df
Out[599]:
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 [600]: df.dtypes
Out[600]:
a str
b int64
c uint8
d float64
e bool
f datetime64[us]
g datetime64[us, US/Eastern]
h category
i category
dtype: object
写入 parquet 文件。
# specify engine="pyarrow" or engine="fastparquet" to use a specific engine
In [601]: df.to_parquet("example.parquet")
从 parquet 文件读取。
In [602]: result = pd.read_parquet("example.parquet")
In [603]: result.dtypes
Out[603]:
a str
b int64
c uint8
d float64
e bool
f datetime64[us]
g datetime64[us, US/Eastern]
h category
i category
dtype: object
通过设置 dtype_backend 参数,您可以控制结果 DataFrame 的默认 dtype。
In [604]: result = pd.read_parquet("example.parquet", dtype_backend="pyarrow")
In [605]: result.dtypes
Out[605]:
a large_string[pyarrow]
b int64[pyarrow]
c uint8[pyarrow]
d double[pyarrow]
e bool[pyarrow]
f timestamp[us][pyarrow]
g timestamp[us, tz=US/Eastern][pyarrow]
h dictionary<values=string, indices=int8, ordere...
i dictionary<values=string, indices=int8, ordere...
dtype: object
注意
请注意,fastparquet 不支持此功能。
读取 parquet 文件的特定列。
In [606]: result = pd.read_parquet("example.parquet", columns=["a", "b"])
In [607]: result.dtypes
Out[607]:
a str
b int64
dtype: object
处理索引#
将 DataFrame 序列化到 parquet 时,可能会将隐式索引作为输出文件中的一个或多个列。例如,此代码
In [608]: df = pd.DataFrame({"a": [1, 2], "b": [3, 4]}, index=[1, 2])
In [609]: df.to_parquet("test.parquet", engine="pyarrow")
创建一个包含三列的 parquet 文件(使用 pyarrow 引擎时为 a、b 和 __index_level_0__,使用 fastparquet 引擎时为 index、a 和 b),因为在这种情况下索引不是默认范围索引。通常,索引可能或可能不写入文件(有关 pyarrow 的 preserve_index 关键字或 fastparquet 的 write_index 关键字,以检查默认行为)。
这个意外的额外列会导致某些数据库(如 Amazon Redshift)拒绝该文件,因为该列在目标表中不存在。
如果您想在写入时省略 DataFrame 的索引,请将 index=False 传递给 to_parquet()。
In [610]: df.to_parquet("test.parquet", index=False)
这将创建一个只包含两列(a 和 b)的 parquet 文件。如果您的 DataFrame 有自定义索引,当您将此文件加载到 DataFrame 中时,您将无法恢复它。
传递 index=True 将始终写入索引,即使这并非底层引擎的默认行为。
分区 Parquet 文件#
Parquet 支持基于一个或多个列的值对数据进行分区。
In [611]: df = pd.DataFrame({"a": [0, 0, 1, 1], "b": [0, 1, 0, 1]})
In [612]: df.to_parquet(path="test", engine="pyarrow", partition_cols=["a"], compression=None)
path 指定数据将被保存的父目录。partition_cols 是数据集将被分区依据的列名。列按给定顺序分区。分区划分由分区列中的唯一值确定。上面的示例创建了一个可能看起来像这样的分区数据集:
test
├── a=0
│ ├── 0bac803e32dc42ae83fddfd029cbdebc.parquet
│ └── ...
└── a=1
├── e6ab24a4f45147b49b54a662f0c412a3.parquet
└── ...
Iceberg#
版本 3.0.0 中新增。
Apache Iceberg 是一个高性能的开源大数据表格式。Iceberg 使大数据能够使用 SQL 表,同时使不同的引擎能够安全地同时处理相同的表。
Iceberg 支持谓词下推和列裁剪,这些功能可以通过 read_iceberg() 函数的 row_filter 和 selected_fields 参数供 pandas 用户使用。这对于从大型表中提取适合内存的数据子集作为 pandas DataFrame 非常方便。
在内部,pandas 使用 PyIceberg 来查询 Iceberg。
一个简单的示例,用于加载定义在 my_catalog catalog 中的 Iceberg 表 my_table 的所有数据。
df = pd.read_iceberg("my_table", catalog_name="my_catalog")
Catalog 必须在 .pyiceberg.yaml 文件中定义,通常位于主目录中。可以使用 catalog_properties 参数更改 catalog 定义的属性。
df = pd.read_iceberg(
"my_table",
catalog_name="my_catalog",
catalog_properties={"s3.secret-access-key": "my_secret"},
)
也可以在 catalog_properties 中完全指定 catalog,而不提供 catalog_name。
df = pd.read_iceberg(
"my_table",
catalog_properties={
"uri": "http://127.0.0.1:8181",
"s3.endpoint": "http://127.0.0.1:9000",
},
)
要创建仅包含部分列的 DataFrame。
df = pd.read_iceberg(
"my_table",
catalog_name="my_catalog",
selected_fields=["my_column_3", "my_column_7"]
)
这将使函数执行得更快,因为其他列不会被读取。它还将节省内存,因为其他列的数据不会加载到 DataFrame 的底层内存中。
要只获取部分行,我们可以使用 limit 参数来完成。
df = pd.read_iceberg(
"my_table",
catalog_name="my_catalog",
limit=100,
)
这将创建一个包含 100 行的 DataFrame,假设表中至少有这么多行。
要根据条件获取部分行,可以使用 row_filter 参数来完成。
df = pd.read_iceberg(
"my_table",
catalog_name="my_catalog",
row_filter="distance > 10.0",
)
还可以通过将快照 ID 作为参数传递给 snapshot_id 来读取特定的快照。
要将 DataFrame 保存到 Iceberg,可以使用 DataFrame.to_iceberg() 方法。
df.to_iceberg("my_table", catalog_name="my_catalog")
要指定目录,其工作方式与 read_iceberg() 中的 catalog_name 和 catalog_properties 参数相同。
可以使用 location 参数指定表的存储位置。
df.to_iceberg(
"my_table",
catalog_name="my_catalog",
location="s://my-data-lake/my-iceberg-tables",
)
可以通过传递一个字典给 snapshot_properties 参数来为表快照添加属性。
有关 Iceberg 格式的更多信息,请参阅 Apache Iceberg 官方页面。
ORC#
与 parquet 格式类似,ORC 格式是用于数据帧的二进制列式序列化。它旨在提高读取数据帧的效率。pandas 提供了 ORC 格式的读取器和写入器,即 read_orc() 和 to_orc()。这需要 pyarrow 库。
警告
强烈建议使用 conda 安装 pyarrow,因为 pyarrow 存在一些问题。
read_orc()和to_orc()尚不支持 Windows,您可以在 安装可选依赖项 中找到有效的环境。有关支持的数据类型,请参阅 Arrow 中支持的 ORC 功能。
当前,将数据帧转换为 ORC 文件时,日期时间列中的时区不会被保留。
In [613]: 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 [614]: df
Out[614]:
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 [615]: df.dtypes
Out[615]:
a str
b int64
c float64
d bool
e datetime64[us]
dtype: object
写入 ORC 文件。
In [616]: df.to_orc("example_pa.orc", engine="pyarrow")
从 ORC 文件读取。
In [617]: result = pd.read_orc("example_pa.orc")
In [618]: result.dtypes
Out[618]:
a str
b int64
c float64
d bool
e datetime64[ns]
dtype: object
读取 ORC 文件的特定列。
In [619]: result = pd.read_orc(
.....: "example_pa.orc",
.....: columns=["a", "b"],
.....: )
.....:
In [620]: result.dtypes
Out[620]:
a str
b int64
dtype: object
SQL 查询#
pandas.io.sql 模块提供了一组查询包装器,用于方便地检索数据并减少对特定数据库 API 的依赖。
在可用时,用户可以优先选择 Apache Arrow ADBC 驱动程序。这些驱动程序应提供最佳性能、空值处理和类型检测。
2.2.0 版新增:添加了对 ADBC 驱动程序的本机支持。
有关 ADBC 驱动程序的完整列表及其开发状态,请参阅 ADBC 驱动程序实现状态文档。
在 ADBC 驱动程序不可用或缺少功能的情况下,用户应选择安装 SQLAlchemy 以及数据库驱动程序库。例如,psycopg2 用于 PostgreSQL,pymysql 用于 MySQL。对于 SQLite,默认情况下已包含在 Python 的标准库中。您可以在 SQLAlchemy 文档中找到每个 SQL 方言支持的驱动程序概述。
如果未安装 SQLAlchemy,可以使用 sqlite3.Connection 代替 SQLAlchemy 引擎、连接或 URI 字符串。
另请参阅一些 食谱示例,了解一些高级策略。
主要功能是
|
将 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 [621]: from sqlalchemy import create_engine
# Create your engine.
In [622]: 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 [623]: import datetime
In [624]: c = ["id", "Date", "Col_1", "Col_2", "Col_3"]
In [625]: 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 [626]: data = pd.DataFrame(d, columns=c)
In [627]: data
Out[627]:
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 [628]: data.to_sql("data", con=engine)
Out[628]: 3
对于某些数据库,写入大型 DataFrame 可能会因为超出数据包大小限制而导致错误。可以通过在调用 to_sql 时设置 chunksize 参数来避免此问题。例如,下面的代码将 data 分批写入数据库,每次 1000 行。
In [629]: data.to_sql("data_chunked", con=engine, chunksize=1000)
Out[629]: 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 |
对象 |
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 |
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 数据类型。当您 having object 类型列时,pandas 将尝试推断数据类型。
您始终可以通过使用 dtype 参数来覆盖默认类型,方法是指定任何列所需的 SQL 类型。此参数需要一个字典,将列名映射到 SQLAlchemy 类型(或 sqlite3 回退模式的字符串)。例如,指定将 SQLAlchemy 的 String 类型用于字符串列,而不是默认的 Text 类型。
In [630]: from sqlalchemy.types import String
In [631]: data.to_sql("data_dtype", con=engine, dtype={"Col_1": String})
Out[631]: 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 [632]: pd.read_sql_table("data", engine)
Out[632]:
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) ... 将返回对象值(字符串)系列。因此,如果查询输出为空,则所有结果列都将作为对象值返回(因为它们是最通用的)。如果您预计查询有时会产生空结果,则可能希望在之后显式类型转换以确保 dtype 的完整性。
您还可以将列名指定为 DataFrame 的索引,并指定要读取的列子集。
In [633]: pd.read_sql_table("data", engine, index_col="id")
Out[633]:
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 [634]: pd.read_sql_table("data", engine, columns=["Col_1", "Col_2"])
Out[634]:
Col_1 Col_2
0 X 27.50
1 Y -12.50
2 Z 5.73
并且您可以显式强制将列解析为日期。
In [635]: pd.read_sql_table("data", engine, parse_dates=["Date"])
Out[635]:
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 [636]: pd.read_sql_query("SELECT * FROM data", engine)
Out[636]:
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 [637]: pd.read_sql_query("SELECT id, Col_1, Col_2 FROM data WHERE id = 42;", engine)
Out[637]:
id Col_1 Col_2
0 42 Y -12.5
read_sql_query() 函数支持 chunksize 参数。指定此参数将返回查询结果块的迭代器。
In [638]: df = pd.DataFrame(np.random.randn(20, 3), columns=list("abc"))
In [639]: df.to_sql(name="data_chunks", con=engine, index=False)
Out[639]: 20
In [640]: 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 [641]: import sqlalchemy as sa
In [642]: pd.read_sql(
.....: sa.text("SELECT * FROM data where Col_1=:col1"), engine, params={"col1": "X"}
.....: )
.....:
Out[642]:
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 [643]: metadata = sa.MetaData()
In [644]: 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 [645]: pd.read_sql(sa.select(data_table).where(data_table.c.Col_3 is True), engine)
Out[645]:
Empty DataFrame
Columns: [index, Date, Col_1, Col_2, Col_3]
Index: []
您可以使用 SQLAlchemy 表达式与传递给 read_sql() 的参数结合,使用 sqlalchemy.bindparam()。
In [646]: import datetime as dt
In [647]: expr = sa.select(data_table).where(data_table.c.Date > sa.bindparam("date"))
In [648]: pd.read_sql(expr, engine, params={"date": dt.datetime(2010, 10, 18)})
Out[648]:
index Date Col_1 Col_2 Col_3
0 1 2010-10-19 Y -12.50 False
1 2 2010-10-20 Z 5.73 True
SQLite 回退#
SQLite 的使用无需 SQLAlchemy 即可支持。此模式需要一个尊重 Python DB-API 的 Python 数据库适配器。
您可以这样创建连接:
import sqlite3
con = sqlite3.connect(":memory:")
然后发出以下查询:
data.to_sql("data", con)
pd.read_sql_query("SELECT * FROM data", con)
Google BigQuery#
pandas-gbq 包提供了从 Google BigQuery 读取/写入的功能。
完整文档可在此处找到:此处。
STATA 格式#
写入 STATA 格式#
方法 DataFrame.to_stata() 将把 DataFrame 写入 .dta 文件。该文件的格式版本始终是 115(Stata 12)。
In [649]: df = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))
In [650]: 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,方法是将它们转换为可以表示数据的最小支持类型。例如,类型为 uint8 的数据将被转换为 int8,如果所有值都小于 100(*Stata* 中非缺失 int8 数据的上限),或者如果值超出此范围,则变量将转换为 int16。
警告
从 int64 转换为 float64 时,如果 int64 值大于 2**53,可能会导致精度损失。
警告
StataWriter 和 DataFrame.to_stata() 只支持固定宽度字符串,最多包含 244 个字符,这是版本 115 dta 文件格式施加的限制。尝试写入超过 244 个字符的 *Stata* dta 文件会引发 ValueError。
从 Stata 格式读取#
顶级函数 read_stata 将读取 dta 文件,并返回一个 DataFrame 或一个 pandas.api.typing.StataReader,该 StataReader 可用于增量读取文件。
In [651]: pd.read_stata("stata.dta")
Out[651]:
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 [652]: 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 [653]: 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 设置为 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*数据值和导入的Categorical变量的类别代码之间存在简单映射,因此原始值可以与导入的类别数据匹配:缺失值被分配代码-1,最小的原始值被分配0,第二小的被分配1,依此类推,直到最大的原始值被分配代码n-1。
注意
Stata 支持部分标签系列。这些系列对某些数据值有值标签,但并非全部。导入部分标签系列将生成一个 Categorical,其中包含已标记值的字符串类别和无标签值的数值类别。
SAS 格式#
顶级函数 read_sas() 可以读取(但不能写入)SAS XPORT (.xpt) 和 SAS7BDAT (.sas7bdat) 格式文件。
SAS 文件只包含两种值类型:ASCII 文本和浮点值(通常为 8 字节,但有时会截断)。对于 xport 文件,没有自动类型转换到整数、日期或分类变量。对于 SAS7BDAT 文件,格式代码可能允许日期变量自动转换为日期。默认情况下,整个文件被读取并作为 DataFrame 返回。
指定 chunksize 或使用 iterator=True 来获取读取器对象(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 的轻松转换。
性能注意事项#
这是对各种 IO 方法的非正式比较,使用了 pandas 0.24.2。计时结果取决于机器,小的差异应忽略。
In [1]: sz = 1000000
In [2]: df = pd.DataFrame({'A': np.random.randn(sz), 'B': [1] * sz})
In [3]: df.info()
<class 'pandas.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关键字来抑制注释。