原文:
pandas.pydata.org/docs/
IO 工具(文本,CSV,HDF5,…)
原文:
pandas.pydata.org/docs/user_guide/io.html
pandas I/O API 是一组顶级reader
函数,如pandas.read_csv()
通常返回一个 pandas 对象。相应的writer
函数是对象方法,如DataFrame.to_csv()
。下面是包含可用reader
和writer
的表格。
格式类型 | 数据描述 | 读取器 | 写入器 |
---|---|---|---|
文本 | CSV | read_csv | to_csv |
文本 | 定宽文本文件 | read_fwf | |
文本 | JSON | read_json | to_json |
文本 | HTML | read_html | to_html |
文本 | LaTeX | Styler.to_latex | |
文本 | XML | read_xml | to_xml |
文本 | 本地剪贴板 | read_clipboard | to_clipboard |
二进制 | MS Excel | read_excel | to_excel |
二进制 | OpenDocument | read_excel | |
二进制 | HDF5 格式 | read_hdf | to_hdf |
二进制 | Feather 格式 | read_feather | to_feather |
二进制 | Parquet 格式 | read_parquet | to_parquet |
二进制 | ORC 格式 | read_orc | to_orc |
二进制 | Stata | read_stata | to_stata |
二进制 | SAS | read_sas | |
二进制 | SPSS | read_spss | |
二进制 | Python Pickle 格式 | read_pickle | to_pickle |
SQL | SQL | read_sql | to_sql |
SQL | Google BigQuery | read_gbq | to_gbq |
这里是一些 IO 方法的非正式性能比较。
注意
对于使用StringIO
类的示例,请确保在 Python 3 中导入它时使用from io import StringIO
。
CSV & 文本文件
用于读取文本文件(也称为平面文件)的主要函数是 read_csv()
。查看食谱以获取一些高级策略。
解析选项
read_csv()
接受以下常见参数:
基本
filepath_or_buffervarious
要么是文件的路径(str
,pathlib.Path
,或 py:py._path.local.LocalPath
),URL(包括 http、ftp 和 S3 地址),或具有 read()
方法的任何对象(例如打开的文件或 StringIO
)。
sepstr,默认为 read_csv()
的 ','
,read_table()
的 t
要使用的分隔符。如果 sep 为 None
,则 C 引擎无法自动检测分隔符,但 Python 解析引擎可以,这意味着将使用后者,并通过 Python 的内置嗅探工具 csv.Sniffer
自动检测分隔符。此外,长度大于 1 且不同于 's '
的分隔符将被解释为正则表达式,并且还将强制使用 Python 解析引擎。请注意,正则表达式分隔符容易忽略带引号的数据。正则表达式示例:'\r\t'
。
delimiterstr,默认为 None
sep 的替代参数名称。
delim_whitespaceboolean,默认为 False
指定是否使用空格(例如 ' '
或 't'
)作为分隔符。等同于设置 sep='s '
。如果此选项设置为 True
,则不应为 delimiter
参数传递任何内容。
列和索引位置及名称
headerint 或整数列表,默认为 'infer'
用作列名和数据起始位置的行号。默认行为是推断列名:如果没有传递名称,则行为与 header=0
相同,并且列名从文件的第一行推断出来,如果显式传递列名,则行为与 header=None
相同。显式传递 header=0
以能够替换现有名称。
头部可以是指定列的 MultiIndex 的行位置的整数列表,例如 [0,1,3]
。未指定的中间行将被跳过(例如在此示例中跳过了 2)。请注意,如果 skip_blank_lines=True
,此参数将忽略注释行和空行,因此 header=0
表示数据的第一行而不是文件的第一行。
namesarray-like,默认为 None
要使用的列名列表。如果文件不包含标题行,则应明确传递header=None
。此列表中不允许重复项。
index_colint,str,int/str 序列或 False,可选,默认为None
用作DataFrame
行标签的列,可以作为字符串名称或列索引给出。如果给出 int/str 序列,则使用 MultiIndex。
注意
可以使用index_col=False
来强制 pandas不使用第一列作为索引,例如当您有一个每行末尾都有分隔符的格式错误文件时。
None
的默认值指示 pandas 进行猜测。如果列标题行中的字段数等于数据文件主体中的字段数,则使用默认索引。如果大于此数,则使用前几列作为索引,以使数据主体中的剩余字段数等于标题中的字段数。
在标题之后的第一行用于确定要放入索引的列数。如果后续行的列数少于第一行,则用NaN
填充。
可以通过usecols
来避免这种情况。这确保了列按原样获取,而尾随数据被忽略。
usecols 类似列表或可调用对象,默认为None
返回列的子集。如果类似列表,则所有元素必须是位置的(即整数索引到文档列)或与用户在names
中提供的列名对应的字符串。如果给出了names
,则不考虑文档标题行。例如,一个有效的类似列表usecols
参数可以是[0, 1, 2]
或['foo', 'bar', 'baz']
。
元素顺序被忽略,因此usecols=[0, 1]
与[1, 0]
相同。要从具有保留元素顺序的data
实例化数据帧,请使用pd.read_csv(data, usecols=['foo', 'bar'])[['foo', 'bar']]
以['foo', 'bar']
顺序或pd.read_csv(data, usecols=['foo', 'bar'])[['bar', 'foo']]
以['bar', 'foo']
顺序。
如果可调用,则将对列名评估可调用函数,返回可调用函数评估为 True 的名称:
代码语言:javascript复制In [1]: import pandas as pd
In [2]: from io import StringIO
In [3]: data = "col1,col2,col3na,b,1na,b,2nc,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
设置一起使用以保留并不解释数据类型。如果指定了转换器,则将应用转换器,而不是数据类型转换。
1.5.0 版本中的新功能:添加了对 defaultdict 的支持。指定一个 defaultdict 作为输入,其中默认值确定未明确列出的列的数据类型。
dtype_backend{“numpy_nullable”,“pyarrow”},默认为 NumPy 支持的数据帧
要使用的 dtype_backend,例如 DataFrame 是否应具有 NumPy 数组,当设置“numpy_nullable”时,所有具有可为空实现的 dtype 都使用可为空 dtype,如果设置“pyarrow”,则所有 dtype 都使用 pyarrow。
dtype_backends 仍处于实验阶段。
2.0 版本中新增。
engine{'c'
, 'python'
, 'pyarrow'
}
使用的解析引擎。C 和 pyarrow 引擎速度更快,而 python 引擎目前功能更完整。目前只有 pyarrow 引擎支持多线程。
1.4.0 版本中新增:添加了“pyarrow”引擎作为实验性引擎,并且某些功能不受支持,或者可能无法正常工作。
转换器字典,默认为None
用于转换某些列中值的函数字典。键可以是整数或列标签。
true_values 列表,默认为None
要视为True
的值。
false_values 列表,默认为None
要视为False
的值。
skipinitialspace 布尔值,默认为False
在分隔符后跳过空格。
skiprows 类似列表或整数,默认为None
要跳过的行号(从 0 开始计数)或要在文件开头跳过的行数(整数)。
如果可调用,则将针对行索引评估可调用函数,如果应跳过该行则返回 True,否则返回 False:
代码语言:javascript复制In [6]: data = "col1,col2,col3na,b,1na,b,2nc,d,3"
In [7]: pd.read_csv(StringIO(data))
Out[7]:
col1 col2 col3
0 a b 1
1 a b 2
2 c d 3
In [8]: pd.read_csv(StringIO(data), skiprows=lambda x: x % 2 != 0)
Out[8]:
col1 col2 col3
0 a b 2
skipfooterint,默认为0
要跳过文件底部的行数(与 engine=’c’ 不兼容)。
nrows 整数,默认为None
要读取的文件行数。用于读取大文件的片段。
low_memory 布尔值,默认为True
在块中内部处理文件,导致解析时使用更少的内存,但可能混合类型推断。为确保没有混合类型,要么设置为False
,要么使用dtype
参数指定类型。请注意,无论如何整个文件都会读入单个DataFrame
,使用chunksize
或iterator
参数以返回分块数据。 (仅适用于 C 解析器)
memory_map 布尔值,默认为 False
如果为filepath_or_buffer
提供了文件路径,则直接将文件对象映射到内存,并直接从那里访问数据。使用此选项可以提高性能,因为不再有任何 I/O 开销。
NA 和缺失数据处理
na_values 标量、字符串、类似列表或字典,默认为None
附加字符串识别为 NA/NaN。如果传递了字典,则为每列指定特定的 NA 值。请参见下面的 na values const 以获取默认情况下解释为 NaN 的值列表。
keep_default_na 布尔值,默认为True
是否在解析数据时包括默认的 NaN 值。根据是否传递了na_values
,行为如下:
- 如果
keep_default_na
为True
,并且指定了na_values
,则na_values
将附加到用于解析的默认 NaN 值。 - 如果
keep_default_na
为True
,并且未指定na_values
,则仅使用默认 NaN 值进行解析。 - 如果
keep_default_na
为False
,且指定了na_values
,则只使用指定的 NaN 值na_values
进行解析。 - 如果
keep_default_na
为False
,且未指定na_values
,则不会将任何字符串解析为 NaN。
请注意,如果传递na_filter
为False
,则keep_default_na
和na_values
参数将被忽略。
na_filter 布尔值,默认为True
检测缺失值标记(空字符串和 na_values 的值)。在没有任何 NA 的数据中,传递na_filter=False
可以提高读取大文件的性能。
verbose 布尔值,默认为False
指示放置在非数字列中的 NA 值的数量。
skip_blank_lines 布尔值,默认为True
如果为True
,则跳过空行而不解释为 NaN 值。
日期时间处理
parse_dates 布尔值或整数列表或名称列表或列表列表或字典,默认为False
。
- 如果为
True
-> 尝试解析索引。 - 如果
[1, 2, 3]
-> 尝试将列 1、2、3 分别解析为单独的日期列。 - 如果
[[1, 3]]
-> 合并列 1 和 3 并解析为单个日期列。 - 如果
{'foo': [1, 3]}
-> 解析列 1、3 为日期,并将结果命名为‘foo’。
注意
存在用于 iso8601 格式日期的快速路径。
infer_datetime_format 布尔值,默认为False
如果为True
并且启用了 parse_dates 用于某一列,则尝试推断日期时间格式以加快处理速度。
自 2.0.0 版本起弃用:此参数的严格版本现在是默认值,传递它不会产生任何效果。
keep_date_col 布尔值,默认为False
如果为True
并且 parse_dates 指定了组合多个列,则保留原始列。
date_parser 函数,默认为None
用于将一系列字符串列转换为日期时间实例数组的函数。默认使用dateutil.parser.parser
进行转换。pandas 将尝试以三种不同的方式调用 date_parser,如果发生异常,则继续下一个:1) 将一个或多个数组(由 parse_dates 定义)作为参数传递;2) 将由 parse_dates 定义的列中的字符串值(按行)连接成单个数组并传递;3) 对每一行使用一个或多个字符串(对应于由 parse_dates 定义的列)调用 date_parser。
自 2.0.0 版本起弃用:改用date_format
,或者读取为object
,然后根据需要应用to_datetime()
。
date_format 字符串或列->格式字典,默认为None
如果与parse_dates
一起使用,将根据此格式解析日期。对于更复杂的情况,请按照object
读取,然后根据需要应用to_datetime()
。
2.0.0 版本中的新功能。
dayfirst 布尔值,默认为False
DD/MM 格式日期,国际和欧洲格式。
cache_dates 布尔值,默认为 True
如果为 True,则使用唯一的转换日期缓存来应用日期时间转换。在解析重复日期字符串时可能会产生显著的加速,特别是带有时区偏移的日期字符串。
迭代
迭代器布尔值,默认为False
返回用于迭代或使用get_chunk()
获取块的TextFileReader
对象。
块大小整数,默认为None
返回用于迭代的TextFileReader
对象。参见下面的迭代和分块。
引用、压缩和文件格式
压缩{'infer'
, 'gzip'
, 'bz2'
, 'zip'
, 'xz'
, 'zstd'
, None
, dict
},默认为'infer'
用于在磁盘数据的即时解压缩。如果为‘infer’,则如果filepath_or_buffer
是以‘.gz’、‘.bz2’、‘.zip’、‘.xz’、‘.zst’结尾的路径,则使用 gzip、bz2、zip、xz 或 zstandard,否则不进行解压缩。如果使用'zip'
,ZIP 文件必须只包含一个要读取的数据文件。设置为None
表示不进行解压缩。也可以是一个字典,其中键'method'
设置为其中之一{'zip'
, 'gzip'
, 'bz2'
, 'zstd
},其他键值对转发到zipfile.ZipFile
、gzip.GzipFile
、bz2.BZ2File
或zstandard.ZstdDecompressor
。例如,可以传递以下内容以获得更快的压缩和创建可重现的 gzip 存档:compression={'method': 'gzip', 'compresslevel': 1, 'mtime': 1}
。
从版本 1.2.0 更改:以前的版本将‘gzip’的字典条目转发到gzip.open
。
千位分隔符字符串,默认为None
千位分隔符。
十进制字符串,默认为'.'
用于识别为小数点的字符。例如,对于欧洲数据使用','
。
浮点精度字符串,默认为 None
指定 C 引擎应使用哪个转换器处理浮点值。选项为None
表示普通转换器,high
表示高精度转换器,round_trip
表示往返转换器。
行终止符字符串(长度为 1),默认为None
用于将文件分成行的字符。仅与 C 解析器有效。
引用字符字符串(长度为 1)
用于表示引用项的起始和结束的字符。引用项可以包括分隔符,它将被忽略。
引用 int 或csv.QUOTE_*
实例,默认为0
控制字段引用行为的csv.QUOTE_*
常量。使用QUOTE_MINIMAL
(0)、QUOTE_ALL
(1)、QUOTE_NONNUMERIC
(2)或QUOTE_NONE
(3)中的一个。
双引号布尔值,默认为True
当指定quotechar
并且quoting
不是QUOTE_NONE
时,指示是否将字段内两个连续的quotechar
元素解释为单个quotechar
元素。
转义字符字符串(长度为 1),默认为None
在引用方式为QUOTE_NONE
时用于转义分隔符的单字符字符串。
注释字符串,默认为None
指示不应解析行的其余部分。如果在行的开头找到,整行将被完全忽略。此参数必须是单个字符。与空行一样(只要skip_blank_lines=True
),完全注释的行由参数header
忽略,但不由skiprows
忽略。例如,如果comment='#'
,使用header=0
解析‘#emptyna,b,cn1,2,3’将导致���a,b,c’被视为标题。
encodingstr,默认为None
读取/写入 UTF 时要使用的编码(例如,'utf-8'
)。Python 标准编码列表。
dialectstr 或csv.Dialect
实例,默认为None
如果提供,此参数将覆盖以下参数的值(默认或非默认):delimiter
、doublequote
、escapechar
、skipinitialspace
、quotechar
和quoting
。如果需要覆盖值,将发出 ParserWarning。有关更多详细信息,请参阅csv.Dialect
文档。
错误处理
on_bad_lines(‘error’、‘warn’、‘skip’),默认为‘error’
指定在遇到坏行(字段过多的行)时要执行的操作。允许的值为:
- ‘error’,遇到坏行时引发 ParserError。
- ‘warn’,遇到坏行时打印警告并跳过该行。
- ‘skip’,遇到坏行时跳过而不引发或警告。
1.3.0 版中的新功能。
指定列数据类型
您可以指示整个DataFrame
或单独的列的数据类型:
In [9]: import numpy as np
In [10]: data = "a,b,c,dn1,2,3,4n5,6,7,8n9,10,11"
In [11]: print(data)
a,b,c,d
1,2,3,4
5,6,7,8
9,10,11
In [12]: df = pd.read_csv(StringIO(data), dtype=object)
In [13]: df
Out[13]:
a b c d
0 1 2 3 4
1 5 6 7 8
2 9 10 11 NaN
In [14]: df["a"][0]
Out[14]: '1'
In [15]: df = pd.read_csv(StringIO(data), dtype={"b": object, "c": np.float64, "d": "Int64"})
In [16]: df.dtypes
Out[16]:
a int64
b object
c float64
d Int64
dtype: object
幸运的是,pandas 提供了多种方法来确保您的列只包含一个dtype
。如果您对这些概念不熟悉,可以查看这里了解有关 dtypes 的更多信息,以及这里了解有关 pandas 中object
转换的更多信息。
例如,您可以使用read_csv()
的converters
参数:
In [17]: data = "col_1n1n2n'A'n4.22"
In [18]: df = pd.read_csv(StringIO(data), converters={"col_1": str})
In [19]: df
Out[19]:
col_1
0 1
1 2
2 'A'
3 4.22
In [20]: df["col_1"].apply(type).value_counts()
Out[20]:
col_1
<class 'str'> 4
Name: count, dtype: int64
或者您可以在读取数据后使用to_numeric()
函数强制转换 dtypes,
In [21]: df2 = pd.read_csv(StringIO(data))
In [22]: df2["col_1"] = pd.to_numeric(df2["col_1"], errors="coerce")
In [23]: df2
Out[23]:
col_1
0 1.00
1 2.00
2 NaN
3 4.22
In [24]: df2["col_1"].apply(type).value_counts()
Out[24]:
col_1
<class 'float'> 4
Name: count, dtype: int64
这将将所有有效解析转换为浮点数,将无效解析保留为NaN
。
最终,如何处理包含混合 dtypes 的列取决于您的具体需求。在上面的情况下,如果您想要将数据异常值设置为NaN
,那么to_numeric()
可能是您最好的选择。然而,如果您希望所有数据被强制转换,无论类型如何,那么使用read_csv()
的converters
参数肯定值得一试。
注意
在某些情况下,读取包含混合 dtype 列的异常数据将导致数据集不一致。如果依赖 pandas 推断列的 dtype,解析引擎将会推断数据的不同块的 dtype,而不是一次推断整个数据集。因此,可能会出现具有混合 dtype 的列。例如,
代码语言:javascript复制In [25]: col_1 = list(range(500000)) ["a", "b"] list(range(500000))
In [26]: df = pd.DataFrame({"col_1": col_1})
In [27]: df.to_csv("foo.csv")
In [28]: mixed_df = pd.read_csv("foo.csv")
In [29]: mixed_df["col_1"].apply(type).value_counts()
Out[29]:
col_1
<class 'int'> 737858
<class 'str'> 262144
Name: count, dtype: int64
In [30]: mixed_df["col_1"].dtype
Out[30]: dtype('O')
将导致mixed_df
包含某些列块的int
dtype,以及由于读取的数据中混合 dtype 而导致其他列块的str
。重要的是要注意,整体列将被标记为object
的dtype
,用于具有混合 dtype 的列。
设置dtype_backend="numpy_nullable"
将导致每列具有可空 dtype。
In [31]: data = """a,b,c,d,e,f,g,h,i,j
....: 1,2.5,True,a,,,,,12-31-2019,
....: 3,4.5,False,b,6,7.5,True,a,12-31-2019,
....: """
....:
In [32]: df = pd.read_csv(StringIO(data), dtype_backend="numpy_nullable", parse_dates=["i"])
In [33]: df
Out[33]:
a b c d e f g h i j
0 1 2.5 True a <NA> <NA> <NA> <NA> 2019-12-31 <NA>
1 3 4.5 False b 6 7.5 True a 2019-12-31 <NA>
In [34]: df.dtypes
Out[34]:
a Int64
b Float64
c boolean
d string[python]
e Int64
f Float64
g boolean
h string[python]
i datetime64[ns]
j Int64
dtype: object
```### 指定分类 dtype
`Categorical`列可以直接通过指定`dtype='category'`或`dtype=CategoricalDtype(categories, ordered)`来解析。
```py
In [35]: data = "col1,col2,col3na,b,1na,b,2nc,d,3"
In [36]: pd.read_csv(StringIO(data))
Out[36]:
col1 col2 col3
0 a b 1
1 a b 2
2 c d 3
In [37]: pd.read_csv(StringIO(data)).dtypes
Out[37]:
col1 object
col2 object
col3 int64
dtype: object
In [38]: pd.read_csv(StringIO(data), dtype="category").dtypes
Out[38]:
col1 category
col2 category
col3 category
dtype: object
可以使用字典规范将单独的列解析为Categorical
:
In [39]: pd.read_csv(StringIO(data), dtype={"col1": "category"}).dtypes
Out[39]:
col1 category
col2 object
col3 int64
dtype: object
指定dtype='category'
将导致一个无序的Categorical
,其categories
是数据中观察到的唯一值。要对类别和顺序进行更多控制,预先创建一个CategoricalDtype
,并将其传递给该列的dtype
。
In [40]: from pandas.api.types import CategoricalDtype
In [41]: dtype = CategoricalDtype(["d", "c", "b", "a"], ordered=True)
In [42]: pd.read_csv(StringIO(data), dtype={"col1": dtype}).dtypes
Out[42]:
col1 category
col2 object
col3 int64
dtype: object
使用dtype=CategoricalDtype
时,dtype.categories
之外的“意外”值被视为缺失值。
In [43]: dtype = CategoricalDtype(["a", "b", "d"]) # No 'c'
In [44]: pd.read_csv(StringIO(data), dtype={"col1": dtype}).col1
Out[44]:
0 a
1 a
2 NaN
Name: col1, dtype: category
Categories (3, object): ['a', 'b', 'd']
这与Categorical.set_categories()
的行为相匹配。
注意
使用dtype='category'
,生成的类别将始终被解析为字符串(对象 dtype)。如果类别是数字的,可以使用to_numeric()
函数进行转换,或者根据需要使用另一个转换器,如to_datetime()
。
当dtype
是具有同质categories
(全部是数字,全部是日期时间等)的CategoricalDtype
时,转换会自动完成。
In [45]: df = pd.read_csv(StringIO(data), dtype="category")
In [46]: df.dtypes
Out[46]:
col1 category
col2 category
col3 category
dtype: object
In [47]: df["col3"]
Out[47]:
0 1
1 2
2 3
Name: col3, dtype: category
Categories (3, object): ['1', '2', '3']
In [48]: new_categories = pd.to_numeric(df["col3"].cat.categories)
In [49]: df["col3"] = df["col3"].cat.rename_categories(new_categories)
In [50]: df["col3"]
Out[50]:
0 1
1 2
2 3
Name: col3, dtype: category
Categories (3, int64): [1, 2, 3]
命名和使用列
处理列名
文件可能有或没有标题行。pandas 假定第一行应该用作列名:
代码语言:javascript复制In [51]: data = "a,b,cn1,2,3n4,5,6n7,8,9"
In [52]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9
In [53]: pd.read_csv(StringIO(data))
Out[53]:
a b c
0 1 2 3
1 4 5 6
2 7 8 9
通过在header
中与names
参数结合使用,可以指示要使用的其他名称以及是否丢弃标题行(如果有):
In [54]: print(data)
a,b,c
1,2,3
4,5,6
7,8,9
In [55]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=0)
Out[55]:
foo bar baz
0 1 2 3
1 4 5 6
2 7 8 9
In [56]: pd.read_csv(StringIO(data), names=["foo", "bar", "baz"], header=None)
Out[56]:
foo bar baz
0 a b c
1 1 2 3
2 4 5 6
3 7 8 9
如果标题在第一行之外的行中,将行号传递给header
。这将跳过前面的行:
In [57]: data = "skip this skip itna,b,cn1,2,3n4,5,6n7,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 默认会区分它们,以防止数据被覆盖:
代码语言:javascript复制In [59]: data = "a,b,an0,1,2n3,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,dn1,2,3,foon4,5,6,barn7,8,9,baz"
In [62]: pd.read_csv(StringIO(data))
Out[62]:
a b c d
0 1 2 3 foo
1 4 5 6 bar
2 7 8 9 baz
In [63]: pd.read_csv(StringIO(data), usecols=["b", "d"])
Out[63]:
b d
0 2 foo
1 5 bar
2 8 baz
In [64]: pd.read_csv(StringIO(data), usecols=[0, 2, 3])
Out[64]:
a c d
0 1 3 foo
1 4 6 bar
2 7 9 baz
In [65]: pd.read_csv(StringIO(data), usecols=lambda x: x.upper() in ["A", "C"])
Out[65]:
a c
0 1 3
1 4 6
2 7 9
usecols
参数也可以用于指定最终结果中不使用的列:
In [66]: pd.read_csv(StringIO(data), usecols=lambda x: x not in ["a", "c"])
Out[66]:
b d
0 2 foo
1 5 bar
2 8 baz
在这种情况下,可调用对象指定我们从输出中排除“a”和“c”列。
注释和空行
忽略行注释和空行
如果指定了comment
参数,则完全注释的行将被忽略。默认情况下,完全空白行也将被忽略。
In [67]: data = "na,b,cn n# commented linen1,2,3nn4,5,6"
In [68]: print(data)
a,b,c
# commented line
1,2,3
4,5,6
In [69]: pd.read_csv(StringIO(data), comment="#")
Out[69]:
a b c
0 1 2 3
1 4 5 6
如果skip_blank_lines=False
,那么read_csv
将不会忽略空行:
In [70]: data = "a,b,cnn1,2,3nnn4,5,6"
In [71]: pd.read_csv(StringIO(data), skip_blank_lines=False)
Out[71]:
a b c
0 NaN NaN NaN
1 1.0 2.0 3.0
2 NaN NaN NaN
3 NaN NaN NaN
4 4.0 5.0 6.0
警告
忽略行的存在可能会导致涉及行号的歧义;参数header
使用行号(忽略注释/空行),而skiprows
使用行号(包括注释/空行):
In [72]: data = "#commentna,b,cnA,B,Cn1,2,3"
In [73]: pd.read_csv(StringIO(data), comment="#", header=1)
Out[73]:
A B C
0 1 2 3
In [74]: data = "A,B,Cn#commentna,b,cn1,2,3"
In [75]: pd.read_csv(StringIO(data), comment="#", skiprows=2)
Out[75]:
a b c
0 1 2 3
如果同时指定了header
和skiprows
,header
将相对于skiprows
的末尾。例如:
In [76]: data = (
....: "# emptyn"
....: "# second empty linen"
....: "# third emptylinen"
....: "X,Y,Zn"
....: "1,2,3n"
....: "A,B,Cn"
....: "1,2.,4.n"
....: "5.,NaN,10.0n"
....: )
....:
In [77]: print(data)
# empty
# second empty line
# third emptyline
X,Y,Z
1,2,3
A,B,C
1,2.,4.
5.,NaN,10.0
In [78]: pd.read_csv(StringIO(data), comment="#", skiprows=4, header=1)
Out[78]:
A B C
0 1.0 2.0 4.0
1 5.0 NaN 10.0
```#### 注释
有时文件中可能包含注释或元数据:
```py
In [79]: data = (
....: "ID,level,categoryn"
....: "Patient1,123000,x # really unpleasantn"
....: "Patient2,23000,y # wouldn't take his medicinen"
....: "Patient3,1234018,z # awesome"
....: )
....:
In [80]: with open("tmp.csv", "w") as fh:
....: fh.write(data)
....:
In [81]: print(open("tmp.csv").read())
ID,level,category
Patient1,123000,x # really unpleasant
Patient2,23000,y # wouldn't take his medicine
Patient3,1234018,z # awesome
默认情况下,解析器会将注释包含在输出中:
代码语言:javascript复制In [82]: df = pd.read_csv("tmp.csv")
In [83]: df
Out[83]:
ID level category
0 Patient1 123000 x # really unpleasant
1 Patient2 23000 y # wouldn't take his medicine
2 Patient3 1234018 z # awesome
我们可以使用comment
关键字来抑制注释:
In [84]: df = pd.read_csv("tmp.csv", comment="#")
In [85]: df
Out[85]:
ID level category
0 Patient1 123000 x
1 Patient2 23000 y
2 Patient3 1234018 z
```### 处理 Unicode 数据
应该使用`encoding`参数来处理编码的 Unicode 数据,这将导致字节字符串在结果中被解码为 Unicode:
```py
In [86]: from io import BytesIO
In [87]: data = b"word,lengthn" b"Trxc3xa4umen,7n" b"Grxc3xbcxc3x9fe,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,cn4,apple,bat,5.7n8,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
代码语言:javascript复制In [94]: data = "index,a,b,cn4,apple,bat,5.7n8,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,cn4,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,cn4,apple,bat,n8,orange,cow,"
In [101]: print(data)
a,b,c
4,apple,bat,
8,orange,cow,
In [102]: pd.read_csv(StringIO(data), usecols=["b", "c"])
Out[102]:
b c
4 bat NaN
8 cow NaN
In [103]: pd.read_csv(StringIO(data), usecols=["b", "c"], index_col=0)
Out[103]:
b c
4 bat NaN
8 cow NaN
```### 日期处理
#### 指定日期列
为了更好地处理日期时间数据,`read_csv()`使用关键字参数`parse_dates`和`date_format`,允许用户指定各种列和日期/时间格式,将输入文本数据转换为`datetime`对象。
最简单的情况是只传入`parse_dates=True`:
```py
In [104]: with open("foo.csv", mode="w") as f:
.....: f.write("date,A,B,Cn20090101,a,1,2n20090102,b,3,4n20090103,c,4,5")
.....:
# Use a column as an index, and parse it as dates.
In [105]: df = pd.read_csv("foo.csv", index_col=0, parse_dates=True)
In [106]: df
Out[106]:
A B C
date
2009-01-01 a 1 2
2009-01-02 b 3 4
2009-01-03 c 4 5
# These are Python datetime objects
In [107]: df.index
Out[107]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', name='date', freq=None)
通常情况下,我们可能希望将日期和时间数据分开存储,或将各种日期字段分开存储。parse_dates
关键字可用于指定要从中解析日期和/或时间的列的组合。
您可以将列列表的列表指定为 parse_dates
,生成的日期列将被添加到输出中(以不影响现有列顺序),新列名将是组件列名的连接:
In [108]: data = (
.....: "KORD,19990127, 19:00:00, 18:56:00, 0.8100n"
.....: "KORD,19990127, 20:00:00, 19:56:00, 0.0100n"
.....: "KORD,19990127, 21:00:00, 20:56:00, -0.5900n"
.....: "KORD,19990127, 21:00:00, 21:18:00, -0.9900n"
.....: "KORD,19990127, 22:00:00, 21:56:00, -0.5900n"
.....: "KORD,19990127, 23:00:00, 22:56:00, -0.5900"
.....: )
.....:
In [109]: with open("tmp.csv", "w") as fh:
.....: fh.write(data)
.....:
In [110]: df = pd.read_csv("tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]])
In [111]: df
Out[111]:
1_2 1_3 0 4
0 1999-01-27 19:00:00 1999-01-27 18:56:00 KORD 0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00 KORD 0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00 KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00 KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00 KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00 KORD -0.59
默认情况下,解析器会删除组件日期列,但您可以通过 keep_date_col
关键字选择保留它们:
In [112]: df = pd.read_csv(
.....: "tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]], keep_date_col=True
.....: )
.....:
In [113]: df
Out[113]:
1_2 1_3 0 ... 2 3 4
0 1999-01-27 19:00:00 1999-01-27 18:56:00 KORD ... 19:00:00 18:56:00 0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00 KORD ... 20:00:00 19:56:00 0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00 KORD ... 21:00:00 20:56:00 -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00 KORD ... 21:00:00 21:18:00 -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00 KORD ... 22:00:00 21:56:00 -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00 KORD ... 23:00:00 22:56:00 -0.59
[6 rows x 7 columns]
请注意,如果您希望将多个列合并为单个日期列,则必须使用嵌套列表。换句话说,parse_dates=[1, 2]
表示应将第二和第三列分别解析为单独的日期列,而 parse_dates=[[1, 2]]
表示应将这两列解析为单个列。
您还可以使用字典指定自定义列名:
代码语言:javascript复制In [114]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}
In [115]: df = pd.read_csv("tmp.csv", header=None, parse_dates=date_spec)
In [116]: df
Out[116]:
nominal actual 0 4
0 1999-01-27 19:00:00 1999-01-27 18:56:00 KORD 0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00 KORD 0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00 KORD -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00 KORD -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00 KORD -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00 KORD -0.59
重要的是要记住,如果要将多个文本列解析为单个日期列,则会在数据前添加一个新列。index_col
规范是基于这组新列而不是原始数据列的:
In [117]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}
In [118]: df = pd.read_csv(
.....: "tmp.csv", header=None, parse_dates=date_spec, index_col=0
.....: ) # index is the nominal column
.....:
In [119]: df
Out[119]:
actual 0 4
nominal
1999-01-27 19:00:00 1999-01-27 18:56:00 KORD 0.81
1999-01-27 20:00:00 1999-01-27 19:56:00 KORD 0.01
1999-01-27 21:00:00 1999-01-27 20:56:00 KORD -0.59
1999-01-27 21:00:00 1999-01-27 21:18:00 KORD -0.99
1999-01-27 22:00:00 1999-01-27 21:56:00 KORD -0.59
1999-01-27 23:00:00 1999-01-27 22:56:00 KORD -0.59
请注意
如果列或索引包含无法解析的日期,则整个列或索引将以对象数据类型不变返回。对于非标准日期时间解析,请在 pd.read_csv
后使用 to_datetime()
。
请注意
read_csv 在解析 iso8601 格式的日期时间字符串(例如“2000-01-01T00:01:02 00:00”及类似变体)时具有快速路径。如果您可以安排数据以这种格式存储日期时间,加载时间将显著加快,观察到的速度提升约为 20 倍。
自版本 2.2.0 起已弃用:在 read_csv 中合并日期列已弃用。请改为在相关结果列上使用 pd.to_datetime
。
日期解析函数
最后,解析器允许您指定自定义的 date_format
。就性能而言,您应该按照以下顺序尝试这些日期解析方法:
- 如果您知道格式,请使用
date_format
,例如:date_format="%d/%m/%Y"
或date_format={column_name: "%d/%m/%Y"}
。 - 如果不同列有不同格式,或者想要向
to_datetime
传递任何额外选项(如utc
),则应以object
类型读取数据,然后使用to_datetime
。
解析具有混合时区的 CSV
pandas 无法原生表示具有混合时区的列或索引。如果您的 CSV 文件包含具有混合时区的列,则默认结果将是一个对象类型的列,其中包含字符串,即使使用 parse_dates
也是如此。要将混合时区值解析为日期时间列,请以 object
类型读取,然后调用 to_datetime()
并设置 utc=True
。
In [120]: content = """
.....: a
.....: 2000-01-01T00:00:00 05:00
.....: 2000-01-01T00:00:00 06:00"""
.....:
In [121]: df = pd.read_csv(StringIO(content))
In [122]: df["a"] = pd.to_datetime(df["a"], utc=True)
In [123]: df["a"]
Out[123]:
0 1999-12-31 19:00:00 00:00
1 1999-12-31 18:00:00 00:00
Name: a, dtype: datetime64[ns, UTC]
```#### 推断日期时间格式
以下是一些可以猜测的日期时间字符串示例(均表示 2011 年 12 月 30 日 00:00:00):
“20111230”
“2011/12/30”
“20111230 00:00:00”
“12/30/2011 00:00:00”
“30/Dec/2011 00:00:00”
“30/December/2011 00:00:00”
请注意,格式推断对 `dayfirst` 敏感。当 `dayfirst=True` 时,它会猜测“01/12/2011”是 12 月 1 日。当 `dayfirst=False`(默认)时,它会猜测“01/12/2011”是 1 月 12 日。
如果尝试解析日期字符串列,pandas 将尝试从第一个非 NaN 元素猜测格式,然后使用该格式解析列的其余部分。如果 pandas 无法猜测格式(例如,如果你的第一个字符串是 `'01 December US/Pacific 2000'`),那么将会发出警告,并且每一行将通过 `dateutil.parser.parse` 单独解析。解析日期的最安全方式是明确设置 `format=`。
```py
In [124]: df = pd.read_csv(
.....: "foo.csv",
.....: index_col=0,
.....: parse_dates=True,
.....: )
.....:
In [125]: df
Out[125]:
A B C
date
2009-01-01 a 1 2
2009-01-02 b 3 4
2009-01-03 c 4 5
如果在同一列中有混合的日期时间格式,你可以传递 format='mixed'
In [126]: data = StringIO("daten12 Jan 2000n2000-01-13n")
In [127]: df = pd.read_csv(data)
In [128]: df['date'] = pd.to_datetime(df['date'], format='mixed')
In [129]: df
Out[129]:
date
0 2000-01-12
1 2000-01-13
或者,如果你的日期时间格式都是 ISO8601(可能不完全相同格式):
代码语言:javascript复制In [130]: data = StringIO("daten2020-01-01n2020-01-01 03:00n")
In [131]: df = pd.read_csv(data)
In [132]: df['date'] = pd.to_datetime(df['date'], format='ISO8601')
In [133]: df
Out[133]:
date
0 2020-01-01 00:00:00
1 2020-01-01 03:00:00
国际日期格式
尽管美国日期格式倾向于 MM/DD/YYYY,许多国际格式使用 DD/MM/YYYY。为方便起见,提供了一个 dayfirst
关键字:
In [134]: data = "date,value,catn1/6/2000,5,an2/6/2000,10,bn3/6/2000,15,c"
In [135]: print(data)
date,value,cat
1/6/2000,5,a
2/6/2000,10,b
3/6/2000,15,c
In [136]: with open("tmp.csv", "w") as fh:
.....: fh.write(data)
.....:
In [137]: pd.read_csv("tmp.csv", parse_dates=[0])
Out[137]:
date value cat
0 2000-01-06 5 a
1 2000-02-06 10 b
2 2000-03-06 15 c
In [138]: pd.read_csv("tmp.csv", dayfirst=True, parse_dates=[0])
Out[138]:
date value cat
0 2000-06-01 5 a
1 2000-06-02 10 b
2 2000-06-03 15 c
将 CSV 写入二进制文件对象
版本 1.2.0 中的新功能。
df.to_csv(..., mode="wb")
允许将 CSV 写入以二进制模式打开的文件对象。在大多数情况下,不需要指定 mode
,因为 Pandas 将自动检测文件对象是以文本模式还是二进制模式打开的。
In [139]: import io
In [140]: data = pd.DataFrame([0, 1, 2])
In [141]: buffer = io.BytesIO()
In [142]: data.to_csv(buffer, encoding="utf-8", compression="gzip")
```### 指定浮点数转换方法
可以指定参数 `float_precision`,以在使用 C 引擎解析时使用特定的浮点数转换器。选项包括普通转换器、高���度转换器和往返转换器(在写入文件后保证往返值)。例如:
```py
In [143]: val = "0.3066101993807095471566981359501369297504425048828125"
In [144]: data = "a,b,cn1,2,{0}".format(val)
In [145]: abs(
.....: pd.read_csv(
.....: StringIO(data),
.....: engine="c",
.....: float_precision=None,
.....: )["c"][0] - float(val)
.....: )
.....:
Out[145]: 5.551115123125783e-17
In [146]: abs(
.....: pd.read_csv(
.....: StringIO(data),
.....: engine="c",
.....: float_precision="high",
.....: )["c"][0] - float(val)
.....: )
.....:
Out[146]: 5.551115123125783e-17
In [147]: abs(
.....: pd.read_csv(StringIO(data), engine="c", float_precision="round_trip")["c"][0]
.....: - float(val)
.....: )
.....:
Out[147]: 0.0
```### 千位分隔符
对于使用千位分隔符编写的大数字,你可以将 `thousands` 关键字设置为长度为 1 的字符串,以便正确解析整数:
默认情况下,带有千位分隔符的数字将被解析为字符串:
```py
In [148]: data = (
.....: "ID|level|categoryn"
.....: "Patient1|123,000|xn"
.....: "Patient2|23,000|yn"
.....: "Patient3|1,234,018|z"
.....: )
.....:
In [149]: with open("tmp.csv", "w") as fh:
.....: fh.write(data)
.....:
In [150]: df = pd.read_csv("tmp.csv", sep="|")
In [151]: df
Out[151]:
ID level category
0 Patient1 123,000 x
1 Patient2 23,000 y
2 Patient3 1,234,018 z
In [152]: df.level.dtype
Out[152]: dtype('O')
thousands
关键字允许整数被正确解析:
In [153]: df = pd.read_csv("tmp.csv", sep="|", thousands=",")
In [154]: df
Out[154]:
ID level category
0 Patient1 123000 x
1 Patient2 23000 y
2 Patient3 1234018 z
In [155]: df.level.dtype
Out[155]: dtype('int64')
```### 缺失值
要控制哪些值被解析为缺失值(用 `NaN` 表示),请在 `na_values` 中指定一个字符串。如果你指定一个字符串列表,那么其中的所有值都被视为缺失值。如果你指定一个数字(一个 `float`,比如 `5.0` 或一个 `integer`,比如 `5`),则相应的等效值也将被视为缺失值(在这种情况下,实际上 `[5.0, 5]` 被识别为 `NaN`)。
要完全覆盖默认识别为缺失的值,请指定 `keep_default_na=False`。
默认识别的 `NaN` 值为 `['-1.#IND', '1.#QNAN', '1.#IND', '-1.#QNAN', '#N/A N/A', '#N/A', 'N/A', 'n/a', 'NA', '<NA>', '#NA', 'NULL', 'null', 'NaN', '-NaN', 'nan', '-nan', 'None', '']`。
让我们考虑一些例子:
```py
pd.read_csv("path_to_file.csv", na_values=[5])
在上面的示例中,5
和 5.0
将被识别为 NaN
,除了默认值。一个字符串首先被解释为数值 5
,然后作为 NaN
。
pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=[""])
在上面的示例中,只有空字段将被识别为NaN
。
pd.read_csv("path_to_file.csv", keep_default_na=False, na_values=["NA", "0"])
在上面的示例中,NA
和 0
作为字符串都被识别为 NaN
。
pd.read_csv("path_to_file.csv", na_values=["Nope"])
默认值,除了字符串"Nope"
,也被识别为NaN
。### 无穷大
inf
类似的值将被解析为np.inf
(正无穷大),而 -inf
将被解析为-np.inf
(负无穷大)。这些将忽略值的大小写,意思是Inf
也将被解析为np.inf
。### 布尔值
常见的值True
、False
、TRUE
和FALSE
都被识别为布尔值。偶尔你可能希望识别其他值为布尔值。要做到这一点,使用true_values
和false_values
选项如下:
In [156]: data = "a,b,cn1,Yes,2n3,No,4"
In [157]: print(data)
a,b,c
1,Yes,2
3,No,4
In [158]: pd.read_csv(StringIO(data))
Out[158]:
a b c
0 1 Yes 2
1 3 No 4
In [159]: pd.read_csv(StringIO(data), true_values=["Yes"], false_values=["No"])
Out[159]:
a b c
0 1 True 2
1 3 False 4
```### 处理“坏”行
一些文件可能有格式错误的行,字段太少或太多。字段太少的行将在尾部字段中填充 NA 值。字段太多的行将默认引发错误:
```py
In [160]: data = "a,b,cn1,2,3n4,5,6,7n8,9,10"
In [161]: pd.read_csv(StringIO(data))
---------------------------------------------------------------------------
ParserError Traceback (most recent call last)
Cell In[161], line 1
----> 1 pd.read_csv(StringIO(data))
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1026, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
1013 kwds_defaults = _refine_defaults_read(
1014 dialect,
1015 delimiter,
(...)
1022 dtype_backend=dtype_backend,
1023 )
1024 kwds.update(kwds_defaults)
-> 1026 return _read(filepath_or_buffer, kwds)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:626, in _read(filepath_or_buffer, kwds)
623 return parser
625 with parser:
--> 626 return parser.read(nrows)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1923, in TextFileReader.read(self, nrows)
1916 nrows = validate_integer("nrows", nrows)
1917 try:
1918 # error: "ParserBase" has no attribute "read"
1919 (
1920 index,
1921 columns,
1922 col_dict,
-> 1923 ) = self._engine.read( # type: ignore[attr-defined]
1924 nrows
1925 )
1926 except Exception:
1927 self.close()
File ~/work/pandas/pandas/pandas/io/parsers/c_parser_wrapper.py:234, in CParserWrapper.read(self, nrows)
232 try:
233 if self.low_memory:
--> 234 chunks = self._reader.read_low_memory(nrows)
235 # destructive to chunks
236 data = _concatenate_chunks(chunks)
File parsers.pyx:838, in pandas._libs.parsers.TextReader.read_low_memory()
File parsers.pyx:905, in pandas._libs.parsers.TextReader._read_rows()
File parsers.pyx:874, in pandas._libs.parsers.TextReader._tokenize_rows()
File parsers.pyx:891, in pandas._libs.parsers.TextReader._check_tokenize_status()
File parsers.pyx:2061, in pandas._libs.parsers.raise_parser_error()
ParserError: Error tokenizing data. C error: Expected 3 fields in line 3, saw 4
你可以选择跳过坏行:
代码语言:javascript复制In [162]: data = "a,b,cn1,2,3n4,5,6,7n8,9,10"
In [163]: pd.read_csv(StringIO(data), on_bad_lines="skip")
Out[163]:
a b c
0 1 2 3
1 8 9 10
在版本 1.4.0 中新增。
或者通过传递一个可调用函数来处理engine="python"
时的错误行。错误行将是由sep
分割的字符串列表:
In [164]: external_list = []
In [165]: def bad_lines_func(line):
.....: external_list.append(line)
.....: return line[-3:]
.....:
In [166]: external_list
Out[166]: []
注意
可调用函数只会处理字段过多的行。其他错误导致的坏行将被默默跳过。
代码语言:javascript复制In [167]: bad_lines_func = lambda line: print(line)
In [168]: data = 'name,typenname a,a is of type anname b,"b" is of type b"'
In [169]: data
Out[169]: 'name,typenname a,a is of type anname b,"b" is of type b"'
In [170]: pd.read_csv(StringIO(data), on_bad_lines=bad_lines_func, engine="python")
Out[170]:
name type
0 name a a is of type a
在这种情况下,该行未被处理,因为这里的“坏行”是由转义字符引起的。
你还可以使用usecols
参数来消除一些行中出现但其他行中没有的多余列数据:
In [171]: pd.read_csv(StringIO(data), usecols=[0, 1, 2])
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[171], line 1
----> 1 pd.read_csv(StringIO(data), usecols=[0, 1, 2])
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1026, in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)
1013 kwds_defaults = _refine_defaults_read(
1014 dialect,
1015 delimiter,
(...)
1022 dtype_backend=dtype_backend,
1023 )
1024 kwds.update(kwds_defaults)
-> 1026 return _read(filepath_or_buffer, kwds)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:620, in _read(filepath_or_buffer, kwds)
617 _validate_names(kwds.get("names", None))
619 # Create the parser.
--> 620 parser = TextFileReader(filepath_or_buffer, **kwds)
622 if chunksize or iterator:
623 return parser
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1620, in TextFileReader.__init__(self, f, engine, **kwds)
1617 self.options["has_index_names"] = kwds["has_index_names"]
1619 self.handles: IOHandles | None = None
-> 1620 self._engine = self._make_engine(f, self.engine)
File ~/work/pandas/pandas/pandas/io/parsers/readers.py:1898, in TextFileReader._make_engine(self, f, engine)
1895 raise ValueError(msg)
1897 try:
-> 1898 return mappingengine
1899 except Exception:
1900 if self.handles is not None:
File ~/work/pandas/pandas/pandas/io/parsers/c_parser_wrapper.py:155, in CParserWrapper.__init__(self, src, **kwds)
152 # error: Cannot determine type of 'names'
153 if len(self.names) < len(usecols): # type: ignore[has-type]
154 # error: Cannot determine type of 'names'
--> 155 self._validate_usecols_names(
156 usecols,
157 self.names, # type: ignore[has-type]
158 )
160 # error: Cannot determine type of 'names'
161 self._validate_parse_dates_presence(self.names) # type: ignore[has-type]
File ~/work/pandas/pandas/pandas/io/parsers/base_parser.py:979, in ParserBase._validate_usecols_names(self, usecols, names)
977 missing = [c for c in usecols if c not in names]
978 if len(missing) > 0:
--> 979 raise ValueError(
980 f"Usecols do not match columns, columns expected but not found: "
981 f"{missing}"
982 )
984 return usecols
ValueError: Usecols do not match columns, columns expected but not found: [0, 1, 2]
如果你想保留所有数据,包括字段过多的行,你可以指定足够数量的names
。这样可以确保字段不足的行填充为NaN
。
In [172]: pd.read_csv(StringIO(data), names=['a', 'b', 'c', 'd'])
Out[172]:
a b c d
0 name type NaN NaN
1 name a a is of type a NaN NaN
2 name b b is of type b" NaN NaN
```### 方言
`dialect`关键字提供了更大的灵活性,用于指定文件格式。默认情况下,它使用 Excel 方言,但你可以指定方言名称或[`csv.Dialect`](https://docs.python.org/3/library/csv.html#csv.Dialect "(在 Python v3.12)")实例。
假设你有一些未封闭引号的数据:
```py
In [173]: data = "label1,label2,label3n" 'index1,"a,c,en' "index2,b,d,f"
In [174]: print(data)
label1,label2,label3
index1,"a,c,e
index2,b,d,f
默认情况下,read_csv
使用 Excel 方言,并将双引号视为引号字符,这会导致在找到关闭双引号之前找到换行符时失败。
我们可以通过使用dialect
来避免这种情况:
In [175]: import csv
In [176]: dia = csv.excel()
In [177]: dia.quoting = csv.QUOTE_NONE
In [178]: pd.read_csv(StringIO(data), dialect=dia)
Out[178]:
label1 label2 label3
index1 "a c e
index2 b d f
所有的方言选项都可以通过关键字参数单独指定:
代码语言:javascript复制In [179]: data = "a,b,c~1,2,3~4,5,6"
In [180]: pd.read_csv(StringIO(data), lineterminator="~")
Out[180]:
a b c
0 1 2 3
1 4 5 6
另一个常见的方言选项是skipinitialspace
,用于跳过分隔符后的任何空白:
In [181]: data = "a, b, cn1, 2, 3n4, 5, 6"
In [182]: print(data)
a, b, c
1, 2, 3
4, 5, 6
In [183]: pd.read_csv(StringIO(data), skipinitialspace=True)
Out[183]:
a b c
0 1 2 3
1 4 5 6
解析器会尽力“做正确的事情”,并且不易受损。类型推断是一件很重要的事情。如果一个列可以被强制转换为整数类型而不改变内容,解析器将这样做。任何非数字列将与其他 pandas 对象一样以对象 dtype 传递。### 引用和转义字符
嵌套字段中的引号(和其他转义字符)可以以多种方式处理。一种方法是使用反斜杠;为了正确解析这些数据,你应该传递escapechar
选项:
In [184]: data = 'a,bn"hello, \"Bob\", nice to see you",5'
In [185]: print(data)
a,b
"hello, "Bob", nice to see you",5
In [186]: pd.read_csv(StringIO(data), escapechar="\")
Out[186]:
a b
0 hello, "Bob", nice to see you 5
```### 固定宽度列的文件
当 `read_csv()` 读取分隔数据时,`read_fwf()` 函数与具有已知和固定列宽的数据文件一起工作。`read_fwf` 的函数参数与 `read_csv` 大致相同,但有两个额外参数,并且 `delimiter` 参数的用法不同:
`colspecs`:一个给出每行固定宽度字段范围的对(元组)列表,作为半开区间(即,[from, to[)。字符串值 ‘infer’ 可以用于指示解析器尝试从数据的前 100 行检测列规格。如果未指定,默认行为是推断。
`widths`:一个字段宽度列表,可以代替 ‘colspecs’ 使用,如果间隔是连续的。
`delimiter`:固定宽度文件中要考虑为填充字符的字符。如果字段的填充字符不是空格(例如,‘~’),可以使用它来指定填充字符。
考虑一个典型的固定宽度数据文件:
```py
In [187]: data1 = (
.....: "id8141 360.242940 149.910199 11950.7n"
.....: "id1594 444.953632 166.985655 11788.4n"
.....: "id1849 364.136849 183.628767 11806.2n"
.....: "id1230 413.836124 184.375703 11916.8n"
.....: "id1948 502.953953 173.237159 12468.3"
.....: )
.....:
In [188]: with open("bar.csv", "w") as f:
.....: f.write(data1)
.....:
为了将此文件解析为 DataFrame
,我们只需向 read_fwf
函数提供列规范和文件名即可:
# Column specifications are a list of half-intervals
In [189]: colspecs = [(0, 6), (8, 20), (21, 33), (34, 43)]
In [190]: df = pd.read_fwf("bar.csv", colspecs=colspecs, header=None, index_col=0)
In [191]: df
Out[191]:
1 2 3
0
id8141 360.242940 149.910199 11950.7
id1594 444.953632 166.985655 11788.4
id1849 364.136849 183.628767 11806.2
id1230 413.836124 184.375703 11916.8
id1948 502.953953 173.237159 12468.3
请注意,当指定了 header=None
参数时,解析器会自动选择列名 X.。或者,您可以只为连续的列提供列宽度:
# Widths are a list of integers
In [192]: widths = [6, 14, 13, 10]
In [193]: df = pd.read_fwf("bar.csv", widths=widths, header=None)
In [194]: df
Out[194]:
0 1 2 3
0 id8141 360.242940 149.910199 11950.7
1 id1594 444.953632 166.985655 11788.4
2 id1849 364.136849 183.628767 11806.2
3 id1230 413.836124 184.375703 11916.8
4 id1948 502.953953 173.237159 12468.3
解析器将处理围绕列周围的额外空白,因此在文件中列之间有额外分隔是可以的。
默认情况下,read_fwf
将尝试通过使用文件的前 100 行推断文件的 colspecs
。它只能在列对齐并且由提供的 delimiter
(默认分隔符是空白符)正确分隔的情况下执行此操作。
In [195]: df = pd.read_fwf("bar.csv", header=None, index_col=0)
In [196]: df
Out[196]:
1 2 3
0
id8141 360.242940 149.910199 11950.7
id1594 444.953632 166.985655 11788.4
id1849 364.136849 183.628767 11806.2
id1230 413.836124 184.375703 11916.8
id1948 502.953953 173.237159 12468.3
read_fwf
支持 dtype
参数,用于指定解析列的类型与推断类型不同。
In [197]: pd.read_fwf("bar.csv", header=None, index_col=0).dtypes
Out[197]:
1 float64
2 float64
3 float64
dtype: object
In [198]: pd.read_fwf("bar.csv", header=None, dtype={2: "object"}).dtypes
Out[198]:
0 object
1 float64
2 object
3 float64
dtype: object
索引
具有“隐式”索引列的文件
考虑标题的条目比数据列的数量少一个的文件:
代码语言:javascript复制In [199]: data = "A,B,Cn20090101,a,1,2n20090102,b,3,4n20090103,c,4,5"
In [200]: print(data)
A,B,C
20090101,a,1,2
20090102,b,3,4
20090103,c,4,5
In [201]: with open("foo.csv", "w") as f:
.....: f.write(data)
.....:
在这种特殊情况下,read_csv
假定第一列将用作 DataFrame
的索引:
In [202]: pd.read_csv("foo.csv")
Out[202]:
A B C
20090101 a 1 2
20090102 b 3 4
20090103 c 4 5
请注意,日期没有自动解析。在这种情况下,您需要像以前一样操作:
代码语言:javascript复制In [203]: df = pd.read_csv("foo.csv", parse_dates=True)
In [204]: df.index
Out[204]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', freq=None)
使用 MultiIndex
读取索引
假设您的数据由两列索引:
代码语言:javascript复制In [205]: data = 'year,indiv,zit,xitn1977,"A",1.2,.6n1977,"B",1.5,.5'
In [206]: print(data)
year,indiv,zit,xit
1977,"A",1.2,.6
1977,"B",1.5,.5
In [207]: with open("mindex_ex.csv", mode="w") as f:
.....: f.write(data)
.....:
read_csv
的 index_col
参数可以接受一个列编号的列表,将多列转换为返回对象的索引的 MultiIndex
:
In [208]: df = pd.read_csv("mindex_ex.csv", index_col=[0, 1])
In [209]: df
Out[209]:
zit xit
year indiv
1977 A 1.2 0.6
B 1.5 0.5
In [210]: df.loc[1977]
Out[210]:
zit xit
indiv
A 1.2 0.6
B 1.5 0.5
使用 MultiIndex
读取列
通过为 header
参数指定行位置列表,您可以读取列的 MultiIndex
。指定非连续行将跳过中间行。
In [211]: mi_idx = pd.MultiIndex.from_arrays([[1, 2, 3, 4], list("abcd")], names=list("ab"))
In [212]: mi_col = pd.MultiIndex.from_arrays([[1, 2], list("ab")], names=list("cd"))
In [213]: df = pd.DataFrame(np.ones((4, 2)), index=mi_idx, columns=mi_col)
In [214]: df.to_csv("mi.csv")
In [215]: print(open("mi.csv").read())
c,,1,2
d,,a,b
a,b,,
1,a,1.0,1.0
2,b,1.0,1.0
3,c,1.0,1.0
4,d,1.0,1.0
In [216]: pd.read_csv("mi.csv", header=[0, 1, 2, 3], index_col=[0, 1])
Out[216]:
c 1 2
d a b
a Unnamed: 2_level_2 Unnamed: 3_level_2
1 1.0 1.0
2 b 1.0 1.0
3 c 1.0 1.0
4 d 1.0 1.0
read_csv
也能够解释更常见的多列索引格式。
In [217]: data = ",a,a,a,b,c,cn,q,r,s,t,u,vnone,1,2,3,4,5,6ntwo,7,8,9,10,11,12"
In [218]: print(data)
,a,a,a,b,c,c
,q,r,s,t,u,v
one,1,2,3,4,5,6
two,7,8,9,10,11,12
In [219]: with open("mi2.csv", "w") as fh:
.....: fh.write(data)
.....:
In [220]: pd.read_csv("mi2.csv", header=[0, 1], index_col=0)
Out[220]:
a b c
q r s t u v
one 1 2 3 4 5 6
two 7 8 9 10 11 12
注意
如果未指定index_col
(例如您没有索引,或者使用df.to_csv(..., index=False)
写入它),则列索引上的任何names
都将丢失。### 自动“嗅探”分隔符
read_csv
能够推断出分隔的(不一定是逗号分隔的)文件,因为 pandas 使用了 csv 模块的csv.Sniffer
类。为此,您必须指定sep=None
。
In [221]: df = pd.DataFrame(np.random.randn(10, 4))
In [222]: df.to_csv("tmp2.csv", sep=":", index=False)
In [223]: pd.read_csv("tmp2.csv", sep=None, engine="python")
Out[223]:
0 1 2 3
0 0.469112 -0.282863 -1.509059 -1.135632
1 1.212112 -0.173215 0.119209 -1.044236
2 -0.861849 -2.104569 -0.494929 1.071804
3 0.721555 -0.706771 -1.039575 0.271860
4 -0.424972 0.567020 0.276232 -1.087401
5 -0.673690 0.113648 -1.478427 0.524988
6 0.404705 0.577046 -1.715002 -1.039268
7 -0.370647 -1.157892 -1.344312 0.844885
8 1.075770 -0.109050 1.643563 -1.469388
9 0.357021 -0.674600 -1.776904 -0.968914
```### 读取多个文件以创建单个 DataFrame
最好使用`concat()`来合并多个文件。请参阅 cookbook 以获取示例。### 通过文件逐块迭代
假设您希望懒惰地迭代(而不是将整个文件读入内存),比如以下内容:
```py
In [224]: df = pd.DataFrame(np.random.randn(10, 4))
In [225]: df.to_csv("tmp.csv", index=False)
In [226]: table = pd.read_csv("tmp.csv")
In [227]: table
Out[227]:
0 1 2 3
0 -1.294524 0.413738 0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2 0.895717 0.805244 -1.206412 2.565646
3 1.431256 1.340309 -1.170299 -0.226169
4 0.410835 0.813850 0.132003 -0.827317
5 -0.076467 -1.187678 1.130127 -1.436737
6 -1.413681 1.607920 1.024180 0.569605
7 0.875906 -2.211372 0.974466 -2.006747
8 -0.410001 -0.078638 0.545952 -1.219217
9 -1.226825 0.769804 -1.281247 -0.727707
通过在read_csv
中指定chunksize
,返回值将是一种TextFileReader
的可迭代对象:
In [228]: with pd.read_csv("tmp.csv", chunksize=4) as reader:
.....: print(reader)
.....: for chunk in reader:
.....: print(chunk)
.....:
<pandas.io.parsers.readers.TextFileReader object at 0x7ff2e5421db0>
0 1 2 3
0 -1.294524 0.413738 0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2 0.895717 0.805244 -1.206412 2.565646
3 1.431256 1.340309 -1.170299 -0.226169
0 1 2 3
4 0.410835 0.813850 0.132003 -0.827317
5 -0.076467 -1.187678 1.130127 -1.436737
6 -1.413681 1.607920 1.024180 0.569605
7 0.875906 -2.211372 0.974466 -2.006747
0 1 2 3
8 -0.410001 -0.078638 0.545952 -1.219217
9 -1.226825 0.769804 -1.281247 -0.727707
从版本 1.2 更改:read_csv/json/sas
通过文件进行迭代时返回上下文管理器。
指定iterator=True
还将返回TextFileReader
对象:
In [229]: with pd.read_csv("tmp.csv", iterator=True) as reader:
.....: print(reader.get_chunk(5))
.....:
0 1 2 3
0 -1.294524 0.413738 0.276662 -0.472035
1 -0.013960 -0.362543 -0.006154 -0.923061
2 0.895717 0.805244 -1.206412 2.565646
3 1.431256 1.340309 -1.170299 -0.226169
4 0.410835 0.813850 0.132003 -0.827317
指定解析器引擎
Pandas 目前支持三种引擎,即 C 引擎、Python 引擎和实验性的 pyarrow 引擎(需要pyarrow
包)。一般来说,对于较大的工作负载,pyarrow 引擎速度最快,在大多数其他工作负载上与 C 引擎速度相当。Python 引擎在大多数工作负载上往往比 pyarrow 和 C 引擎慢。但是,pyarrow 引擎比 C 引擎要脆弱得多,与 Python 引擎相比,缺少一些功能。
在可能的情况下,pandas 使用 C 解析器(指定为engine='c'
),但如果指定了 C 不支持的选项,可能会退回到 Python。
目前,C 和 pyarrow 引擎不支持的选项包括:
-
sep
除了单个字符(例如正则表达式分隔符)之外的其他字符 -
skipfooter
-
sep=None
与delim_whitespace=False
除非显式使用engine='python'
选择 Python 引擎,否则指定上述任何选项都会产生ParserWarning
。
pyarrow 引擎不支持的选项,未在上述列表中列出的包括:
-
float_precision
-
chunksize
-
comment
-
nrows
-
thousands
-
memory_map
-
dialect
-
on_bad_lines
-
delim_whitespace
-
quoting
-
lineterminator
-
converters
-
decimal
-
iterator
-
dayfirst
-
infer_datetime_format
-
verbose
-
skipinitialspace
-
low_memory
使用engine='pyarrow'
指定这些选项将引发ValueError
。
读取/写入远程文件
您可以传递 URL 以读取或写入许多 pandas 的 IO 函数的远程文件 - 以下示例显示了如何读取 CSV 文件:
代码语言:javascript复制df = pd.read_csv("https://download.bls.gov/pub/time.series/cu/cu.item", sep="t")
版本 1.3.0 中的新功能。
可以通过将键值映射的字典传递给storage_options
关键字参数来发送自定义标头以及 HTTP(s)请求:
headers = {"User-Agent": "pandas"}
df = pd.read_csv(
"https://download.bls.gov/pub/time.series/cu/cu.item",
sep="t",
storage_options=headers
)
所有不是本地文件或 HTTP(s) 的 URL 都由fsspec处理(如果安装了),以及它的各种文件系统实现(包括 Amazon S3、Google Cloud、SSH、FTP、webHDFS 等)。其中一些实现将需要安装其他包,例如 S3 URL 需要s3fs库:
代码语言:javascript复制df = pd.read_json("s3://pandas-test/adatafile.json")
当涉及远程存储系统时,你可能需要通过环境变量或特殊位置的配置文件进行额外配置。例如,要访问 S3 存储桶中的数据,你需要在S3Fs 文档中列出的几种方式之一中定义凭据。对于几个存储后端也是如此,你应该按照fsimpl1中内置到fsspec
中的实现和fsimpl2中未包含在主fsspec
分发中的实现的链接进行操作。
你也可以直接将参数传递给后端驱动程序。由于 fsspec
不使用 AWS_S3_HOST
环境变量,因此我们可以直接定义一个包含 endpoint_url 的字典,并将对象传递给 storage option 参数:
storage_options = {"client_kwargs": {"endpoint_url": "http://127.0.0.1:5555"}}}
df = pd.read_json("s3://pandas-test/test-1", storage_options=storage_options)
更多示例配置和文档可以在S3Fs 文档中找到。
如果你没有 S3 凭据,仍然可以通过指定匿名连接来访问公共数据,例如
版本 1.2.0 中新增。
代码语言:javascript复制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,设置为 False 以在具有分层索引的DataFrame
中打印每个行的每个 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}, … , {column -> value}]index
类似字典 {index -> {column -> value}}columns
类似于{column -> {index -> value}}的字典values
仅值数组table
符合 JSON Table Schema的表格 - 默认为
-
date_format
:日期转换类型,‘epoch’表示时间戳,‘iso’表示 ISO8601。 -
double_precision
:编码浮点值时要使用的小数位数,默认为 10。 -
force_ascii
:强制编码字符串为 ASCII,默认为 True。 -
date_unit
:要编码的时间单位,控制时间戳和 ISO8601 精度。其中之一为’s’、‘ms’、‘us’或’ns’,分别表示秒、毫秒、微秒和纳秒。默认为’ms’。 -
default_handler
:如果对象无法以其他方式转换为适合 JSON 格式的格式,则调用的处理程序。接受一个参数,即要转换的对象,并返回一个可序列化的对象。 -
lines
:如果是records
方向,则将每个记录写成一行 json。 -
mode
:写入路径时的字符串,写入模式。‘w’表示写入,‘a’表示追加。默认为‘w’
注意NaN
、NaT
和None
将被转换为null
,而datetime
对象将根据date_format
和date_unit
参数进行转换。
In [230]: dfj = pd.DataFrame(np.random.randn(5, 2), columns=list("AB"))
In [231]: json = dfj.to_json()
In [232]: json
Out[232]: '{"A":{"0":-0.1213062281,"1":0.6957746499,"2":0.9597255933,"3":-0.6199759194,"4":-0.7323393705},"B":{"0":-0.0978826728,"1":0.3417343559,"2":-1.1103361029,"3":0.1497483186,"4":0.6877383895}}'
方向选项
对生成的 JSON 文件/字符串的格式有许多不同的选项。考虑以下DataFrame
和Series
:
In [233]: dfjo = pd.DataFrame(
.....: dict(A=range(1, 4), B=range(4, 7), C=range(7, 10)),
.....: columns=list("ABC"),
.....: index=list("xyz"),
.....: )
.....:
In [234]: dfjo
Out[234]:
A B C
x 1 4 7
y 2 5 8
z 3 6 9
In [235]: sjo = pd.Series(dict(x=15, y=16, z=17), name="D")
In [236]: sjo
Out[236]:
x 15
y 16
z 17
Name: D, dtype: int64
列导向(DataFrame
的默认值)将数据序列化为嵌套的 JSON 对象,其中列标签充当主要索引:
In [237]: dfjo.to_json(orient="columns")
Out[237]: '{"A":{"x":1,"y":2,"z":3},"B":{"x":4,"y":5,"z":6},"C":{"x":7,"y":8,"z":9}}'
# Not available for Series
索引导向(Series
的默认值)类似于列导向,但现在索引标签是主要的:
In [238]: dfjo.to_json(orient="index")
Out[238]: '{"x":{"A":1,"B":4,"C":7},"y":{"A":2,"B":5,"C":8},"z":{"A":3,"B":6,"C":9}}'
In [239]: sjo.to_json(orient="index")
Out[239]: '{"x":15,"y":16,"z":17}'
记录导向将数据序列化为列->值记录的 JSON 数组,不包括索引标签。这对于将DataFrame
数据传递给绘图库非常有用,例如 JavaScript 库d3.js
:
In [240]: dfjo.to_json(orient="records")
Out[240]: '[{"A":1,"B":4,"C":7},{"A":2,"B":5,"C":8},{"A":3,"B":6,"C":9}]'
In [241]: sjo.to_json(orient="records")
Out[241]: '[15,16,17]'
值导向是一个简单的选项,它将值仅序列化为嵌套的 JSON 值数组,不包括列和索引标签:
代码语言:javascript复制In [242]: dfjo.to_json(orient="values")
Out[242]: '[[1,4,7],[2,5,8],[3,6,9]]'
# Not available for Series
拆分导向序列化为包含值、索引和列的单独条目的 JSON 对象。对于Series
,还包括名称:
In [243]: dfjo.to_json(orient="split")
Out[243]: '{"columns":["A","B","C"],"index":["x","y","z"],"data":[[1,4,7],[2,5,8],[3,6,9]]}'
In [244]: sjo.to_json(orient="split")
Out[244]: '{"name":"D","index":["x","y","z"],"data":[15,16,17]}'
表导向序列化为 JSON Table Schema,允许保留元数据,包括但不限于 dtypes 和索引名称。
注意
任何编码为 JSON 对象的方向选项在往返序列化期间不会保留索引和列标签的顺序。如果希望保留标签顺序,请使用split
选项,因为它使用有序容器。
日期处理
以 ISO 日期格式编写:
代码语言:javascript复制In [245]: dfd = pd.DataFrame(np.random.randn(5, 2), columns=list("AB"))
In [246]: dfd["date"] = pd.Timestamp("20130101")
In [247]: dfd = dfd.sort_index(axis=1, ascending=False)
In [248]: json = dfd.to_json(date_format="iso")
In [249]: json
Out[249]: '{"date":{"0":"2013-01-01T00:00:00.000","1":"2013-01-01T00:00:00.000","2":"2013-01-01T00:00:00.000","3":"2013-01-01T00:00:00.000","4":"2013-01-01T00:00:00.000"},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'
以微秒为单位的 ISO 日期格式编写:
代码语言:javascript复制In [250]: json = dfd.to_json(date_format="iso", date_unit="us")
In [251]: json
Out[251]: '{"date":{"0":"2013-01-01T00:00:00.000000","1":"2013-01-01T00:00:00.000000","2":"2013-01-01T00:00:00.000000","3":"2013-01-01T00:00:00.000000","4":"2013-01-01T00:00:00.000000"},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'
Epoch 时间戳,以秒为单位:
代码语言:javascript复制In [252]: json = dfd.to_json(date_format="epoch", date_unit="s")
In [253]: json
Out[253]: '{"date":{"0":1,"1":1,"2":1,"3":1,"4":1},"B":{"0":0.403309524,"1":0.3016244523,"2":-1.3698493577,"3":1.4626960492,"4":-0.8265909164},"A":{"0":0.1764443426,"1":-0.1549507744,"2":-2.1798606054,"3":-0.9542078401,"4":-1.7431609117}}'
写入文件,带有日期索引和日期列:
代码语言:javascript复制In [254]: dfj2 = dfj.copy()
In [255]: dfj2["date"] = pd.Timestamp("20130101")
In [256]: dfj2["ints"] = list(range(5))
In [257]: dfj2["bools"] = True
In [258]: dfj2.index = pd.date_range("20130101", periods=5)
In [259]: dfj2.to_json("test.json")
In [260]: with open("test.json") as fh:
.....: print(fh.read())
.....:
{"A":{"1356998400000":-0.1213062281,"1357084800000":0.6957746499,"1357171200000":0.9597255933,"1357257600000":-0.6199759194,"1357344000000":-0.7323393705},"B":{"1356998400000":-0.0978826728,"1357084800000":0.3417343559,"1357171200000":-1.1103361029,"1357257600000":0.1497483186,"1357344000000":0.6877383895},"date":{"1356998400000":1356,"1357084800000":1356,"1357171200000":1356,"1357257600000":1356,"1357344000000":1356},"ints":{"1356998400000":0,"1357084800000":1,"1357171200000":2,"1357257600000":3,"1357344000000":4},"bools":{"1356998400000":true,"1357084800000":true,"1357171200000":true,"1357257600000":true,"1357344000000":true}}
回退行为
如果 JSON 序列化程序无法直接处理容器内容,则会以以下方式回退:
- 如果 dtype 不受支持(例如
np.complex_
),则会调用default_handler
(如果提供)处理每个值,否则会引发异常。 - 如果一个对象不受支持,它将尝试以下操作:
- 检查对象是否定义了
toDict
方法并调用它。toDict
方法应返回一个将被 JSON 序列化的dict
。 - 如果提供了一个,则调用
default_handler
。 - 通过遍历其内容将对象转换为
dict
。但是这通常会失败并出现OverflowError
或给出意外结果。
- 检查对象是否定义了
对于不受支持的对象或数据类型,通常最好的方法是提供一个default_handler
。例如:
>>> DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json() # raises
RuntimeError: Unhandled numpy dtype 15
可以通过指定简单的default_handler
来处理:
In [261]: pd.DataFrame([1.0, 2.0, complex(1.0, 2.0)]).to_json(default_handler=str)
Out[261]: '{"0":{"0":"(1 0j)","1":"(2 0j)","2":"(1 2j)"}}'
```### 读取 JSON
将 JSON 字符串读取到 pandas 对象可以使用多个参数。如果未提供或提供的是`None`,则解析器将尝试解析`DataFrame`。要明确强制解析`Series`,请传递`typ=series`。
`filepath_or_buffer`:**有效**的 JSON 字符串或文件句柄 / StringIO。该字符串可以是 URL。有效的 URL 方案包括 http、ftp、S3 和 file。对于文件 URL,预期有一个主机。例如,本地文件可以是 file ://localhost/path/to/table.json
`typ`:要恢复的对象类型(series 或 frame),默认为‘frame’。
`orient`:
Series:
默认为`index`
允许的值为{`split`,`records`,`index`}。
DataFrame
默认为`columns`
允许的值为{`split`,`records`,`index`,`columns`,`values`,`table`}。
JSON 字符串的格式
| `split` | 类似字典 {index -> [index],columns -> [columns],data -> [values]} |
| --- | --- |
| `records` | 类似列表 [{column -> value},…,{column -> value}] |
| `index` | 类似字典 {index -> {column -> value}} |
| `columns` | 类似字典 {column -> {index -> value}} |
| `values` | 仅值数组 |
| `table` | 符合 JSON [Table Schema](https://specs.frictionlessdata.io/table-schema/) 的规范 |
`dtype`:如果为 True,则推断数据类型;如果是列到数据类型的字典,则使用它们;如果为`False`,则根本不推断数据类型,默认为 True,仅适用于数据。
`convert_axes`:布尔值,尝试将轴转换为正确的数据类型,默认为`True`。
`convert_dates`:要解析日期的列的列表;如果为`True`,则尝试解析类似日期的列,默认为`True`。
`keep_default_dates`:布尔值,默认为`True`。如果解析日期,则解析默认的类似日期的列。
`precise_float`:布尔值,默认为`False`。设置为启用更高精度(strtod)函数在将字符串解码为双精度值时的使用。默认(`False`)为使用快速但不太精确的内置功能。
`date_unit`:字符串,用于检测日期转换的时间戳单位。默认为 None。默认情况下,将检测时间戳精度,如果不希望这样,则传递‘s’,‘ms’,‘us’或‘ns’中的一个来强制时间戳精度为秒,毫秒,微秒或纳秒。
`lines`:每行读取一个 json 对象。
`encoding`:用于解码 py3 字节的编码。
`chunksize`:与`lines=True`组合使用时,每次迭代读取`chunksize`行的`pandas.api.typing.JsonReader`。
`engine`: 要么是 `"ujson"`,内置的 JSON 解析器,要么是 `"pyarrow"`,它会分派到 pyarrow 的 `pyarrow.json.read_json`。当 `lines=True` 时,仅可用 `"pyarrow"`。
如果 JSON 不可解析,解析器将引发 `ValueError/TypeError/AssertionError` 中的一个。
如果在编码为 JSON 时使用了非默认的 `orient`,请确保在此处传递相同的选项,以便解码产生合理的结果,请参阅 Orient Options 获取概述。
#### 数据转换
默认情况下 `convert_axes=True`、`dtype=True` 和 `convert_dates=True` 将尝试解析轴和所有数据为适当的类型,包括日期。如果需要覆盖特定的 dtypes,请将字典传递给 `dtype`。只有在需要保留类似字符串的数字(例如 '1'、'2')时,才应将 `convert_axes` 设置为 `False`。
注意
如果 `convert_dates=True` 并且数据和/或列标签看起来像是日期,则大整数值可能会转换为日期。确切的阈值取决于指定的 `date_unit`。‘看起来像日期’ 意味着列标签符合以下标准之一:
它以 `'_at'` 结尾
它以 `'_time'` 结尾
它以 `'timestamp'` 开始
它是 `'modified'`
它是 `'date'`
警告
在读取 JSON 数据时,自动强制转换为 dtypes 会有一些怪异之处:
索引可以以不同的顺序从序列化中重建,即,返回的顺序不能保证与序列化之前相同。
如果列是 `float` 数据,则如果安全的话会转换为 `integer`,例如列为 `1.`。
布尔列将在重建时转换为 `integer`
因此,有时您可能希望通过 `dtype` 关键字参数指定特定的 dtypes。
从 JSON 字符串中读取:
```py
In [262]: from io import StringIO
In [263]: pd.read_json(StringIO(json))
Out[263]:
date B A
0 1 0.403310 0.176444
1 1 0.301624 -0.154951
2 1 -1.369849 -2.179861
3 1 1.462696 -0.954208
4 1 -0.826591 -1.743161
从文件中读取:
代码语言:javascript复制In [264]: pd.read_json("test.json")
Out[264]:
A B date ints bools
2013-01-01 -0.121306 -0.097883 1356 0 True
2013-01-02 0.695775 0.341734 1356 1 True
2013-01-03 0.959726 -1.110336 1356 2 True
2013-01-04 -0.619976 0.149748 1356 3 True
2013-01-05 -0.732339 0.687738 1356 4 True
不要转换任何数据(但仍然转换轴和日期):
代码语言:javascript复制In [265]: pd.read_json("test.json", dtype=object).dtypes
Out[265]:
A object
B object
date object
ints object
bools object
dtype: object
指定转换的 dtypes:
代码语言:javascript复制In [266]: pd.read_json("test.json", dtype={"A": "float32", "bools": "int8"}).dtypes
Out[266]:
A float32
B float64
date int64
ints int64
bools int8
dtype: object
保留字符串索引:
代码语言:javascript复制In [267]: from io import StringIO
In [268]: si = pd.DataFrame(
.....: np.zeros((4, 4)), columns=list(range(4)), index=[str(i) for i in range(4)]
.....: )
.....:
In [269]: si
Out[269]:
0 1 2 3
0 0.0 0.0 0.0 0.0
1 0.0 0.0 0.0 0.0
2 0.0 0.0 0.0 0.0
3 0.0 0.0 0.0 0.0
In [270]: si.index
Out[270]: Index(['0', '1', '2', '3'], dtype='object')
In [271]: si.columns
Out[271]: Index([0, 1, 2, 3], dtype='int64')
In [272]: json = si.to_json()
In [273]: sij = pd.read_json(StringIO(json), convert_axes=False)
In [274]: sij
Out[274]:
0 1 2 3
0 0 0 0 0
1 0 0 0 0
2 0 0 0 0
3 0 0 0 0
In [275]: sij.index
Out[275]: Index(['0', '1', '2', '3'], dtype='object')
In [276]: sij.columns
Out[276]: Index(['0', '1', '2', '3'], dtype='object')
以纳秒写入的日期需要以纳秒读取:
代码语言:javascript复制In [277]: from io import StringIO
In [278]: json = dfj2.to_json(date_unit="ns")
# Try to parse timestamps as milliseconds -> Won't Work
In [279]: dfju = pd.read_json(StringIO(json), date_unit="ms")
In [280]: dfju
Out[280]:
A B date ints bools
1356998400000000000 -0.121306 -0.097883 1356998400 0 True
1357084800000000000 0.695775 0.341734 1356998400 1 True
1357171200000000000 0.959726 -1.110336 1356998400 2 True
1357257600000000000 -0.619976 0.149748 1356998400 3 True
1357344000000000000 -0.732339 0.687738 1356998400 4 True
# Let pandas detect the correct precision
In [281]: dfju = pd.read_json(StringIO(json))
In [282]: dfju
Out[282]:
A B date ints bools
2013-01-01 -0.121306 -0.097883 2013-01-01 0 True
2013-01-02 0.695775 0.341734 2013-01-01 1 True
2013-01-03 0.959726 -1.110336 2013-01-01 2 True
2013-01-04 -0.619976 0.149748 2013-01-01 3 True
2013-01-05 -0.732339 0.687738 2013-01-01 4 True
# Or specify that all timestamps are in nanoseconds
In [283]: dfju = pd.read_json(StringIO(json), date_unit="ns")
In [284]: dfju
Out[284]:
A B date ints bools
2013-01-01 -0.121306 -0.097883 1356998400 0 True
2013-01-02 0.695775 0.341734 1356998400 1 True
2013-01-03 0.959726 -1.110336 1356998400 2 True
2013-01-04 -0.619976 0.149748 1356998400 3 True
2013-01-05 -0.732339 0.687738 1356998400 4 True
通过设置 dtype_backend
参数,您可以控制用于结果 DataFrame 的默认 dtypes。
In [285]: data = (
.....: '{"a":{"0":1,"1":3},"b":{"0":2.5,"1":4.5},"c":{"0":true,"1":false},"d":{"0":"a","1":"b"},'
.....: '"e":{"0":null,"1":6.0},"f":{"0":null,"1":7.5},"g":{"0":null,"1":true},"h":{"0":null,"1":"a"},'
.....: '"i":{"0":"12-31-2019","1":"12-31-2019"},"j":{"0":null,"1":null}}'
.....: )
.....:
In [286]: df = pd.read_json(StringIO(data), dtype_backend="pyarrow")
In [287]: df
Out[287]:
a b c d e f g h i j
0 1 2.5 True a <NA> <NA> <NA> <NA> 12-31-2019 None
1 3 4.5 False b 6 7.5 True a 12-31-2019 None
In [288]: df.dtypes
Out[288]:
a int64[pyarrow]
b double[pyarrow]
c bool[pyarrow]
d string[pyarrow]
e int64[pyarrow]
f double[pyarrow]
g bool[pyarrow]
h string[pyarrow]
i string[pyarrow]
j null[pyarrow]
dtype: object
```### 规范化
pandas 提供了一个实用函数,可以接受字典或字典列表,并将这种半结构化数据 *规范化* 为一个平面表。
```py
In [289]: data = [
.....: {"id": 1, "name": {"first": "Coleen", "last": "Volk"}},
.....: {"name": {"given": "Mark", "family": "Regner"}},
.....: {"id": 2, "name": "Faye Raker"},
.....: ]
.....:
In [290]: pd.json_normalize(data)
Out[290]:
id name.first name.last name.given name.family name
0 1.0 Coleen Volk NaN NaN NaN
1 NaN NaN NaN Mark Regner NaN
2 2.0 NaN NaN NaN NaN Faye Raker
代码语言:javascript复制In [291]: data = [
.....: {
.....: "state": "Florida",
.....: "shortname": "FL",
.....: "info": {"governor": "Rick Scott"},
.....: "county": [
.....: {"name": "Dade", "population": 12345},
.....: {"name": "Broward", "population": 40000},
.....: {"name": "Palm Beach", "population": 60000},
.....: ],
.....: },
.....: {
.....: "state": "Ohio",
.....: "shortname": "OH",
.....: "info": {"governor": "John Kasich"},
.....: "county": [
.....: {"name": "Summit", "population": 1234},
.....: {"name": "Cuyahoga", "population": 1337},
.....: ],
.....: },
.....: ]
.....:
In [292]: pd.json_normalize(data, "county", ["state", "shortname", ["info", "governor"]])
Out[292]:
name population state shortname info.governor
0 Dade 12345 Florida FL Rick Scott
1 Broward 40000 Florida FL Rick Scott
2 Palm Beach 60000 Florida FL Rick Scott
3 Summit 1234 Ohio OH John Kasich
4 Cuyahoga 1337 Ohio OH John Kasich
max_level 参数提供了更多控制规范化结束的级别。使用 max_level=1 将规范化到所提供字典的第一个嵌套级别。
代码语言:javascript复制In [293]: data = [
.....: {
.....: "CreatedBy": {"Name": "User001"},
.....: "Lookup": {
.....: "TextField": "Some text",
.....: "UserField": {"Id": "ID001", "Name": "Name001"},
.....: },
.....: "Image": {"a": "b"},
.....: }
.....: ]
.....:
In [294]: pd.json_normalize(data, max_level=1)
Out[294]:
CreatedBy.Name Lookup.TextField Lookup.UserField Image.a
0 User001 Some text {'Id': 'ID001', 'Name': 'Name001'} b
```### 行分隔的 json
pandas 能够读取和写入行分隔的 JSON 文件,这在使用 Hadoop 或 Spark 进行数据处理的流水线中很常见。
对于以行分隔的 JSON 文件,pandas 还可以返回一个迭代器,每次读取 `chunksize` 行。这对于大文件或从流中读取非常有用。
```py
In [295]: from io import StringIO
In [296]: jsonl = """
.....: {"a": 1, "b": 2}
.....: {"a": 3, "b": 4}
.....: """
.....:
In [297]: df = pd.read_json(StringIO(jsonl), lines=True)
In [298]: df
Out[298]:
a b
0 1 2
1 3 4
In [299]: df.to_json(orient="records", lines=True)
Out[299]: '{"a":1,"b":2}n{"a":3,"b":4}n'
# reader is an iterator that returns ``chunksize`` lines each iteration
In [300]: with pd.read_json(StringIO(jsonl), lines=True, chunksize=1) as reader:
.....: reader
.....: for chunk in reader:
.....: print(chunk)
.....:
Empty DataFrame
Columns: []
Index: []
a b
0 1 2
a b
1 3 4
也可以通过指定 engine="pyarrow"
来使用 pyarrow 读取行分隔的 json。
In [301]: from io import BytesIO
In [302]: df = pd.read_json(BytesIO(jsonl.encode()), lines=True, engine="pyarrow")
In [303]: df
Out[303]:
a b
0 1 2
1 3 4
新版本 2.0.0 中的新增功能。 ### 表模式
Table Schema是一种描述表格数据集的 JSON 对象的规范。JSON 包括有关字段名称、类型和其他属性的信息。您可以使用table
方向构建一个具有两个字段schema
和data
的 JSON 字符串。
In [304]: df = pd.DataFrame(
.....: {
.....: "A": [1, 2, 3],
.....: "B": ["a", "b", "c"],
.....: "C": pd.date_range("2016-01-01", freq="d", periods=3),
.....: },
.....: index=pd.Index(range(3), name="idx"),
.....: )
.....:
In [305]: df
Out[305]:
A B C
idx
0 1 a 2016-01-01
1 2 b 2016-01-02
2 3 c 2016-01-03
In [306]: df.to_json(orient="table", date_format="iso")
Out[306]: '{"schema":{"fields":[{"name":"idx","type":"integer"},{"name":"A","type":"integer"},{"name":"B","type":"string"},{"name":"C","type":"datetime"}],"primaryKey":["idx"],"pandas_version":"1.4.0"},"data":[{"idx":0,"A":1,"B":"a","C":"2016-01-01T00:00:00.000"},{"idx":1,"A":2,"B":"b","C":"2016-01-02T00:00:00.000"},{"idx":2,"A":3,"B":"c","C":"2016-01-03T00:00:00.000"}]}'
schema
字段包含fields
键,它本身包含列名到类型对的列表,包括Index
或MultiIndex
(请参阅下面的类型列表)。如果(多)索引是唯一的,则schema
字段还包含一个primaryKey
字段。
第二个字段data
包含使用records
方向序列化的数据。索引包括在内,任何日期时间都是 ISO 8601 格式,根据 Table Schema 规范的要求。
支持的类型的完整列表在 Table Schema 规范中有描述。此表显示了从 pandas 类型的映射:
pandas 类型 | Table Schema 类型 |
---|---|
int64 | integer |
float64 | number |
bool | boolean |
datetime64[ns] | datetime |
timedelta64[ns] | duration |
categorical | any |
object | str |
有关生成的表模式的一些注意事项:
schema
对象包含一个pandas_version
字段。这包含 pandas 模式的版本,并将随每个修订版递增。
在序列化时,所有日期都转换为 UTC。即使是时区无关的值,也被视为具有偏移量为 0 的 UTC 时间。
代码语言:javascript复制In [307]: from pandas.io.json import build_table_schema
In [308]: s = pd.Series(pd.date_range("2016", periods=4))
In [309]: build_table_schema(s)
Out[309]:
{'fields': [{'name': 'index', 'type': 'integer'},
{'name': 'values', 'type': 'datetime'}],
'primaryKey': ['index'],
'pandas_version': '1.4.0'}
具有时区的日期时间(在序列化之前),包含一个额外的字段tz
,其中包含时区名称(例如'US/Central'
)。
In [310]: s_tz = pd.Series(pd.date_range("2016", periods=12, tz="US/Central"))
In [311]: build_table_schema(s_tz)
Out[311]:
{'fields': [{'name': 'index', 'type': 'integer'},
{'name': 'values', 'type': 'datetime', 'tz': 'US/Central'}],
'primaryKey': ['index'],
'pandas_version': '1.4.0'}
在序列化之前,将周期转换为时间戳,因此具有被转换为 UTC 的相同行为。此外,周期将包含一个额外的字段freq
,其中包含周期的频率,例如'A-DEC'
。
In [312]: s_per = pd.Series(1, index=pd.period_range("2016", freq="Y-DEC", periods=4))
In [313]: build_table_schema(s_per)
Out[313]:
{'fields': [{'name': 'index', 'type': 'datetime', 'freq': 'YE-DEC'},
{'name': 'values', 'type': 'integer'}],
'primaryKey': ['index'],
'pandas_version': '1.4.0'}
分类使用any
类型和列出可能值集合的enum
约束。此外,还包括一个ordered
字段:
In [314]: s_cat = pd.Series(pd.Categorical(["a", "b", "a"]))
In [315]: build_table_schema(s_cat)
Out[315]:
{'fields': [{'name': 'index', 'type': 'integer'},
{'name': 'values',
'type': 'any',
'constraints': {'enum': ['a', 'b']},
'ordered': False}],
'primaryKey': ['index'],
'pandas_version': '1.4.0'}
如果索引是唯一的,则包含一个包含标签数组的primaryKey
字段:
In [316]: s_dupe = pd.Series([1, 2], index=[1, 1])
In [317]: build_table_schema(s_dupe)
Out[317]:
{'fields': [{'name': 'index', 'type': 'integer'},
{'name': 'values', 'type': 'integer'}],
'pandas_version': '1.4.0'}
primaryKey
的行为与 MultiIndexes 相同,但在这种情况下,primaryKey
是一个数组:
In [318]: s_multi = pd.Series(1, index=pd.MultiIndex.from_product([("a", "b"), (0, 1)]))
In [319]: build_table_schema(s_multi)
Out[319]:
{'fields': [{'name': 'level_0', 'type': 'string'},
{'name': 'level_1', 'type': 'integer'},
{'name': 'values', 'type': 'integer'}],
'primaryKey': FrozenList(['level_0', 'level_1']),
'pandas_version': '1.4.0'}
默认命名大致遵循以下规则:
- 对于系列,使用
object.name
。如果没有,则名称为values
。 - 对于
DataFrames
,使用列名的字符串版本。 - 对于
Index
(而不是MultiIndex
),使用index.name
,如果为 None,则使用index
。 - 对于
MultiIndex
,使用mi.names
。如果任何级别没有名称,则使用level_<i>
。
read_json
还接受orient='table'
作为参数。这样可以以往返的方式保留元数据,如数据类型和索引名称。
In [320]: df = pd.DataFrame(
.....: {
.....: "foo": [1, 2, 3, 4],
.....: "bar": ["a", "b", "c", "d"],
.....: "baz": pd.date_range("2018-01-01", freq="d", periods=4),
.....: "qux": pd.Categorical(["a", "b", "c", "c"]),
.....: },
.....: index=pd.Index(range(4), name="idx"),
.....: )
.....:
In [321]: df
Out[321]:
foo bar baz qux
idx
0 1 a 2018-01-01 a
1 2 b 2018-01-02 b
2 3 c 2018-01-03 c
3 4 d 2018-01-04 c
In [322]: df.dtypes
Out[322]:
foo int64
bar object
baz datetime64[ns]
qux category
dtype: object
In [323]: df.to_json("test.json", orient="table")
In [324]: new_df = pd.read_json("test.json", orient="table")
In [325]: new_df
Out[325]:
foo bar baz qux
idx
0 1 a 2018-01-01 a
1 2 b 2018-01-02 b
2 3 c 2018-01-03 c
3 4 d 2018-01-04 c
In [326]: new_df.dtypes
Out[326]:
foo int64
bar object
baz datetime64[ns]
qux category
dtype: object
请注意,作为 Index
的名称的文字字符串 ‘index’ 不具有往返性,MultiIndex
中以 ‘level_’ 开头的任何名称也是如此。这些在 DataFrame.to_json()
中默认用于指示缺失值,随后的读取无法区分意图。
In [327]: df.index.name = "index"
In [328]: df.to_json("test.json", orient="table")
In [329]: new_df = pd.read_json("test.json", orient="table")
In [330]: print(new_df.index.name)
None
当使用 orient='table'
以及用户定义的 ExtensionArray
时,生成的模式将在相应的 fields
元素中包含一个额外的 extDtype
键。这个额外的键不是标准的,但确实可以为扩展类型(例如 read_json(df.to_json(orient="table"), orient="table")
)启用 JSON 往返。
如果您已正确注册了 ExtensionDtype
,那么extDtype
键将携带扩展名的名称,pandas 将使用该名称进行查找并将序列化的数据重新转换为您的自定义 dtype。
HTML
读取 HTML 内容
警告
我们强烈建议您阅读下面关于 BeautifulSoup4/html5lib/lxml 解析器的 HTML 表格解析陷阱。
顶级的 read_html()
函数可以接受一个 HTML 字符串/文件/URL,并将 HTML 表格解析为 pandas DataFrame
的列表。让我们看一些例子。
注意
read_html
返回一个 DataFrame
对象的 list
,即使在 HTML 内容中只包含一个表格。
无选项读取 URL:
代码语言:javascript复制In [320]: url = "https://www.fdic.gov/resources/resolutions/bank-failures/failed-bank-list"
In [321]: pd.read_html(url)
Out[321]:
[ Bank NameBank CityCity StateSt ... Acquiring InstitutionAI Closing DateClosing FundFund
0 Almena State Bank Almena KS ... Equity Bank October 23, 2020 10538
1 First City Bank of Florida Fort Walton Beach FL ... United Fidelity Bank, fsb October 16, 2020 10537
2 The First State Bank Barboursville WV ... MVB Bank, Inc. April 3, 2020 10536
3 Ericson State Bank Ericson NE ... Farmers and Merchants Bank February 14, 2020 10535
4 City National Bank of New Jersey Newark NJ ... Industrial Bank November 1, 2019 10534
.. ... ... ... ... ... ... ...
558 Superior Bank, FSB Hinsdale IL ... Superior Federal, FSB July 27, 2001 6004
559 Malta National Bank Malta OH ... North Valley Bank May 3, 2001 4648
560 First Alliance Bank & Trust Co. Manchester NH ... Southern New Hampshire Bank & Trust February 2, 2001 4647
561 National State Bank of Metropolis Metropolis IL ... Banterra Bank of Marion December 14, 2000 4646
562 Bank of Honolulu Honolulu HI ... Bank of the Orient October 13, 2000 4645
[563 rows x 7 columns]]
注意
上述 URL 的数据每个星期一都会更改,因此上面生成的数据可能会略有不同。
在 HTTP 请求中传递标题时读取 URL:
代码语言:javascript复制In [322]: url = 'https://www.sump.org/notes/request/' # HTTP request reflector
In [323]: pd.read_html(url)
Out[323]:
[ 0 1
0 Remote Socket: 51.15.105.256:51760
1 Protocol Version: HTTP/1.1
2 Request Method: GET
3 Request URI: /notes/request/
4 Request Query: NaN,
0 Accept-Encoding: identity
1 Host: www.sump.org
2 User-Agent: Python-urllib/3.8
3 Connection: close]
In [324]: headers = {
In [325]: 'User-Agent':'Mozilla Firefox v14.0',
In [326]: 'Accept':'application/json',
In [327]: 'Connection':'keep-alive',
In [328]: 'Auth':'Bearer 2*/f3 fe68df*4'
In [329]: }
In [340]: pd.read_html(url, storage_options=headers)
Out[340]:
[ 0 1
0 Remote Socket: 51.15.105.256:51760
1 Protocol Version: HTTP/1.1
2 Request Method: GET
3 Request URI: /notes/request/
4 Request Query: NaN,
0 User-Agent: Mozilla Firefox v14.0
1 AcceptEncoding: gzip, deflate, br
2 Accept: application/json
3 Connection: keep-alive
4 Auth: Bearer 2*/f3 fe68df*4]
注意
我们可以看到上面我们传递的标题反映在 HTTP 请求中。
从上述 URL 中读取文件内容,并将其作为字符串传递给 read_html
:
In [331]: html_str = """
.....: <table>
.....: <tr>
.....: <th>A</th>
.....: <th colspan="1">B</th>
.....: <th rowspan="1">C</th>
.....: </tr>
.....: <tr>
.....: <td>a</td>
.....: <td>b</td>
.....: <td>c</td>
.....: </tr>
.....: </table>
.....: """
.....:
In [332]: with open("tmp.html", "w") as f:
.....: f.write(html_str)
.....:
In [333]: df = pd.read_html("tmp.html")
In [334]: df[0]
Out[334]:
A B C
0 a b c
如果您愿意,甚至可以传递一个 StringIO
的实例:
In [335]: dfs = pd.read_html(StringIO(html_str))
In [336]: dfs[0]
Out[336]:
A B C
0 a b c
注意
由于具有如此多的网络访问功能会减慢文档构建速度,因此 IPython 评估器未运行以下示例。如果您发现错误或无法运行的示例,请毫不犹豫地在 pandas GitHub 问题页面 上报告。
读取一个包含特定文本的表格的 URL:
代码语言:javascript复制match = "Metcalf Bank"
df_list = pd.read_html(url, match=match)
指定一个标题行(默认情况下,<thead>
中的 <th>
或 <td>
元素用于形成列索引,如果 <thead>
中包含多行,则会创建一个 MultiIndex);如果指定了,则标题行取自数据减去已解析的标题元素(<th>
元素)。
dfs = pd.read_html(url, header=0)
指定一个索引列:
代码语言:javascript复制dfs = pd.read_html(url, index_col=0)
指定要跳过的行数:
代码语言:javascript复制dfs = pd.read_html(url, skiprows=0)
使用列表指定要跳过的行数(range
也适用):
dfs = pd.read_html(url, skiprows=range(2))
指定 HTML 属性:
代码语言:javascript复制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 的值:
代码语言:javascript复制dfs = pd.read_html(url, na_values=["No Acquirer"])
指定是否保留默认的 NaN 值集合:
代码语言:javascript复制dfs = pd.read_html(url, keep_default_na=False)
为列指定转换器。这对于具有前导零的数值文本数据非常有用。默认情况下,数值列会转换为数值类型,前导零会丢失。为了避免这种情况,我们可以将这些列转换为字符串。
代码语言:javascript复制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},
)
使用上述某种组合:
代码语言:javascript复制dfs = pd.read_html(url, match="Metcalf Bank", index_col=0)
读取 pandas to_html
输出(会损失浮点数精度):
df = pd.DataFrame(np.random.randn(2, 2))
s = df.to_html(float_format="{0:.40g}".format)
dfin = pd.read_html(s, index_col=0)
如果 lxml
后端在提供唯一解析器的情况下解析失败,则会引发错误。如果您只有一个解析器,可以只提供一个字符串,但是,如果函数期望一个字符串序列,那么传递一个包含一个字符串的列表被认为是一种良好的做法。您可以使用:
dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor=["lxml"])
或者您可以不带列表传递 flavor='lxml'
:
dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor="lxml")
但是,如果你已经安装了 bs4 和 html5lib,并且传递了 None
或 ['lxml', 'bs4']
,那么解析很可能会成功。请注意,一旦解析成功,函数将立即返回。
dfs = pd.read_html(url, "Metcalf Bank", index_col=0, flavor=["lxml", "bs4"])
可以使用 extract_links="all"
从单元格中提取链接和文本。
In [337]: html_table = """
.....: <table>
.....: <tr>
.....: <th>GitHub</th>
.....: </tr>
.....: <tr>
.....: <td><a href="https://github.com/pandas-dev/pandas">pandas</a></td>
.....: </tr>
.....: </table>
.....: """
.....:
In [338]: df = pd.read_html(
.....: StringIO(html_table),
.....: extract_links="all"
.....: )[0]
.....:
In [339]: df
Out[339]:
(GitHub, None)
0 (pandas, https://github.com/pandas-dev/pandas)
In [340]: df[("GitHub", None)]
Out[340]:
0 (pandas, https://github.com/pandas-dev/pandas)
Name: (GitHub, None), dtype: object
In [341]: df[("GitHub", None)].str[1]
Out[341]:
0 https://github.com/pandas-dev/pandas
Name: (GitHub, None), dtype: object
版本 1.5.0 中的新功能。 ### 写入 HTML 文件
DataFrame
对象具有一个实例方法 to_html
,它将 DataFrame
的内容呈现为 HTML 表格。函数参数与上面描述的 to_string
方法相同。
注意
由于篇幅限制,这里没有显示 DataFrame.to_html
的所有可能选项。有关完整选项集,请参阅 DataFrame.to_html()
。
注意
在支持 HTML 渲染的环境(如 Jupyter Notebook)中,`display(HTML(…))`` 将把原始 HTML 渲染到环境中。
代码语言:javascript复制In [342]: from IPython.display import display, HTML
In [343]: df = pd.DataFrame(np.random.randn(2, 2))
In [344]: df
Out[344]:
0 1
0 -0.345352 1.314232
1 0.690579 0.995761
In [345]: html = df.to_html()
In [346]: print(html) # raw html
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>-0.345352</td>
<td>1.314232</td>
</tr>
<tr>
<th>1</th>
<td>0.690579</td>
<td>0.995761</td>
</tr>
</tbody>
</table>
In [347]: display(HTML(html))
<IPython.core.display.HTML object>
columns
参数将限制显示的列:
In [348]: html = df.to_html(columns=[0])
In [349]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>-0.345352</td>
</tr>
<tr>
<th>1</th>
<td>0.690579</td>
</tr>
</tbody>
</table>
In [350]: display(HTML(html))
<IPython.core.display.HTML object>
float_format
接受一个 Python 可调用对象来控制浮点数值的精度:
In [351]: html = df.to_html(float_format="{0:.10f}".format)
In [352]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>-0.3453521949</td>
<td>1.3142323796</td>
</tr>
<tr>
<th>1</th>
<td>0.6905793352</td>
<td>0.9957609037</td>
</tr>
</tbody>
</table>
In [353]: display(HTML(html))
<IPython.core.display.HTML object>
bold_rows
默认会使行标签加粗,但你可以关闭这个选项:
In [354]: html = df.to_html(bold_rows=False)
In [355]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>-0.345352</td>
<td>1.314232</td>
</tr>
<tr>
<td>1</td>
<td>0.690579</td>
<td>0.995761</td>
</tr>
</tbody>
</table>
In [356]: display(HTML(html))
<IPython.core.display.HTML object>
classes
参数提供了为生成的 HTML 表格添加 CSS 类的功能。请注意,这些类会 追加 到现有的 'dataframe'
类中。
In [357]: print(df.to_html(classes=["awesome_table_class", "even_more_awesome_class"]))
<table border="1" class="dataframe awesome_table_class even_more_awesome_class">
<thead>
<tr style="text-align: right;">
<th></th>
<th>0</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>-0.345352</td>
<td>1.314232</td>
</tr>
<tr>
<th>1</th>
<td>0.690579</td>
<td>0.995761</td>
</tr>
</tbody>
</table>
render_links
参数提供了向包含 URL 的单元格添加超链接的功能。
In [358]: url_df = pd.DataFrame(
.....: {
.....: "name": ["Python", "pandas"],
.....: "url": ["https://www.python.org/", "https://pandas.pydata.org"],
.....: }
.....: )
.....:
In [359]: html = url_df.to_html(render_links=True)
In [360]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>name</th>
<th>url</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>Python</td>
<td><a href="https://www.python.org/" target="_blank">https://www.python.org/</a></td>
</tr>
<tr>
<th>1</th>
<td>pandas</td>
<td><a href="https://pandas.pydata.org" target="_blank">https://pandas.pydata.org</a></td>
</tr>
</tbody>
</table>
In [361]: display(HTML(html))
<IPython.core.display.HTML object>
最后,escape
参数允许您控制结果 HTML 中是否转义 “<”,“>” 和 “&” 字符(默认情况下为 True
)。因此,要获得不带转义字符的 HTML,请传递 escape=False
。
In [362]: df = pd.DataFrame({"a": list("&<>"), "b": np.random.randn(3)})
已转义:
代码语言:javascript复制In [363]: html = df.to_html()
In [364]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>&</td>
<td>2.396780</td>
</tr>
<tr>
<th>1</th>
<td><</td>
<td>0.014871</td>
</tr>
<tr>
<th>2</th>
<td>></td>
<td>3.357427</td>
</tr>
</tbody>
</table>
In [365]: display(HTML(html))
<IPython.core.display.HTML object>
未转义:
代码语言:javascript复制In [366]: html = df.to_html(escape=False)
In [367]: print(html)
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>&</td>
<td>2.396780</td>
</tr>
<tr>
<th>1</th>
<td><</td>
<td>0.014871</td>
</tr>
<tr>
<th>2</th>
<td>></td>
<td>3.357427</td>
</tr>
</tbody>
</table>
In [368]: display(HTML(html))
<IPython.core.display.HTML object>
注意
一些浏览器可能无法显示前两个 HTML 表格的渲染差异。 ### HTML 表格解析的陷阱
在解析顶级 pandas io 函数 read_html
中用于解析 HTML 表格的库的版本存在一些问题。
与 lxml 有关的问题
- 优点
- lxml 非常快速。
- lxml 需要正确安装 Cython 才能安装。
- 缺点
- lxml 在没有提供 严格有效的标记 的情况下,不 对其解析结果做出任何保证。
- 综上所述,我们选择允许您,用户,使用lxml后端,但是如果lxml无法解析,则将使用html5lib。
- 因此,强烈建议您安装BeautifulSoup4和html5lib,这样即使lxml失败,您仍将获得有效结果(假设其他一切有效)。
使用BeautifulSoup4 使用lxml 作为后端的问题
- 由于BeautifulSoup4本质上只是一个围绕解析器后端的包装器,因此上述问题在这里同样存在。
使用BeautifulSoup4 使用html5lib 作为后端的问题
- 优点
- html5lib比lxml宽容得多,因此以更理智的方式处理现实中的标记,而不仅仅是,例如,删除一个元素而不通知您。
- html5lib 会自动从无效标记生成有效的 HTML5 标记。这对于解析 HTML 表格非常重要,因为它保证了一个有效的文档。但是,这并不意味着它是“正确的”,因为修复标记的过程没有一个单一的定义。
- html5lib是纯 Python 的,除了自己的安装之外,不需要额外的构建步骤。
- 缺点
- 使用html5lib的最大缺点是速度极慢。但是请考虑到许多网页上的表格都不足以使解析算法运行时间成为问题。更可能的是瓶颈将出现在通过网络从 URL 读取原始文本的过程中,即 IO(输入输出)。对于非常大的表格,这可能不成立。## LaTeX
在版本 1.3.0 中新增。
目前没有从 LaTeX 读取的方法,只有输出方法。
编写到 LaTeX 文件
注意
DataFrame 和 Styler 对象目前具有to_latex
方法。我们建议使用 Styler.to_latex()方法,因为它在条件样式方面更灵活,而后者可能会在将来被弃用。
请查阅 Styler.to_latex 的文档,其中提供了条件样式的示例并解释了其关键字参数的操作。
对于简单的应用程序,以下模式已足够。
代码语言:javascript复制In [369]: df = pd.DataFrame([[1, 2], [3, 4]], index=["a", "b"], columns=["c", "d"])
In [370]: print(df.style.to_latex())
begin{tabular}{lrr}
& c & d \
a & 1 & 2 \
b & 3 & 4 \
end{tabular}
要在输出之前格式化值,请链式调用 Styler.format 方法。
代码语言:javascript复制In [371]: print(df.style.format("€ {}").to_latex())
begin{tabular}{lrr}
& c & d \
a & € 1 & € 2 \
b & € 3 & € 4 \
end{tabular}
XML
读取 XML
版本 1.3.0 中的新功能。
顶级的 read_xml()
函数可以接受 XML 字符串/文件/URL,并将节点和属性解析到 pandas 的 DataFrame
中。
注意
由于没有标准的 XML 结构,设计类型可以以多种方式变化,read_xml
最适用于较平坦、较浅的版本。如果 XML 文档嵌套层级较深,则使用 stylesheet
功能将 XML 转换为较平坦的版本。
让我们看几个例子。
读取 XML 字符串:
代码语言:javascript复制In [372]: from io import StringIO
In [373]: xml = """<?xml version="1.0" encoding="UTF-8"?>
.....: <bookstore>
.....: <book category="cooking">
.....: <title lang="en">Everyday Italian</title>
.....: <author>Giada De Laurentiis</author>
.....: <year>2005</year>
.....: <price>30.00</price>
.....: </book>
.....: <book category="children">
.....: <title lang="en">Harry Potter</title>
.....: <author>J K. Rowling</author>
.....: <year>2005</year>
.....: <price>29.99</price>
.....: </book>
.....: <book category="web">
.....: <title lang="en">Learning XML</title>
.....: <author>Erik T. Ray</author>
.....: <year>2003</year>
.....: <price>39.95</price>
.....: </book>
.....: </bookstore>"""
.....:
In [374]: df = pd.read_xml(StringIO(xml))
In [375]: df
Out[375]:
category title author year price
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00
1 children Harry Potter J K. Rowling 2005 29.99
2 web Learning XML Erik T. Ray 2003 39.95
读取无选项的 URL:
代码语言:javascript复制In [376]: df = pd.read_xml("https://www.w3schools.com/xml/books.xml")
In [377]: df
Out[377]:
category title author year price cover
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00 None
1 children Harry Potter J K. Rowling 2005 29.99 None
2 web XQuery Kick Start Vaidyanathan Nagarajan 2003 49.99 None
3 web Learning XML Erik T. Ray 2003 39.95 paperback
读取 “books.xml” 文件的内容并将其作为字符串传递给 read_xml
:
In [378]: file_path = "books.xml"
In [379]: with open(file_path, "w") as f:
.....: f.write(xml)
.....:
In [380]: with open(file_path, "r") as f:
.....: df = pd.read_xml(StringIO(f.read()))
.....:
In [381]: df
Out[381]:
category title author year price
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00
1 children Harry Potter J K. Rowling 2005 29.99
2 web Learning XML Erik T. Ray 2003 39.95
将 “books.xml” 的内容读取为 StringIO
或 BytesIO
实例,并将其传递给 read_xml
:
In [382]: with open(file_path, "r") as f:
.....: sio = StringIO(f.read())
.....:
In [383]: df = pd.read_xml(sio)
In [384]: df
Out[384]:
category title author year price
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00
1 children Harry Potter J K. Rowling 2005 29.99
2 web Learning XML Erik T. Ray 2003 39.95
代码语言:javascript复制In [385]: with open(file_path, "rb") as f:
.....: bio = BytesIO(f.read())
.....:
In [386]: df = pd.read_xml(bio)
In [387]: df
Out[387]:
category title author year price
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00
1 children Harry Potter J K. Rowling 2005 29.99
2 web Learning XML Erik T. Ray 2003 39.95
甚至可以从 AWS S3 存储桶读取 XML,例如 NIH NCBI PMC Article Datasets,提供生物医学和生命科学期刊:
代码语言:javascript复制In [388]: df = pd.read_xml(
.....: "s3://pmc-oa-opendata/oa_comm/xml/all/PMC1236943.xml",
.....: xpath=".//journal-meta",
.....: )
.....:
In [389]: df
Out[389]:
journal-id journal-title issn publisher
0 Cardiovasc Ultrasound Cardiovascular Ultrasound 1476-7120 NaN
使用 lxml 作为默认的 parser
,您可以访问扩展了 Python 的 ElementTree API 的功能齐全的 XML 库。其中一个强大的工具是能够使用更具表达力的 XPath 有选择地或有条件地查询节点:
In [390]: df = pd.read_xml(file_path, xpath="//book[year=2005]")
In [391]: df
Out[391]:
category title author year price
0 cooking Everyday Italian Giada De Laurentiis 2005 30.00
1 children Harry Potter J K. Rowling 2005 29.99
仅指定要解析的元素或属性:
代码语言:javascript复制In [392]: df = pd.read_xml(file_path, elems_only=True)
In [393]: df
Out[393]:
title author year price
0 Everyday Italian Giada De Laurentiis 2005 30.00
1 Harry Potter J K. Rowling 2005 29.99
2 Learning XML Erik T. Ray 2003 39.95
代码语言:javascript复制In [394]: df = pd.read_xml(file_path, attrs_only=True)
In [395]: df
Out[395]:
category
0 cooking
1 children
2 web
XML 文档可以具有带有前缀的命名空间和不带前缀的默认命名空间,两者都用特殊属性 xmlns
表示。为了在命名空间上下文中按节点解析,xpath
必须引用一个前缀。
例如,下面的 XML 包含一个带有前缀 doc
和 URI 为 https://example.com
的命名空间。为了解析 doc:row
节点,必须使用 namespaces
。
In [396]: xml = """<?xml version='1.0' encoding='utf-8'?>
.....: <doc:data >
.....: <doc:row>
.....: <doc:shape>square</doc:shape>
.....: <doc:degrees>360</doc:degrees>
.....: <doc:sides>4.0</doc:sides>
.....: </doc:row>
.....: <doc:row>
.....: <doc:shape>circle</doc:shape>
.....: <doc:degrees>360</doc:degrees>
.....: <doc:sides/>
.....: </doc:row>
.....: <doc:row>
.....: <doc:shape>triangle</doc:shape>
.....: <doc:degrees>180</doc:degrees>
.....: <doc:sides>3.0</doc:sides>
.....: </doc:row>
.....: </doc:data>"""
.....:
In [397]: df = pd.read_xml(StringIO(xml),
.....: xpath="//doc:row",
.....: namespaces={"doc": "https://example.com"})
.....:
In [398]: df
Out[398]:
shape degrees sides
0 square 360 4.0
1 circle 360 NaN
2 triangle 180 3.0
类似地,XML 文档可以具有没有前缀的默认命名空间。未分配临时前缀将返回零个节点并引发 ValueError
。但是,分配 任何 临时名称以更正 URI 允许按节点解析。
In [399]: xml = """<?xml version='1.0' encoding='utf-8'?>
.....: <data >
.....: <row>
.....: <shape>square</shape>
.....: <degrees>360</degrees>
.....: <sides>4.0</sides>
.....: </row>
.....: <row>
.....: <shape>circle</shape>
.....: <degrees>360</degrees>
.....: <sides/>
.....: </row>
.....: <row>
.....: <shape>triangle</shape>
.....: <degrees>180</degrees>
.....: <sides>3.0</sides>
.....: </row>
.....: </data>"""
.....:
In [400]: df = pd.read_xml(StringIO(xml),
.....: xpath="//pandas:row",
.....: namespaces={"pandas": "https://example.com"})
.....:
In [401]: df
Out[401]:
shape degrees sides
0 square 360 4.0
1 circle 360 NaN
2 triangle 180 3.0
但是,如果 XPath 不引用默认的节点名称,例如 /*
,则不需要 namespaces
。
注意
由于 xpath
标识要解析的内容的父级,因此仅解析包含子节点或当前属性的直接后代。因此,read_xml
将不会解析孙子节点或其他后代的文本,并且不会解析任何后代的属性。要检索更低级别的内容,请将 xpath 调整为更低级别。例如,
In [402]: xml = """
.....: <data>
.....: <row>
.....: <shape sides="4">square</shape>
.....: <degrees>360</degrees>
.....: </row>
.....: <row>
.....: <shape sides="0">circle</shape>
.....: <degrees>360</degrees>
.....: </row>
.....: <row>
.....: <shape sides="3">triangle</shape>
.....: <degrees>180</degrees>
.....: </row>
.....: </data>"""
.....:
In [403]: df = pd.read_xml(StringIO(xml), xpath="./row")
In [404]: df
Out[404]:
shape degrees
0 square 360
1 circle 360
2 triangle 180
显示在 shape
元素上的属性 sides
未按预期解析,因为此属性位于 row
元素的子节点而不是 row
元素本身。换句话说,sides
属性是 row
元素的孙级后代。但是,xpath
目标是 row
元素,仅涵盖其子节点和属性。
使用 lxml 作为解析器,您可以使用 XSLT 脚本展平嵌套的 XML 文档,该脚本也可以是字符串/文件/URL 类型。作为背景,XSLT 是一种特殊用途的语言,写在一个特殊的 XML 文件中,可以使用 XSLT 处理器将原始 XML 文档转换为其他 XML、HTML,甚至文本(CSV、JSON 等)。
例如,考虑芝加哥“L”列车的稍微嵌套的结构,其中 station
和 rides
元素将数据封装在各自的部分中。使用下面的 XSLT,lxml
可以将原始的嵌套文档转换为更扁平的输出(如下所示,仅用于演示),以便更容易解析为 DataFrame
:
In [405]: xml = """<?xml version='1.0' encoding='utf-8'?>
.....: <response>
.....: <row>
.....: <station id="40850" name="Library"/>
.....: <month>2020-09-01T00:00:00</month>
.....: <rides>
.....: <avg_weekday_rides>864.2</avg_weekday_rides>
.....: <avg_saturday_rides>534</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
.....: </rides>
.....: </row>
.....: <row>
.....: <station id="41700" name="Washington/Wabash"/>
.....: <month>2020-09-01T00:00:00</month>
.....: <rides>
.....: <avg_weekday_rides>2707.4</avg_weekday_rides>
.....: <avg_saturday_rides>1909.8</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
.....: </rides>
.....: </row>
.....: <row>
.....: <station id="40380" name="Clark/Lake"/>
.....: <month>2020-09-01T00:00:00</month>
.....: <rides>
.....: <avg_weekday_rides>2949.6</avg_weekday_rides>
.....: <avg_saturday_rides>1657</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
.....: </rides>
.....: </row>
.....: </response>"""
.....:
In [406]: xsl = """<xsl:stylesheet version="1.0" >
.....: <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
.....: <xsl:strip-space elements="*"/>
.....: <xsl:template match="/response">
.....: <xsl:copy>
.....: <xsl:apply-templates select="row"/>
.....: </xsl:copy>
.....: </xsl:template>
.....: <xsl:template match="row">
.....: <xsl:copy>
.....: <station_id><xsl:value-of select="station/@id"/></station_id>
.....: <station_name><xsl:value-of select="station/@name"/></station_name>
.....: <xsl:copy-of select="month|rides/*"/>
.....: </xsl:copy>
.....: </xsl:template>
.....: </xsl:stylesheet>"""
.....:
In [407]: output = """<?xml version='1.0' encoding='utf-8'?>
.....: <response>
.....: <row>
.....: <station_id>40850</station_id>
.....: <station_name>Library</station_name>
.....: <month>2020-09-01T00:00:00</month>
.....: <avg_weekday_rides>864.2</avg_weekday_rides>
.....: <avg_saturday_rides>534</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>417.2</avg_sunday_holiday_rides>
.....: </row>
.....: <row>
.....: <station_id>41700</station_id>
.....: <station_name>Washington/Wabash</station_name>
.....: <month>2020-09-01T00:00:00</month>
.....: <avg_weekday_rides>2707.4</avg_weekday_rides>
.....: <avg_saturday_rides>1909.8</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>1438.6</avg_sunday_holiday_rides>
.....: </row>
.....: <row>
.....: <station_id>40380</station_id>
.....: <station_name>Clark/Lake</station_name>
.....: <month>2020-09-01T00:00:00</month>
.....: <avg_weekday_rides>2949.6</avg_weekday_rides>
.....: <avg_saturday_rides>1657</avg_saturday_rides>
.....: <avg_sunday_holiday_rides>1453.8</avg_sunday_holiday_rides>
.....: </row>
.....: </response>"""
.....:
In [408]: df = pd.read_xml(StringIO(xml), stylesheet=xsl)
In [409]: df
Out[409]:
station_id station_name ... avg_saturday_rides avg_sunday_holiday_rides
0 40850 Library ... 534.0 417.2
1 41700 Washington/Wabash ... 1909.8 1438.6
2 40380 Clark/Lake ... 1657.0 1453.8
[3 rows x 6 columns]
对于非常大的 XML 文件,其大小可能在几百兆字节到几十个字节之间,pandas.read_xml()
支持使用 lxml 的 iterparse 和 etree 的 iterparse 解析这些庞大文件,并且这些方法是内存高效的方法,可以遍历 XML 树并提取特定的元素和属性,而无需将整个树保留在内存中。
新功能,版本 1.5.0。
要使用此功能,必须将物理 XML 文件路径传递给 read_xml
并使用 iterparse
参数。文件不应该被压缩或指向在线源,而应存储在本地磁盘上。此外,iterparse
应该是一个字典,其中键是文档中的重复节点(它们成为行),值是任何重复节点的后代(即,子节点、孙子节点)的元素或属性的列表。由于此方法不使用 XPath,因此后代不需要彼此共享相同的关系。下面显示了读取维基百科非常大(12 GB )的最新文章数据转储的示例。
In [1]: df = pd.read_xml(
... "/path/to/downloaded/enwikisource-latest-pages-articles.xml",
... iterparse = {"page": ["title", "ns", "id"]}
... )
... df
Out[2]:
title ns id
0 Gettysburg Address 0 21450
1 Main Page 0 42950
2 Declaration by United Nations 0 8435
3 Constitution of the United States of America 0 8435
4 Declaration of Independence (Israel) 0 17858
... ... ... ...
3578760 Page:Black cat 1897 07 v2 n10.pdf/17 104 219649
3578761 Page:Black cat 1897 07 v2 n10.pdf/43 104 219649
3578762 Page:Black cat 1897 07 v2 n10.pdf/44 104 219649
3578763 The History of Tom Jones, a Foundling/Book IX 0 12084291
3578764 Page:Shakespeare of Stratford (1926) Yale.djvu/91 104 21450
[3578765 rows x 3 columns]
```### 编写 XML
新功能,版本 1.3.0。
`DataFrame` 对象具有一个名为 `to_xml` 的实例方法,它将 `DataFrame` 的内容呈现为 XML 文档。
注意
此方法不支持 XML 的特殊属性,包括 DTD、CData、XSD 模式、处理指令、注释等。只支持根级别的命名空间。但是,`stylesheet` 允许在初始输出之后进行设计更改。
让我们看几个示例。
编写没有选项的 XML:
```py
In [410]: geom_df = pd.DataFrame(
.....: {
.....: "shape": ["square", "circle", "triangle"],
.....: "degrees": [360, 360, 180],
.....: "sides": [4, np.nan, 3],
.....: }
.....: )
.....:
In [411]: print(geom_df.to_xml())
<?xml version='1.0' encoding='utf-8'?>
<data>
<row>
<index>0</index>
<shape>square</shape>
<degrees>360</degrees>
<sides>4.0</sides>
</row>
<row>
<index>1</index>
<shape>circle</shape>
<degrees>360</degrees>
<sides/>
</row>
<row>
<index>2</index>
<shape>triangle</shape>
<degrees>180</degrees>
<sides>3.0</sides>
</row>
</data>
编写具有新根和行名称的 XML:
代码语言:javascript复制In [412]: print(geom_df.to_xml(root_name="geometry", row_name="objects"))
<?xml version='1.0' encoding='utf-8'?>
<geometry>
<objects>
<index>0</index>
<shape>square</shape>
<degrees>360</degrees>
<sides>4.0</sides>
</objects>
<objects>
<index>1</index>
<shape>circle</shape>
<degrees>360</degrees>
<sides/>
</objects>
<objects>
<index>2</index>
<shape>triangle</shape>
<degrees>180</degrees>
<sides>3.0</sides>
</objects>
</geometry>
编写基于属性的 XML:
代码语言:javascript复制In [413]: print(geom_df.to_xml(attr_cols=geom_df.columns.tolist()))
<?xml version='1.0' encoding='utf-8'?>
<data>
<row index="0" shape="square" degrees="360" sides="4.0"/>
<row index="1" shape="circle" degrees="360"/>
<row index="2" shape="triangle" degrees="180" sides="3.0"/>
</data>
编写混合元素和属性:
代码语言:javascript复制In [414]: print(
.....: geom_df.to_xml(
.....: index=False,
.....: attr_cols=['shape'],
.....: elem_cols=['degrees', 'sides'])
.....: )
.....:
<?xml version='1.0' encoding='utf-8'?>
<data>
<row shape="square">
<degrees>360</degrees>
<sides>4.0</sides>
</row>
<row shape="circle">
<degrees>360</degrees>
<sides/>
</row>
<row shape="triangle">
<degrees>180</degrees>
<sides>3.0</sides>
</row>
</data>
任何具有分层列的 DataFrame
将被展平为以下划线分隔的 XML 元素名称:
In [415]: ext_geom_df = pd.DataFrame(
.....: {
.....: "type": ["polygon", "other", "polygon"],
.....: "shape": ["square", "circle", "triangle"],
.....: "degrees": [360, 360, 180],
.....: "sides": [4, np.nan, 3],
.....: }
.....: )
.....:
In [416]: pvt_df = ext_geom_df.pivot_table(index='shape',
.....: columns='type',
.....: values=['degrees', 'sides'],
.....: aggfunc='sum')
.....:
In [417]: pvt_df
Out[417]:
degrees sides
type other polygon other polygon
shape
circle 360.0 NaN 0.0 NaN
square NaN 360.0 NaN 4.0
triangle NaN 180.0 NaN 3.0
In [418]: print(pvt_df.to_xml())
<?xml version='1.0' encoding='utf-8'?>
<data>
<row>
<shape>circle</shape>
<degrees_other>360.0</degrees_other>
<degrees_polygon/>
<sides_other>0.0</sides_other>
<sides_polygon/>
</row>
<row>
<shape>square</shape>
<degrees_other/>
<degrees_polygon>360.0</degrees_polygon>
<sides_other/>
<sides_polygon>4.0</sides_polygon>
</row>
<row>
<shape>triangle</shape>
<degrees_other/>
<degrees_polygon>180.0</degrees_polygon>
<sides_other/>
<sides_polygon>3.0</sides_polygon>
</row>
</data>
使用默认命名空间编写 XML:
代码语言:javascript复制In [419]: print(geom_df.to_xml(namespaces={"": "https://example.com"}))
<?xml version='1.0' encoding='utf-8'?>
<data >
<row>
<index>0</index>
<shape>square</shape>
<degrees>360</degrees>
<sides>4.0</sides>
</row>
<row>
<index>1</index>
<shape>circle</shape>
<degrees>360</degrees>
<sides/>
</row>
<row>
<index>2</index>
<shape>triangle</shape>
<degrees>180</degrees>
<sides>3.0</sides>
</row>
</data>
使用命名空间前缀编写 XML:
代码语言:javascript复制In [420]: print(
.....: geom_df.to_xml(namespaces={"doc": "https://example.com"},
.....: prefix="doc")
.....: )
.....:
<?xml version='1.0' encoding='utf-8'?>
<doc:data >
<doc:row>
<doc:index>0</doc:index>
<doc:shape>square</doc:shape>
<doc:degrees>360</doc:degrees>
<doc:sides>4.0</doc:sides>
</doc:row>
<doc:row>
<doc:index>1</doc:index>
<doc:shape>circle</doc:shape>
<doc:degrees>360</doc:degrees>
<doc:sides/>
</doc:row>
<doc:row>
<doc:index>2</doc:index>
<doc:shape>triangle</doc:shape>
<doc:degrees>180</doc:degrees>
<doc:sides>3.0</doc:sides>
</doc:row>
</doc:data>
编写没有声明或美化打印的 XML:
代码语言:javascript复制In [421]: print(
.....: geom_df.to_xml(xml_declaration=False,
.....: pretty_print=False)
.....: )
.....:
<data><row><index>0</index><shape>square</shape><degrees>360</degrees><sides>4.0</sides></row><row><index>1</index><shape>circle</shape><degrees>360</degrees><sides/></row><row><index>2</index><shape>triangle</shape><degrees>180</degrees><sides>3.0</sides></row></data>
编写 XML 并使用样式表进行转换:
代码语言:javascript复制In [422]: xsl = """<xsl:stylesheet version="1.0" >
.....: <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
.....: <xsl:strip-space elements="*"/>
.....: <xsl:template match="/data">
.....: <geometry>
.....: <xsl:apply-templates select="row"/>
.....: </geometry>
.....: </xsl:template>
.....: <xsl:template match="row">
.....: <object index="{index}">
.....: <xsl:if test="shape!='circle'">
.....: <xsl:attribute name="type">polygon</xsl:attribute>
.....: </xsl:if>
.....: <xsl:copy-of select="shape"/>
.....: <property>
.....: <xsl:copy-of select="degrees|sides"/>
.....: </property>
.....: </object>
.....: </xsl:template>
.....: </xsl:stylesheet>"""
.....:
In [423]: print(geom_df.to_xml(stylesheet=xsl))
<?xml version="1.0"?>
<geometry>
<object index="0" type="polygon">
<shape>square</shape>
<property>
<degrees>360</degrees>
<sides>4.0</sides>
</property>
</object>
<object index="1">
<shape>circle</shape>
<property>
<degrees>360</degrees>
<sides/>
</property>
</object>
<object index="2" type="polygon">
<shape>triangle</shape>
<property>
<degrees>180</degrees>
<sides>3.0</sides>
</property>
</object>
</geometry>
XML 最终注意事项
- 所有 XML 文档遵循W3C 规范。
etree
和lxml
解析器将无法解析任何不符合规范或遵循 XML 语法规则的标记文档。请注意,除非遵循 XHTML 规范,否则 HTML 不是 XML 文档。然而,其他流行的标记类型,包括 KML、XAML、RSS、MusicML、MathML 都符合XML 模式。 - 出于上述原因,如果您的应用在 pandas 操作之前构建 XML,请使用适当的 DOM 库(如
etree
和lxml
)构建必要的文档,而不是通过字符串连接或正则表达式调��。请始终记住,XML 是一个带有标记规则的特殊文本文件。 - 对于非常大的 XML 文件(几百 MB 到 GB),XPath 和 XSLT 可能会成为占用大量内存的操作。确保有足够的可用 RAM 来读取和写入大型 XML 文件(大约是文本大小的 5 倍)。
- 因为 XSLT 是一种编程语言,请谨慎使用,因为这样的脚本可能在您的环境中构成安全风险,并且可能运行大型或无限递归操作。始终在小片段上测试脚本,然后再进行完整运行。
- etree解析器支持
read_xml
和to_xml
的所有功能,除了复杂的 XPath 和任何 XSLT。尽管功能有限,etree
仍然是一个可靠且功能强大的解析器和树构建器。对于较大的文件,其性能可能略逊于lxml
,但在小到中等大小的文件上相对不易察觉。
Excel 文件
read_excel()
方法可以使用openpyxl
Python 模块读取 Excel 2007 (.xlsx)文件。可以使用xlrd
读取 Excel 2003(.xls)文件。可以使用pyxlsb
读取二进制 Excel(.xlsb)文件。所有格式都可以使用 calamine 引擎读取。to_excel()
实例方法用于将DataFrame
保存到 Excel。通常语义与处理 csv 数据类似。有关一些高级策略,请参阅 cookbook。
注意
当engine=None
时,将使用以下逻辑确定引擎:
- 如果
path_or_buffer
是 OpenDocument 格式(.odf,.ods,.odt),那么将使用odf。 - 否则,如果
path_or_buffer
是 xls 格式,则将使用xlrd
。 - 否则,如果
path_or_buffer
是 xlsb 格式,则将使用pyxlsb
。 - 否则将使用
openpyxl
。
读取 Excel 文件
在最基本的用例中,read_excel
接受 Excel 文件的路径,以及指示要解析哪个工作表的sheet_name
。
在使用engine_kwargs
参数时,pandas 将这些参数传递给引擎。因此,重要的是要知道 pandas 内部使用的函数。
- 对于引擎 openpyxl,pandas 使用
openpyxl.load_workbook()
来读取(.xlsx)和(.xlsm)文件。 - 对于引擎 xlrd,pandas 使用
xlrd.open_workbook()
来读取(.xls)文件。 - 对于引擎 pyxlsb,pandas 使用
pyxlsb.open_workbook()
来读取(.xlsb)文件。 - 对于引擎 odf,pandas 使用
odf.opendocument.load()
来读取(.ods)文件。 - 对于引擎 calamine,pandas 使用
python_calamine.load_workbook()
来读取(.xlsx)、(.xlsm)、(.xls)、(.xlsb)、(.ods)文件。
# Returns a DataFrame
pd.read_excel("path_to_file.xls", sheet_name="Sheet1")
ExcelFile
类
为了方便处理同一文件中的多个工作表,可以使用ExcelFile
类来包装文件,并可以将其传递给read_excel
。读取多个工作表时将获得性能优势,因为文件只会读入内存一次。
xlsx = pd.ExcelFile("path_to_file.xls")
df = pd.read_excel(xlsx, "Sheet1")
ExcelFile
类也可以用作上下文管理器。
with pd.ExcelFile("path_to_file.xls") as xls:
df1 = pd.read_excel(xls, "Sheet1")
df2 = pd.read_excel(xls, "Sheet2")
sheet_names
属性将生成文件中工作表名称的列表。
ExcelFile
的主要用例是使用不同参数解析多个工作表:
data = {}
# For when Sheet1's format differs from Sheet2
with pd.ExcelFile("path_to_file.xls") as xls:
data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=1)
请注意,如果所有工作表都使用相同的解析参数,则可以简单地将工作表名称列表传递给read_excel
,而不会降低性能。
# using the ExcelFile class
data = {}
with pd.ExcelFile("path_to_file.xls") as xls:
data["Sheet1"] = pd.read_excel(xls, "Sheet1", index_col=None, na_values=["NA"])
data["Sheet2"] = pd.read_excel(xls, "Sheet2", index_col=None, na_values=["NA"])
# equivalent using the read_excel function
data = pd.read_excel(
"path_to_file.xls", ["Sheet1", "Sheet2"], index_col=None, na_values=["NA"]
)
ExcelFile
也可以使用xlrd.book.Book
对象作为参数调用。这允许用户控制如何读取 Excel 文件。例如,可以通过调用xlrd.open_workbook()
并使用on_demand=True
来按需加载工作表。
import xlrd
xlrd_book = xlrd.open_workbook("path_to_file.xls", on_demand=True)
with pd.ExcelFile(xlrd_book) as xls:
df1 = pd.read_excel(xls, "Sheet1")
df2 = pd.read_excel(xls, "Sheet2")
```#### 指定工作表
注意
第二个参数是`sheet_name`,不要与`ExcelFile.sheet_names`混淆。
注意
ExcelFile 的属性`sheet_names`提供对工作表列表的访问。
参数`sheet_name`允许指定要读取的工作表。
参数`sheet_name`的默认值为 0,表示读取第一个工作表
传递一个字符串来引用工作簿中特定工作表的名称。
传递一个整数来引用工作表的索引。索引遵循 Python 约定,从 0 开始。
传递一个字符串或整数列表,返回指定工作表的字典。
传递`None`返回所有可用工作表的字典。
```py
# Returns a DataFrame
pd.read_excel("path_to_file.xls", "Sheet1", index_col=None, na_values=["NA"])
使用工作表索引:
代码语言:javascript复制# Returns a DataFrame
pd.read_excel("path_to_file.xls", 0, index_col=None, na_values=["NA"])
使用所有默认值:
代码语言:javascript复制# Returns a DataFrame
pd.read_excel("path_to_file.xls")
使用 None 获取所有工作表:
代码语言:javascript复制# Returns a dictionary of DataFrames
pd.read_excel("path_to_file.xls", sheet_name=None)
使用列表获取多个工作表:
代码语言:javascript复制# Returns the 1st and 4th sheet, as a dictionary of DataFrames.
pd.read_excel("path_to_file.xls", sheet_name=["Sheet1", 3])
read_excel
可以通过将sheet_name
设置为工作表名称列表、工作表位置列表或None
来读取多个工作表。可以通过工作表索引或工作表名称指定工作表,分别使用整数或字符串。 #### 读取MultiIndex
read_excel
可以通过将列列表传递给index_col
和将行列表传递给header
来读取MultiIndex
索引。如果index
或columns
具有序列化级别名称,也可以通过指定构成级别的行/列来读取这些级别。
例如,要读取没有名称的MultiIndex
索引:
In [424]: df = pd.DataFrame(
.....: {"a": [1, 2, 3, 4], "b": [5, 6, 7, 8]},
.....: index=pd.MultiIndex.from_product([["a", "b"], ["c", "d"]]),
.....: )
.....:
In [425]: df.to_excel("path_to_file.xlsx")
In [426]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1])
In [427]: df
Out[427]:
a b
a c 1 5
d 2 6
b c 3 7
d 4 8
如果索引具有级别名称,则将使用相同的参数进行解析。
代码语言:javascript复制In [428]: df.index = df.index.set_names(["lvl1", "lvl2"])
In [429]: df.to_excel("path_to_file.xlsx")
In [430]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1])
In [431]: df
Out[431]:
a b
lvl1 lvl2
a c 1 5
d 2 6
b c 3 7
d 4 8
如果源文件既有 MultiIndex
索引又有列,则应将分别指定的列表传递给 index_col
和 header
:
In [432]: df.columns = pd.MultiIndex.from_product([["a"], ["b", "d"]], names=["c1", "c2"])
In [433]: df.to_excel("path_to_file.xlsx")
In [434]: df = pd.read_excel("path_to_file.xlsx", index_col=[0, 1], header=[0, 1])
In [435]: df
Out[435]:
c1 a
c2 b d
lvl1 lvl2
a c 1 5
d 2 6
b c 3 7
d 4 8
在指定了 index_col
中的列中的缺失值将被向前填充,以允许使用 to_excel
的 merged_cells=True
进行往返。为了避免向前填充缺失值,请在读取数据后使用 set_index
而不是 index_col
。
解析特定列
在 Excel 中,用户经常会插入列进行临时计算,而您可能不想读取这些列。read_excel
接受一个 usecols
关键字,允许您指定要解析的列的子集。
您可以将逗号分隔的一组 Excel 列和范围指定为字符串:
代码语言:javascript复制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:
代码语言:javascript复制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` 写入的工作表的名称。例如:
```py
df.to_excel("path_to_file.xlsx", sheet_name="Sheet1")
具有 .xlsx
扩展名的文件将使用 xlsxwriter
(如果可用)或 openpyxl
进行写入。
DataFrame
将以尽量模仿 REPL 输出的方式写入。index_label
将放在第二行而不是第一行。您可以通过将to_excel()
中的merge_cells
选项设置为False
将其放在第一行。
df.to_excel("path_to_file.xlsx", index_label="label", merge_cells=False)
为了将单独的DataFrame
写入单个 Excel 文件的不同工作表中,可以传递一个ExcelWriter
。
with pd.ExcelWriter("path_to_file.xlsx") as writer:
df1.to_excel(writer, sheet_name="Sheet1")
df2.to_excel(writer, sheet_name="Sheet2")
当使用engine_kwargs
参数时,pandas 将这些参数传递给引擎。因此,重要的是要知道 pandas 内部使用的是哪个函数。
- 对于引擎 openpyxl,pandas 使用
openpyxl.Workbook()
创建一个新工作表,使用openpyxl.load_workbook()
将数据追加到现有工作表。openpyxl 引擎写入(.xlsx
)和(.xlsm
)文件。 - 对于引擎 xlsxwriter,pandas 使用
xlsxwriter.Workbook()
写入(.xlsx
)文件。 - 对于引擎 odf,pandas 使用
odf.opendocument.OpenDocumentSpreadsheet()
写入(.ods
)文件。
将 Excel 文件写入内存
pandas 支持将 Excel 文件写入类似缓冲区的对象,如StringIO
或BytesIO
,使用ExcelWriter
。
from io import BytesIO
bio = BytesIO()
# By setting the 'engine' in the ExcelWriter constructor.
writer = pd.ExcelWriter(bio, engine="xlsxwriter")
df.to_excel(writer, sheet_name="Sheet1")
# Save the workbook
writer.save()
# Seek to the beginning and read to copy the workbook to a variable in memory
bio.seek(0)
workbook = bio.read()
注意
engine
是可选的但建议设置。设置引擎确定生成的工作簿版本。设置engine='xlrd'
将生成一个 Excel 2003 格式的工作簿(xls)。使用'openpyxl'
或'xlsxwriter'
将生成一个 Excel 2007 格式的工作簿(xlsx)。如果省略,将生成一个 Excel 2007 格式的工作簿。### Excel 写入器引擎
pandas 通过两种方法选择 Excel 写入器:
-
engine
关键字参数 - 文件扩展名(通过配置选项中指定的默认值)
默认情况下,pandas 使用XlsxWriter用于.xlsx
,openpyxl用于.xlsm
。如果安装了多个引擎,可以通过设置配置选项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)将冻结第一行和第一列(默认为`None`)。
使用 [Xlsxwriter](https://xlsxwriter.readthedocs.io) 引擎提供了许多控制使用 `to_excel` 方法创建的 Excel 工作表格式的选项。在 [Xlsxwriter](https://xlsxwriter.readthedocs.io) 文档中可以找到出色的示例:[`xlsxwriter.readthedocs.io/working_with_pandas.html`](https://xlsxwriter.readthedocs.io/working_with_pandas.html) ## OpenDocument 电子表格
Excel 文件 的 io 方法还支持使用 [odfpy](https://pypi.org/project/odfpy/) 模块读取和写入 OpenDocument 电子表格。读取和写入 OpenDocument 电子表格���语义和功能与使用 `engine='odf'` 可以为 Excel 文件 做的事情相匹配。需要安装可选依赖‘odfpy’。
`read_excel()` 方法可以读取 OpenDocument 电子表格
```py
# Returns a DataFrame
pd.read_excel("path_to_file.ods", engine="odf")
类似地,to_excel()
方法可以写入 OpenDocument 电子表格
# Writes DataFrame to a .ods file
df.to_excel("path_to_file.ods", engine="odf")
```## 二进制 Excel(.xlsb)文件
`read_excel()` 方法还可以使用 `pyxlsb` 模块读取二进制 Excel 文件。读取二进制 Excel 文件的语义和功能大部分与使用 `engine='pyxlsb'` 可以为 Excel 文件 做的事情相匹配。`pyxlsb` 不识别文件中的日期时间类型,而会返回浮点数(如果需要识别日期时间类型,可以使用 calamine)。
```py
# Returns a DataFrame
pd.read_excel("path_to_file.xlsb", engine="pyxlsb")
注意
目前 pandas 只支持读取二进制 Excel 文件。写入尚未实现。 ## Calamine(Excel 和 ODS 文件)
read_excel()
方法可以使用 python-calamine
模块读取 Excel 文件(.xlsx
, .xlsm
, .xls
, .xlsb
)和 OpenDocument 电子表格(.ods
)。该模块是 Rust 库 calamine 的绑定,大多数情况下比其他引擎更快。需要安装可选依赖python-calamine
。
# Returns a DataFrame
pd.read_excel("path_to_file.xlsb", engine="calamine")
```## 剪贴板
抓取数据的一个方便方法是使用 `read_clipboard()` 方法,它获取剪贴板缓冲区的内容并将其传递给 `read_csv` 方法。例如,您可以将以下文本复制到剪贴板(在许多操作系统上为 CTRL-C):
```py
A B C
x 1 4 p
y 2 5 q
z 3 6 r
然后通过调用以下方式直接将数据导入到 DataFrame
中:
>>> clipdf = pd.read_clipboard()
>>> clipdf
A B C
x 1 4 p
y 2 5 q
z 3 6 r
to_clipboard
方法可用于将 DataFrame
的内容写入剪贴板。然后您可以将剪贴板内容粘贴到其他应用程序中(在许多操作系统上为 CTRL-V)。这里我们演示将 DataFrame
写入剪贴板并读取回来。
>>> df = pd.DataFrame(
... {"A": [1, 2, 3], "B": [4, 5, 6], "C": ["p", "q", "r"]}, index=["x", "y", "z"]
... )
>>> df
A B C
x 1 4 p
y 2 5 q
z 3 6 r
>>> df.to_clipboard()
>>> pd.read_clipboard()
A B C
x 1 4 p
y 2 5 q
z 3 6 r
我们可以看到,我们得到了之前写入剪贴板的相同内容。
注意
在 Linux 上,您可能需要安装 xclip 或 xsel(与 PyQt5、PyQt4 或 qtpy 一起)才能使用这些方法。 ## Pickling
所有 pandas 对象都配备有to_pickle
方法,使用 Python 的cPickle
模块将数据结构保存到磁盘使用 pickle 格式。
In [436]: df
Out[436]:
c1 a
c2 b d
lvl1 lvl2
a c 1 5
d 2 6
b c 3 7
d 4 8
In [437]: df.to_pickle("foo.pkl")
pandas
命名空间中的read_pickle
函数可用于从文件加载任何 pickled pandas 对象(或任何其他 pickled 对象):
In [438]: pd.read_pickle("foo.pkl")
Out[438]:
c1 a
c2 b d
lvl1 lvl2
a c 1 5
d 2 6
b c 3 7
d 4 8
警告
从不受信任的来源接收 pickled 数据可能不安全。
参见:docs.python.org/3/library/pickle.html
警告
read_pickle()
仅向后兼容到几个次要版本。
压缩的 pickle 文件
read_pickle()
、DataFrame.to_pickle()
和Series.to_pickle()
可以读取和写入压缩的 pickle 文件。支持gzip
、bz2
、xz
、zstd
的压缩类型用于读取和写入。zip
文件格式仅支持读取,且必须只包含一个要读取的数据文件。
压缩类型可以是一个显式参数,也可以从文件扩展名中推断出来。如果是‘infer’,则在文件名以'.gz'
、'.bz2'
、'.zip'
、'.xz'
或'.zst'
结尾时使用gzip
、bz2
、zip
、xz
或zstd
。
压缩参数也可以是一个dict
,以便传递选项给压缩协议。必须有一个设置为压缩协议名称的'method'
键,必须是{'zip'
、'gzip'
、'bz2'
、'xz'
、'zstd'
}之一。所有其他键值对都传递给底层压缩库。
In [439]: df = pd.DataFrame(
.....: {
.....: "A": np.random.randn(1000),
.....: "B": "foo",
.....: "C": pd.date_range("20130101", periods=1000, freq="s"),
.....: }
.....: )
.....:
In [440]: df
Out[440]:
A B C
0 -0.317441 foo 2013-01-01 00:00:00
1 -1.236269 foo 2013-01-01 00:00:01
2 0.896171 foo 2013-01-01 00:00:02
3 -0.487602 foo 2013-01-01 00:00:03
4 -0.082240 foo 2013-01-01 00:00:04
.. ... ... ...
995 -0.171092 foo 2013-01-01 00:16:35
996 1.786173 foo 2013-01-01 00:16:36
997 -0.575189 foo 2013-01-01 00:16:37
998 0.820750 foo 2013-01-01 00:16:38
999 -1.256530 foo 2013-01-01 00:16:39
[1000 rows x 3 columns]
使用显式压缩类型:
代码语言:javascript复制In [441]: df.to_pickle("data.pkl.compress", compression="gzip")
In [442]: rt = pd.read_pickle("data.pkl.compress", compression="gzip")
In [443]: rt
Out[443]:
A B C
0 -0.317441 foo 2013-01-01 00:00:00
1 -1.236269 foo 2013-01-01 00:00:01
2 0.896171 foo 2013-01-01 00:00:02
3 -0.487602 foo 2013-01-01 00:00:03
4 -0.082240 foo 2013-01-01 00:00:04
.. ... ... ...
995 -0.171092 foo 2013-01-01 00:16:35
996 1.786173 foo 2013-01-01 00:16:36
997 -0.575189 foo 2013-01-01 00:16:37
998 0.820750 foo 2013-01-01 00:16:38
999 -1.256530 foo 2013-01-01 00:16:39
[1000 rows x 3 columns]
从扩展名推断压缩类型:
代码语言:javascript复制In [444]: df.to_pickle("data.pkl.xz", compression="infer")
In [445]: rt = pd.read_pickle("data.pkl.xz", compression="infer")
In [446]: rt
Out[446]:
A B C
0 -0.317441 foo 2013-01-01 00:00:00
1 -1.236269 foo 2013-01-01 00:00:01
2 0.896171 foo 2013-01-01 00:00:02
3 -0.487602 foo 2013-01-01 00:00:03
4 -0.082240 foo 2013-01-01 00:00:04
.. ... ... ...
995 -0.171092 foo 2013-01-01 00:16:35
996 1.786173 foo 2013-01-01 00:16:36
997 -0.575189 foo 2013-01-01 00:16:37
998 0.820750 foo 2013-01-01 00:16:38
999 -1.256530 foo 2013-01-01 00:16:39
[1000 rows x 3 columns]
默认为‘infer’:
代码语言:javascript复制In [447]: df.to_pickle("data.pkl.gz")
In [448]: rt = pd.read_pickle("data.pkl.gz")
In [449]: rt
Out[449]:
A B C
0 -0.317441 foo 2013-01-01 00:00:00
1 -1.236269 foo 2013-01-01 00:00:01
2 0.896171 foo 2013-01-01 00:00:02
3 -0.487602 foo 2013-01-01 00:00:03
4 -0.082240 foo 2013-01-01 00:00:04
.. ... ... ...
995 -0.171092 foo 2013-01-01 00:16:35
996 1.786173 foo 2013-01-01 00:16:36
997 -0.575189 foo 2013-01-01 00:16:37
998 0.820750 foo 2013-01-01 00:16:38
999 -1.256530 foo 2013-01-01 00:16:39
[1000 rows x 3 columns]
In [450]: df["A"].to_pickle("s1.pkl.bz2")
In [451]: rt = pd.read_pickle("s1.pkl.bz2")
In [452]: rt
Out[452]:
0 -0.317441
1 -1.236269
2 0.896171
3 -0.487602
4 -0.082240
...
995 -0.171092
996 1.786173
997 -0.575189
998 0.820750
999 -1.256530
Name: A, Length: 1000, dtype: float64
传递选项给压缩协议以加快压缩速度:
代码语言:javascript复制In [453]: df.to_pickle("data.pkl.gz", compression={"method": "gzip", "compresslevel": 1})
```## msgpack
pandas 在 1.0.0 版本中移除了对`msgpack`的支持。建议改用 pickle。
或者,您也可以使用 Arrow IPC 序列化格式来传输 pandas 对象。有关 pyarrow 的文档,请参见[这里](https://arrow.apache.org/docs/python/ipc.html)。 ## HDF5(PyTables)
`HDFStore`是一个类似字典的对象,使用高性能 HDF5 格式读写 pandas,使用优秀的[PyTables](https://www.pytables.org/)库。查看 cookbook 了解一些高级策略
警告
pandas 使用 PyTables 来读写 HDF5 文件,允许使用 pickle 序列化对象数据。从不受信任的来源接收 pickled 数据可能不安全。
更多信息请参见:[`docs.python.org/3/library/pickle.html`](https://docs.python.org/3/library/pickle.html)。
```py
In [454]: store = pd.HDFStore("store.h5")
In [455]: print(store)
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
对象可以像向字典添加键值对一样写入文件:
代码语言:javascript复制In [456]: index = pd.date_range("1/1/2000", periods=8)
In [457]: s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])
In [458]: df = pd.DataFrame(np.random.randn(8, 3), index=index, columns=["A", "B", "C"])
# store.put('s', s) is an equivalent method
In [459]: store["s"] = s
In [460]: store["df"] = df
In [461]: store
Out[461]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
在当前或以后的 Python 会话中,您可以检索存储的对象:
代码语言:javascript复制# store.get('df') is an equivalent method
In [462]: store["df"]
Out[462]:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
# dotted (attribute) access provides get as well
In [463]: store.df
Out[463]:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
删除由键指定的对象:
代码语言:javascript复制# store.remove('df') is an equivalent method
In [464]: del store["df"]
In [465]: store
Out[465]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
关闭存储并使用上下文管理器:
代码语言:javascript复制In [466]: store.close()
In [467]: store
Out[467]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
In [468]: store.is_open
Out[468]: False
# Working with, and automatically closing the store using a context manager
In [469]: with pd.HDFStore("store.h5") as store:
.....: store.keys()
.....:
读/写 API
HDFStore
支持使用read_hdf
进行读取和to_hdf
进行写入的顶级 API,类似于read_csv
和to_csv
的工作方式。
In [470]: df_tl = pd.DataFrame({"A": list(range(5)), "B": list(range(5))})
In [471]: df_tl.to_hdf("store_tl.h5", key="table", append=True)
In [472]: pd.read_hdf("store_tl.h5", "table", where=["index>2"])
Out[472]:
A B
3 3 3
4 4 4
HDFStore 默认情况下不会删除所有缺失的行。可以通过设置dropna=True
来更改此行为。
In [473]: df_with_missing = pd.DataFrame(
.....: {
.....: "col1": [0, np.nan, 2],
.....: "col2": [1, np.nan, np.nan],
.....: }
.....: )
.....:
In [474]: df_with_missing
Out[474]:
col1 col2
0 0.0 1.0
1 NaN NaN
2 2.0 NaN
In [475]: df_with_missing.to_hdf("file.h5", key="df_with_missing", format="table", mode="w")
In [476]: pd.read_hdf("file.h5", "df_with_missing")
Out[476]:
col1 col2
0 0.0 1.0
1 NaN NaN
2 2.0 NaN
In [477]: df_with_missing.to_hdf(
.....: "file.h5", key="df_with_missing", format="table", mode="w", dropna=True
.....: )
.....:
In [478]: pd.read_hdf("file.h5", "df_with_missing")
Out[478]:
col1 col2
0 0.0 1.0
2 2.0 NaN
固定格式
上面的示例显示了使用put
进行存储,它将 HDF5 写入PyTables
中的固定数组格式,称为fixed
格式。这些类型的存储一旦写入就不可追加(尽管您可以简单地删除它们并重新写入)。它们也不可查询;必须完全检索它们。它们也不支持具有非唯一列名的数据框。使用put
或to_hdf
时,默认情况下指定fixed
格式,或通过format='fixed'
或format='f'
指定。
警告
如果尝试使用where
检索fixed
格式,将引发TypeError
:
In [479]: pd.DataFrame(np.random.randn(10, 2)).to_hdf("test_fixed.h5", key="df")
In [480]: pd.read_hdf("test_fixed.h5", "df", where="index>5")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[480], line 1
----> 1 pd.read_hdf("test_fixed.h5", "df", where="index>5")
File ~/work/pandas/pandas/pandas/io/pytables.py:452, in read_hdf(path_or_buf, key, mode, errors, where, start, stop, columns, iterator, chunksize, **kwargs)
447 raise ValueError(
448 "key must be provided when HDF5 "
449 "file contains multiple datasets."
450 )
451 key = candidate_only_group._v_pathname
--> 452 return store.select(
453 key,
454 where=where,
455 start=start,
456 stop=stop,
457 columns=columns,
458 iterator=iterator,
459 chunksize=chunksize,
460 auto_close=auto_close,
461 )
462 except (ValueError, TypeError, LookupError):
463 if not isinstance(path_or_buf, HDFStore):
464 # if there is an error, close the store if we opened it.
File ~/work/pandas/pandas/pandas/io/pytables.py:906, in HDFStore.select(self, key, where, start, stop, columns, iterator, chunksize, auto_close)
892 # create the iterator
893 it = TableIterator(
894 self,
895 s,
(...)
903 auto_close=auto_close,
904 )
--> 906 return it.get_result()
File ~/work/pandas/pandas/pandas/io/pytables.py:2029, in TableIterator.get_result(self, coordinates)
2026 where = self.where
2028 # directly return the result
-> 2029 results = self.func(self.start, self.stop, where)
2030 self.close()
2031 return results
File ~/work/pandas/pandas/pandas/io/pytables.py:890, in HDFStore.select.<locals>.func(_start, _stop, _where)
889 def func(_start, _stop, _where):
--> 890 return s.read(start=_start, stop=_stop, where=_where, columns=columns)
File ~/work/pandas/pandas/pandas/io/pytables.py:3278, in BlockManagerFixed.read(self, where, columns, start, stop)
3270 def read(
3271 self,
3272 where=None,
(...)
3276 ) -> DataFrame:
3277 # start, stop applied to rows, so 0th axis only
-> 3278 self.validate_read(columns, where)
3279 select_axis = self.obj_type()._get_block_manager_axis(0)
3281 axes = []
File ~/work/pandas/pandas/pandas/io/pytables.py:2922, in GenericFixed.validate_read(self, columns, where)
2917 raise TypeError(
2918 "cannot pass a column specification when reading "
2919 "a Fixed format store. this store must be selected in its entirety"
2920 )
2921 if where is not None:
-> 2922 raise TypeError(
2923 "cannot pass a where specification when reading "
2924 "from a Fixed format store. this store must be selected in its entirety"
2925 )
TypeError: cannot pass a where specification when reading from a Fixed format store. this store must be selected in its entirety
```### 表格格式
`HDFStore`支持另一种磁盘上的`PyTables`格式,即`table`格式。在概念上,`table`的形状非常类似于 DataFrame,具有行和列。`table`可以在相同或其他会话中追加。此外,支持删除和查询类型操作。通过`format='table'`或`format='t'`指定此格式以进行`append`或`put`或`to_hdf`。
还可以将此格式设置为选项`pd.set_option('io.hdf.default_format','table')`,以使`put/append/to_hdf`默认存储为`table`格式。
```py
In [481]: store = pd.HDFStore("store.h5")
In [482]: df1 = df[0:4]
In [483]: df2 = df[4:]
# append data (creates a table automatically)
In [484]: store.append("df", df1)
In [485]: store.append("df", df2)
In [486]: store
Out[486]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
# select the entire object
In [487]: store.select("df")
Out[487]:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
# the type of stored data
In [488]: store.root.df._v_attrs.pandas_type
Out[488]: 'frame_table'
注意
您还可以通过将format='table'
或format='t'
传递给put
操作来创建一个table
。 ### 分层键
存储的键可以指定为字符串。这些可以采用分层路径名称格式(例如foo/bar/bah
),这将生成子存储(或PyTables
术语中的Groups
)的层次结构。键可以指定为没有前导‘/’的,并且始终是绝对的(例如,‘foo’指的是‘/foo’)。删除操作可以删除子存储中的所有内容以及以下内容,因此要小心。
In [489]: store.put("foo/bar/bah", df)
In [490]: store.append("food/orange", df)
In [491]: store.append("food/apple", df)
In [492]: store
Out[492]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
# a list of keys are returned
In [493]: store.keys()
Out[493]: ['/df', '/food/apple', '/food/orange', '/foo/bar/bah']
# remove all nodes under this level
In [494]: store.remove("food")
In [495]: store
Out[495]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
您可以使用walk
方法遍历组层次结构,该方法将为每个组键生成一个元组,以及其内容的相对键。
In [496]: for (path, subgroups, subkeys) in store.walk():
.....: for subgroup in subgroups:
.....: print("GROUP: {}/{}".format(path, subgroup))
.....: for subkey in subkeys:
.....: key = "/".join([path, subkey])
.....: print("KEY: {}".format(key))
.....: print(store.get(key))
.....:
GROUP: /foo
KEY: /df
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
GROUP: /foo/bar
KEY: /foo/bar/bah
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
警告
无法像上面描述的在根节点下存储的项目那样,以点(属性)访问的方式检索分层键。
代码语言:javascript复制In [497]: store.foo.bar.bah
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[497], line 1
----> 1 store.foo.bar.bah
File ~/work/pandas/pandas/pandas/io/pytables.py:613, in HDFStore.__getattr__(self, name)
611 """allow attribute access to get stores"""
612 try:
--> 613 return self.get(name)
614 except (KeyError, ClosedFileError):
615 pass
File ~/work/pandas/pandas/pandas/io/pytables.py:813, in HDFStore.get(self, key)
811 if group is None:
812 raise KeyError(f"No object named {key} in the file")
--> 813 return self._read_group(group)
File ~/work/pandas/pandas/pandas/io/pytables.py:1878, in HDFStore._read_group(self, group)
1877 def _read_group(self, group: Node):
-> 1878 s = self._create_storer(group)
1879 s.infer_axes()
1880 return s.read()
File ~/work/pandas/pandas/pandas/io/pytables.py:1752, in HDFStore._create_storer(self, group, format, value, encoding, errors)
1750 tt = "generic_table"
1751 else:
-> 1752 raise TypeError(
1753 "cannot create a storer if the object is not existing "
1754 "nor a value are passed"
1755 )
1756 else:
1757 if isinstance(value, Series):
TypeError: cannot create a storer if the object is not existing nor a value are passed
代码语言:javascript复制# you can directly access the actual PyTables node but using the root node
In [498]: store.root.foo.bar.bah
Out[498]:
/foo/bar/bah (Group) ''
children := ['axis0' (Array), 'axis1' (Array), 'block0_items' (Array), 'block0_values' (Array)]
相反,使用显式的基于字符串的键:
代码语言:javascript复制In [499]: store["foo/bar/bah"]
Out[499]:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
```### 存储类型
#### 在表中存储混合类型
支持存储混合 dtype 数据。字符串以使用附加列的最大大小存储为固定宽度。尝试追加更长字符串将引发`ValueError`。
将 `min_itemsize={'values': size}` 作为附加参数传递给 append 将为字符串列设置更大的最小值。目前支持存储 `floats, strings, ints, bools, datetime64`。对于字符串列,将 `nan_rep = 'nan'` 传递给 append 将更改磁盘上的默认 nan 表示(将转换为/从 `np.nan`),默认为 `nan`。
```py
In [500]: df_mixed = pd.DataFrame(
.....: {
.....: "A": np.random.randn(8),
.....: "B": np.random.randn(8),
.....: "C": np.array(np.random.randn(8), dtype="float32"),
.....: "string": "string",
.....: "int": 1,
.....: "bool": True,
.....: "datetime64": pd.Timestamp("20010102"),
.....: },
.....: index=list(range(8)),
.....: )
.....:
In [501]: df_mixed.loc[df_mixed.index[3:5], ["A", "B", "string", "datetime64"]] = np.nan
In [502]: store.append("df_mixed", df_mixed, min_itemsize={"values": 50})
In [503]: df_mixed1 = store.select("df_mixed")
In [504]: df_mixed1
Out[504]:
A B C ... int bool datetime64
0 0.013747 -1.166078 -1.292080 ... 1 True 1970-01-01 00:00:00.978393600
1 -0.712009 0.247572 1.526911 ... 1 True 1970-01-01 00:00:00.978393600
2 -0.645096 1.687406 0.288504 ... 1 True 1970-01-01 00:00:00.978393600
3 NaN NaN 0.097771 ... 1 True NaT
4 NaN NaN 1.536408 ... 1 True NaT
5 -0.023202 0.043702 0.926790 ... 1 True 1970-01-01 00:00:00.978393600
6 2.359782 0.088224 -0.676448 ... 1 True 1970-01-01 00:00:00.978393600
7 -0.143428 -0.813360 -0.179724 ... 1 True 1970-01-01 00:00:00.978393600
[8 rows x 7 columns]
In [505]: df_mixed1.dtypes.value_counts()
Out[505]:
float64 2
float32 1
object 1
int64 1
bool 1
datetime64[ns] 1
Name: count, dtype: int64
# we have provided a minimum string column size
In [506]: store.root.df_mixed.table
Out[506]:
/df_mixed/table (Table(8,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Float64Col(shape=(2,), dflt=0.0, pos=1),
"values_block_1": Float32Col(shape=(1,), dflt=0.0, pos=2),
"values_block_2": StringCol(itemsize=50, shape=(1,), dflt=b'', pos=3),
"values_block_3": Int64Col(shape=(1,), dflt=0, pos=4),
"values_block_4": BoolCol(shape=(1,), dflt=False, pos=5),
"values_block_5": Int64Col(shape=(1,), dflt=0, pos=6)}
byteorder := 'little'
chunkshape := (689,)
autoindex := True
colindexes := {
"index": Index(6, mediumshuffle, zlib(1)).is_csi=False}