存储多级索引的 DataFrames
将多级索引的 DataFrames
存储为表与存储/选择同质索引的 DataFrames
非常相似。
In [507]: index = pd.MultiIndex(
.....: levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
.....: codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
.....: names=["foo", "bar"],
.....: )
.....:
In [508]: df_mi = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])
In [509]: df_mi
Out[509]:
A B C
foo bar
foo one -1.303456 -0.642994 -0.649456
two 1.012694 0.414147 1.950460
three 1.094544 -0.802899 -0.583343
bar one 0.410395 0.618321 0.560398
two 1.434027 -0.033270 0.343197
baz two -1.646063 -0.695847 -0.429156
three -0.244688 -1.428229 -0.138691
qux one 1.866184 -1.446617 0.036660
two -1.660522 0.929553 -1.298649
three 3.565769 0.682402 1.041927
In [510]: store.append("df_mi", df_mi)
In [511]: store.select("df_mi")
Out[511]:
A B C
foo bar
foo one -1.303456 -0.642994 -0.649456
two 1.012694 0.414147 1.950460
three 1.094544 -0.802899 -0.583343
bar one 0.410395 0.618321 0.560398
two 1.434027 -0.033270 0.343197
baz two -1.646063 -0.695847 -0.429156
three -0.244688 -1.428229 -0.138691
qux one 1.866184 -1.446617 0.036660
two -1.660522 0.929553 -1.298649
three 3.565769 0.682402 1.041927
# the levels are automatically included as data columns
In [512]: store.select("df_mi", "foo=bar")
Out[512]:
A B C
foo bar
bar one 0.410395 0.618321 0.560398
two 1.434027 -0.033270 0.343197
注意
index
关键字是保留的,不能用作级别名称。### 查询
查询表
select
和 delete
操作有一个可选的条件,可以指定选择/删除数据的子集。这允许在磁盘上有一个非常大的表,并且只检索数据的一部分。
使用 Term
类在底层指定查询,作为布尔表达式。
-
index
和columns
是DataFrames
的支持索引器。 - 如果指定了
data_columns
,则可以将其用作额外的索引器。 - 多级索引中的级别名称,默认名称为
level_0
、level_1
,如果未提供。
有效的比较运算符有:
=, ==, !=, >, >=, <, <=
有效的布尔表达式与以下组合:
-
|
:或 -
&
:和 -
(
和)
:用于分组
这些规则类似于在 pandas 中用于索引的布尔表达式的使用方式。
注意
-
=
将自动扩展为比较运算符==
-
~
是非运算符,但只能在非��有限的情况下使用 - 如果传递了表达式的列表/元组,它们将通过
&
组合。
以下是有效的表达式:
-
'index >= date'
-
"columns = ['A', 'D']"
-
"columns in ['A', 'D']"
-
'columns = A'
-
'columns == A'
-
"~(columns = ['A', 'B'])"
-
'index > df.index[3] & string = "bar"'
-
'(index > df.index[3] & index <= df.index[6]) | string = "bar"'
-
"ts >= Timestamp('2012-02-01')"
-
"major_axis>=20130101"
indexers
在子表达式的左侧:
columns
、major_axis
、ts
子表达式的右侧(比较运算符后)可以是:
- 将被评估的函数,例如
Timestamp('2012-02-01')
- 字符串,例如
"bar"
- 类似日期的格式,例如
20130101
,或"20130101"
- 列表,例如
"['A', 'B']"
- 在本地命名空间中定义的变量,例如
date
注意
不建议通过将字符串插入查询表达式来查询字符串。只需将感兴趣的字符串赋值给一个变量,并在表达式中使用该变量。例如,这样做
代码语言:javascript复制string = "HolyMoly'"
store.select("df", "index == string")
而不是这样
代码语言:javascript复制string = "HolyMoly'"
store.select('df', f'index == {string}')
后者将不起作用,并将引发 SyntaxError
。请注意 string
变量中有一个单引号后跟一个双引号。
如果必须插值,请使用 '%r'
格式说明符
store.select("df", "index == %r" % string)
将引用 string
。
以下是一些示例:
代码语言:javascript复制In [513]: dfq = pd.DataFrame(
.....: np.random.randn(10, 4),
.....: columns=list("ABCD"),
.....: index=pd.date_range("20130101", periods=10),
.....: )
.....:
In [514]: store.append("dfq", dfq, format="table", data_columns=True)
使用内联列引用。
代码语言:javascript复制In [515]: store.select("dfq", "index>pd.Timestamp('20130104') & columns=['A', 'B']")
Out[515]:
A B
2013-01-05 -0.830545 -0.457071
2013-01-06 0.431186 1.049421
2013-01-07 0.617509 -0.811230
2013-01-08 0.947422 -0.671233
2013-01-09 -0.183798 -1.211230
2013-01-10 0.361428 0.887304
使用内联列引用。
代码语言:javascript复制In [516]: store.select("dfq", where="A>0 or C>0")
Out[516]:
A B C D
2013-01-02 0.658179 0.362814 -0.917897 0.010165
2013-01-03 0.905122 1.848731 -1.184241 0.932053
2013-01-05 -0.830545 -0.457071 1.565581 1.148032
2013-01-06 0.431186 1.049421 0.383309 0.595013
2013-01-07 0.617509 -0.811230 -2.088563 -1.393500
2013-01-08 0.947422 -0.671233 -0.847097 -1.187785
2013-01-10 0.361428 0.887304 0.266457 -0.399641
columns
关键字可以用来选择要返回的列的列表,这相当于传递 'columns=list_of_columns_to_filter'
:
In [517]: store.select("df", "columns=['A', 'B']")
Out[517]:
A B
2000-01-01 0.858644 -0.851236
2000-01-02 -0.080372 -1.268121
2000-01-03 0.816983 1.965656
2000-01-04 0.712795 -0.062433
2000-01-05 -0.298721 -1.988045
2000-01-06 1.103675 1.382242
2000-01-07 -0.729161 -0.142928
2000-01-08 -1.005977 0.465222
可以指定 start
和 stop
参数以限制总搜索空间。这些是以表中总行数为单位的。
注意
如果查询表达式具有未知变量引用,则 select
将引发 ValueError
。通常,这意味着您正在尝试选择一个不是数据列的列。
如果查询表达式无效,则 select
将引发 SyntaxError
。
查询 timedelta64[ns]
您可以使用 timedelta64[ns]
类型进行存储和查询。时间间隔可以以 <float>(<unit>)
的格式指定,其中浮点数可以是有符号的(也可以是分数),单位可以是 D,s,ms,us,ns
用于时间间隔。以下是一个示例:
In [518]: from datetime import timedelta
In [519]: dftd = pd.DataFrame(
.....: {
.....: "A": pd.Timestamp("20130101"),
.....: "B": [
.....: pd.Timestamp("20130101") timedelta(days=i, seconds=10)
.....: for i in range(10)
.....: ],
.....: }
.....: )
.....:
In [520]: dftd["C"] = dftd["A"] - dftd["B"]
In [521]: dftd
Out[521]:
A B C
0 2013-01-01 2013-01-01 00:00:10 -1 days 23:59:50
1 2013-01-01 2013-01-02 00:00:10 -2 days 23:59:50
2 2013-01-01 2013-01-03 00:00:10 -3 days 23:59:50
3 2013-01-01 2013-01-04 00:00:10 -4 days 23:59:50
4 2013-01-01 2013-01-05 00:00:10 -5 days 23:59:50
5 2013-01-01 2013-01-06 00:00:10 -6 days 23:59:50
6 2013-01-01 2013-01-07 00:00:10 -7 days 23:59:50
7 2013-01-01 2013-01-08 00:00:10 -8 days 23:59:50
8 2013-01-01 2013-01-09 00:00:10 -9 days 23:59:50
9 2013-01-01 2013-01-10 00:00:10 -10 days 23:59:50
In [522]: store.append("dftd", dftd, data_columns=True)
In [523]: store.select("dftd", "C<'-3.5D'")
Out[523]:
A B C
4 1970-01-01 00:00:01.356998400 2013-01-05 00:00:10 -5 days 23:59:50
5 1970-01-01 00:00:01.356998400 2013-01-06 00:00:10 -6 days 23:59:50
6 1970-01-01 00:00:01.356998400 2013-01-07 00:00:10 -7 days 23:59:50
7 1970-01-01 00:00:01.356998400 2013-01-08 00:00:10 -8 days 23:59:50
8 1970-01-01 00:00:01.356998400 2013-01-09 00:00:10 -9 days 23:59:50
9 1970-01-01 00:00:01.356998400 2013-01-10 00:00:10 -10 days 23:59:50
```#### 查询 MultiIndex
通过使用级别的名称可以实现从 `MultiIndex` 中选择。
```py
In [524]: df_mi.index.names
Out[524]: FrozenList(['foo', 'bar'])
In [525]: store.select("df_mi", "foo=baz and bar=two")
Out[525]:
A B C
foo bar
baz two -1.646063 -0.695847 -0.429156
如果 MultiIndex
级别名称为 None
,则可以通过 level_n
关键字自动使用 level_n
选择 MultiIndex
的级别。
In [526]: index = pd.MultiIndex(
.....: levels=[["foo", "bar", "baz", "qux"], ["one", "two", "three"]],
.....: codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3, 3], [0, 1, 2, 0, 1, 1, 2, 0, 1, 2]],
.....: )
.....:
In [527]: df_mi_2 = pd.DataFrame(np.random.randn(10, 3), index=index, columns=["A", "B", "C"])
In [528]: df_mi_2
Out[528]:
A B C
foo one -0.219582 1.186860 -1.437189
two 0.053768 1.872644 -1.469813
three -0.564201 0.876341 0.407749
bar one -0.232583 0.179812 0.922152
two -1.820952 -0.641360 2.133239
baz two -0.941248 -0.136307 -1.271305
three -0.099774 -0.061438 -0.845172
qux one 0.465793 0.756995 -0.541690
two -0.802241 0.877657 -2.553831
three 0.094899 -2.319519 0.293601
In [529]: store.append("df_mi_2", df_mi_2)
# the levels are automatically included as data columns with keyword level_n
In [530]: store.select("df_mi_2", "level_0=foo and level_1=two")
Out[530]:
A B C
foo two 0.053768 1.872644 -1.469813
索引
您可以在数据已经在表中的情况下(在 append/put
操作之后)使用 create_table_index
为表创建/修改索引。强烈建议创建表索引。当您使用具有索引维度作为 where
的 select
时,这将大大加快查询速度。
注意
索引会自动创建在可索引和您指定的任何数据列上。通过向 append
传递 index=False
可以关闭此行为。
# we have automagically already created an index (in the first section)
In [531]: i = store.root.df.table.cols.index.index
In [532]: i.optlevel, i.kind
Out[532]: (6, 'medium')
# change an index by passing new parameters
In [533]: store.create_table_index("df", optlevel=9, kind="full")
In [534]: i = store.root.df.table.cols.index.index
In [535]: i.optlevel, i.kind
Out[535]: (9, 'full')
在向存储附加大量数据时,通常很有用关闭每次附加的索引创建,然后在最后重新创建。
代码语言:javascript复制In [536]: df_1 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))
In [537]: df_2 = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))
In [538]: st = pd.HDFStore("appends.h5", mode="w")
In [539]: st.append("df", df_1, data_columns=["B"], index=False)
In [540]: st.append("df", df_2, data_columns=["B"], index=False)
In [541]: st.get_storer("df").table
Out[541]:
/df/table (Table(20,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
"B": Float64Col(shape=(), dflt=0.0, pos=2)}
byteorder := 'little'
chunkshape := (2730,)
然后在附加完成后创建索引。
代码语言:javascript复制In [542]: st.create_table_index("df", columns=["B"], optlevel=9, kind="full")
In [543]: st.get_storer("df").table
Out[543]:
/df/table (Table(20,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
"B": Float64Col(shape=(), dflt=0.0, pos=2)}
byteorder := 'little'
chunkshape := (2730,)
autoindex := True
colindexes := {
"B": Index(9, fullshuffle, zlib(1)).is_csi=True}
In [544]: st.close()
请参阅这里如何在现有存储上创建完全排序索引(CSI)。
通过数据列查询
您可以指定(并索引)您希望能够执行查询的特定列(除了可始终查询的 indexable
列之外)。例如,假设您想要执行此常见操作,在磁盘上,并仅返回与此查询匹配的框架。您可以指定 data_columns = True
来强制所有列都成为 data_columns
。
In [545]: df_dc = df.copy()
In [546]: df_dc["string"] = "foo"
In [547]: df_dc.loc[df_dc.index[4:6], "string"] = np.nan
In [548]: df_dc.loc[df_dc.index[7:9], "string"] = "bar"
In [549]: df_dc["string2"] = "cool"
In [550]: df_dc.loc[df_dc.index[1:3], ["B", "C"]] = 1.0
In [551]: df_dc
Out[551]:
A B C string string2
2000-01-01 0.858644 -0.851236 1.058006 foo cool
2000-01-02 -0.080372 1.000000 1.000000 foo cool
2000-01-03 0.816983 1.000000 1.000000 foo cool
2000-01-04 0.712795 -0.062433 0.736755 foo cool
2000-01-05 -0.298721 -1.988045 1.475308 NaN cool
2000-01-06 1.103675 1.382242 -0.650762 NaN cool
2000-01-07 -0.729161 -0.142928 -1.063038 foo cool
2000-01-08 -1.005977 0.465222 -0.094517 bar cool
# on-disk operations
In [552]: store.append("df_dc", df_dc, data_columns=["B", "C", "string", "string2"])
In [553]: store.select("df_dc", where="B > 0")
Out[553]:
A B C string string2
2000-01-02 -0.080372 1.000000 1.000000 foo cool
2000-01-03 0.816983 1.000000 1.000000 foo cool
2000-01-06 1.103675 1.382242 -0.650762 NaN cool
2000-01-08 -1.005977 0.465222 -0.094517 bar cool
# getting creative
In [554]: store.select("df_dc", "B > 0 & C > 0 & string == foo")
Out[554]:
A B C string string2
2000-01-02 -0.080372 1.0 1.0 foo cool
2000-01-03 0.816983 1.0 1.0 foo cool
# this is in-memory version of this type of selection
In [555]: df_dc[(df_dc.B > 0) & (df_dc.C > 0) & (df_dc.string == "foo")]
Out[555]:
A B C string string2
2000-01-02 -0.080372 1.0 1.0 foo cool
2000-01-03 0.816983 1.0 1.0 foo cool
# we have automagically created this index and the B/C/string/string2
# columns are stored separately as ``PyTables`` columns
In [556]: store.root.df_dc.table
Out[556]:
/df_dc/table (Table(8,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": Float64Col(shape=(1,), dflt=0.0, pos=1),
"B": Float64Col(shape=(), dflt=0.0, pos=2),
"C": Float64Col(shape=(), dflt=0.0, pos=3),
"string": StringCol(itemsize=3, shape=(), dflt=b'', pos=4),
"string2": StringCol(itemsize=4, shape=(), dflt=b'', pos=5)}
byteorder := 'little'
chunkshape := (1680,)
autoindex := True
colindexes := {
"index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"B": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"C": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"string": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"string2": Index(6, mediumshuffle, zlib(1)).is_csi=False}
将许多列转换为 data columns
会导致一些性能下降,因此用户需要指定这些列。此外,在第一次附加/放置操作之后,您不能更改数据列(也不能更改索引列)(当然,您可以简单地读取数据并创建新表!)。
迭代器
您可以将 iterator=True
或 chunksize=number_in_a_chunk
传递给 select
和 select_as_multiple
以返回结果的迭代器。默认情况下,每次返回 50,000 行。
In [557]: for df in store.select("df", chunksize=3):
.....: print(df)
.....:
A B C
2000-01-01 0.858644 -0.851236 1.058006
2000-01-02 -0.080372 -1.268121 1.561967
2000-01-03 0.816983 1.965656 -1.169408
A B C
2000-01-04 0.712795 -0.062433 0.736755
2000-01-05 -0.298721 -1.988045 1.475308
2000-01-06 1.103675 1.382242 -0.650762
A B C
2000-01-07 -0.729161 -0.142928 -1.063038
2000-01-08 -1.005977 0.465222 -0.094517
注意
您还可以使用带有 read_hdf
的迭代器,该迭代器在完成迭代时会自动打开然后关闭存储。
for df in pd.read_hdf("store.h5", "df", chunksize=3):
print(df)
请注意,chunksize 关键字适用于源行。因此,如果你正在进行一个查询,那么 chunksize 将把表中的总行数细分,并应用查询,返回一个可能大小不等的块的迭代器。
这里有一个生成查询并使用它创建相等大小返回块的方法。
代码语言:javascript复制In [558]: dfeq = pd.DataFrame({"number": np.arange(1, 11)})
In [559]: dfeq
Out[559]:
number
0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10
In [560]: store.append("dfeq", dfeq, data_columns=["number"])
In [561]: def chunks(l, n):
.....: return [l[i: i n] for i in range(0, len(l), n)]
.....:
In [562]: evens = [2, 4, 6, 8, 10]
In [563]: coordinates = store.select_as_coordinates("dfeq", "number=evens")
In [564]: for c in chunks(coordinates, 2):
.....: print(store.select("dfeq", where=c))
.....:
number
1 2
3 4
number
5 6
7 8
number
9 10
高级查询
选择单列
要检索单个可索引或数据列,请使用方法select_column
。这将使你能够快速获取索引。这些返回一个结果的Series
,由行号索引。目前这些方法不接受where
选择器。
In [565]: store.select_column("df_dc", "index")
Out[565]:
0 2000-01-01
1 2000-01-02
2 2000-01-03
3 2000-01-04
4 2000-01-05
5 2000-01-06
6 2000-01-07
7 2000-01-08
Name: index, dtype: datetime64[ns]
In [566]: store.select_column("df_dc", "string")
Out[566]:
0 foo
1 foo
2 foo
3 foo
4 NaN
5 NaN
6 foo
7 bar
Name: string, dtype: object
选择坐标
有时候你想要获取查询的坐标(也就是索引位置)。这将返回结果位置的Index
。这些坐标也可以传递给后续的where
操作。
In [567]: df_coord = pd.DataFrame(
.....: np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
.....: )
.....:
In [568]: store.append("df_coord", df_coord)
In [569]: c = store.select_as_coordinates("df_coord", "index > 20020101")
In [570]: c
Out[570]:
Index([732, 733, 734, 735, 736, 737, 738, 739, 740, 741,
...
990, 991, 992, 993, 994, 995, 996, 997, 998, 999],
dtype='int64', length=268)
In [571]: store.select("df_coord", where=c)
Out[571]:
0 1
2002-01-02 0.007717 1.168386
2002-01-03 0.759328 -0.638934
2002-01-04 -1.154018 -0.324071
2002-01-05 -0.804551 -1.280593
2002-01-06 -0.047208 1.260503
... ... ...
2002-09-22 -1.139583 0.344316
2002-09-23 -0.760643 -1.306704
2002-09-24 0.059018 1.775482
2002-09-25 1.242255 -0.055457
2002-09-26 0.410317 2.194489
[268 rows x 2 columns]
```##### 使用 where 掩码进行选择
有时候你的查询可能涉及创建一个要选择的行列表。通常这个`mask`会是一个索引操作的结果`index`。这个示例选择了一个 datetimeindex 中为 5 的月份。
```py
In [572]: df_mask = pd.DataFrame(
.....: np.random.randn(1000, 2), index=pd.date_range("20000101", periods=1000)
.....: )
.....:
In [573]: store.append("df_mask", df_mask)
In [574]: c = store.select_column("df_mask", "index")
In [575]: where = c[pd.DatetimeIndex(c).month == 5].index
In [576]: store.select("df_mask", where=where)
Out[576]:
0 1
2000-05-01 1.479511 0.516433
2000-05-02 -0.334984 -1.493537
2000-05-03 0.900321 0.049695
2000-05-04 0.614266 -1.077151
2000-05-05 0.233881 0.493246
... ... ...
2002-05-27 0.294122 0.457407
2002-05-28 -1.102535 1.215650
2002-05-29 -0.432911 0.753606
2002-05-30 -1.105212 2.311877
2002-05-31 2.567296 2.610691
[93 rows x 2 columns]
存储器对象
如果你想要检查存储的对象,请通过get_storer
检索。你可以在程序中使用这个方法来获取对象中的行数。
In [577]: store.get_storer("df_dc").nrows
Out[577]: 8
多表查询
方法append_to_multiple
和select_as_multiple
可以同时从多个表中执行追加/选择操作。其思想是有一个表(称之为选择器表),你在这个表中索引大部分/全部列,并执行你的查询。其他表是数据表,其索引与选择器表的索引匹配。然后你可以在选择器表上执行非常快速的查询,同时获取大量数据。这种方法类似于拥有一个非常宽的表,但能够实现更高效的查询。
append_to_multiple
方法根据d
,一个将表名映射到你想要在该表中的‘列’列表的字典,将给定的单个 DataFrame 拆分成多个表。如果在列表的位置使用None
,那么该表将具有给定 DataFrame 的其余未指定的列。参数selector
定义了哪个表是选择器表(你可以从中进行查询)。参数dropna
将从输入的DataFrame
中删除行,以确保表同步。这意味着如果要写入的表中的一行完全由np.nan
组成,那么该行将从所有表中删除。
如果dropna
为 False,用户需要负责同步表格。请记住,完全由np.Nan
行组成的行不会被写入 HDFStore,因此如果选择调用dropna=False
,某些表可能比其他表有更多的行,因此select_as_multiple
可能无法工作,或者可能返回意外结果。
In [578]: df_mt = pd.DataFrame(
.....: np.random.randn(8, 6),
.....: index=pd.date_range("1/1/2000", periods=8),
.....: columns=["A", "B", "C", "D", "E", "F"],
.....: )
.....:
In [579]: df_mt["foo"] = "bar"
In [580]: df_mt.loc[df_mt.index[1], ("A", "B")] = np.nan
# you can also create the tables individually
In [581]: store.append_to_multiple(
.....: {"df1_mt": ["A", "B"], "df2_mt": None}, df_mt, selector="df1_mt"
.....: )
.....:
In [582]: store
Out[582]:
<class 'pandas.io.pytables.HDFStore'>
File path: store.h5
# individual tables were created
In [583]: store.select("df1_mt")
Out[583]:
A B
2000-01-01 0.162291 -0.430489
2000-01-02 NaN NaN
2000-01-03 0.429207 -1.099274
2000-01-04 1.869081 -1.466039
2000-01-05 0.092130 -1.726280
2000-01-06 0.266901 -0.036854
2000-01-07 -0.517871 -0.990317
2000-01-08 -0.231342 0.557402
In [584]: store.select("df2_mt")
Out[584]:
C D E F foo
2000-01-01 -2.502042 0.668149 0.460708 1.834518 bar
2000-01-02 0.130441 -0.608465 0.439872 0.506364 bar
2000-01-03 -1.069546 1.236277 0.116634 -1.772519 bar
2000-01-04 0.137462 0.313939 0.748471 -0.943009 bar
2000-01-05 0.836517 2.049798 0.562167 0.189952 bar
2000-01-06 1.112750 -0.151596 1.503311 0.939470 bar
2000-01-07 -0.294348 0.335844 -0.794159 1.495614 bar
2000-01-08 0.860312 -0.538674 -0.541986 -1.759606 bar
# as a multiple
In [585]: store.select_as_multiple(
.....: ["df1_mt", "df2_mt"],
.....: where=["A>0", "B>0"],
.....: selector="df1_mt",
.....: )
.....:
Out[585]:
Empty DataFrame
Columns: [A, B, C, D, E, F, foo]
Index: []
从表中删除
您可以通过指定where
有选择性地从表中删除。在删除行时,重要的是要了解PyTables
通过擦除行然后移动后续数据来删除行。因此,删除操作可能是一个非常昂贵的操作,具体取决于数据的方向。为了获得最佳性能,最好让您要删除的维度成为indexables
的第一个维度。
数据按照indexables
的顺序(在磁盘上)进行排序。这里有一个简单的用例。你存储面板类型的数据,日期在major_axis
中,id 在minor_axis
中。然后数据被交错存储如下:
- date_1
- id_1
- id_2
- .
- id_n
- date_2
- id_1
- .
- id_n
应该清楚,对major_axis
进行删除操作会相当快,因为一个块被移除,然后后续数据被移动。另一方面,对minor_axis
进行删除操作将非常昂贵。在这种情况下,重新编写使用where
选择除缺失数据外的所有数据的表几乎肯定会更快。
警告
请注意,HDF5 不会自动回收 h5 文件中的空间。因此,反复删除(或移除节点)然后再添加,会增加文件大小。
若要重新打包和清理文件,请使用 ptrepack。
注意事项 & 警告
压缩
PyTables
允许对存储的数据进行压缩。这适用于���有类型的存储,不仅仅是表格。用于控制压缩的两个参数是complevel
和complib
。
-
complevel
指定数据压缩的难度。complevel=0
和complevel=None
禁用压缩,0<complevel<10
启用压缩。 -
complib
指定要使用的压缩库。如果未指定任何内容,则使用默认库zlib
。压缩库通常会针对良好的压缩率或速度进行优化,结果将取决于数据类型。选择哪种类型的压缩取决于您的具体需求和数据。支持的压缩库列表:- zlib:默认的压缩库。在压缩方面经典,能够获得很高的压缩率,但速度有些慢。
- lzo:快速的压缩和解压。
- bzip2:压缩率很高。
- blosc:快速的压缩和解压。
支持替代的 blosc 压缩器:
- blosc:blosclz 这是
blosc
的默认压缩器 - blosc:lz4:紧凑、非常流行且快速的压缩器。
- blosc:lz4hc:LZ4 的改进版本,在牺牲速度的情况下产生更好的压缩比。
- blosc:snappy:在许多地方使用的流行压缩器。
- blosc:zlib:经典;比前几个稍慢,但实现更好的压缩比。
- blosc:zstd:一个极其平衡的编解码器;它在以上其他编解码器中提供最佳的���缩比,并且速度相当快。
- blosc:blosclz 这是
如果
complib
被定义为除列出的库之外的内容,则会引发ValueError
异常。
注意
如果在您的平台上缺少complib
选项指定的库,则压缩默认为zlib
,无需进一步操作。
为文件中的所有对象启用压缩:
代码语言:javascript复制store_compressed = pd.HDFStore(
"store_compressed.h5", complevel=9, complib="blosc:blosclz"
)
或在未启用压缩的存储中进行即时压缩(仅适用于表):
代码语言:javascript复制store.append("df", df, complib="zlib", complevel=5)
ptrepack
当表在写入后进行压缩时,PyTables
提供更好的写入性能,而不是在一开始就打开压缩。您可以使用提供的PyTables
实用程序ptrepack
。此外,ptrepack
可以在事后更改压缩级别。
ptrepack --chunkshape=auto --propindexes --complevel=9 --complib=blosc in.h5 out.h5
此外,ptrepack in.h5 out.h5
将重新打包文件,以便您可以重用先前删除的空间。或者,可以简单地删除文件并重新写入,或者使用copy
方法。 #### 注意事项
警告
HDFStore
对于写入不是线程安全的。底层的PyTables
仅支持并发读取(通过线程或进程)。如果您需要同时进行读取和写入,您需要在单个线程中的单个进程中串行化这些操作。否则,您的数据将被破坏。有关更多信息,请参见(GH 2397)。
- 如果您使用锁来管理多个进程之间的写入访问权限,可能需要在释放写入锁之前使用
fsync()
。为了方便起见,您可以使用store.flush(fsync=True)
来为您执行此操作。 - 一旦创建了
table
,列(DataFrame)就是固定的;只能追加完全相同的列 - 请注意时区(例如,
pytz.timezone('US/Eastern')
)在不同时区版本之间不一定相等。因此,如果使用一个版本的时区库将数据本地化到 HDFStore 中的特定时区,并且使用另一个版本更新数据,则数据将被转换为 UTC,因为这些时区不被视为相等。要么使用相同版本的时区库,要么使用带有更新时区定义的tz_convert
。
警告
如果列名不能用作属性选择器,则PyTables
将显示NaturalNameWarning
。自然标识符仅包含字母、数字和下划线,并且不能以数字开头。其他标识符不能在where
子句中使用,通常是一个坏主意。 ### 数据类型
HDFStore
将对象 dtype 映射到PyTables
底层 dtype。这意味着以下类型已知可用:
类型 | 表示缺失值 |
---|---|
floating : float64, float32, float16 | np.nan |
integer : int64, int32, int8, uint64,uint32, uint8 | |
布尔值 | |
datetime64[ns] | NaT |
timedelta64[ns] | NaT |
分类:请参见下面的部分 | |
object:strings | np.nan |
不支持unicode
列,将失败。
分类数据
您可以将包含category
dtypes 的数据写入HDFStore
。查询的工作方式与对象数组相同。但是,category
dtyped 数据以更有效的方式存储。
In [586]: dfcat = pd.DataFrame(
.....: {"A": pd.Series(list("aabbcdba")).astype("category"), "B": np.random.randn(8)}
.....: )
.....:
In [587]: dfcat
Out[587]:
A B
0 a -1.520478
1 a -1.069391
2 b -0.551981
3 b 0.452407
4 c 0.409257
5 d 0.301911
6 b -0.640843
7 a -2.253022
In [588]: dfcat.dtypes
Out[588]:
A category
B float64
dtype: object
In [589]: cstore = pd.HDFStore("cats.h5", mode="w")
In [590]: cstore.append("dfcat", dfcat, format="table", data_columns=["A"])
In [591]: result = cstore.select("dfcat", where="A in ['b', 'c']")
In [592]: result
Out[592]:
A B
2 b -0.551981
3 b 0.452407
4 c 0.409257
6 b -0.640843
In [593]: result.dtypes
Out[593]:
A category
B float64
dtype: object
字符串列
min_itemsize
HDFStore
的底层实现对字符串列使用固定的列宽(itemsize)。字符串列的 itemsize 是在第一次追加时传递给HDFStore
的数据的长度的最大值。后续的追加可能会引入一个比列能容纳的更大的字符串,将引发异常(否则可能会对这些列进行静默截断,导致信息丢失)。在未来,我们可能会放宽这一限制,允许用户指定截断。
在第一次创建表时传递min_itemsize
,以先验指定特定字符串列的最小长度。min_itemsize
可以是一个整数,或将列名映射到整数的字典。您可以将values
作为一个键传递,以允许所有可索引或data_columns具有此最小长度。
传递min_itemsize
字典将导致所有传递的列自动创建为data_columns。
注意
如果没有传递任何data_columns
,那么min_itemsize
将是传递的任何字符串的长度的最大值
In [594]: dfs = pd.DataFrame({"A": "foo", "B": "bar"}, index=list(range(5)))
In [595]: dfs
Out[595]:
A B
0 foo bar
1 foo bar
2 foo bar
3 foo bar
4 foo bar
# A and B have a size of 30
In [596]: store.append("dfs", dfs, min_itemsize=30)
In [597]: store.get_storer("dfs").table
Out[597]:
/dfs/table (Table(5,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": StringCol(itemsize=30, shape=(2,), dflt=b'', pos=1)}
byteorder := 'little'
chunkshape := (963,)
autoindex := True
colindexes := {
"index": Index(6, mediumshuffle, zlib(1)).is_csi=False}
# A is created as a data_column with a size of 30
# B is size is calculated
In [598]: store.append("dfs2", dfs, min_itemsize={"A": 30})
In [599]: store.get_storer("dfs2").table
Out[599]:
/dfs2/table (Table(5,)) ''
description := {
"index": Int64Col(shape=(), dflt=0, pos=0),
"values_block_0": StringCol(itemsize=3, shape=(1,), dflt=b'', pos=1),
"A": StringCol(itemsize=30, shape=(), dflt=b'', pos=2)}
byteorder := 'little'
chunkshape := (1598,)
autoindex := True
colindexes := {
"index": Index(6, mediumshuffle, zlib(1)).is_csi=False,
"A": Index(6, mediumshuffle, zlib(1)).is_csi=False}
nan_rep
字符串列将使用nan_rep
字符串表示来序列化np.nan
(缺失值)。默认为字符串值nan
。您可能会无意中将实际的nan
值转换为缺失值。
In [600]: dfss = pd.DataFrame({"A": ["foo", "bar", "nan"]})
In [601]: dfss
Out[601]:
A
0 foo
1 bar
2 nan
In [602]: store.append("dfss", dfss)
In [603]: store.select("dfss")
Out[603]:
A
0 foo
1 bar
2 NaN
# here you need to specify a different nan rep
In [604]: store.append("dfss2", dfss, nan_rep="_nan_")
In [605]: store.select("dfss2")
Out[605]:
A
0 foo
1 bar
2 nan
性能
- 与
fixed
存储相比,tables
格式会带来写入性能的损失。好处在于能够追加/删除和查询(可能是非常大量的数据)。与常规存储相比,写入时间通常更长。查询时间可能非常快,特别是在索引轴上。 - 您可以通过在
append
中传递chunksize=<int>
来指定写入的块大小(默认为 50000)。这将显著降低写入时的内存使用。 - 您可以通过在第一次
append
中传递expectedrows=<int>
来设置PyTables
预期的总行数。这将优化读/写性能。 - 可以将重复行写入表中,但在选择时会被过滤掉(选择最后的项目;因此表在主要、次要对上是唯一的)
- 如果您尝试存储将由 PyTables 进行 pickle 处理的类型(而不是作为固有类型存储),将会引发
PerformanceWarning
。有关更多信息和一些解决方案,请参见这里。 ## Feather
Feather 为数据框提供了二进制列序列化。它旨在使数据框的读写高效,并使数据在数据分析语言之间的共享变得容易。
Feather 旨在忠实地序列化和反序列化 DataFrames,支持所有 pandas 的数据类型,包括分类和带有时区的日期时间等扩展数据类型。
几个注意事项:
- 该格式不会为
DataFrame
写入Index
或MultiIndex
,如果提供了非默认的索引,则会引发错误。您可以使用.reset_index()
存储索引,或使用.reset_index(drop=True)
忽略它。 - 不支持重复的列名和非字符串的列名
- 不支持对象数据类型列中的实际 Python 对象。在尝试序列化时,这些将引发一个有用的错误消息。
查看完整文档。
代码语言:javascript复制In [606]: df = pd.DataFrame(
.....: {
.....: "a": list("abc"),
.....: "b": list(range(1, 4)),
.....: "c": np.arange(3, 6).astype("u1"),
.....: "d": np.arange(4.0, 7.0, dtype="float64"),
.....: "e": [True, False, True],
.....: "f": pd.Categorical(list("abc")),
.....: "g": pd.date_range("20130101", periods=3),
.....: "h": pd.date_range("20130101", periods=3, tz="US/Eastern"),
.....: "i": pd.date_range("20130101", periods=3, freq="ns"),
.....: }
.....: )
.....:
In [607]: df
Out[607]:
a b c ... g h i
0 a 1 3 ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1 b 2 4 ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2 c 3 5 ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002
[3 rows x 9 columns]
In [608]: df.dtypes
Out[608]:
a object
b int64
c uint8
d float64
e bool
f category
g datetime64[ns]
h datetime64[ns, US/Eastern]
i datetime64[ns]
dtype: object
写入一个 feather 文件。
代码语言:javascript复制In [609]: df.to_feather("example.feather")
从一个 feather 文件中读取。
代码语言:javascript复制In [610]: result = pd.read_feather("example.feather")
In [611]: result
Out[611]:
a b c ... g h i
0 a 1 3 ... 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000
1 b 2 4 ... 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001
2 c 3 5 ... 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002
[3 rows x 9 columns]
# we preserve dtypes
In [612]: result.dtypes
Out[612]:
a object
b int64
c uint8
d float64
e bool
f category
g datetime64[ns]
h datetime64[ns, US/Eastern]
i datetime64[ns]
dtype: object
```## Parquet
[Apache Parquet](https://parquet.apache.org/) 为数据框提供了分区的二进制列序列化。它旨在使数据框的读写高效,并使数据在数据分析语言之间的共享变得容易。Parquet 可以使用各种压缩技术来尽可能地缩小文件大小,同时保持良好的读取性能。
Parquet 旨在忠实地序列化和反序列化 `DataFrame`,支持所有 pandas 的数据类型,包括带有时区的日期时间等扩展数据类型。
几个注意事项。
不支持重复的列名和非字符串的列名。
`pyarrow` 引擎始终将索引写入输出,但 `fastparquet` 仅写入非默认索引。这个额外的列可能会给那些不希望看到它的非 pandas 消费者带来问题。您可以使用 `index` 参数强制包含或省略索引,而不管底层引擎如何。
如果指定了索引级别名称,则必须是字符串。
在 `pyarrow` 引擎中,非字符串类型的分类数据类型可以序列化为 parquet,但会反序列化为其原始数据类型。
`pyarrow` 引擎保留了具有字符串类型的分类数据类型的 `ordered` 标志。`fastparquet` 不保留 `ordered` 标志。
不支持的类型包括 `Interval` 和实际的 Python 对象类型。在尝试序列化时,这些将引发一个有用的错误消息。`Period` 类型在 pyarrow >= 0.16.0 中受支持。
`pyarrow` 引擎保留扩展数据类型,如可空整数和字符串数据类型(需要 pyarrow >= 0.16.0,并要求扩展类型实现所需的协议,请参阅扩展类型文档)。
您可以指定一个`engine`来指导序列化。这可以是`pyarrow`、`fastparquet`或`auto`中的一个。如果未指定引擎,则会检查`pd.options.io.parquet.engine`选项;如果这也是`auto`,则尝试`pyarrow`,并回退到`fastparquet`。
参阅[pyarrow](https://arrow.apache.org/docs/python/)和[fastparquet](https://fastparquet.readthedocs.io/en/latest/)的文档。
注意
这些引擎非常相似,几乎可以读/写完全相同的 Parquet 格式文件。`pyarrow>=8.0.0`支持时间间隔数据,`fastparquet>=0.1.4`支持时区感知日期时间。这些库之间的区别在于具有不同的底层依赖关系(`fastparquet`使用`numba`,而`pyarrow`使用 C 库)。
```py
In [613]: df = pd.DataFrame(
.....: {
.....: "a": list("abc"),
.....: "b": list(range(1, 4)),
.....: "c": np.arange(3, 6).astype("u1"),
.....: "d": np.arange(4.0, 7.0, dtype="float64"),
.....: "e": [True, False, True],
.....: "f": pd.date_range("20130101", periods=3),
.....: "g": pd.date_range("20130101", periods=3, tz="US/Eastern"),
.....: "h": pd.Categorical(list("abc")),
.....: "i": pd.Categorical(list("abc"), ordered=True),
.....: }
.....: )
.....:
In [614]: df
Out[614]:
a b c d e f g h i
0 a 1 3 4.0 True 2013-01-01 2013-01-01 00:00:00-05:00 a a
1 b 2 4 5.0 False 2013-01-02 2013-01-02 00:00:00-05:00 b b
2 c 3 5 6.0 True 2013-01-03 2013-01-03 00:00:00-05:00 c c
In [615]: df.dtypes
Out[615]:
a object
b int64
c uint8
d float64
e bool
f datetime64[ns]
g datetime64[ns, US/Eastern]
h category
i category
dtype: object
写入 Parquet 文件。
代码语言:javascript复制In [616]: df.to_parquet("example_pa.parquet", engine="pyarrow")
In [617]: df.to_parquet("example_fp.parquet", engine="fastparquet")
从 Parquet 文件中读取。
代码语言:javascript复制In [618]: result = pd.read_parquet("example_fp.parquet", engine="fastparquet")
In [619]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow")
In [620]: result.dtypes
Out[620]:
a object
b int64
c uint8
d float64
e bool
f datetime64[ns]
g datetime64[ns, US/Eastern]
h category
i category
dtype: object
通过设置dtype_backend
参数,您可以控制生成的 DataFrame 使用的默认数据类型。
In [621]: result = pd.read_parquet("example_pa.parquet", engine="pyarrow", dtype_backend="pyarrow")
In [622]: result.dtypes
Out[622]:
a string[pyarrow]
b int64[pyarrow]
c uint8[pyarrow]
d double[pyarrow]
e bool[pyarrow]
f timestamp[ns][pyarrow]
g timestamp[ns, tz=US/Eastern][pyarrow]
h dictionary<values=string, indices=int32, order...
i dictionary<values=string, indices=int32, order...
dtype: object
注意
请注意,这对于fastparquet
不受支持。
仅读取 Parquet 文件的特定列。
代码语言:javascript复制In [623]: result = pd.read_parquet(
.....: "example_fp.parquet",
.....: engine="fastparquet",
.....: columns=["a", "b"],
.....: )
.....:
In [624]: result = pd.read_parquet(
.....: "example_pa.parquet",
.....: engine="pyarrow",
.....: columns=["a", "b"],
.....: )
.....:
In [625]: result.dtypes
Out[625]:
a object
b int64
dtype: object
处理索引
将 DataFrame 序列化为 parquet 文件可能会将隐式索引作为一个或多个列包含在输出文件中。因此,这段代码:
代码语言:javascript复制In [626]: df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
In [627]: df.to_parquet("test.parquet", engine="pyarrow")
如果您使用pyarrow
进行序列化,将创建一个包含三列的 Parquet 文件:a
、b
和__index_level_0__
。如果您使用fastparquet
,索引可能会或可能不会写入文件。
这意外的额外列会导致一些数据库(如 Amazon Redshift)拒绝该文件,因为该列在目标表中不存在。
如果你想在写入时省略数据框的索引,请在to_parquet()
中传递index=False
:
In [628]: df.to_parquet("test.parquet", index=False)
当你将这个文件加载到DataFrame
中时,这将创建一个只包含两个预期列a
和b
的 Parquet 文件。如果你的DataFrame
有自定义索引,当你加载这个文件时将不会得到它。
传递index=True
将始终写入索引,即使这不是底层引擎的默认行为。
对 Parquet 文件进行分区
Parquet 支持根据一个或多个列的值对数据进行分区。
代码语言:javascript复制In [629]: df = pd.DataFrame({"a": [0, 0, 1, 1], "b": [0, 1, 0, 1]})
In [630]: df.to_parquet(path="test", engine="pyarrow", partition_cols=["a"], compression=None)
path
指定将数据保存到的父目录。partition_cols
是数据集将根据其进行分区的列名。列按给定顺序进行分区。分区拆分由分区列中的唯一值确定。上面的示例创建了一个可能如下所示的分区数据集:
test
├── a=0
│ ├── 0bac803e32dc42ae83fddfd029cbdebc.parquet
│ └── ...
└── a=1
├── e6ab24a4f45147b49b54a662f0c412a3.parquet
└── ...
```## ORC
与 parquet 格式类似,[ORC 格式](https://orc.apache.org/)是用于数据框的二进制列序列化。它旨在使数据框的读取效率更高。pandas 为 ORC 格式提供了读取器和写入器,`read_orc()`和`to_orc()`。这需要[pyarrow](https://arrow.apache.org/docs/python/)库。
警告
建议使用 conda 安装 pyarrow,因为 pyarrow 存在一些问题。
`to_orc()`需要 pyarrow>=7.0.0。
`read_orc()`和`to_orc()`目前尚不支持 Windows,您可以在安装可选依赖项中找到有效的环境。
有关支持的数据类型,请参考[Arrow 中支持的 ORC 功能](https://arrow.apache.org/docs/cpp/orc.html#data-types)。
目前,将数据框转换为 ORC 文件时,日期时间列中的时区信息不会被保留。
```py
In [631]: df = pd.DataFrame(
.....: {
.....: "a": list("abc"),
.....: "b": list(range(1, 4)),
.....: "c": np.arange(4.0, 7.0, dtype="float64"),
.....: "d": [True, False, True],
.....: "e": pd.date_range("20130101", periods=3),
.....: }
.....: )
.....:
In [632]: df
Out[632]:
a b c d e
0 a 1 4.0 True 2013-01-01
1 b 2 5.0 False 2013-01-02
2 c 3 6.0 True 2013-01-03
In [633]: df.dtypes
Out[633]:
a object
b int64
c float64
d bool
e datetime64[ns]
dtype: object
写入 orc 文件。
代码语言:javascript复制In [634]: df.to_orc("example_pa.orc", engine="pyarrow")
从 orc 文件中读取。
代码语言:javascript复制In [635]: result = pd.read_orc("example_pa.orc")
In [636]: result.dtypes
Out[636]:
a object
b int64
c float64
d bool
e datetime64[ns]
dtype: object
仅读取 orc 文件的某些列。
代码语言:javascript复制In [637]: result = pd.read_orc(
.....: "example_pa.orc",
.....: columns=["a", "b"],
.....: )
.....:
In [638]: result.dtypes
Out[638]:
a object
b int64
dtype: object
```## SQL 查询
`pandas.io.sql`模块提供了一组查询包装器,旨在促进数据检索并减少对特定于数据库的 API 的依赖。
如有可能,用户可能首选选择[Apache Arrow ADBC](https://arrow.apache.org/adbc/current/index.html)驱动程序。这些驱动程序应提供最佳性能、空值处理和类型检测。
> 版本 2.2.0 中的新功能:增加了对 ADBC 驱动程序的本机支持
有关 ADBC 驱动程序及其开发状态的完整列表,请参阅[ADBC 驱动程序实现状态](https://arrow.apache.org/adbc/current/driver/status.html)文档。
如果没有 ADBC 驱动程序或缺少功能,则用户应选择安装 SQLAlchemy 以及其数据库驱动程序库。这些驱动程序的示例是[psycopg2](https://www.psycopg.org/)用于 PostgreSQL 或[pymysql](https://github.com/PyMySQL/PyMySQL)用于 MySQL。对于[SQLite](https://docs.python.org/3/library/sqlite3.html),这在 Python 的标准库中默认包含。您可以在[SQLAlchemy 文档](https://docs.sqlalchemy.org/en/latest/dialects/index.html)中找到每个 SQL 方言支持的驱动程序的概述。
如果未安装 SQLAlchemy,可以使用[`sqlite3.Connection`](https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection "(在 Python v3.12 中)")代替 SQLAlchemy 引擎、连接或 URI 字符串。
还可以查看一些烹饪书示例以获取一些高级策略。
关键函数包括:
| `read_sql_table`(table_name, con[, schema, ...]) | 将 SQL 数据库表读取到数据框中。 |
| --- | --- |
| `read_sql_query`(sql, con[, index_col, ...]) | 将 SQL 查询读取到数据框中。 |
| `read_sql`(sql, con[, index_col, ...]) | 将 SQL 查询或数据库表读取到数据框中。 |
| `DataFrame.to_sql`(name, con, *[, schema, ...]) | 将存储在数据框中的记录写入 SQL 数据库。 |
注意
函数`read_sql()`是对`read_sql_table()`和`read_sql_query()`(以及向后兼容性)的便捷包装,并根据提供的输入(数据库表名或 SQL 查询)委托给特定函数。如果表名包含特殊字符,则不需要对表名加引号。
在以下示例中,我们使用 [SQlite](https://www.sqlite.org/index.html) SQL 数据库引擎。您可以使用一个临时 SQLite 数据库,其中数据存储在“内存”中。
要使用 ADBC 驱动程序进行连接,您需要使用包管理器安装`adbc_driver_sqlite`。安装后,您可以使用 ADBC 驱动程序提供的 DBAPI 接口连接到数据库。
```py
import adbc_driver_sqlite.dbapi as sqlite_dbapi
# Create the connection
with sqlite_dbapi.connect("sqlite:///:memory:") as conn:
df = pd.read_sql_table("data", conn)
要使用 SQLAlchemy 进行连接,您可以使用create_engine()
函数从数据库 URI 创建一个引擎对象。您只需要为每个要连接的数据库创建一次引擎。有关create_engine()
和 URI 格式化的更多信息,请参见下面的示例和 SQLAlchemy 文档
In [639]: from sqlalchemy import create_engine
# Create your engine.
In [640]: engine = create_engine("sqlite:///:memory:")
如果您想管理自己的连接,可以传递其中一个。下面的示例使用 Python 上下文管理器打开与数据库的连接,在块完成后自动关闭连接。请参阅SQLAlchemy 文档了解数据库连接是如何处理的。
代码语言:javascript复制with engine.connect() as conn, conn.begin():
data = pd.read_sql_table("data", conn)
警告
当你打开与数据库的连接时,你也有责任关闭它。保持连接打开的副作用可能包括锁定数据库或其他破坏性行为。
写入数据框
假设以下数据存储在一个DataFrame
data
中,我们可以使用to_sql()
将其插入到数据库中。
id | 日期 | 列 _1 | 列 _2 | 列 _3 |
---|---|---|---|---|
26 | 2012-10-18 | X | 25.7 | True |
42 | 2012-10-19 | Y | -12.4 | False |
63 | 2012-10-20 | Z | 5.73 | True |
In [641]: import datetime
In [642]: c = ["id", "Date", "Col_1", "Col_2", "Col_3"]
In [643]: d = [
.....: (26, datetime.datetime(2010, 10, 18), "X", 27.5, True),
.....: (42, datetime.datetime(2010, 10, 19), "Y", -12.5, False),
.....: (63, datetime.datetime(2010, 10, 20), "Z", 5.73, True),
.....: ]
.....:
In [644]: data = pd.DataFrame(d, columns=c)
In [645]: data
Out[645]:
id Date Col_1 Col_2 Col_3
0 26 2010-10-18 X 27.50 True
1 42 2010-10-19 Y -12.50 False
2 63 2010-10-20 Z 5.73 True
In [646]: data.to_sql("data", con=engine)
Out[646]: 3
在某些数据库中,写入大型 DataFrame 可能会因超出数据包大小限制而导致错误。可以通过在调用 to_sql
时设置 chunksize
参数来避免这种情况。例如,以下代码将以每次 1000 行的批量方式将 data
写入数据库:
In [647]: data.to_sql("data_chunked", con=engine, chunksize=1000)
Out[647]: 3
SQL 数据类型
确保跨 SQL 数据库的一致数据类型管理具有挑战性。并非每个 SQL 数据库都提供相同的类型,即使提供了,给定类型的实现方式也可能有微妙的差异,对类型的保留方式可能产生细微影响。
为了最大限度地保留数据库类型,建议用户在可用时使用 ADBC 驱动程序。Arrow 类型系统提供了更广泛的类型数组,与历史上的 pandas/NumPy 类型系统更接近匹配数据库类型。举例来说,注意一下不同数据库和 pandas 后端中可用的类型(非穷尽列表):
numpy/pandas | arrow | postgres | sqlite |
---|---|---|---|
int16/Int16 | int16 | SMALLINT | INTEGER |
int32/Int32 | int32 | INTEGER | INTEGER |
int64/Int64 | int64 | BIGINT | INTEGER |
float32 | float32 | REAL | REAL |
float64 | float64 | DOUBLE PRECISION | REAL |
object | string | TEXT | TEXT |
bool | bool_ | BOOLEAN | |
datetime64[ns] | timestamp(us) | TIMESTAMP | |
datetime64[ns,tz] | timestamp(us,tz) | TIMESTAMPTZ | |
date32 | DATE | ||
month_day_nano_interval | INTERVAL | ||
binary | BINARY | BLOB | |
decimal128 | DECIMAL [1] | ||
list | ARRAY [1] | ||
struct |
复合类型
[1]
脚注
如果您希望在 DataFrame 的整个生命周期中尽可能保留数据库类型,建议用户利用 read_sql()
的 dtype_backend="pyarrow"
参数。
# for roundtripping
with pg_dbapi.connect(uri) as conn:
df2 = pd.read_sql("pandas_table", conn, dtype_backend="pyarrow")
这将防止您的数据被转换为传统的 pandas/NumPy 类型系统,后者经常以使 SQL 类型无法往返的方式进行转换。
如果没有可用的 ADBC 驱动程序,to_sql()
将尝试根据数据的 dtype 将数据映射到适当的 SQL 数据类型。当您有 dtype 为 object
的列时,pandas 将尝试推断数据类型。
您可以通过使用 dtype
参数指定任何列的所需 SQL 类型来始终覆盖默认类型。该参数需要一个将列名映射到 SQLAlchemy 类型(或字符串以用于 sqlite3 回退模式)的字典。例如,指定为字符串列使用 sqlalchemy 的 String
类型而不是默认的 Text
类型:
In [648]: from sqlalchemy.types import String
In [649]: data.to_sql("data_dtype", con=engine, dtype={"Col_1": String})
Out[649]: 3
注意
由于不同数据库版本对 timedelta 的支持有限,类型为timedelta64
的列将被写入为纳秒整数值到数据库中,并会引发警告。唯一的例外是在使用 ADBC PostgreSQL 驱动程序时,此时 timedelta 将被写入数据库作为INTERVAL
。
注意
category
dtype 的列将被转换为密集表示,就像使用np.asarray(categorical)
一样(例如,对于字符串类别,这将生成一个字符串数组)。因此,将数据库表重新读取时不会生成分类数据。
日期时间数据类型
使用 ADBC 或 SQLAlchemy,to_sql()
能够写入时区无关或时区感知的日期时间数据。然而,最终存储在数据库中的数据取决于所使用的数据库系统支持的日期时间数据类型。
下表列出了一些常见数据库支持的日期时间数据类型。其他数据库方言可能有不同的日期时间数据类型。
数据库 | SQL 日期时间类型 | 时区支持 |
---|---|---|
SQLite | TEXT | 否 |
MySQL | TIMESTAMP 或 DATETIME | 否 |
PostgreSQL | TIMESTAMP 或 TIMESTAMP WITH TIME ZONE | 是 |
当将带有时区信息的数据写入不支持时区的数据库时,数据将被写入为相对于时区的本地时间的时区无关时间戳。
read_sql_table()
也能够读取时区感知或时区无关的日期时间数据。当读取TIMESTAMP WITH TIME ZONE
类型时,pandas 将数据转换为 UTC 时间。
插入方法
参数method
控制所使用的 SQL 插入子句。可能的值包括:
-
None
:使用标准 SQLINSERT
子句(每行一个)。 -
'multi'
:在单个INSERT
子句中传递多个值。它使用一种特殊的 SQL 语法,不是所有后端都支持。这通常对于像Presto和Redshift这样的分析数据库提供更好的性能,但如果表包含许多列,则传统 SQL 后端的性能会更差。有关更多信息,请查看 SQLAlchemy 的文档。 - 具有签名
(pd_table, conn, keys, data_iter)
的可调用函数:这可用于基于特定后端方言功能实现更高性能的插入方法。
使用 PostgreSQL 的示例可调用复制子句:
代码语言:javascript复制# Alternative to_sql() *method* for DBs that support COPY FROM
import csv
from io import StringIO
def psql_insert_copy(table, conn, keys, data_iter):
"""
Execute SQL statement inserting data
Parameters
----------
table : pandas.io.sql.SQLTable
conn : sqlalchemy.engine.Engine or sqlalchemy.engine.Connection
keys : list of str
Column names
data_iter : Iterable that iterates the values to be inserted
"""
# gets a DBAPI connection that can provide a cursor
dbapi_conn = conn.connection
with dbapi_conn.cursor() as cur:
s_buf = StringIO()
writer = csv.writer(s_buf)
writer.writerows(data_iter)
s_buf.seek(0)
columns = ', '.join(['"{}"'.format(k) for k in keys])
if table.schema:
table_name = '{}.{}'.format(table.schema, table.name)
else:
table_name = table.name
sql = 'COPY {} ({}) FROM STDIN WITH CSV'.format(
table_name, columns)
cur.copy_expert(sql=sql, file=s_buf)
读取表
read_sql_table()
将读取给定表名的数据库表,可选择性地读取一部分列。
注意
为了使用read_sql_table()
,你必须安装 ADBC 驱动程序或 SQLAlchemy 可选依赖项。
In [650]: pd.read_sql_table("data", engine)
Out[650]:
index id Date Col_1 Col_2 Col_3
0 0 26 2010-10-18 X 27.50 True
1 1 42 2010-10-19 Y -12.50 False
2 2 63 2010-10-20 Z 5.73 True
注意
ADBC 驱动程序将数据库类型直接映射回 arrow 类型。对于其他驱动程序,请注意 pandas 从查询输出中推断列 dtype,而不是通过查找物理数据库模式中的数据类型。例如,假设userid
是表中的整数列。那么,直观地,select userid ...
将返回整数值系列,而select cast(userid as text) ...
将返回对象值(str)系列。因此,如果查询输出为空,则所有生成的列将作为对象值返回(因为它们是最一般的)。如果你预见到你的查询有时会生成��结果,你可能希望在之后明确进行类型转换以确保 dtype 的完整性。
你还可以指定列的名称作为DataFrame
索引,并指定要读取的列的子集。
In [651]: pd.read_sql_table("data", engine, index_col="id")
Out[651]:
index Date Col_1 Col_2 Col_3
id
26 0 2010-10-18 X 27.50 True
42 1 2010-10-19 Y -12.50 False
63 2 2010-10-20 Z 5.73 True
In [652]: pd.read_sql_table("data", engine, columns=["Col_1", "Col_2"])
Out[652]:
Col_1 Col_2
0 X 27.50
1 Y -12.50
2 Z 5.73
你还可以明确强制将列解析为日期:
代码语言:javascript复制In [653]: pd.read_sql_table("data", engine, parse_dates=["Date"])
Out[653]:
index id Date Col_1 Col_2 Col_3
0 0 26 2010-10-18 X 27.50 True
1 1 42 2010-10-19 Y -12.50 False
2 2 63 2010-10-20 Z 5.73 True
如果需要,你可以明确指定格式字符串,或传递给pandas.to_datetime()
的参数字典:
pd.read_sql_table("data", engine, parse_dates={"Date": "%Y-%m-%d"})
pd.read_sql_table(
"data",
engine,
parse_dates={"Date": {"format": "%Y-%m-%d %H:%M:%S"}},
)
使用has_table()
可以检查表是否存在
模式支持
通过read_sql_table()
和to_sql()
函数中的schema
关键字支持从不同模式读取和写入。但请注意,这取决于数据库类型(sqlite 没有模式)。例如:
df.to_sql(name="table", con=engine, schema="other_schema")
pd.read_sql_table("table", engine, schema="other_schema")
查询
你可以在read_sql_query()
函数中使用原始 SQL 进行查询。在这种情况下,你必须使用适合你的数据库的 SQL 变体。当使用 SQLAlchemy 时,你还可以传递数据库无关的 SQLAlchemy 表达式语言构造。
In [654]: pd.read_sql_query("SELECT * FROM data", engine)
Out[654]:
index id Date Col_1 Col_2 Col_3
0 0 26 2010-10-18 00:00:00.000000 X 27.50 1
1 1 42 2010-10-19 00:00:00.000000 Y -12.50 0
2 2 63 2010-10-20 00:00:00.000000 Z 5.73 1
当然,你可以指定一个更“复杂”的查询。
代码语言:javascript复制In [655]: pd.read_sql_query("SELECT id, Col_1, Col_2 FROM data WHERE id = 42;", engine)
Out[655]:
id Col_1 Col_2
0 42 Y -12.5
read_sql_query()
函数支持chunksize
参数。指定这个参数将返回查询结果的迭代器:
In [656]: df = pd.DataFrame(np.random.randn(20, 3), columns=list("abc"))
In [657]: df.to_sql(name="data_chunks", con=engine, index=False)
Out[657]: 20
代码语言:javascript复制In [658]: for chunk in pd.read_sql_query("SELECT * FROM data_chunks", engine, chunksize=5):
.....: print(chunk)
.....:
a b c
0 -0.395347 -0.822726 -0.363777
1 1.676124 -0.908102 -1.391346
2 -1.094269 0.278380 1.205899
3 1.503443 0.932171 -0.709459
4 -0.645944 -1.351389 0.132023
a b c
0 0.210427 0.192202 0.661949
1 1.690629 -1.046044 0.618697
2 -0.013863 1.314289 1.951611
3 -1.485026 0.304662 1.194757
4 -0.446717 0.528496 -0.657575
a b c
0 -0.876654 0.336252 0.172668
1 0.337684 -0.411202 -0.828394
2 -0.244413 1.094948 0.087183
3 1.125934 -1.480095 1.205944
4 -0.451849 0.452214 -2.208192
a b c
0 -2.061019 0.044184 -0.017118
1 1.248959 -0.675595 -1.908296
2 -0.125934 1.491974 0.648726
3 0.391214 0.438609 1.634248
4 1.208707 -1.535740 1.620399
引擎连接示例
要使用 SQLAlchemy 连接,你可以使用create_engine()
函数从数据库 URI 创建一个引擎对象。你只需要为每个要连接的数据库创建一次引擎。
from sqlalchemy import create_engine
engine = create_engine("postgresql://scott:tiger@localhost:5432/mydatabase")
engine = create_engine("mysql mysqldb://scott:tiger@localhost/foo")
engine = create_engine("oracle://scott:[[email protected]](/cdn-cgi/l/email-protection):1521/sidname")
engine = create_engine("mssql pyodbc://mydsn")
# sqlite://<nohostname>/<path>
# where <path> is relative:
engine = create_engine("sqlite:///foo.db")
# or absolute, starting with a slash:
engine = create_engine("sqlite:absolute/path/to/foo.db")
欲了解更多信息,请参阅 SQLAlchemy 文档中的示例文档
高级 SQLAlchemy 查询
你可以使用 SQLAlchemy 构造描述你的查询。
使用sqlalchemy.text()
以与后端无关的方式指定查询参数
In [659]: import sqlalchemy as sa
In [660]: pd.read_sql(
.....: sa.text("SELECT * FROM data where Col_1=:col1"), engine, params={"col1": "X"}
.....: )
.....:
Out[660]:
index id Date Col_1 Col_2 Col_3
0 0 26 2010-10-18 00:00:00.000000 X 27.5 1
如果您有数据库的 SQLAlchemy 描述,可以使用 SQLAlchemy 表达式表示 where 条件
代码语言:javascript复制In [661]: metadata = sa.MetaData()
In [662]: data_table = sa.Table(
.....: "data",
.....: metadata,
.....: sa.Column("index", sa.Integer),
.....: sa.Column("Date", sa.DateTime),
.....: sa.Column("Col_1", sa.String),
.....: sa.Column("Col_2", sa.Float),
.....: sa.Column("Col_3", sa.Boolean),
.....: )
.....:
In [663]: pd.read_sql(sa.select(data_table).where(data_table.c.Col_3 is True), engine)
Out[663]:
Empty DataFrame
Columns: [index, Date, Col_1, Col_2, Col_3]
Index: []
您可以将 SQLAlchemy 表达式与传递给 read_sql()
的参数结合使用 sqlalchemy.bindparam()
In [664]: import datetime as dt
In [665]: expr = sa.select(data_table).where(data_table.c.Date > sa.bindparam("date"))
In [666]: pd.read_sql(expr, engine, params={"date": dt.datetime(2010, 10, 18)})
Out[666]:
index Date Col_1 Col_2 Col_3
0 1 2010-10-19 Y -12.50 False
1 2 2010-10-20 Z 5.73 True
Sqlite 回退
支持使用 sqlite 而不使用 SQLAlchemy。此模式需要一个遵守 Python DB-API 的 Python 数据库适配器。
您可以这样创建连接:
代码语言:javascript复制import sqlite3
con = sqlite3.connect(":memory:")
然后发出以下查询:
代码语言:javascript复制data.to_sql("data", con)
pd.read_sql_query("SELECT * FROM data", con)
```## Google BigQuery
`pandas-gbq` 包提供了与 Google BigQuery 读写的功能。
pandas 与这个外部包集成。如果安装了 `pandas-gbq`,则可以使用 pandas 方法 `pd.read_gbq` 和 `DataFrame.to_gbq`,这将调用 `pandas-gbq` 中的相应函数。
完整文档可以在[这里](https://pandas-gbq.readthedocs.io/en/latest/)找到。 ## Stata 格式
### 写入到 Stata 格式
方法 `DataFrame.to_stata()` 将 DataFrame 写入 .dta 文件。此文件的格式版本始终为 115(Stata 12)。
```py
In [667]: df = pd.DataFrame(np.random.randn(10, 2), columns=list("AB"))
In [668]: df.to_stata("stata.dta")
Stata 数据文件具有有限的数据类型支持;只能在 .dta
文件中存储包含 244 个或更少字符的字符串,int8
, int16
, int32
, float32
和 float64
。此外,Stata 保留某些值来表示缺失数据。导出特定数据类型的非缺失值超出 Stata 允许范围的值将重新定义变量为下一个更大的大小。例如,在 Stata 中,int8
值限制在 -127 和 100 之间,因此值大于 100 的变量将触发转换为 int16
。浮点数据类型中的 nan
值存储为基本缺失数据类型(Stata 中的 .
)。
注意
无法导出整数数据类型的缺失数据值。
Stata 写入器优雅地处理其他数据类型,包括 int64
, bool
, uint8
, uint16
, uint32
,通过将其转换为可以表示数据的最小支持类型。例如,类型为 uint8
的数据将被转换为 int8
,如果所有值都小于 100(Stata 中非缺失 int8
数据的上限),或者,如果值超出此范围,则变量将被转换为 int16
。
警告
从 int64
转换为 float64
可能会导致精度损失,如果 int64
值大于 2**53。
警告
StataWriter
和 DataFrame.to_stata()
仅支持包含最多 244 个字符的固定宽度字符串,这是版本 115 dta 文件格式所施加的限制。尝试写入长度超过 244 个字符的字符串的 Stata dta 文件会引发 ValueError
。 ### 从 Stata 格式读取
顶层函数 read_stata
将读取一个 dta 文件,并返回一个 DataFrame
或一个 pandas.api.typing.StataReader
,可用于逐步读取文件。
In [669]: pd.read_stata("stata.dta")
Out[669]:
index A B
0 0 -0.165614 0.490482
1 1 -0.637829 0.067091
2 2 -0.242577 1.348038
3 3 0.647699 -0.644937
4 4 0.625771 0.918376
5 5 0.401781 -1.488919
6 6 -0.981845 -0.046882
7 7 -0.306796 0.877025
8 8 -0.336606 0.624747
9 9 -1.582600 0.806340
指定chunksize
会产生一个pandas.api.typing.StataReader
实例,可以用来一次从文件中读取chunksize
行。StataReader
对象可以用作迭代器。
In [670]: with pd.read_stata("stata.dta", chunksize=3) as reader:
.....: for df in reader:
.....: print(df.shape)
.....:
(3, 3)
(3, 3)
(3, 3)
(1, 3)
要获得更精细的控制,请使用iterator=True
并在每次调用read()
时指定chunksize
。
In [671]: with pd.read_stata("stata.dta", iterator=True) as reader:
.....: chunk1 = reader.read(5)
.....: chunk2 = reader.read(5)
.....:
目前index
被检索为一列。
参数convert_categoricals
指示是否应读取值标签并使用它们创建Categorical
变量。值标签也可以通过函数value_labels
检索,但在使用之前需要调用read()
。
参数convert_missing
指示是否应保留 Stata 中的缺失值表示。如果为False
(默认值),缺失值将表示为np.nan
。如果为True
,缺失值将使用StataMissingValue
对象表示,并且包含缺失值的列将具有object
数据类型。
注意
read_stata()
和StataReader
支持.dta 格式 113-115(Stata 10-12)、117(Stata 13)和 118(Stata 14)。
注意
��置preserve_dtypes=False
将升级为标准的 pandas 数据类型:所有整数类型为int64
,浮点数据为float64
。默认情况下,导入时保留 Stata 数据类型。
注意
所有StataReader
对象,无论是由read_stata()
(使用iterator=True
或chunksize
)创建还是手动实例化,都必须作为上下文管理器使用(例如with
语句)。虽然close()
方法可用,但不受支持。它不是公共 API 的一部分,并将在未来的某个时候被删除而没有警告。
分类数据
分类
数据可以导出为Stata数据文件,作为带有值标签的数据。导出的数据包括底层类别代码作为整数数据值和类别作为值标签。在导出时,Stata没有明确的等价Categorical
,并且关于变量是否有序的信息会丢失。
警告
Stata仅支持字符串值标签,因此在导出数据时会调用str
。使用非字符串类别导出Categorical
变量会产生警告,并且如果类别的str
表示不唯一,则可能导致信息丢失。
类似地,可以使用关键字参数convert_categoricals
(默认为True
)从Stata数据文件中导入带有值标签的Categorical
变量。关键字参数order_categoricals
(默认为True
)确定导入的Categorical
变量是否有序。
注意
在导入分类数据时,Stata 数据文件中的变量值不会被保留,因为Categorical
变量始终使用介于-1
和n-1
之间的整数数据类型,其中n
是类别数。如果需要原始值,可以通过设置convert_categoricals=False
来导入原始数据(但不包括变量标签)。原始值可以与导入的分类数据匹配,因为原始Stata数据值与导入的Categorical
变量的类别代码之间存在简单的映射:缺失值被分配代码-1
,最小的原始值被分配0
,第二小的被分配1
,依此类推,直到最大的原始值被分配代码n-1
。
注意
Stata 支持部分标记的系列。这些系列对一些数据值有值标签,但并非所有数据值都有。导入部分标记的系列将产生一个具有字符串类别的Categorical
,对于已标记的值和没有标记的值,将产生数值类别。 ## SAS 格式
顶层函数read_sas()
可以读取(但不能写入)SAS XPORT(.xpt)和 SAS7BDAT(.sas7bdat)格式文件。
SAS 文件只包含两种值类型:ASCII 文本和浮点值(通常为 8 字节,但有时被截断)。对于 xport 文件,没有自动将类型转换为整数、日期或分类变量。对于 SAS7BDAT 文件,格式代码可能允许日期变量自动转换为日期。默认情况下,整个文件被读取并返回为DataFrame
。
指定chunksize
或使用iterator=True
以获取读取器对象(XportReader
或SAS7BDATReader
),以逐步读取文件。读取器对象还具有包含有关文件及其变量的其他信息的属性。
读取一个 SAS7BDAT 文件:
代码语言:javascript复制df = pd.read_sas("sas_data.sas7bdat")
获取一个迭代器,并每次读取一个 XPORT 文件的 100,000 行:
代码语言:javascript复制def do_something(chunk):
pass
with pd.read_sas("sas_xport.xpt", chunk=100000) as rdr:
for chunk in rdr:
do_something(chunk)
可从 SAS 网站获取 xport 文件格式的规范。
没有关于 SAS7BDAT 格式的官方文档。 ## SPSS 格式
顶层函数read_spss()
可以读取(但不能写入)SPSS SAV(.sav)和 ZSAV(.zsav)格式文件。
SPSS 文件包含列名。默认情况下,整个文件被读取,分类列被转换为pd.Categorical
,并返回一个包含所有列的DataFrame
。
指定usecols
参数以获取列的子集。指定convert_categoricals=False
以避免将分类列转换为pd.Categorical
。
读取一个 SPSS 文件:
代码语言:javascript复制df = pd.read_spss("spss_data.sav")
从 SPSS 文件中提取usecols
中包含的列的子集,并避免将分类列转换为pd.Categorical
:
df = pd.read_spss(
"spss_data.sav",
usecols=["foo", "bar"],
convert_categoricals=False,
)
有关 SAV 和 ZSAV 文件格式的更多信息,请参阅此处。 ## 其他文件格式
pandas 本身仅支持与其表格数据模型清晰映射的有限一组文件格式的 IO。为了将其他文件格式读取和写入 pandas,我们建议使用来自更广泛社区的这些软件包。
netCDF
xarray提供了受到 pandas DataFrame
启发的数据结构,用于处理多维数据集,重点放在 netCDF 文件格式上,并且易于与 pandas 之间进行转换。 ## 性能考虑
这是对各种 IO 方法的非正式比较,使用 pandas 0.24.2。时间取决于机器,应忽略小差异。
代码语言:javascript复制In [1]: sz = 1000000
In [2]: df = pd.DataFrame({'A': np.random.randn(sz), 'B': [1] * sz})
In [3]: df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000000 entries, 0 to 999999
Data columns (total 2 columns):
A 1000000 non-null float64
B 1000000 non-null int64
dtypes: float64(1), int64(1)
memory usage: 15.3 MB
下面将使用以下测试函数来比较几种 IO 方法的性能:
代码语言:javascript复制import numpy as np
import os
sz = 1000000
df = pd.DataFrame({"A": np.random.randn(sz), "B": [1] * sz})
sz = 1000000
np.random.seed(42)
df = pd.DataFrame({"A": np.random.randn(sz), "B": [1] * sz})
def test_sql_write(df):
if os.path.exists("test.sql"):
os.remove("test.sql")
sql_db = sqlite3.connect("test.sql")
df.to_sql(name="test_table", con=sql_db)
sql_db.close()
def test_sql_read():
sql_db = sqlite3.connect("test.sql")
pd.read_sql_query("select * from test_table", sql_db)
sql_db.close()
def test_hdf_fixed_write(df):
df.to_hdf("test_fixed.hdf", key="test", mode="w")
def test_hdf_fixed_read():
pd.read_hdf("test_fixed.hdf", "test")
def test_hdf_fixed_write_compress(df):
df.to_hdf("test_fixed_compress.hdf", key="test", mode="w", complib="blosc")
def test_hdf_fixed_read_compress():
pd.read_hdf("test_fixed_compress.hdf", "test")
def test_hdf_table_write(df):
df.to_hdf("test_table.hdf", key="test", mode="w", format="table")
def test_hdf_table_read():
pd.read_hdf("test_table.hdf", "test")
def test_hdf_table_write_compress(df):
df.to_hdf(
"test_table_compress.hdf", key="test", mode="w", complib="blosc", format="table"
)
def test_hdf_table_read_compress():
pd.read_hdf("test_table_compress.hdf", "test")
def test_csv_write(df):
df.to_csv("test.csv", mode="w")
def test_csv_read():
pd.read_csv("test.csv", index_col=0)
def test_feather_write(df):
df.to_feather("test.feather")
def test_feather_read():
pd.read_feather("test.feather")
def test_pickle_write(df):
df.to_pickle("test.pkl")
def test_pickle_read():
pd.read_pickle("test.pkl")
def test_pickle_write_compress(df):
df.to_pickle("test.pkl.compress", compression="xz")
def test_pickle_read_compress():
pd.read_pickle("test.pkl.compress", compression="xz")
def test_parquet_write(df):
df.to_parquet("test.parquet")
def test_parquet_read():
pd.read_parquet("test.parquet")
在写入时,速度最快的三个函数是test_feather_write
,test_hdf_fixed_write
和test_hdf_fixed_write_compress
。
In [4]: %timeit test_sql_write(df)
3.29 s ± 43.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [5]: %timeit test_hdf_fixed_write(df)
19.4 ms ± 560 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [6]: %timeit test_hdf_fixed_write_compress(df)
19.6 ms ± 308 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [7]: %timeit test_hdf_table_write(df)
449 ms ± 5.61 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [8]: %timeit test_hdf_table_write_compress(df)
448 ms ± 11.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [9]: %timeit test_csv_write(df)
3.66 s ± 26.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [10]: %timeit test_feather_write(df)
9.75 ms ± 117 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [11]: %timeit test_pickle_write(df)
30.1 ms ± 229 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [12]: %timeit test_pickle_write_compress(df)
4.29 s ± 15.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [13]: %timeit test_parquet_write(df)
67.6 ms ± 706 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
在读取时,速度最快的三个函数是test_feather_read
,test_pickle_read
和test_hdf_fixed_read
。
In [14]: %timeit test_sql_read()
1.77 s ± 17.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [15]: %timeit test_hdf_fixed_read()
19.4 ms ± 436 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [16]: %timeit test_hdf_fixed_read_compress()
19.5 ms ± 222 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [17]: %timeit test_hdf_table_read()
38.6 ms ± 857 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [18]: %timeit test_hdf_table_read_compress()
38.8 ms ± 1.49 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [19]: %timeit test_csv_read()
452 ms ± 9.04 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [20]: %timeit test_feather_read()
12.4 ms ± 99.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [21]: %timeit test_pickle_read()
18.4 ms ± 191 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [22]: %timeit test_pickle_read_compress()
915 ms ± 7.48 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [23]: %timeit test_parquet_read()
24.4 ms ± 146 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
文件test.pkl.compress
,test.parquet
和test.feather
在磁盘上占用的空间最少(以字节为单位)。
29519500 Oct 10 06:45 test.csv
16000248 Oct 10 06:45 test.feather
8281983 Oct 10 06:49 test.parquet
16000857 Oct 10 06:47 test.pkl
7552144 Oct 10 06:48 test.pkl.compress
34816000 Oct 10 06:42 test.sql
24009288 Oct 10 06:43 test_fixed.hdf
24009288 Oct 10 06:43 test_fixed_compress.hdf
24458940 Oct 10 06:44 test_table.hdf
24458940 Oct 10 06:44 test_table_compress.hdf
```## CSV 和文本文件
用于读取文本文件(也称为平面文件)的主要函数是`read_csv()`。查看 cookbook 以获取一些高级策略。
### 解析选项
`read_csv()`接受以下常见参数:
#### 基本
filepath_or_buffervarious
要么是文件路径([`str`](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")),[`pathlib.Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path "(在 Python v3.12 中)"),或`py:py._path.local.LocalPath`),URL(包括 http,ftp 和 S3 位置),或具有`read()`方法的任何对象(例如打开的文件或[`StringIO`](https://docs.python.org/3/library/io.html#io.StringIO "(在 Python v3.12 中)"))。
sepstr,默认为`','`用于`read_csv()`,`t`用于`read_table()`
要使用的分隔符。如果 sep 为`None`,C 引擎无法自动检测分隔符,但 Python 解析引擎可以,这意味着将使用后者,并通过 Python 的内置 sniffer 工具[`csv.Sniffer`](https://docs.python.org/3/library/csv.html#csv.Sniffer "(in Python v3.12)")自动检测分隔符。此外,长于 1 个字符且不同于`'s '`的分隔符将被解释为正则表达式,并且还会强制使用 Python 解析引擎。请注意,正则表达式分隔符容易忽略带引号的数据。正则表达式示例:`'\r\t'`。
分隔符字符串,默认为`None`
sep 的替代参数名称。
delim_whitespace 布尔值,默认为 False
指定是否使用空格(例如`' '`或`'t'`)作为分隔符。等效于设置`sep='s '`。如果将此选项设置为`True`,则不应为`delimiter`参数传递任何内容。
#### 列和索引位置及名称
headerint 或 int 列表,默认为`'infer'`
用作列名和数据起始位置的行号。默认行为是推断列名:如果没有传递名称,则行为与`header=0`相同,并且列名从文件的第一行推断出来,如果显式传递了列名,则行为与`header=None`相同。显式传递`header=0`以能够替换现有名称。
表头可以是指定列的 MultiIndex 的行位置列表,例如`[0,1,3]`。未指定的中间行将被跳过(例如,在此示例中跳过了 2)。请注意,如果`skip_blank_lines=True`,此参数将忽略注释行和空行,因此`header=0`表示数据的第一行而不是文件的第一行。
names 数组样式,默认为`None`
要使用的列名列表。如果文件不包含表头行,则应明确传递`header=None`。不允许在此列表中存在重复项。
index_colint、str、int/str 序列或 False,可选,默认为`None`
用作`DataFrame`行标签的列,可以是字符串名称或列索引。如果给定 int/str 序列,则使用 MultiIndex。
注意
`index_col=False`可用于强制 pandas*不*将第一列用作索引,例如当您有一个每行末尾都有分隔符的格式不正确的文件时。
`None`的默认值指示 pandas 进行猜测。如果列头行中的字段数等于数据文件主体中的字段数,则使用默认索引。如果大于,则使用前几列作为索引,以使数据主体中的字段数等于列头中的字段数。
表头后的第一行用于确定列数,这些列将进入索引。如果后续行的列数少于第一行,则用`NaN`填充。
可通过 `usecols` 避免这种情况。这确保列按原样采取,并且尾随数据被忽略。
usecols 类似列表或可调用函数,默认为 `None`。
返回列的子集。如果类似列表,所有元素必须是位置的(即整数索引到文档列)或与用户在 `names` 中提供的列名对应的字符串,或从文档标题行中推断出的列名。如果给定了 `names`,则不考虑文档标题行。例如,一个有效的类似列表 `usecols` 参数可以是 `[0, 1, 2]` 或 `['foo', 'bar', 'baz']`。
元素顺序被忽略,因此 `usecols=[0, 1]` 与 `[1, 0]` 相同。要从保留元素顺序的 `data` 实例化 DataFrame,请使用 `pd.read_csv(data, usecols=['foo', 'bar'])[['foo', 'bar']]` 以 `['foo', 'bar']` 顺序的列或 `pd.read_csv(data, usecols=['foo', 'bar'])[['bar', 'foo']]` 以 `['bar', 'foo']` 顺序。
如果可调用,则将对列名评估可调用函数,返回可调用函数评估为 True 的列名:
```py
In [1]: import pandas as pd
In [2]: from io import StringIO
In [3]: data = "col1,col2,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
设置一起使用以保留并不解释 dtype。如果指定了转换器,则将应用转换器而不是 dtype 转换。
版本 1.5.0 中新增功能:支持 defaultdict。指定一个 defaultdict 作为输入,其中默认值确定未明确列出的列的 dtype。
dtype_backend{“numpy_nullable”, “pyarrow”},默认为 NumPy 支持的 DataFrames。
要使用的 dtype_backend,例如 DataFrame 是否应具有 NumPy 数组,当设置“numpy_nullable”时,所有具有可为空实现的 dtype 都使用可为空 dtype,如果设置“pyarrow”,则所有 dtype 都使用 pyarrow。
dtype_backends 仍处于实验阶段。
版本 2.0 中新增。
engine{'c'
, 'python'
, 'pyarrow'
}
要使用的解析引擎。C 和 pyarrow 引擎更快,而 python 引擎目前更完整。目前只有 pyarrow 引擎支持多线程。
版本 1.4.0 中新增功能:添加了“pyarrow”引擎作为实验性引擎,某些功能不受支持,或者在此引擎下可能无法正常工作。
转换器字典,默认为 None
。
用于在某些列中转换值的函数字典。键可以是整数或列标签。
true_values 列表,默认为 None
。
要视为 True
的值。
false_values 列表,默认为 None
。
要视为 False
的值。
skipinitialspace 布尔值,默认为 False
。
在分隔符后跳过空格。
skiprows 类型为列表或整数,默认为 None
。
要跳过的行号(从 0 开始)或要跳过的行数(int)文件开头。
如果可调用,可调用函数将针对行索引进行评估,如果应跳过该行则返回 True,否则返回 False:
代码语言: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’下不支持)。
nrowsint,默认为None
要读取的文件行数。用于读取大文件的片段。
low_memoryboolean,默认为True
在块中内部处理文件,从而在解析时降低内存使用,但可能混合类型推断。为确保没有混合类型,要么设置为False
,要么使用dtype
参数指定类型。请注意,无论如何整个文件都会读入单个DataFrame
,使用chunksize
或iterator
参数以块返回数据。(仅适用于 C 解析器)
memory_mapboolean,默认为 False
如果为filepath_or_buffer
提供了文件路径,则将文件对象直接映射到内存并直接从那里访问数据。使用��选项可以提高性能,因为不再有任何 I/O 开销。
NA 和缺失数据处理
na_valuesscalar、str、类似列表或字典,默认为None
附加字符串识别为 NA/NaN。如果传递字典,则为每列指定特定的 NA 值。有关默认解释为 NaN 的值列表,请参见 na values const。
keep_default_naboolean,默认为True
是否在解析数据时包括默认的 NaN 值。根据是否传入na_values
,行为如下:
- 如果
keep_default_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_filterboolean,默认为True
检测缺失值标记(空字符串和 na_values 的值)。在没有任何 NA 的数据中,传递na_filter=False
可以提高读取大文件的性能。
verboseboolean,默认为False
指示放置在非数字列中的 NA 值的数量。
skip_blank_linesboolean,默认为True
如果为True
,则跳过空行而不是解释为 NaN 值。
日期时间处理
parse_datesboolean 或 int 或名称列表或列表或字典,默认为False
。
- 如果为
True
-> 尝试解析索引。 - 如果为
[1, 2, 3]
-> 尝试将列 1、2、3 分别解析为单独的日期列。 - 如果为
[[1, 3]]
-> 合并列 1 和 3 并解析为单个日期列。 - 如果
{'foo': [1, 3]}
-> 将列 1、3 解析为日期并调用结果为‘foo’。
注意
存在一个针对 iso8601 格式日期的快速路径。
infer_datetime_format 布尔值,默认为False
如果为True
并且为列启用了 parse_dates,则尝试推断日期时间格式以加快处理速度。
自版本 2.0.0 起已弃用:此参数的严格版本现在是默认值,传递它没有任何效果。
keep_date_col 布尔值,默认为False
如果为True
并且 parse_dates 指定了组合多个列,则保留原始列。
date_parserfunction,默认为None
用于将一系列字符串列转换为日期时间实例数组的函数。默认使用dateutil.parser.parser
进行转换。pandas 将尝试以三种不同的方式调用 date_parser,如果发生异常,则会继续下一个:1) 将一个或多个数组(由 parse_dates 定义)作为参数传递;2) 将由 parse_dates 定义的列中的字符串值(按行)连接成单个数组并传递;3) 对每一行使用一个或多个字符串(对应于由 parse_dates 定义的列)作为参数调用 date_parser。
自版本 2.0.0 起已弃用:改用date_format
,或按object
读取,然后根据需要应用to_datetime()
。
date_formatstr 或列->格式的字典,默认为None
如果与parse_dates
一起使用,将根据此格式解析日期。对于更复杂的情况,请按object
读取,然后根据需要应用to_datetime()
。
自版本 2.0.0 起新增。
dayfirst 布尔值,默认为False
DD/MM 格式日期,国际和欧洲格式。
cache_dates 布尔值,默认为 True
如果为True
,则使用一个唯一的转换日期缓存来应用日期时间转换。在解析重复日期字符串时可能会产生显著的加速,特别是带有时区偏移的日期字符串。
迭代
iterator 布尔值,默认为False
返回用于迭代或使用get_chunk()
获取块的TextFileReader
对象。
chunksizeint,默认为None
返回用于迭代的TextFileReader
对象。请参阅下面的迭代和分块。
引用、压缩和文件格式
压缩{'infer'
,'gzip'
,'bz2'
,'zip'
,'xz'
,'zstd'
,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
。
thousandsstr,默认为None
千位分隔符。
decimalstr,默认为'.'
用作小数点的字符。例如,欧洲数据可以使用,
。
float_precisionstring,默认为 None
指定 C 引擎应使用���个转换器来处理浮点值。选项为None
表示普通转换器,high
表示高精度转换器,round_trip
表示往返转换器。
lineterminatorstr(长度为 1),默认为None
用于将文件分成行的字符。仅与 C 解析器有效。
quotecharstr(长度为 1)
用于表示引用项的开始和结束的字符。引用项可以包括分隔符,它将被忽略。
quotingint 或csv.QUOTE_*
实例,默认为0
控制字段引用行为,使用csv.QUOTE_*
常量之一。使用QUOTE_MINIMAL
(0)、QUOTE_ALL
(1)、QUOTE_NONNUMERIC
(2)或QUOTE_NONE
(3)中的一个。
doublequoteboolean,默认为True
当指定了quotechar
并且quoting
不是QUOTE_NONE
时,指示是否将字段内连续的两个quotechar
元素解释为单个quotechar
元素。
escapecharstr(长度为 1),默认为None
用于在引用为QUOTE_NONE
时转义分隔符的一个字符字符串。
commentstr,默认为None
指示不应解析行的其余部分。如果在行的开头找到,整行将被忽略。此参数必须是一个单个字符。像空行一样(只要skip_blank_lines=True
),完全注释的行由参数header
忽略,但不由skiprows
忽略。例如,如果comment='#'
,使用header=0
解析‘#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
。如果您对这些概念不熟悉,您可以在这里了解有关 dtype 的更多信息,并在这里了解有关 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()
函数强制转换 dtype,
In [21]: df2 = pd.read_csv(StringIO(data))
In [22]: df2["col_1"] = pd.to_numeric(df2["col_1"], errors="coerce")
In [23]: df2
Out[23]:
col_1
0 1.00
1 2.00
2 NaN
3 4.22
In [24]: df2["col_1"].apply(type).value_counts()
Out[24]:
col_1
<class 'float'> 4
Name: count, dtype: int64
这将将所有有效解析转换为浮点数,将无效解析保留为NaN
。
最终,如何处理包含混合 dtype 的列取决于您的具体需求。在上面的情况下,如果您想要将数据异常值设为NaN
,那么to_numeric()
可能是您最好的选择。然而,如果您希望所有数据都被强制转换,无论类型如何,那么使用read_csv()
的converters
参数肯定值得一试。
注意
在某些情况下,读取包含混合 dtype 的列的异常数据将导致数据集不一致。如果您依赖 pandas 推断列的 dtype,解析引擎将会推断数据的不同块的 dtype,而不是一次推断整个数据集的 dtype。因此,您可能会得到包含混合 dtype 的列。例如,
代码语言: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
可以通过指定`dtype='category'`或`dtype=CategoricalDtype(categories, ordered)`直接解析`Categorical`列。
```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
是数据中观察到的唯一值。要对 categories 和顺序进行更多控制,请提前创建CategoricalDtype
,并将其传递给该列的dtype
。
In [40]: from pandas.api.types import CategoricalDtype
In [41]: dtype = CategoricalDtype(["d", "c", "b", "a"], ordered=True)
In [42]: pd.read_csv(StringIO(data), dtype={"col1": dtype}).dtypes
Out[42]:
col1 category
col2 object
col3 int64
dtype: object
使用dtype=CategoricalDtype
时,dtype.categories
之外的“意外”值被视为缺失值。
In [43]: dtype = CategoricalDtype(["a", "b", "d"]) # No 'c'
In [44]: pd.read_csv(StringIO(data), dtype={"col1": dtype}).col1
Out[44]:
0 a
1 a
2 NaN
Name: col1, dtype: category
Categories (3, object): ['a', 'b', 'd']
这与Categorical.set_categories()
的行为相匹配。
注意
使用dtype='category'
,生成的 categories 将始终被解析为字符串(object dtype)。如果 categories 是数字,可以使用to_numeric()
函数进行转换,或者根据需要使用另一个转换器,如to_datetime()
。
当dtype
是具有同质categories
(全部为数字、全部为日期时间等)的CategoricalDtype
时,转换会自动完成。
In [45]: df = pd.read_csv(StringIO(data), dtype="category")
In [46]: df.dtypes
Out[46]:
col1 category
col2 category
col3 category
dtype: object
In [47]: df["col3"]
Out[47]:
0 1
1 2
2 3
Name: col3, dtype: category
Categories (3, object): ['1', '2', '3']
In [48]: new_categories = pd.to_numeric(df["col3"].cat.categories)
In [49]: df["col3"] = df["col3"].cat.rename_categories(new_categories)
In [50]: df["col3"]
Out[50]:
0 1
1 2
2 3
Name: col3, dtype: category
Categories (3, int64): [1, 2, 3]
命名和使用列
处理列名
文件可能有或没有标题行。pandas 假定第一行应该用作列名:
代码语言: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"}
。 - 如果不同列有不同格式,或者想要将任何额外选项(如
utc
)传递给to_datetime
,则应以object
dtype 读取数据,然后使用to_datetime
。
解析具有混合时区的 CSV
pandas 无法本地表示具有混合时区的列或索引。如果您的 CSV 文件包含具有混合时区的列,则默认结果将是一个对象 dtype 列,其中包含字符串,即使使用parse_dates
也是如此。要将混合时区值解析为日期时间列,请以object
dtype 读取,然后调用to_datetime()
并使用utc=True
。
In [120]: content = """
.....: a
.....: 2000-01-01T00:00:00 05:00
.....: 2000-01-01T00:00:00 06:00"""
.....:
In [121]: df = pd.read_csv(StringIO(content))
In [122]: df["a"] = pd.to_datetime(df["a"], utc=True)
In [123]: df["a"]
Out[123]:
0 1999-12-31 19:00:00 00:00
1 1999-12-31 18:00:00 00:00
Name: a, dtype: datetime64[ns, UTC]
```#### 推断日期时间格式
这里有一些可以猜测的日期时间字符串示例(均表示 2011 年 12 月 30 日 00:00:00):
“20111230”
“2011/12/30”
“20111230 00:00:00”
“12/30/2011 00:00:00”
“30/Dec/2011 00:00:00”
“30/December/2011 00:00:00”
请注意,格式推断对`dayfirst`很敏感。使用`dayfirst=True`,它会猜测“01/12/2011”为 12 月 1 日。使用`dayfirst=False`(默认),它会猜测“01/12/2011”为 1 月 12 日。
如果尝试解析日期字符串列,pandas 将尝试从第一个非 NaN 元素猜测格式,然后使用该格式解析列的其余部分。如果 pandas 无法猜测格式(例如,如果您的第一个字符串是`'01 December US/Pacific 2000'`),则会发出警告,并且每行将通过`dateutil.parser.parse`单独解析。解析日期的最安全方式是显式设置`format=`。
```py
In [124]: df = pd.read_csv(
.....: "foo.csv",
.....: index_col=0,
.....: parse_dates=True,
.....: )
.....:
In [125]: df
Out[125]:
A B C
date
2009-01-01 a 1 2
2009-01-02 b 3 4
2009-01-03 c 4 5
如果同一列中有混合的日期时间格式,可以传递format='mixed'
In [126]: data = StringIO("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')
```### NA 值
要控制哪些值被解析为缺失值(用 `NaN` 表示),请在 `na_values` 中指定一个字符串。如果您指定了一个字符串列表,那么其中的所有值都将被视为缺失值。如果您指定了一个数字(一个 `float`,比如 `5.0` 或一个 `integer`,比如 `5`),则相应的等价值也将暗示一个缺失值(在这种情况下,实际上 `[5.0, 5]` 被认为是 `NaN`)。
要完全覆盖默认被识别为缺失的值,请指定 `keep_default_na=False`。
默认的 `NaN` 被识别的值为 `['-1.#IND', '1.#QNAN', '1.#IND', '-1.#QNAN', '#N/A N/A', '#N/A', 'N/A', 'n/a', 'NA', '<NA>', '#NA', 'NULL', 'null', 'NaN', '-NaN', 'nan', '-nan', 'None', '']`。
让我们考虑一些例子:
```py
pd.read_csv("path_to_file.csv", na_values=[5])
在上面的例子中,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
解析器会尽力“做正确的事情”而不会变得脆弱。类型推断是一件很重要的事情。如果可以将列强制转换为整数 dtype 而不改变内容,则解析器将这样做。任何非数字列将像其他 pandas 对象一样以 object 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 引擎慢。但是,与 C 引擎相比,pyarrow 引擎要不那么稳定,缺少一些与 Python 引擎相比的功能。
在可能的情况下,pandas 使用 C 解析器(指定为engine='c'
),但如果指定了不受 C 支持的选项,则可能会退回到 Python。
目前,C 和 pyarrow 引擎不支持的选项包括:
-
sep
不是单个字符(例如正则表达式分隔符) -
skipfooter
-
sep=None
与delim_whitespace=False
指定上述任何选项将产生一个ParserWarning
,除非显式选择engine='python'
来选择 Python 引擎。
pyarrow 引擎不支持的选项,不在上面的列表中包括:
-
float_precision
-
chunksize
-
comment
-
nrows
-
thousands
-
memory_map
-
dialect
-
on_bad_lines
-
delim_whitespace
-
quoting
-
lineterminator
-
converters
-
decimal
-
iterator
-
dayfirst
-
infer_datetime_format
-
verbose
-
skipinitialspace
-
low_memory
使用engine='pyarrow'
指定这些选项将引发ValueError
。
读取/写入远程文件
您可以传递一个 URL 给许多 pandas 的 IO 函数来读取或写入远程文件 - 以下示例显示了读取 CSV 文件:
代码语言:javascript复制df = pd.read_csv("https://download.bls.gov/pub/time.series/cu/cu.item", sep="t")
版本 1.3.0 中的新功能。
可以通过将头键值映射的字典传递给storage_options
关键字参数来发送自定义标头,如下所示:
headers = {"User-Agent": "pandas"}
df = pd.read_csv(
"https://download.bls.gov/pub/time.series/cu/cu.item",
sep="t",
storage_options=headers
)
所有不是本地文件或 HTTP(s) 的 URL 都由fsspec处理,如果安装了它,以及其各种文件系统实现(包括 Amazon S3、Google Cloud、SSH、FTP、webHDFS…)。其中一些实现将需要安装其他软件包,例如 S3 URL 需要s3fs库:
代码语言:javascript复制df = pd.read_json("s3://pandas-test/adatafile.json")
当处理远程存储系统时,您可能需要在特殊位置的环境变量或配置文件中进行额外配置。例如,要访问您的 S3 存储桶中的数据,您需要在S3Fs documentation中列出的几种方式之一中定义凭据。对于几个存储后端,情况也是如此,您应该遵循fsspec
内置的fsimpl1和未包含在主fsspec
分发中的fsimpl2的链接。
您还可以直接将参数传递给后端驱动程序。由于fsspec
不使用AWS_S3_HOST
环境变量,因此我们可以直接定义一个包含 endpoint_url 的字典,并将对象传递给存储选项参数:
storage_options = {"client_kwargs": {"endpoint_url": "http://127.0.0.1:5555"}}}
df = pd.read_json("s3://pandas-test/test-1", storage_options=storage_options)
更多示例配置和文档可以在S3Fs documentation中找到。
如果您没有 S3 凭据,仍然可以通过指定匿名连接来访问公共数据,例如
新版本 1.2.0 中。
代码语言: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
: 是否写出列名(默认为 T