Inferire quali colonne sono datetime


14

Ho un enorme frame di dati con molte colonne, molte delle quali sono di tipo datetime.datetime. Il problema è che molti hanno anche tipi misti, inclusi ad esempio datetime.datetimevalori e Nonevalori (e potenzialmente altri valori non validi):

0         2017-07-06 00:00:00
1         2018-02-27 21:30:05
2         2017-04-12 00:00:00
3         2017-05-21 22:05:00
4         2018-01-22 00:00:00
                 ...         
352867    2019-10-04 00:00:00
352868                   None
352869            some_string
Name: colx, Length: 352872, dtype: object

Quindi risulta una objectcolonna di tipo. Questo può essere risolto con df.colx.fillna(pd.NaT). Il problema è che il frame di dati è troppo grande per cercare singole colonne.

Un altro approccio è quello di utilizzare pd.to_datetime(col, errors='coerce'), tuttavia questo eseguirà il cast su datetimemolte colonne che contengono valori numerici.

Potrei anche fare df.fillna(float('nan'), inplace=True), anche se le colonne contenenti le date sono ancora di objecttipo e avrebbero ancora lo stesso problema.

Quale approccio potrei seguire per trasmettere a datetime quelle colonne i cui valori in realtà contengono datetimevalori, ma potrebbero anche contenere Nonee potenzialmente alcuni valori non validi (menzionando poiché altrimenti farebbe pd.to_datetimeuna clausola in a try/ except)? Qualcosa come una versione flessibile dipd.to_datetime(col)


Gli oggetti sono memorizzati nel tipo DataFrame datetime.datetimeo pandas._libs.tslibs.timestamps.Timestamp? Se il primo la mia raccomandazione sarebbe quella di cambiare qualunque cosa abbia creato il datetime nel tipo che pandasgestisce un po 'meglio.
ALollz,

Nelle Nonetue colonne sono presenti Noneo effettivi rappresentanti di stringhe?
Erfan,

Sono None, non stringhe. Potenzialmente ci possono essere anche valori errati ... @erfan
yatu,

3
Quindi mi chiedo, come è il modello sql nel tuo database? Poiché sql forza alcuni tipi di colonne. Come sei arrivato a colonne di tipo misto? Puoi forse mostrare anche una colonna che ha datetimee valuesin essa?
Erfan,

1
utilizzare il parser dateutil per indovinare datetime. Può essere impostata la soglia di diversi (diciamo 5 date) in colonna per essere sicuri stackoverflow.com/questions/9507648/…
Serge

Risposte:


1

Il problema principale che vedo è quando analizzo i valori numerici.

Proporrei prima di convertirli in stringhe


Impostare

dat = {
    'index': [0, 1, 2, 3, 4, 352867, 352868, 352869],
    'columns': ['Mixed', 'Numeric Values', 'Strings'],
    'data': [
        ['2017-07-06 00:00:00', 1, 'HI'],
        ['2018-02-27 21:30:05', 1, 'HI'],
        ['2017-04-12 00:00:00', 1, 'HI'],
        ['2017-05-21 22:05:00', 1, 'HI'],
        ['2018-01-22 00:00:00', 1, 'HI'],
        ['2019-10-04 00:00:00', 1, 'HI'],
        ['None', 1, 'HI'],
        ['some_string', 1, 'HI']
    ]
}

df = pd.DataFrame(**dat)

df

                      Mixed  Numeric Values Strings
0       2017-07-06 00:00:00               1      HI
1       2018-02-27 21:30:05               1      HI
2       2017-04-12 00:00:00               1      HI
3       2017-05-21 22:05:00               1      HI
4       2018-01-22 00:00:00               1      HI
352867  2019-10-04 00:00:00               1      HI
352868                 None               1      HI
352869          some_string               1      HI

Soluzione

df.astype(str).apply(pd.to_datetime, errors='coerce')

                     Mixed Numeric Values Strings
0      2017-07-06 00:00:00            NaT     NaT
1      2018-02-27 21:30:05            NaT     NaT
2      2017-04-12 00:00:00            NaT     NaT
3      2017-05-21 22:05:00            NaT     NaT
4      2018-01-22 00:00:00            NaT     NaT
352867 2019-10-04 00:00:00            NaT     NaT
352868                 NaT            NaT     NaT
352869                 NaT            NaT     NaT

Bene, sembra che questo semplifichi enormemente il problema. Non ci avevo nemmeno pensato. Lo scenario ideale era semplicemente applicare pd.to_datetimee coercegli errori, poiché ce ne sono molti. Il problema era con le colonne numeriche. Ma non mi è venuto in mente che le colonne numeriche lanciate su stringa non sono analizzate dai panda to_datetime. Grazie mille, questo aiuta davvero!
yatu,

