Оптимизированный метод (для фреймов данных), чтобы найти временной диапазон перекрывается с указанным часовым диапазоном - PullRequest
0 голосов
/ 02 мая 2018

Я предвосхищу это, говоря, что у меня есть метод, который работает, но я стремлюсь оптимизировать и изучить некоторые другие Pythonic методы работы с DataFrames.

Предпосылка заключается в следующем: у меня есть несколько "посещений" пользователем определенного местоположения. Эти диапазоны могут быть от любой даты до любой даты, но могут быть в хронологическом порядке:

Jan 1, 15:00 to Jan 1, 18:35 
Jan 3, 09:12 to Jan 5, 10:54 
Jan 5, 11:00 to Jan 6, 19:48
etc.

Теперь у меня есть эти времена прибытия и отправления в DataFrame, и я рассчитываю определить общее количество времени, которое пользователь проводит между часами с 8 вечера до 8 утра каждый день.

Мой текущий метод заключается в применении пользовательской функции к каждой строке:

def find_8am_8pm_hours(t1, t2):
    if t1 > t2:
        raise Exception('t1 must be before t2')
    total = dt.timedelta(minutes=0)
    while t1 < t2:
        t1 += dt.timedelta(minutes=1)
        if (t1.time() < dt.time(8, 0)) or (t1.time() > dt.time(20, 0)):
            total += dt.timedelta(minutes=1)
    return total 

и примените это к DataFrame с помощью:

df['Time Spent 8am-8pm'] = df.apply(lambda row: find_8am_8pm_hours(row['Arrival Time'], row['Departure Time']), axis=1)

Первоначально я написал функцию с гранулярностью за секунды, но на самом деле потребовалось некоторое время для запуска даже на очень маленьких наборах данных (несколько секунд выполнения для набора данных с ~ 20 строками). Как только я изменил приближение на минуты, небольшие наборы данных запускались очень быстро, но я представляю, что при больших наборах данных алгоритм будет занимать много времени.

Я знаю, что цикл while является основным виновником, но я не мог придумать ни одного более элегантного метода. Я также рассмотрел операторы if / else для обработки конкретных случаев того, как перекрывалось время, но для обработки 24 + часовых диапазонов было бы 20 или более различных типов дел для обработки.

Ответы [ 2 ]

0 голосов
/ 02 мая 2018

Позвольте мне помочь вам разобраться с логикой в ​​ваших задачах, часть реализации должна быть простой, либо на Python / Pandas, либо на другом языке программирования.

См. Следующую диаграмму. Я разделил окно на 6 зон на 8AM и 8PM в течение 1-2 последовательных дней (зависит от установленного времени прибытия и времени отправления, о которых я расскажу ниже):

            +---day1--+---day2--+
            |   z1    |   z4    |
            +---------+---------+<-- 8AM (a8)
            |   z2    |   z5    |
(p8) 8PM -->+---------+---------+
            |   z3    |   z6    |
            +---------+---------+

Сначала мы вычисляем delta_in_days между двумя временными метками t1 и t2 , каждый отдельный дельта-день дает вам дополнительные 12 часов на итоговую сумму.

Добавление delta_in_days ко времени прибытия, чтобы мы могли сосредоточиться на окне, которое находится в пределах 1 дня (24 часа). Предположим, ts - это скорректированное время прибытия, а te - время отправления. (Примечание: я изначально определил их как время начала и время окончания, назвав их ts и te ) затем

  • ts = t1 + delta_in_days
  • te = t2

Также установлено:

  • p8 в тот же день, что и ts, но в 8 вечера
  • a8 в тот же день, что и te, но в 8:00

Ниже приведен список возможных случаев с псевдокодом:

Дело-1:

ts и te в тот же день - в основном в day2 и p8 > a8

if both in the same zone: z4(te < a8) or z6(ts > p8): 
    total = te - ts
else:
    total = max(0, te - p8) + max(0, a8 - ts)

Дело-2:

ts , te в разные дни, если te в z6, то ts должно быть в z3. Помните, что после установленного времени прибытия ts и te должны находиться в пределах 24-часового окна.

