Python 将字符串列转换为datetime,允许缺少但不无效

Python 将字符串列转换为datetime,允许缺少但不无效,python,numpy,pandas,python-datetime,Python,Numpy,Pandas,Python Datetime,我有一个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

我有一个
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
但是,我知道字符串操作不应该是
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
有关,但我确实想利用动态键入并用datetime替换字符串(除非ValueError被提升)。。。那我该怎么做呢

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']
[4]中的
:dfBad
出[4]:
客户ID事件日期注册日期
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
在[7]中:cols
Out[7]:['eventDate','registerDate']
在[9]中:dts=dfBad[cols].apply(lambda x:pd.to_datetime(x,errors='concurve',format='%m/%d/%Y'))
In[10]:dts
出[10]:
事件日期注册日期
0 1992-06-10   2002-06-08
2012年8月24日NaT
2 2015-04-24   2015-04-20
3纳特纳特
4 2009-10-14   2009-10-10
在[11]中:mask=pd.isnull(dts)和(dfBad[cols]!=“”)
在[12]中:掩码
出[12]:
事件日期注册日期
0假假
1假真
2假假
3假假
4假假
在[13]中:mask.any()
出[13]:
事件日期错误
注册日期为真
数据类型:bool
[14]:is_bad=mask.any()
在[23]:if是坏的。any():
…:raise VALUERROR(“列{0}中的错误日期”。格式(is_bad[is_bad]。index.tolist())
…:其他:
…:df[cols]=dts
...:     
---------------------------------------------------------------------------
ValueError回溯(最近一次调用上次)
在()
1如果是坏的。任何()
---->2 raise VALUERROR(“列{0}中的错误日期”。格式(is_bad[is_bad]。index.tolist())
3其他:
4 df[cols]=dts
5.
ValueError:列['registerDate'中的日期不正确

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

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
,然后用掩码将非空但无效的字符串与空字符串区分开来。

您只需使用
pd.to\u datetime
参数
errors='concurve'
so
pd.to\u datetime(x,errors='concurve'))
其中
x
是您的dfcolumn@EdChum谢谢你,但是我想对无效的日期字符串提出
ValueError
,我想对无效的日期字符串提出
ValueError
。设置
errors='concurve'
可以防止出现这种情况。但这里的要点是,对于无效或空字符串,您将获得
np.NaT
(不是时间),您可以使用
dropna
@EdChum将其过滤掉。空字符串与无效字符串之间存在差异,我知道并希望转到
np.NaT
,如问题标题中所述以及示例
parseStrToDt
Nice中所示,如果找到它们,我不希望也不想提出它们,谢谢。还有一个步骤需要做,那就是识别好的列并转换它们,只留下坏的列(将在其他地方处理),但我将把它作为一个学习练习。