使用 pd.to_datetime 处理多种日期时间格式

Posted

技术标签:

【中文标题】使用 pd.to_datetime 处理多种日期时间格式【英文标题】:Handling multiple datetime formats with pd.to_datetime 【发布时间】:2018-04-25 15:44:49 【问题描述】:

我有一个数据时间数据,它们的格式类似于2906201701AUG2017。 如您所见,月份位于数据中间。

当我使用pd.to_datetime 时,我想将此数据转换为日期时间,但它不起作用。

你知道解决这个问题的好方法吗?

【问题讨论】:

您的专栏有混合格式吗?如果不是,第一种格式很简单——'%d%m%Y',第二种是'%d%b%Y'。如果是,您需要在转换前进行一些预处理。 【参考方案1】:

你可以使用pd.to_datetime的格式arg:

In [11]: s = pd.Series(["29062017", "01AUG2017"])

In [12]: pd.to_datetime(s, format="%d%m%Y", errors="coerce")
Out[12]:
0   2017-06-29
1          NaT
dtype: datetime64[ns]

In [13]: pd.to_datetime(s, format="%d%b%Y", errors="coerce")
Out[13]:
0          NaT
1   2017-08-01
dtype: datetime64[ns]

注意:coerce 参数意味着失败将是NaT

然后将NaNs 从一个填入另一个,例如使用fillna:

In [14]: pd.to_datetime(s, format="%d%m%Y", errors="coerce").fillna(pd.to_datetime(s, format="%d%b%Y", errors="coerce"))
Out[14]:
0   2017-06-29
1   2017-08-01
dtype: datetime64[ns]

任何不匹配任一格式的字符串都将保留 NaT。

【讨论】:

这是一种选择。我实际上是在考虑替换后进行转换(如我的回答)。 @cᴏʟᴅsᴘᴇᴇᴅ 我敢打赌这会更快,但我认为两者都很好。 我也是这么认为的,因为基于正则表达式的替换通常很慢。我在s = pd.concat([s] * 100000) 上做了一个基准测试……有趣的是……你的是1.01s,我的是1s。并驾齐驱!如果您看到任何不同的结果,请告诉我。 添加了一些您可能会感兴趣的选项 @cᴏʟᴅsᴘᴇᴇᴅ - 很久以前 - 在 pandas 正在使用的 python 日期时间库中四处寻找(也许现在更好/不同了)......我不会再匆匆忙忙 - 日期时间处理是一个痛苦的世界。【参考方案2】:

替代方法是使用映射器和replace 将月份代码替换为对应的数字:

s = pd.Series(["29062017", "01AUG2017"]); s

0     29062017
1    01AUG2017
dtype: object

m = 'JAN' : '01', ..., 'AUG' : '08', ...  # you fill in the rest

s = s.replace(m, regex=True); s

0    29062017
1    01082017
dtype: object

现在您只需要一个pd.to_datetime 电话:

pd.to_datetime(s, format="%d%m%Y", errors="coerce")

0   2017-06-29
1   2017-08-01
dtype: datetime64[ns]

【讨论】:

添加了一些您可能会感兴趣的选项【参考方案3】:

因为你有两种类型的日期时间......

s.apply(lambda x : pd.to_datetime(x, format="%d%m%Y") if x.isdigit() else pd.to_datetime(x, format="%d%b%Y"))

Out[360]: 
0   2017-06-29
1   2017-08-01
dtype: datetime64[ns]

【讨论】:

添加了一些您可能会感兴趣的选项 @piRSquared 如果有超过 3 种类型,我会更喜欢你的方法 :-) s.apply(lambda x : pd.to_datetime(x, format="%d%%Y".format("m" if x.isdigit() else "b")))【参考方案4】:

我想权衡一些选择

设置

m = dict(
    JAN='01', FEB='02', MAR='03', APR='04',
    MAY='05', JUN='06', JUL='07', AUG='08',
    SEP='09', OCT='10', NOV='11', DEC='12'
)

m2 = m.copy()
m2.update(v: v for v in m.values())

