【问题标题】:pandas convert string columns to datetime, allowing missing but not invalidpandas 将字符串列转换为日期时间,允许缺失但不无效
【发布时间】:2016-07-26 15:36:24
【问题描述】:

我有一个pandas 数据框,其中包含多列表示日期的字符串,空字符串表示缺失的日期。例如

import numpy as np
import pandas as pd

# expected date format is 'm/%d/%Y'

custId = np.array(list(range(1,6)))
eventDate = np.array(["06/10/1992","08/24/2012","04/24/2015","","10/14/2009"])
registerDate = np.array(["06/08/2002","08/20/2012","04/20/2015","","10/10/2009"])

# both date columns of dfGood should convert to datetime without error
dfGood = pd.DataFrame({'custId':custId, 'eventDate':eventDate, 'registerDate':registerDate}) 

我正在尝试:

  • 高效地将所有字符串均为有效日期或为空的列转换为datetime64 类型的列(NaT 为空)
  • 当任何非空字符串不符合预期格式时,引发ValueError

应该提出ValueError 的示例:

# 2nd string invalid
registerDate = np.array(["06/08/2002","20/08/2012","04/20/2015","","10/10/2009"]) 
# eventDate column should convert, registerDate column should raise ValueError
dfBad = pd.DataFrame({'custId':custId, 'eventDate':eventDate, 'registerDate':registerDate})

这个函数在元素级别做我想要的:

from datetime import datetime

def parseStrToDt(s, format = '%m/%d/%Y'):
    """Parse a string to datetime with the supplied format."""
    return pd.NaT if s=='' else datetime.strptime(s, format)

print(parseStrToDt("")) # correctly returns NaT
print(parseStrToDt("12/31/2011")) # correctly returns 2011-12-31 00:00:00
print(parseStrToDt("12/31/11")) # correctly raises ValueError

但是,我有read 认为字符串操作不应该是np.vectorize-d。我认为这可以使用pandas.DataFrame.apply 有效地完成,如:

dfGood[['eventDate','registerDate']].applymap(lambda s: parseStrToDt(s)) # raises TypeError

dfGood.loc[:,'eventDate'].apply(lambda s: parseStrToDt(s)) # raises same TypeError

我猜测TypeError 与我的函数返回不同的dtype 有关,但我确实想利用动态类型并将字符串替换为日期时间(除非引发 ValueError)。 ..那么我该怎么做呢?

【问题讨论】:

  • 您可以使用pd.to_datetime 和参数errors='coerce' 所以pd.to_datetime(x, errors='coerce') 其中x 是您的df 列
  • @EdChum 谢谢,但pd.to_datetime(dfBad['registerDate'], errors='coerce') 不会引发ValueError,我希望在无效日期字符串上引发ValueError。设置 errors='coerce' 可以防止这种情况发生。
  • 但这里的重点是,对于无效或空字符串,您将获得 np.NaT(不是时间),您可以使用 dropna 过滤掉这些字符串
  • @EdChum 我知道空字符串和 想要 转到np.NaT 和无效字符串之间存在区别,这是我不希望和想要的提出ValueError,如果它们被发现,在问题标题中引用并在示例中显示parseStrToDt

标签: python numpy pandas python-datetime


【解决方案1】:

pandas 没有完全复制您想要的选项,这是一种方法,应该相对有效。

In [4]: dfBad
Out[4]: 
   custId   eventDate registerDate
0       1  06/10/1992   06/08/2002
1       2  08/24/2012   20/08/2012
2       3  04/24/2015   04/20/2015
3       4                         
4       5  10/14/2009   10/10/2009

In [7]: cols
Out[7]: ['eventDate', 'registerDate']

In [9]: dts = dfBad[cols].apply(lambda x: pd.to_datetime(x, errors='coerce', format='%m/%d/%Y'))

In [10]: dts
Out[10]: 
   eventDate registerDate
0 1992-06-10   2002-06-08
1 2012-08-24          NaT
2 2015-04-24   2015-04-20
3        NaT          NaT
4 2009-10-14   2009-10-10

In [11]: mask = pd.isnull(dts) & (dfBad[cols] != '')

In [12]: mask
Out[12]: 
  eventDate registerDate
0     False        False
1     False         True
2     False        False
3     False        False
4     False        False


In [13]: mask.any()
Out[13]: 
eventDate       False
registerDate     True
dtype: bool

In [14]: is_bad = mask.any()

In [23]: if is_bad.any():
    ...:     raise ValueError("bad dates in col(s) {0}".format(is_bad[is_bad].index.tolist()))
    ...: else:
    ...:     df[cols] = dts
    ...:     
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-23-579c06ce3c77> in <module>()
      1 if is_bad.any():
----> 2     raise ValueError("bad dates in col(s) {0}".format(is_bad[is_bad].index.tolist()))
      3 else:
      4     df[cols] = dts
      5 

ValueError: bad dates in col(s) ['registerDate']

【讨论】:

  • 很好,谢谢。还需要一个步骤,即识别好列并转换它们,只留下坏列例外(将在其他地方处理),但我将把它作为一个学习练习。
【解决方案2】:

为了进一步接受已接受的答案,我将所有有效或缺失字符串的列替换为其解析的日期时间,然后为剩余的未解析列引发错误:

dtCols = ['eventDate', 'registerDate']
dts = dfBad[dtCols].apply(lambda x: pd.to_datetime(x, errors='coerce', format='%m/%d/%Y'))

mask = pd.isnull(dts) & (dfBad[dtCols] != '')
colHasError = mask.any()

invalidCols = colHasError[colHasError].index.tolist() 
validCols = list(set(dtCols) - set(invalidCols))

dfBad[validCols] = dts[validCols] # replace the completely valid/empty string cols with dates
if colHasError.any():
    raise ValueError("bad dates in col(s) {0}".format(invalidCols))
# raises:  ValueError: bad dates in col(s) ['registerDate']

print(dfBad) # eventDate got converted, registerDate didn't

不过,接受的答案包含主要见解,即继续将错误强制转换为 NaT,然后将非空但无效的字符串与带有掩码的空字符串区分开来。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-06-28
    • 2022-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-25
    • 2022-09-23
    相关资源
    最近更新 更多