2016-07-26 19 views
1

Ich habe eine pandas Datenrahmen mit mehreren Spalten von Zeichenfolgen für Daten, mit leeren Zeichenfolgen für fehlende Daten. Zum BeispielPandas konvertieren Zeichenfolge Spalten zu Datetime, so dass fehlende, aber nicht ungültig

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}) 

Ich versuche zu:

  • Effizientes Spalten konvertieren, in der alle Strings gültige Daten oder leer in Spalten vom Typ datetime64 (mit NaT für die leeren) sind
  • ValueError anheben, wenn alle Nicht leere Zeichenfolge stimmt nicht mit dem erwarteten Format überein,

Beispiel wo ValueError angehoben werden soll:

# 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}) 

Diese Funktion tut, was ich auf Elementebene will:

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 

Ich habe jedoch read, dass String-Operationen nicht np.vectorize -d sein sollten. Ich dachte, dies effizient pandas.DataFrame.apply mit getan werden könnte, wie in:

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

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

Ich vermute, dass die TypeError hat etwas mit meiner Funktion, die ein unterschiedliches dtype zu tun, aber ich mag die Vorteile von dynamischer Typisierung nehmen und Ersetzen Sie die Zeichenfolge durch eine Datetime (es sei denn, ValueError wird erhöht) ... wie kann ich das tun?

+0

Sie können 'pd.to_datetime' einfach mit param' errors = 'coerce'' verwenden, also 'pd.to_datetime (x, errors =' coerce ')' wo 'x' ist Ihre df-Spalte – EdChum

+0

@EdChum thanks but' pd.to_datetime (dfBad ['registerDate'], errors = 'coerce') 'hebt' ValueError' nicht hervor, und ich suche nach 'ValueError' für ungültige Datumszeichenfolgen. Die Einstellung 'errors = 'coerce'' verhindert dies. – C8H10N4O2

+0

Aber der Punkt hier ist, dass Sie 'np.NaT' (Not A Time) für ungültige oder leere Zeichenfolgen erhalten und Sie können diese mit' dropna' ausfiltern. – EdChum

Antwort

1

pandas hat keine Option, die genau repliziert, was Sie wollen, hier ist eine Möglichkeit, es zu tun, die relativ effizient sein sollte.

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'] 
+0

Schön, danke. Es ist noch ein weiterer Schritt erforderlich, nämlich die guten Spalten zu identifizieren und diese zu konvertieren, wobei nur die fehlerhaften Spalten mit Ausnahme (die anderswo behandelt werden) übrig bleiben, aber ich werde das als eine Lernübung betrachten. – C8H10N4O2

1

Nur ein wenig weiter die akzeptierte Antwort zu nehmen, habe ich die Spalten aller gültigen oder fehlenden Saiten mit ihren analysiert Datetimes ersetzt und hob dann einen Fehler für die verbleibenden unparsed Spalten:

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 

Die angenommene Antwort enthält jedoch die Haupteinsicht, die darin besteht, Fehler zu NaT zu erzwingen und dann die nicht leeren aber ungültigen Strings von den leeren mit der Maske zu unterscheiden.