4

Questa funzione imposterà il tipo di dati di una colonna su datetime, se qualsiasi valore nella colonna corrisponde allo schema regex (\ d {4} - \ d {2} - \ d {2}) + (ad es. 2019-01-01 ). Ringraziamo questa risposta su come cercare la stringa in tutte le colonne e il filtro DataFrame di Pandas che hanno aiutato con l'impostazione e l'applicazione della maschera.

def presume_date(dataframe):
    """ Set datetime by presuming any date values in the column
        indicates that the column data type should be datetime.

    Args:
        dataframe: Pandas dataframe.

    Returns:
        Pandas dataframe.

    Raises:
        None
    """
    df = dataframe.copy()
    mask = dataframe.astype(str).apply(lambda x: x.str.match(
        r'(\d{4}-\d{2}-\d{2})+').any())
    df_dates = df.loc[:, mask].apply(pd.to_datetime, errors='coerce')
    for col in df_dates.columns:
        df[col] = df_dates[col]
    return df

A partire dal suggerimento da utilizzare dateutil, questo può essere d'aiuto. Sta ancora lavorando sulla presunzione che se ci sono valori simili a una data in una colonna, che la colonna dovrebbe essere un datetime. Ho provato a considerare diversi metodi di iterazione di frame di dati che sono più veloci. Penso che questa risposta su Come iterare su righe in un DataFrame in Pandas abbia fatto un buon lavoro nel descriverli.

Si noti che dateutil.parserutilizzerà il giorno o l'anno corrente per qualsiasi stringa come 'dicembre' o 'novembre 2019' senza valori di anno o giorno.

import pandas as pd
import datetime
from dateutil.parser import parse

df = pd.DataFrame(columns=['are_you_a_date','no_dates_here'])
df = df.append(pd.Series({'are_you_a_date':'December 2015','no_dates_here':'just a string'}), ignore_index=True)
df = df.append(pd.Series({'are_you_a_date':'February 27 2018','no_dates_here':'just a string'}), ignore_index=True)
df = df.append(pd.Series({'are_you_a_date':'May 2017 12','no_dates_here':'just a string'}), ignore_index=True)
df = df.append(pd.Series({'are_you_a_date':'2017-05-21','no_dates_here':'just a string'}), ignore_index=True)
df = df.append(pd.Series({'are_you_a_date':None,'no_dates_here':'just a string'}), ignore_index=True)
df = df.append(pd.Series({'are_you_a_date':'some_string','no_dates_here':'just a string'}), ignore_index=True)
df = df.append(pd.Series({'are_you_a_date':'Processed: 2019/01/25','no_dates_here':'just a string'}), ignore_index=True)
df = df.append(pd.Series({'are_you_a_date':'December','no_dates_here':'just a string'}), ignore_index=True)


def parse_dates(x):
    try:
        return parse(x,fuzzy=True)
    except ValueError:
        return ''
    except TypeError:
        return ''


list_of_datetime_columns = []
for row in df:
    if any([isinstance(parse_dates(row[0]),
                       datetime.datetime) for row in df[[row]].values]):
        list_of_datetime_columns.append(row)

df_dates = df.loc[:, list_of_datetime_columns].apply(pd.to_datetime, errors='coerce')

for col in list_of_datetime_columns:
    df[col] = df_dates[col]

Nel caso in cui desideri utilizzare anche i valori di datatime dateutil.parser, puoi aggiungere questo:

for col in list_of_datetime_columns:
    df[col] = df[col].apply(lambda x: parse_dates(x))

Questa è una buona idea, ma sfortunatamente sto cercando qualcosa che possa generalizzare in diversi formati di data e ora, quindi senza codificare il formato.
Apprezzo

@yatu Non è un problema - mi è capitato di lavorare su qualcosa che ne aveva bisogno. Mi chiedo se puoi generalizzare a tutti i formati di data e ora? Potrebbe essere necessario solo rendere conto in anticipo per tutti i formati che ti aspetteresti di vedere; oppure, tutti i formati che considereresti validi come datetime.
Sì, questo è Rick

@yatu In realtà quel dateutilmodulo citato da @Serge sembra che potrebbe essere utile.
Sì, questo è Rick

@yatu, per favore vedi la mia risposta aggiornata. Ho usato dateutil.parseper identificare i diversi tipi di stringhe di data.
Sì, questo è Rick

Sembra buono! non ho molto tempo ora, darò un'occhiata appena posso @yes
yatu
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.