Python 数据处理(十五)
9 日期处理
9.1 指定日期列
为了更好地处理 datetime
数据,read_csv()
提供了 parse_dates
和 date_parser
关键字参数来指定日期/时间格式,以便将输入文本数据转换为 datetime
对象
最简单的一种情况是只传递 parse_dates=True
# Use a column as an index, and parse it as dates.
In [97]: df = pd.read_csv("foo.csv", index_col=0, parse_dates=True)
In [98]: df
Out[98]:
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 [99]: df.index
Out[99]: DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03'], dtype='datetime64[ns]', name='date', freq=None)
通常情况下,我们可能希望单独存储日期和时间数据,或者单独存储各种日期字段。
parse_dates
参数可用于指定要从中解析日期和/或时间的列组合
您可以为 parse_dates
指定一组列,组合后的日期列将被添加到输出结果的前面(为了不影响现有的列顺序),新的列名将是以 _
连接的列名
In [100]: print(open("tmp.csv").read())
KORD,19990127, 19:00:00, 18:56:00, 0.8100
KORD,19990127, 20:00:00, 19:56:00, 0.0100
KORD,19990127, 21:00:00, 20:56:00, -0.5900
KORD,19990127, 21:00:00, 21:18:00, -0.9900
KORD,19990127, 22:00:00, 21:56:00, -0.5900
KORD,19990127, 23:00:00, 22:56:00, -0.5900
In [101]: df = pd.read_csv("tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]])
In [102]: df
Out[102]:
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 [103]: df = pd.read_csv(
.....: "tmp.csv", header=None, parse_dates=[[1, 2], [1, 3]], keep_date_col=True
.....: )
.....:
In [104]: df
Out[104]:
1_2 1_3 0 1 2 3 4
0 1999-01-27 19:00:00 1999-01-27 18:56:00 KORD 19990127 19:00:00 18:56:00 0.81
1 1999-01-27 20:00:00 1999-01-27 19:56:00 KORD 19990127 20:00:00 19:56:00 0.01
2 1999-01-27 21:00:00 1999-01-27 20:56:00 KORD 19990127 21:00:00 20:56:00 -0.59
3 1999-01-27 21:00:00 1999-01-27 21:18:00 KORD 19990127 21:00:00 21:18:00 -0.99
4 1999-01-27 22:00:00 1999-01-27 21:56:00 KORD 19990127 22:00:00 21:56:00 -0.59
5 1999-01-27 23:00:00 1999-01-27 22:56:00 KORD 19990127 23:00:00 22:56:00 -0.59
注意,如果您希望将多个列合并为一个日期列,则必须使用嵌套列表。
也就是说,parse_dates=[1,2]
表示第二和第三列应该分别解析为单独的日期列,而 parse_dates=[[1,2]]
表示这两列应该解析为一个单独的列
你也可以使用字典来自定义组合的名称列
In [105]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}
In [106]: df = pd.read_csv("tmp.csv", header=None, parse_dates=date_spec)
In [107]: df
Out[107]:
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 [108]: date_spec = {"nominal": [1, 2], "actual": [1, 3]}
In [109]: df = pd.read_csv(
.....: "tmp.csv", header=None, parse_dates=date_spec, index_col=0
.....: ) # index is the nominal column
.....:
In [110]: df
Out[110]:
actual 0 4
nominal
1999-01-27 19:00:00 1999-01-27 18:56:00 KORD 0.81
1999-01-27 20:00:00 1999-01-27 19:56:00 KORD 0.01
1999-01-27 21:00:00 1999-01-27 20:56:00 KORD -0.59
1999-01-27 21:00:00 1999-01-27 21:18:00 KORD -0.99
1999-01-27 22:00:00 1999-01-27 21:56:00 KORD -0.59
1999-01-27 23:00:00 1999-01-27 22:56:00 KORD -0.59
注意:
如果列或索引包含无法解析的日期,则整个列或索引将不变地作为 object
数据类型返回。
对于非标准日期时间解析,请在 pd.read_csv
之后使用 to_datetime()
解析
9.2 日期解析函数
解析器允许您指定一个自定义的 date_parser
函数,以充分利用日期解析 API
的灵活性
In [111]: df = pd.read_csv(
.....: "tmp.csv", header=None, parse_dates=date_spec, date_parser=pd.to_datetime
.....: )
.....:
In [112]: df
Out[112]:
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
pandas
将尝试以三种不同的方式调用 date_parser
函数。如果抛出异常,则尝试下一种方式
-
date_parser
首先使用一个或多个数组作为参数被调用,这些数组是使用parse_dates
定义的(例如,date_parser(['2013', '2013'], ['1', '2'])
) -
如果
1
失败了,则会调用date_parser
,并将所有列按行连接到单个数组中(例如,date_parser(['2013 1','2013 2'])
)
注意,在性能方面,你应该尝试按以下顺序来解析日期
- 尝试使用
infer_datetime_format=True
来推断格式 - 如果你知道格式,可以使用
pd.to_datetime()
:date_parser=lambda x: pd.to_datetime(x, format=...)
- 如果是非标准时间日期格式,请使用自定义
date_parser
函数
9.3 解析带有混合时区的 CSV
pandas
本身不能表示带有混合时区的列或索引。如果 CSV
文件包含混合时区的列,则默认结果将是一个字符串类型的列,即使使用了 parse_dates
也一样
In [113]: content = """\
.....: a
.....: 2000-01-01T00:00:00+05:00
.....: 2000-01-01T00:00:00+06:00"""
.....:
In [114]: df = pd.read_csv(StringIO(content), parse_dates=["a"])
In [115]: df["a"]
Out[115]:
0 2000-01-01 00:00:00+05:00
1 2000-01-01 00:00:00+06:00
Name: a, dtype: object
要将混合时区值解析为 datetime
类型,需要传递一个部分应用的 to_datetime()
,且设置参数 utc=True
作为 date_parser
In [116]: df = pd.read_csv(
.....: StringIO(content),
.....: parse_dates=["a"],
.....: date_parser=lambda col: pd.to_datetime(col, utc=True),
.....: )
.....:
In [117]: df["a"]
Out[117]:
0 1999-12-31 19:00:00+00:00
1 1999-12-31 18:00:00+00:00
Name: a, dtype: datetime64[ns, UTC]
9.4 推断 datetime 格式
如果您为部分或所有列启用了 parse_dates
,并且 datetime
字符串都是相同的格式,那么通过设置 infer_datetime_format=True
,可以大大提升解析速度。
如果无法猜测格式,或者猜测的格式不能正确解析整个字符串列,将会使用通用的解析方式
下面是一些可以猜测的 datetime
字符串的例子(都表示 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
注意:infer_datetime_format
对 dayfirst
敏感。如果 dayfirst=True
,它就会把 01/12/2011
认为是 12
月 1
日。dayfirst=False
(默认值)将把 01/12/2011
猜测为 1
月 12
日
# Try to infer the format for the index column
In [118]: df = pd.read_csv(
.....: "foo.csv",
.....: index_col=0,
.....: parse_dates=True,
.....: infer_datetime_format=True,
.....: )
.....:
In [119]: df
Out[119]:
A B C
date
2009-01-01 a 1 2
2009-01-02 b 3 4
2009-01-03 c 4 5
9.5 国际日期格式
虽然美国的日期格式往往是 MM/DD/YYYY
,但许多国际格式使用 DD/MM/YYYY
代替。为方便起见,提供了 dayfirst
关键字
In [120]: print(open("tmp.csv").read())
date,value,cat
1/6/2000,5,a
2/6/2000,10,b
3/6/2000,15,c
In [121]: pd.read_csv("tmp.csv", parse_dates=[0])
Out[121]:
date value cat
0 2000-01-06 5 a
1 2000-02-06 10 b
2 2000-03-06 15 c
In [122]: pd.read_csv("tmp.csv", dayfirst=True, parse_dates=[0])
Out[122]:
date value cat
0 2000-06-01 5 a
1 2000-06-02 10 b
2 2000-06-03 15 c
9.6 将 CSV 写入二进制文件对象
df.to_csv(..., mode="wb")
允许将 CSV
写入打开的二进制模式的文件对象。
在大多数情况下,无需指定模式,因为 Pandas
会自动检测文件对象是以文本模式还是二进制模式打开的
In [123]: import io
In [124]: data = pd.DataFrame([0, 1, 2])
In [125]: buffer = io.BytesIO()
In [126]: data.to_csv(buffer, encoding="utf-8", compression="gzip")