Ustal, które kolumny są datetime

14

Mam ogromną ramkę danych z wieloma kolumnami, z których wiele jest typu datetime.datetime. Problem polega na tym, że wiele z nich ma również typy mieszane, w tym na przykład datetime.datetimewartości i Nonewartości (i potencjalnie inne nieprawidłowe wartości):

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

W rezultacie powstaje objectkolumna typu. Można to rozwiązać za pomocą df.colx.fillna(pd.NaT). Problem polega na tym, że ramka danych jest zbyt duża, aby wyszukiwać pojedyncze kolumny.

Można też zastosować inne podejście pd.to_datetime(col, errors='coerce'), ale spowoduje to rzutowanie na datetimewiele kolumn zawierających wartości liczbowe.

Mógłbym to zrobić df.fillna(float('nan'), inplace=True), chociaż kolumny zawierające daty są nadal objecttypu i nadal miałyby ten sam problem.

Co mogę śledzić podejście do obsady do datetime te kolumny, których wartości naprawdę zawierają datetimewartości, ale może też zawierać Nonei potencjalnie kilka nieprawidłowych wartości (podając ponieważ w przeciwnym razie pd.to_datetimew sposób try/ exceptklauzula zrobi)? Coś w rodzaju elastycznej wersjipd.to_datetime(col)

yatu
źródło
Czy obiekt jest przechowywany w typie DataFrame datetime.datetimelub pandas._libs.tslibs.timestamps.Timestamp? Jeśli to pierwsze, zalecałbym zmianę cokolwiek, co stworzyło datetime, na typ, który pandasradzi sobie nieco lepiej.
ALollz
Czy Nonew Twoich kolumnach znajdują się rzeczywiste Noneczy łańcuchowe reprezentacje tego?
Erfan
Nie są Nonesznurkiem. Potencjalnie mogą być również błędne wartości ... @erfan
yatu
3
Zastanawiam się, w jaki sposób model sql w twojej bazie danych? Ponieważ sql wymusza określone typy kolumn. Jak skończyły się kolumny typu mieszanego? Czy może też pokazać kolumnę, która ma datetimei valuesw nim?
Erfan
1
użyj parsera dateutil, aby odgadnąć datę / godzinę. Można ustawić próg kilku (powiedzmy 5 dat) w kolumnie, aby mieć pewność, że stackoverflow.com/questions/9507648/...
Serge

Odpowiedzi:

1

Główny problem, jaki widzę, dotyczy analizy wartości liczbowych.

Proponuję najpierw przekonwertować je na łańcuchy


Ustawiać

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

Rozwiązanie

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
źródło
Wygląda na to, że to bardzo upraszcza problem. Nawet o tym nie myślałem. Idealnym scenariuszem było po prostu zastosowanie pd.to_datetimei coercebłędy, ponieważ jest ich wiele. Problem dotyczył kolumn numerycznych. Ale nie przyszło mi do głowy, że kolumny liczbowe rzutowane na ciąg nie są analizowane przez pandy to_datetime. Dzięki bardzo, to naprawdę pomaga!
yatu
4

Ta funkcja ustawi typ danych kolumny na datetime, jeśli jakakolwiek wartość w kolumnie odpowiada wzorowi wyrażeń regularnych (\ d {4} - \ d {2} - \ d {2}) + (np. 01.01.2019 ). Podziękowania dla tej odpowiedzi dotyczącej wyszukiwania ciągu we wszystkich kolumnach Pandas DataFrame i filtrowania, które pomogły w ustawieniu i zastosowaniu maski.

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

dateutilMoże to pomóc w pracy z sugestią użycia . Nadal działa założenie, że jeśli w kolumnie znajdują się wartości podobne do daty, kolumna powinna być datą / godziną. Próbowałem rozważyć różne metody iteracji ramek danych, które są szybsze. Myślę, że ta odpowiedź na temat iteracji po wierszach w DataFrame w Pandach dobrze je opisała.

Pamiętaj, że dateutil.parserużyje bieżącego dnia lub roku dla dowolnych ciągów, takich jak „grudzień” lub „listopad 2019”, bez wartości roku lub dnia.

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]

Jeśli chcesz również użyć wartości datatime dateutil.parser, możesz dodać to:

for col in list_of_datetime_columns:
    df[col] = df[col].apply(lambda x: parse_dates(x))
Tak, to jest Rick
źródło
To fajny pomysł, ale niestety szukam czegoś, co można by uogólnić na potencjalnie kilka różnych formatów daty i godziny, więc bez twardego kodowania formatu. Doceń wysiłek
yatu
@yatu Nie ma problemu - akurat pracowałem nad czymś, co tego potrzebowało. Zastanawiam się jednak, czy możesz uogólnić na wszystkie formaty datetime? Być może będziesz musiał z wyprzedzeniem uwzględnić wszystkie formaty, których możesz się spodziewać; lub wszystkie formaty, które uważasz za ważne w danych.
Tak, to jest Rick
@yatu Właściwie ten dateutilmoduł wymieniony przez @Serge wygląda na przydatny.
Tak, to jest Rick
@yatu proszę zobaczyć moją zaktualizowaną odpowiedź. Kiedyś dateutil.parseidentyfikowałem wiele różnych rodzajów ciągów dat.
Tak, to jest Rick
Wygląda dobrze! nie mam teraz dużo czasu, spojrzę tak szybko, jak to możliwe @ tak
yatu