Déduire quelles colonnes sont datetime

14

J'ai une énorme trame de données avec de nombreuses colonnes, dont beaucoup sont de type datetime.datetime. Le problème est que beaucoup ont également des types mixtes, y compris par exemple des datetime.datetimevaleurs et des Nonevaleurs (et potentiellement d'autres valeurs invalides):

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

D'où une objectcolonne de type. Cela peut être résolu avec df.colx.fillna(pd.NaT). Le problème est que la trame de données est trop grande pour rechercher des colonnes individuelles.

Une autre approche consiste à utiliser pd.to_datetime(col, errors='coerce'), mais cela transtypera à de datetimenombreuses colonnes qui contiennent des valeurs numériques.

Je pourrais aussi le faire df.fillna(float('nan'), inplace=True), même si les colonnes contenant les dates sont toujours de objecttype et auraient toujours le même problème.

Quelle approche pourrais-je suivre pour convertir en datetime les colonnes dont les valeurs contiennent vraiment des datetimevaleurs, mais pourraient également contenir None, et potentiellement des valeurs invalides (en mentionnant qu'autrement une clause pd.to_datetimedans une try/ exceptferait)? Quelque chose comme une version flexible depd.to_datetime(col)

yatu
la source
L'objet est-il stocké dans le type DataFrame datetime.datetimeou pandas._libs.tslibs.timestamps.Timestamp? Si l'ancien ma recommandation serait de changer tout ce qui a créé le datetime au type qui pandasgère un peu mieux.
ALollz
Est-ce que Nonedans vos colonnes, des Nonereprésentants réels ou des chaînes de caractères?
Erfan
Ce ne sont Nonepas des ficelles. Potentiellement , il peut y avoir de mauvaises valeurs aussi ... @erfan
YATU
3
Alors je me demande, comment est le modèle sql dans votre base de données? Puisque sql force certains types de colonnes. Comment vous êtes-vous retrouvé avec des colonnes de type mixte? Pouvez-vous peut-être aussi montrer une colonne qui a datetimeet valuesen elle?
Erfan
1
utilisez l'analyseur de dateutil pour deviner datetime. Peut être fixé un seuil de plusieurs (disons 5 dates) dans la colonne pour être sûr stackoverflow.com/questions/9507648/…
Serge

Réponses:

1

Le principal problème que je vois est lors de l'analyse des valeurs numériques.

Je proposerais d'abord de les convertir en chaînes


Installer

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

Solution

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
la source
Eh bien, il semble que cela simplifie énormément le problème. Je n'y ai même pas pensé. Le scénario idéal était de simplement appliquer pd.to_datetimeet coerceles erreurs, car il y en a beaucoup. Le problème était avec les colonnes numériques. Mais il ne m'est pas venu à l'esprit que les colonnes numériques converties en chaîne ne sont pas analysées par les pandas to_datetime. Merci beaucoup, cela aide vraiment!
yatu
4

Cette fonction définira le type de données d'une colonne sur datetime, si une valeur de la colonne correspond au modèle d'expression régulière (\ d {4} - \ d {2} - \ d {2}) + (par exemple 2019-01-01 ). Merci à cette réponse sur la façon de rechercher une chaîne dans toutes les colonnes Pandas DataFrame et de filtrer qui a aidé à définir et à appliquer le masque.

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

En partant de la suggestion d'utilisation dateutil, cela peut vous aider. Il fonctionne toujours sur la présomption que s'il y a des valeurs de type date dans une colonne, que la colonne doit être un datetime. J'ai essayé de considérer différentes méthodes d'itérations de trame de données qui sont plus rapides. Je pense que cette réponse sur Comment itérer sur les lignes d'un DataFrame dans Pandas a fait un bon travail pour les décrire.

Notez que dateutil.parserle jour ou l'année en cours sera utilisé pour toutes les chaînes telles que «décembre» ou «novembre 2019» sans valeur d'année ou de jour.

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]

Dans le cas où vous souhaitez également utiliser les valeurs de datatime de dateutil.parser, vous pouvez ajouter ceci:

for col in list_of_datetime_columns:
    df[col] = df[col].apply(lambda x: parse_dates(x))
Oui c'est Rick
la source
C'est une bonne idée, mais malheureusement je cherche quelque chose qui peut se généraliser à potentiellement plusieurs formats de datetime différents, donc sans coder en dur le format. Appréciez l'effort cependant
yatu
@yatu Pas de problème - il se trouve que je travaillais sur quelque chose qui en avait besoin. Je me demande si vous pouvez généraliser à tous les formats datetime cependant? Vous devrez peut-être simplement tenir compte à l'avance de tous les formats que vous attendez à voir; ou, tous les formats que vous considéreriez comme des datetime valides.
Oui, c'est Rick
@yatu En fait, ce dateutilmodule mentionné par @Serge semble être utile.
Oui, c'est Rick
@yatu s'il vous plaît voir ma réponse mise à jour. J'avais l'habitude dateutil.parsed'identifier de nombreux types de chaînes de dates.
Oui, c'est Rick
Cela semble bon! ne pas beaucoup de temps maintenant, va jeter un oeil dès que je peux @yes
YATU