if te > p8 + 1day:
    total = (te - p8 - 1day) + (a8 - ts)

Дело-3:

ts , te в разные дни, если ts в z1, то te должно быть в z4

if ts < a8 - 1day
    total = (a8 - 1day - ts) + (te - p8)

Дело-4:

ts в [z2, z3], а te в [z4, z5]

total = min(a8, te) - max(p8, ts)  

Код в Python:

import pandas as pd
from io import StringIO

str="""Jan 1, 15:00 to Jan 1, 18:35 
Jan 3, 09:12 to Jan 5, 10:54 
Jan 5, 21:00 to Jan 6, 23:48
Jan 5, 23:00 to Jan 6, 20:48
Jan 5, 03:00 to Jan 6, 02:48
Jan 5, 10:00 to Jan 6, 05:48
Jan 5, 21:00 to Jan 6, 10:48
"""

df = pd.read_table(StringIO(str)
     , sep='\s*to\s*'
     , engine='python'
     , names=['t1','t2']
)

for field in ['t1', 't2']:
    df[field] = pd.to_datetime(df[field], format="%b %d, %H:%M")

delta_1_day = pd.Timedelta('1 days')
# add 12 hours for each delta_1_day
ns_spent_in_1_day = int(delta_1_day.value*12/24)

# the total time is counted in nano seconds
def count_off_hour_in_ns(x):
    t1 = x['t1']
    t2 = x['t2']

    # number of days from t1 to t2
    delta_days = (t2 - t1).days
    if delta_days <= 0:
        return 0

    # add delta_days to start-time so ts and te in 1-day window
    # define the start-time(ts) and end-time(te) of the window
    ts = t1 + pd.Timedelta('{} days'.format(delta_days))
    te = t2

    # 8PM the same day as ts
    p8 = ts.replace(hour=20, minute=0, second=0)

    # 8AM the same day as te
    a8 = te.replace(hour=8, minute=0, second=0)

    # Case-1: te and ts on the same day
    if p8 > a8:
        if te < a8 or ts > p8:
            total = (te - ts).value
        else:
            total = max(0, (te - p8).value) + max(0, (a8 - ts).value)
    # Below ts and te all in different days
    # Case-2: te in z6
    elif te > p8 + delta_1_day:
        total = (te - p8 - delta_1_day + a8 - ts).value
    # Case-3: ts in z1
    elif ts < a8 - delta_1_day:
        total = (a8 - delta_1_day - ts + te - p8).value
    # Case-4: other cases
    else:
        total = (min(te, a8) - max(ts, p8)).value

    return total + delta_days * ns_spent_in_1_day

df['total'] = df.apply(count_off_hour_in_ns, axis=1)

print(df)

                   t1                  t2           total
0 1900-01-01 15:00:00 1900-01-01 18:35:00               0
1 1900-01-03 09:12:00 1900-01-05 10:54:00  86400000000000
2 1900-01-05 21:00:00 1900-01-06 23:48:00  53280000000000
3 1900-01-05 23:00:00 1900-01-06 20:48:00  35280000000000
4 1900-01-05 03:00:00 1900-01-06 02:48:00  42480000000000
5 1900-01-05 10:00:00 1900-01-06 05:48:00  35280000000000
6 1900-01-05 21:00:00 1900-01-06 10:48:00  39600000000000

Дайте мне знать, если это работает.

0 голосов
/ 02 мая 2018

Метод, о котором я думаю, состоит в том, чтобы функция разбивала каждый отдельный временной интервал на 24-часовой блок (вырезать каждый временной диапазон путем разделения на 8 часов вечера). Для каждого 24-часового блока может быть только 3 категории:

  1. Прибытие до 8 утра, отъезд до 8 утра (прибытие ~ отправление)
  2. Прибытие до 8 утра, отъезд после 8 утра (прибытие ~ 8 утра)
  3. Прибытие после 8:00 (0 часов)

schedule

Тогда просто сложите каждый блок за 24 часа вместе.

Таким образом, функция выполняет не более нескольких арифметических операций, вместо итерации до 60 * 60 * 24 = 86 400 раз в день данных.

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