Лучший выбор из Pandas DatatimeIndex по дням недели и времени - PullRequest
1 голос
/ 24 октября 2019

Проблема: выбор из панд DatetimeIndex по дням недели и времени. Например, я хотел бы выбрать все элементы в период со вторника 20:00 до пятницы 06:00.

Вопрос: Есть ли лучшее решение, чем мое решение ниже?

У меня есть существующее решение(см. ниже), но мне это не очень нравится по следующим причинам:

  • Он преобразует метки времени в числа с плавающей точкой и выполняет сравнения с плавающей точкой с обычными проблемами точности.
  • Преобразование из расширенного типа данных в примитивный тип данных с надуманным отображением для выполняемой задачи не кажется ни очень элегантным, ни питонским. специальная обработка (не входит в приведенный ниже пример).

Мой рабочий пример:

import pandas as pd
from datetime import time
import calendar

# The DatetimeIndex to selection from
idx = pd.date_range('2019-01-01', '2019-01-31', freq='H')

# Converts a datetime to a time-of-day fraction in [0, 1)
def datetime_to_time_frac(t):
    return t.hour / 24 + t.minute / (24 * 60) + t.second / (24 * 60 * 60)

# Converts a datetime to a float representing weekday (Monday: 0 to Sunday: 6) + time-of-day fraction in [0, 1)
def datetime_to_weekday_time_frac(t):
    return t.weekday + datetime_to_time_frac(t)

# DatetimeIndex converted to float
idx_conv = datetime_to_weekday_time_frac(idx)

# Boolean mask selecting items between Tuesday 20:00 and Friday 06:00
mask = (idx_conv >= calendar.TUESDAY + datetime_to_time_frac(time(20, 0)))\
     & (idx_conv <= calendar.FRIDAY + datetime_to_time_frac(time(6, 0)))

# Validation of mask in a pivot table
df = pd.DataFrame(index=idx[mask])
df['Date'] = df.index.date
df['Weekday'] = df.index.weekday
weekdays = list(calendar.day_abbr)
df['WeekdayName'] = df.Weekday.map(lambda x: weekdays[x])
df['Hour'] = df.index.hour
df.pivot_table(index=['Date', 'WeekdayName'], columns='Hour', values='Weekday', aggfunc='count')

Окончательный поворотный вывод показывает, что код работает правильно, но у меня есть чувствочто есть более элегантный и идиоматический способ решения этой проблемы.

(Код основан на Python 3 с последними Pandas.)

Pivoted final output for code validation

Ответы [ 2 ]

0 голосов
/ 24 октября 2019

Следующее должно достичь того, что вы ищете:

def make_date_mask(day_start, time_start, day_end, time_end, series):
    flipped = False
    if day_start > day_end:
        # Need to flip the ordering, then negate at the end
        day_start, time_start, day_end, time_end = (
            day_end, time_end, day_start, time_start
        )
        flipped = True

    time_start = datetime.strptime(time_start, "%H:%M:%S").time()
    time_end = datetime.strptime(time_end, "%H:%M:%S").time()

    # Get everything for the specified days, inclusive
    mask = series.dt.dayofweek.between(day_start, day_end)
    # Filter things that happen before the begining of the start time
    # of the start day
    mask = mask & ~(
        (series.dt.dayofweek == day_start) 
        & (series.dt.time < time_start)
    )
    # Filter things that happen after the ending time of the end day
    mask = mask & ~(
        (series.dt.dayofweek == day_end) 
        & (series.dt.time > time_end)
    )

    if flipped:
        # Negate the mask to get the actual result and add in the
        # times that were exactly on the boundaries, just in case
        mask = ~mask | (
            (series.dt.dayofweek == day_start) 
            & (series.dt.time == time_start)
        ) | (
            (series.dt.dayofweek == day_end) 
            & (series.dt.time == time_end)
        )
    return mask

Используя это с вашим примером:

import pandas as pd

df = pd.DataFrame({
    "dates": pd.date_range('2019-01-01', '2019-01-31', freq='H')
})
filtered_df = df[make_date_mask(6, "23:00:00", 0, "00:30:00", df["dates"])]

filtered выглядит так:

                  dates
143 2019-01-06 23:00:00
144 2019-01-07 00:00:00
311 2019-01-13 23:00:00
312 2019-01-14 00:00:00
479 2019-01-20 23:00:00
480 2019-01-21 00:00:00
647 2019-01-27 23:00:00
648 2019-01-28 00:00:00
0 голосов
/ 24 октября 2019

Кажется, что вы можете использовать функцию внутреннего индекса в pandas, чтобы индексировать это немного более четко. Я избегаю преобразования в дробное время, и, по общему признанию, то, что я делаю, работает только в течение целых часов. Основным отличием является использование встроенной функциональности панд и исключение calendars в качестве импорта. Вот что я сделал, это в основном эквивалентно вашему очень конкретному примеру вторника-пятницы, но если вам нужны только интервалы по часам, вы можете адаптировать это к более общему случаю.

import pandas as pd

idx = pd.date_range('2019-01-01', '2019-01-31', freq='H')
df = pd.DataFrame(index=idx)

# Build a series of filters for each part of your weekly interval.
tues = (df.index.weekday == 1) & (df.index.hour >= 6)
weds_thurs = df.index.weekday.isin([2,3])
fri = (df.index.weekday == 4) & (df.index.hour <= 20)

# The mask is just the union of all those conditions
mask = tues | weds_thurs | fri

# now apply the mask and the rest is basically what you were doing
df = df.loc[mask]
df['Date'] = df.index.date
df['Weekday'] = df.index.weekday
df['WeekdayName'] = df.index.weekday_name
df['Hour'] = df.index.hour
df.pivot_table(index=['Date', 'WeekdayName'], columns='Hour', values='Weekday', aggfunc='count')

ТеперьЯ вижу вывод, который выглядит следующим образом: enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...