Python 数据处理(十五)

9 日期处理

9.1 指定日期列

为了更好地处理 datetime 数据,read_csv() 提供了 parse_datesdate_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 函数。如果抛出异常,则尝试下一种方式

  1. date_parser 首先使用一个或多个数组作为参数被调用,这些数组是使用 parse_dates 定义的(例如,date_parser(['2013', '2013'], ['1', '2'])

  2. 如果 1 失败了,则会调用 date_parser ,并将所有列按行连接到单个数组中(例如,date_parser(['2013 1','2013 2'])

注意,在性能方面,你应该尝试按以下顺序来解析日期

  1. 尝试使用 infer_datetime_format=True 来推断格式
  2. 如果你知道格式,可以使用 pd.to_datetime(): date_parser=lambda x: pd.to_datetime(x, format=...)
  3. 如果是非标准时间日期格式,请使用自定义 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 字符串的例子(都表示 2011123000: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_formatdayfirst 敏感。如果 dayfirst=True,它就会把 01/12/2011 认为是 121 日。dayfirst=False(默认值)将把 01/12/2011 猜测为 112

# 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")
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容