Inferir qué columnas son datetime

14

Tengo un gran marco de datos con muchas columnas, muchas de las cuales son de tipo datetime.datetime. El problema es que muchos también tienen tipos mixtos, que incluyen, por ejemplo, datetime.datetimevalores y Nonevalores (y potencialmente otros valores no válidos):

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

Por lo tanto, resulta en una objectcolumna de tipo. Esto se puede resolver con df.colx.fillna(pd.NaT). El problema es que el marco de datos es demasiado grande para buscar columnas individuales.

Otro enfoque es usar pd.to_datetime(col, errors='coerce'), sin embargo, esto se convertirá en datetimemuchas columnas que contienen valores numéricos.

También podría hacerlo df.fillna(float('nan'), inplace=True), aunque las columnas que contienen fechas todavía son de objecttipo, y todavía tendrían el mismo problema.

¿Qué enfoque podría seguir al elenco de fecha y hora a las columnas cuyos valores realmente no contener datetimevalores, sino que también podría contener None, y posiblemente algunos valores no válidos (mencionando ya que de otra una pd.to_datetimeen un try/ exceptcláusula harían)? Algo así como una versión flexible depd.to_datetime(col)

yatu
fuente
¿El objeto está almacenado en el tipo DataFrame datetime.datetimeo pandas._libs.tslibs.timestamps.Timestamp? Si lo primero, mi recomendación sería cambiar lo que haya creado la fecha y hora al tipo que se pandasmaneje un poco mejor.
ALollz
¿Están Noneen sus columnas, Nonerepresentativos reales o de cadena?
Erfan
Son None, no una cadena. Potencialmente puede haber valores erróneos también ... @erfan
Yatu
3
Entonces me pregunto, ¿cómo está el modelo SQL en su base de datos? Dado que sql fuerza ciertos tipos de columnas. ¿Cómo terminaste con columnas de tipo mixto? ¿Puedes quizás también mostrar una columna que tiene datetimey valuesen ella?
Erfan
1
use dateutil parser para adivinar datetime. Se puede establecer el umbral de varios (digamos 5 fechas) en la columna para asegurarse de stackoverflow.com/questions/9507648/…
Serge

Respuestas:

1

El principal problema que veo es al analizar valores numéricos.

Propondría convertirlos a cadenas primero


Preparar

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

Solución

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
piRSquared
fuente
Bueno, parece que esto simplifica enormemente el problema. Ni siquiera pensé en esto. El escenario ideal era simplemente aplicar pd.to_datetimey coercelos errores, ya que hay muchos. El problema fue con las columnas numéricas. Pero no se me ocurrió que las columnas numéricas convertidas en cuerdas no sean analizadas por los pandas ' to_datetime. Muchas gracias, esto realmente ayuda!
Yatu
4

Esta función establecerá el tipo de datos de una columna en fecha y hora, si algún valor en la columna coincide con el patrón de expresiones regulares (\ d {4} - \ d {2} - \ d {2}) + (por ejemplo, 2019-01-01 ) Agradezca esta respuesta sobre cómo buscar cadenas en todas las columnas y filtros Pandas DataFrame que ayudaron a configurar y aplicar la máscara.

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 partir de la sugerencia de uso dateutil, esto puede ayudar. Todavía está funcionando bajo la presunción de que si hay valores similares a una fecha en una columna, la columna debe ser una fecha y hora. Traté de considerar diferentes métodos de iteraciones de marcos de datos que son más rápidos. Creo que esta respuesta sobre Cómo iterar sobre filas en un DataFrame en Pandas hizo un buen trabajo al describirlos.

Tenga en cuenta que dateutil.parserusará el día o año actual para cualquier cadena como 'Diciembre' o 'Noviembre de 2019' sin valores de año o día.

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]

En caso de que también desee utilizar los valores de datatime de dateutil.parser, puede agregar esto:

for col in list_of_datetime_columns:
    df[col] = df[col].apply(lambda x: parse_dates(x))
Si, este es Rick
fuente
Esta es una buena idea, pero desafortunadamente estoy buscando algo que pueda generalizarse a varios formatos diferentes de fecha y hora, por lo tanto, sin codificar el formato. Sin embargo, aprecio el esfuerzo
yatu
@yatu No es un problema. Simplemente estaba trabajando en algo que necesitaba esto. Sin embargo, me pregunto si puede generalizar a todos los formatos de fecha y hora. Es posible que tenga que dar cuenta de antemano de todos los formatos que esperaría ver; o, todos los formatos que consideraría válidos de fecha y hora.
Sí, este es Rick
@yatu En realidad, ese dateutilmódulo mencionado por @Serge parece que podría ser útil.
Sí, este es Rick
@yatu, por favor vea mi respuesta actualizada. Solía dateutil.parseidentificar muchos tipos diferentes de cadenas de fecha.
Sí, este es Rick
¡Se ve bien! no tienen mucho tiempo, se echa un vistazo tan pronto como pueda @yes
Yatu