Вначале я скажу, что у меня есть метод, который работает, но я хочу оптимизировать и изучить еще несколько методов Pythonic для работы с DataFrames.
Предпосылка такова: у меня было несколько "посещений" пользователем определенного места. Эти диапазоны могут быть от любого datetime до любого datetime, но расположены в хронологическом порядке:
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
, и я хочу определить общее количество времени, которое пользователь проводит с 20:00 до 8:00 каждый день.
Мой текущий метод - применить настраиваемую функцию к каждой строке:
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 или более различных типов случаев.
Метод, о котором я думаю, состоит в том, чтобы функция разбивала каждый временной диапазон на 24-часовые блоки (вырезая каждый временной диапазон, разбивая его в 20:00). Для каждого 24-часового блока может быть только 3 категории:
Затем просто сложите каждый блок по 24 часа.
Таким образом, функция выполняет только несколько арифметических операций, а не повторяет до 60 * 60 * 24 = 86400 раз в день над данными.
Позвольте мне помочь вам разобраться в некоторой логике ваших проблем, часть реализации должна быть простой, либо на 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), затем
Также установите:
p8
в тот же день, что и ts
, но в 20:00a8
в тот же день, что и te
, но в 8 утраНиже перечислены возможные случаи с псевдокодом:
Дело 1:
ts и te в один день - в основном в день 2 и 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
Сообщите мне, если это сработает.