Inferir quais colunas são datetime

14

Eu tenho um enorme quadro de dados com muitas colunas, muitas das quais são do tipo datetime.datetime. O problema é que muitos também têm tipos mistos, incluindo, por exemplo, datetime.datetimevalores e Nonevalores (e potencialmente outros valores invá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

Portanto, resultando em uma objectcoluna de tipo. Isso pode ser resolvido com df.colx.fillna(pd.NaT). O problema é que o quadro de dados é muito grande para procurar colunas individuais.

Outra abordagem é usar pd.to_datetime(col, errors='coerce'), no entanto, isso será convertido para datetimemuitas colunas que contêm valores numéricos.

Eu também poderia fazer df.fillna(float('nan'), inplace=True), embora as colunas que contêm datas ainda sejam do objecttipo e ainda tenham o mesmo problema.

Que abordagem que eu poderia seguir para elenco para datetime aquelas colunas cujos valores realmente conter datetimevalores, mas também pode conter None, e eventualmente alguns valores inválidos (mencionando pois caso contrário um pd.to_datetimeem um try/ exceptcláusula faria)? Algo como uma versão flexível dopd.to_datetime(col)

yatu
fonte
O objeto é armazenado no tipo DataFrame datetime.datetimeou pandas._libs.tslibs.timestamps.Timestamp? Se o primeiro, minha recomendação seria alterar o que criou a data e hora para o tipo que pandaslida um pouco melhor.
ALollz 28/10/19
Os representantes das Nonecolunas são reais Noneou de string?
quer
Eles são None, não cordas. Potencialmente pode haver valores errados também ... @erfan
yatu
3
Então eu me pergunto, como está o modelo sql no seu banco de dados? Como o sql força certos tipos de colunas. Como você acabou com colunas de tipo misto? Você também pode mostrar uma coluna que contém datetimee contém values?
quer
11
use o analisador dateutil para adivinhar data e hora. Pode ser definido o limite de vários (digamos 5 datas) na coluna para garantir que stackoverflow.com/questions/9507648/…
7897 Serge

Respostas:

1

O principal problema que vejo é ao analisar valores numéricos.

Eu proporia convertê-los em seqüências de caracteres primeiro


Configuração

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

Solução

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
fonte
Bem, parece que isso simplifica enormemente o problema. Eu nem pensei nisso. O cenário ideal era simplesmente aplicar pd.to_datetimee coerceos erros, desde que existam muitos. O problema estava nas colunas numéricas. Mas não me ocorreu que colunas numéricas convertidas em string não sejam analisadas pelos pandas to_datetime. Muito obrigado, isso realmente ajuda!
yatu 13/11/19
4

Essa função definirá o tipo de dados de uma coluna para datetime, se algum valor na coluna corresponder ao padrão de regex (\ d {4} - \ d {2} - \ d {2}) + (por exemplo, 01-01-2019 ) Agradeça a esta resposta sobre como pesquisar por seqüência de caracteres em todas as colunas do Pandas DataFrame e o filtro que ajudou na configuração e aplicação da 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

Trabalhando com a sugestão de uso dateutil, isso pode ajudar. Ele ainda está trabalhando com a suposição de que, se houver algum valor semelhante a uma data em uma coluna, essa coluna deve ser uma data e hora. Tentei considerar diferentes métodos de iterações de dataframe que são mais rápidos. Acho que esta resposta em Como iterar sobre linhas em um DataFrame no Pandas fez um bom trabalho descrevendo-as.

Observe que dateutil.parserusará o dia ou o ano atual para quaisquer cadeias de caracteres como 'dezembro' ou 'novembro de 2019' sem valores de ano ou dia.

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]

Caso você também queira usar os valores de data dateutil.parsere hora de , você pode adicionar isto:

for col in list_of_datetime_columns:
    df[col] = df[col].apply(lambda x: parse_dates(x))
Sim, este é Rick
fonte
É uma boa idéia, mas infelizmente estou procurando algo que possa generalizar para potencialmente vários formatos diferentes de data e hora, portanto, sem codificar o formato. Aprecie o esforço embora
yatu 7/11/19
@yatu Não é um problema - eu estava trabalhando em algo que precisava disso. Gostaria de saber se você pode generalizar para todos os formatos de data e hora embora? Talvez seja necessário apenas contabilizar antecipadamente todos os formatos que você esperaria ver; ou, todos os formatos que você consideraria válidos datetime.
Sim, este é Rick
@yatu Na verdade, esse dateutilmódulo mencionado por @Serge parece que poderia ser útil.
Sim, este é Rick
@yatu, veja minha resposta atualizada. Eu costumava dateutil.parseidentificar muitos tipos diferentes de cadeias de datas.
Sim, este é Rick.
Parece bom! Não tenho muito tempo agora, vai dar uma olhada, logo que eu puder @yes
yatu