f = lambda x: m.get(x, x)

选项 1 列表理解

pd.Series(
    pd.to_datetime(
        [x[:2] + f(x[2:5]) + x[5:] for x in s.values.tolist()],
        format='%d%m%Y'),
    s.index)

0   2017-06-29
1   2017-08-01
dtype: datetime64[ns]

选项 2 创建数据框

pd.to_datetime(
    pd.DataFrame(dict(
        day=s.str[:2],
        year=s.str[-4:],
        month=s.str[2:-4].map(m2)
    )))

0   2017-06-29
1   2017-08-01
dtype: datetime64[ns]

选项 2B 创建数据框

pd.to_datetime(
    pd.DataFrame(dict(
        day=s.str[:2],
        year=s.str[-4:],
        month=s.str[2:-4].map(f)
    )))

0   2017-06-29
1   2017-08-01
dtype: datetime64[ns]

选项 2C 创建数据框 我估计这是最快的

pd.to_datetime(
    pd.DataFrame(dict(
        day=s.str[:2].astype(int),
        year=s.str[-4:].astype(int),
        month=s.str[2:-4].map(m2).astype(int)
    )))

0   2017-06-29
1   2017-08-01
dtype: datetime64[ns]

测试

s = pd.Series(["29062017", "01AUG2017"] * 100000)

%timeit pd.to_datetime(s.replace(m, regex=True), format='%d%m%Y')
%timeit pd.to_datetime(s.str[:2] + s.str[2:5].replace(m) + s.str[5:], format='%d%m%Y')
%timeit pd.to_datetime(s.str[:2] + s.str[2:5].map(f) + s.str[5:], format='%d%m%Y')
%timeit pd.to_datetime(s, format='%d%m%Y', errors='coerce').fillna(pd.to_datetime(s, format='%d%b%Y', errors='coerce'))
%timeit pd.Series(pd.to_datetime([x[:2] + f(x[2:5]) + x[5:] for x in s.values.tolist()], format='%d%m%Y'), s.index)
%timeit pd.to_datetime(pd.DataFrame(dict(day=s.str[:2], year=s.str[-4:], month=s.str[2:-4].map(m2))))
%timeit pd.to_datetime(pd.DataFrame(dict(day=s.str[:2], year=s.str[-4:], month=s.str[2:-4].map(f))))
%timeit pd.to_datetime(pd.DataFrame(dict(day=s.str[:2].astype(int), year=s.str[-4:].astype(int), month=s.str[2:-4].map(m2).astype(int))))

1.39 s ± 24 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
690 ms ± 17.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
613 ms ± 13.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
533 ms ± 14.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
529 ms ± 8.04 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
557 ms ± 13 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
607 ms ± 26.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
328 ms ± 31.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

【讨论】:

我没有看到 pd.to_datetime 可以采用 DataFrame,我猜它已经被 cythonized 并且 datetime 解析器(可悲)仍然是 python?我很惊讶它是最快的!【参考方案5】:

这是我对这个问题的解决方案:

def set_date(col):
    # date_formates = ["21 June, 2018", "12/11/2018 09:15:32", "April-21" ]
    date_formats = ["%d %B, %Y", "%d/%m/%Y %H:%M:%S", "%B-%y", "%d %B, %Y", "%m/%d/Y"] # Can add different date formats to this list to test
    for x in date_formats:
        col = pd.to_datetime(col, errors="ignore", format= f"x")

    col = pd.to_datetime(col, errors="coerce") # To remove errors in the columns like strings or numbers
    return col

【讨论】:

以上是关于使用 pd.to_datetime 处理多种日期时间格式的主要内容,如果未能解决你的问题,请参考以下文章

pandas中对日期型数据进行处理

如何使用 pd.to_datetime() 设置频率?

时间转换py.datetime & pd.to_datetime

pd.datetime( )和pd.to_datetime( )

pd.to_datetime 上的未知字符串格式

为啥 pandas 在调用 pd.to_datetime() 时返回时间戳而不是 datetime 对象?