Визначте, які стовпці мають дату


14

У мене величезна рамка даних з багатьма колонками, багато з яких мають тип datetime.datetime. Проблема полягає в тому, що багато людей також мають змішані типи, включаючи, наприклад, datetime.datetimeзначення та Noneзначення (та потенційно інші недійсні значення):

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

Звідси виходить objectстовпець типу. Це можна вирішити за допомогою df.colx.fillna(pd.NaT). Проблема полягає в тому, що фрейм даних занадто великий для пошуку окремих стовпців.

Інший підхід полягає у використанні pd.to_datetime(col, errors='coerce'), однак це стосується datetimeбагатьох стовпців, що містять числові значення.

Я також міг би зробити це df.fillna(float('nan'), inplace=True), хоча стовпці, що містять дати, все ще мають objectтип, і все одно матимуть ту саму проблему.

Який підхід я міг би дотримуватися, щоб передати ці дати стовпцям, чиї значення дійсно містять datetimeзначення, але можуть також містити Noneй потенційно деякі недійсні значення (згадуючи про те, оскільки в іншому випадку це було б pd.to_datetimeу try/ / exceptпункті)? Щось на кшталт гнучкої версіїpd.to_datetime(col)


Чи зберігається об'єкт типу DataFrame datetime.datetimeабо pandas._libs.tslibs.timestamps.Timestamp? Якщо попередня моя рекомендація - змінити будь-який створений час дати на тип, який pandasобробляє трохи краще.
ALollz

Чи є Noneу ваших стовпцях фактичними Noneчи рядковими його представниками?
Ерфан

Вони є None, а не струнні. Потенційно можуть бути і неправильні значення також ... @erfan
yatu

3
Тоді мені цікаво, як модель sql у вашій базі даних? Оскільки sql змушує певні типи стовпців. Як ви закінчилися стовпцями змішаного типу? Ви можете, можливо, також показати стовпчик, який має datetimeі valuesв ньому?
Ерфан

1
використовуйте аналізатор dateutil, щоб відгадати час дати. Може бути встановлений поріг кількох (скажімо, 5 дат) у стовпці, щоб бути впевненим stackoverflow.com/questions/9507648/…
Серж

Відповіді:


1

Основна проблема, яку я бачу, - це при аналізі числових значень.

Я б запропонував спершу перетворити їх у рядки


Налаштування

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

Рішення

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

Добре виглядає, що це просто значно спрощує проблему. Я навіть не думав про це. Ідеальний сценарій полягав у простому застосуванні, pd.to_datetimeа coerceпомилок, напевно, їх багато. Проблема була в числових стовпцях. Але мені не прийшло в голову, що числові стовпці, передані на рядок, не розбираються пандами ' to_datetime. Велике спасибі, це справді допомагає!
яту

4

Ця функція встановить тип даних стовпця на дату, якщо якесь значення стовпця відповідає шаблону регулярного вираження (\ d {4} - \ d {2} - \ d {2}) + (наприклад, 2019-01-01 ). Завдяки цій відповіді про те, як шукати рядок у всіх колонках і фільтрах Pandas DataFrame, які допомогли встановити та застосувати маску.

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

Працюючи з пропозиції щодо використання dateutil, це може допомогти. Він все ще працює над презумпцією, що якщо у стовпці є якісь подібні дати, то стовпець має бути датою. Я спробував розглянути різні методи ітерації кадрів даних, які є більш швидкими. Я думаю, що ця відповідь далі як перебирати рядки в DataFrame в Pandas , вдало описала їх.

Зауважте, що dateutil.parserпоточний день чи рік буде використаний для будь-яких рядків, таких як "Грудень" або "Листопад 2019" без значень року або дня.

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]

Якщо ви також хочете використовувати значення даних timetime dateutil.parser, ви можете додати це:

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

Це гарна ідея, але, на жаль, я шукаю те, що може узагальнити до потенційно кількох різних форматів дати, тому без жорсткого кодування формату.
Вдячність

@yatu Не проблема - у мене просто трапилось щось над цим працювати. Цікаво, чи можна узагальнити всі формати дати? Можливо, вам доведеться просто заздалегідь обліковувати всі формати, які ви очікуєте побачити; або, всі формати, які ви вважаєте за дійсні дати.
Так, це Рік

@yatu Насправді цей dateutilмодуль, згаданий @Serge, схоже, може бути корисним.
Так, це Рік

@yatu, будь ласка, дивіться мою оновлену відповідь. Я використовував dateutil.parseдля визначення багатьох різних рядків дат.
Так, це Рік

Виглядає добре! Не маю багато часу, я погляну, як тільки я можу @yes
yatu
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.