Определите, выходит ли событие (со временем начала и окончания) за пределы определенного времени (например, 6 вечера) в dataframe с использованием Python (pandas, datetime)

#python #pandas #datetime #time-series #google-calendar-api

#python #pandas #дата и время #временные ряды #google-calendar-api

Вопрос:

Я создаю программу на Python, используя библиотеки pandas и datetime, которые будут рассчитывать оплату за мою случайную работу каждую неделю, поэтому я могу перекрестно ссылаться на свою банковскую выписку вместо просмотра платежных ведомостей. Данные, которые я анализирую, взяты из API календаря Google, который синхронизируется с моим рабочим графиком. Он печатает события в этом конкретном календаре в файл csv в этом формате:

Начать Конец Название Часы
0 02.12.2020 07:00 02.12.2020 16:00 Сдвиг 9.0
1 04.12.2020 18:00 04.12.2020 21:00 Сдвиг 3.0
2 05.12.2020 07:00 05.12.2020 12:00 Сдвиг 5.0
3 06.12.2020 09:00 06.12.2020 18:00 Сдвиг 9.0
4 07.12.2020 19:00 07.12.2020 23:00 Сдвиг 4.0
5 08.12.2020 19:00 08.12.2020 23:00 Сдвиг 4.0
6 09.12.2020 10:00 09.12.2020 15:00 Сдвиг 5.0

Поскольку я работаю на этой работе, я должен учитывать несколько вещей, таких как ставки штрафов (базовый уровень, после 6 вечера с понедельника по пятницу, субботу и воскресенье у всех разные ставки). Мне интересно, могу ли я проанализировать этот csv с помощью datetime и подсчитать, сколько часов до 6 вечера и сколько после 6 вечера. Итак, используя это в качестве примера, результат будет выглядеть так:

Начать Конец Название Часы
1 04.12.2020 15:00 04.12.2020 21:00 Сдвиг 6.0
Начать Конец Название Общее количество часов За несколько часов до 3 часов дня Часов после 3 часов дня
1 04.12.2020 15:00 04.12.2020 21:00 Сдвиг 6.0 3.0 3.0

Я могу использовать это, чтобы получить день недели, но я просто не уверен, как анализировать определенные промежутки времени на предмет штрафных ставок:

df['day_of_week'] = df['Start'].dt.day_name()

Я ценю любую помощь в Python или даже в других языках / методах кодирования, к которым это может быть применено 🙂

Редактировать: вот как выглядит мой dataframe на данный момент

Начать Конец Название Часы day_of_week Платить неделя_о_году
0 2020-12-02 07:00:00 2020-12-02 16:00:00 Сдвиг 9.0 Среда 337.30 49

ОТРЕДАКТИРУЙТЕ в ответ на комментарий Дэвида Эриксона.

значение переменная bool
0 2020-12-02 07:00:00 Начать False
1 2020-12-02 08:00:00 Начать False
2 2020-12-02 09:00:00 Начать False
3 2020-12-02 10:00:00 Начать False
4 2020-12-02 11:00:00 Начать False
5 2020-12-02 12:00:00 Начать False
6 2020-12-02 13:00:00 Начать False
7 2020-12-02 14:00:00 Начать False
8 2020-12-02 15:00:00 Начать False
9 2020-12-02 16:00:00 Конец False
10 2020-12-04 18:00:00 Start False
11 2020-12-04 19:00:00 Start True
12 2020-12-04 20:00:00 Start True
13 2020-12-04 21:00:00 Конец Верно
14 2020-12-05 07:00:00 Начать False
15 2020-12-05 08:00:00 Начать False
16 2020-12-05 09:00:00 Начать False
17 2020-12-05 10:00:00 Начать False
18 2020-12-05 11:00:00 Начать False
19 2020-12-05 12:00:00 Конец False
20 2020-12-06 09:00:00 Начать False
21 2020-12-06 10:00:00 Начать False
22 2020-12-06 11:00:00 Начать False
23 2020-12-06 12:00:00 Начать False
24 2020-12-06 13:00:00 Начать False
25 2020-12-06 14:00:00 Начать False
26 2020-12-06 15:00:00 Начать False
27 2020-12-06 6:00:00 Начать False
28 2020-12-06 17:00:00 Начать False
29 2020-12-06 18:00:00 Конец False
30 2020-12-07 19:00:00 Начать False
31 2020-12-07 20:00:00 Начать Верно
32 2020-12-07 21:00:00 Начать Верно
33 2020-12-07 22:00:00 Начать Верно
34 2020-12-07 23:00:00 Конец Верно
35 2020-12-08 19:00:00 Начать False
36 2020-12-08 20:00:00 Начать Верно
37 2020-12-08 21:00:00 Начать Верно
38 2020-12-08 22:00:00 Начать Верно
39 2020-12-08 23:00:00 Конец Верно
40 2020-12-09 10:00:00 Начать False
41 2020-12-09 11:00:00 Начать False
42 2020-12-09 12:00:00 Начать False
43 2020-12-09 13:00:00 Начать False
44 2020-12-09 14:00:00 Начать False
45 2020-12-09 15:00:00 Конец False
46 2020-12-11 19:00:00 Начать False
47 2020-12-11 20:00:00 Начать Верно
48 2020-12-11 21:00:00 Начать Верно
49 2020-12-11 22:00:00 Начать Верно

Ответ №1:

ОБНОВЛЕНИЕ: (2020-12-19)

Я просто отфильтровал Start строки, поскольку вы были правы, что вычисляется дополнительная строка. Кроме того, я перешел dayfirst=True pd.to_datetime() к правильному преобразованию даты. Я также очистил вывод с помощью некоторых дополнительных столбцов.

 higher_pay = 40
lower_pay = 30

df['Start'], df['End'] = pd.to_datetime(df['Start'], dayfirst=True), pd.to_datetime(df['End'], dayfirst=True)
start = df['Start']
df1 = df[['Start', 'End']].melt(value_name='Date').set_index('Date')
s = df1.groupby('variable').cumcount()
df1 = df1.groupby(s, group_keys=False).resample('1H').asfreq().join(s.rename('Shift').to_frame()).ffill().reset_index()
df1 = df1[~df1['Date'].isin(start)]
df1['Day'] = df1['Date'].dt.day_name()
df1['Week'] = df1['Date'].dt.isocalendar().week
m = (df1['Date'].dt.hour > 18) | (df1['Day'].isin(['Saturday', 'Sunday']))
df1['Higher Pay Hours'] = np.where(m, 1, 0)
df1['Lower Pay Hours'] = np.where(m, 0, 1)
df1['Pay'] = np.where(m, higher_pay, lower_pay)
df1 = df1.groupby(['Shift', 'Day', 'Week']).sum().reset_index()
df2 = df.merge(df1, how='left', left_index=True, right_on='Shift').drop('Shift', axis=1)
df2
Out[1]: 
                Start                 End  Title  Hours        Day  Week  
0 2020-12-02 07:00:00 2020-12-02 16:00:00  Shift    9.0  Wednesday    49   
1 2020-12-04 18:00:00 2020-12-04 21:00:00  Shift    3.0     Friday    49   
2 2020-12-05 07:00:00 2020-12-05 12:00:00  Shift    5.0   Saturday    49   
3 2020-12-06 09:00:00 2020-12-06 18:00:00  Shift    9.0     Sunday    49   
4 2020-12-07 19:00:00 2020-12-07 23:00:00  Shift    4.0     Monday    50   
5 2020-12-08 19:00:00 2020-12-08 23:00:00  Shift    4.0    Tuesday    50   
6 2020-12-09 10:00:00 2020-12-09 15:00:00  Shift    5.0  Wednesday    50   

   Higher Pay Hours  Lower Pay Hours  Pay  
0                 0                9  270  
1                 3                0  120  
2                 5                0  200  
3                 9                0  360  
4                 4                0  160  
5                 4                0  160  
6                 0                5  150  
 

Вероятно, есть более краткие способы сделать это, но я подумал, что повторная выборка фрейма данных и затем подсчет часов были бы чистым подходом. Вы можете melt использовать фрейм данных, чтобы иметь Start и End в том же столбце, и заполнить промежутки времени, resample убедившись groupby , что это значения ‘Start’ и ‘End’, которые изначально были в одной строке. Самый простой способ выяснить, какие строки изначально были вместе, — это получить совокупный подсчет cumcount значений в новом фрейме данных, сгруппированных по ‘Start’ и ‘End’. Я покажу вам, как это работает позже в ответе.

Полный код:

 df['Start'], df['End'] = pd.to_datetime(df['Start']), pd.to_datetime(df['End'])
df = df[['Start', 'End']].melt().set_index('value')
df = df.groupby(df.groupby('variable').cumcount(), group_keys=False).resample('1H').asfreq().ffill().reset_index()
m = (df['value'].dt.hour > 18) | (df['value'].dt.day_name().isin(['Saturday', 'Sunday']))
print('Normal Rate No. of Hours', df[m].shape[0])
print('Higher Rate No. of Hours', df[~m].shape[0])
Normal Rate No. of Hours 20
Higher Rate No. of Hours 26
 

Добавим еще несколько деталей…

Шаг 1: Расплавьте dataframe: вам нужны только два столбца ‘Start’ и ‘End’, чтобы получить желаемый результат

 df = df[['Start', 'End']].melt().set_index('value')
df
Out[1]: 
                    variable
value                       
2020-02-12 07:00:00    Start
2020-04-12 18:00:00    Start
2020-05-12 07:00:00    Start
2020-06-12 09:00:00    Start
2020-07-12 19:00:00    Start
2020-08-12 19:00:00    Start
2020-09-12 10:00:00    Start
2020-02-12 16:00:00      End
2020-04-12 21:00:00      End
2020-05-12 12:00:00      End
2020-06-12 18:00:00      End
2020-07-12 23:00:00      End
2020-08-12 23:00:00      End
2020-09-12 15:00:00      End
 

Шаг 2: Создайте группу в рамках подготовки к повторной выборке: * Как вы можете видеть, группы 0-6 выстраиваются друг с другом, представляя ‘
Начало» и «Конец», как они были вместе ранее

 df.groupby('variable').cumcount()
Out[2]: 
value
2020-02-12 07:00:00    0
2020-04-12 18:00:00    1
2020-05-12 07:00:00    2
2020-06-12 09:00:00    3
2020-07-12 19:00:00    4
2020-08-12 19:00:00    5
2020-09-12 10:00:00    6
2020-02-12 16:00:00    0
2020-04-12 21:00:00    1
2020-05-12 12:00:00    2
2020-06-12 18:00:00    3
2020-07-12 23:00:00    4
2020-08-12 23:00:00    5
2020-09-12 15:00:00    6
 

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

 df.groupby(df.groupby('variable').cumcount(), group_keys=False).resample('1H').asfreq().ffill().reset_index()
Out[3]: 
                 value variable
0  2020-02-12 07:00:00    Start
1  2020-02-12 08:00:00    Start
2  2020-02-12 09:00:00    Start
3  2020-02-12 10:00:00    Start
4  2020-02-12 11:00:00    Start
5  2020-02-12 12:00:00    Start
6  2020-02-12 13:00:00    Start
7  2020-02-12 14:00:00    Start
8  2020-02-12 15:00:00    Start
9  2020-02-12 16:00:00      End
10 2020-04-12 18:00:00    Start
11 2020-04-12 19:00:00    Start
12 2020-04-12 20:00:00    Start
13 2020-04-12 21:00:00      End
14 2020-05-12 07:00:00    Start
15 2020-05-12 08:00:00    Start
16 2020-05-12 09:00:00    Start
17 2020-05-12 10:00:00    Start
18 2020-05-12 11:00:00    Start
19 2020-05-12 12:00:00      End
20 2020-06-12 09:00:00    Start
21 2020-06-12 10:00:00    Start
22 2020-06-12 11:00:00    Start
23 2020-06-12 12:00:00    Start
24 2020-06-12 13:00:00    Start
25 2020-06-12 14:00:00    Start
26 2020-06-12 15:00:00    Start
27 2020-06-12 16:00:00    Start
28 2020-06-12 17:00:00    Start
29 2020-06-12 18:00:00      End
30 2020-07-12 19:00:00    Start
31 2020-07-12 20:00:00    Start
32 2020-07-12 21:00:00    Start
33 2020-07-12 22:00:00    Start
34 2020-07-12 23:00:00      End
35 2020-08-12 19:00:00    Start
36 2020-08-12 20:00:00    Start
37 2020-08-12 21:00:00    Start
38 2020-08-12 22:00:00    Start
39 2020-08-12 23:00:00      End
40 2020-09-12 10:00:00    Start
41 2020-09-12 11:00:00    Start
42 2020-09-12 12:00:00    Start
43 2020-09-12 13:00:00    Start
44 2020-09-12 14:00:00    Start
45 2020-09-12 15:00:00      End
 

Шаг 4 — Оттуда вы можете вычислить логический ряд, который я вызвал m : * Истинные значения представляют условия, выполненные для «Более высокой скорости».

 m = (df['value'].dt.hour > 18) | (df['value'].dt.day_name().isin(['Saturday', 'Sunday']))
m
Out[4]: 
0     False
1     False
2     False
3     False
4     False
5     False
6     False
7     False
8     False
9     False
10     True
11     True
12     True
13     True
14    False
15    False
16    False
17    False
18    False
19    False
20    False
21    False
22    False
23    False
24    False
25    False
26    False
27    False
28    False
29    False
30     True
31     True
32     True
33     True
34     True
35     True
36     True
37     True
38     True
39     True
40     True
41     True
42     True
43     True
44     True
45     True
 

Шаг 5: Отфильтруйте фрейм данных по True или False , чтобы подсчитать общее количество часов для значений нормальной скорости и более высокой скорости и печати.

 print('Normal Rate No. of Hours', df[m].shape[0])
print('Higher Rate No. of Hours', df[~m].shape[0])
Normal Rate No. of Hours 20
Higher Rate No. of Hours 26
 

Комментарии:

1. Большое спасибо за публикацию этого! Определенно поставьте меня на правильный путь с помощью функции melt. Единственное, что я пытаюсь выяснить сейчас, это то, что если начало смены прошло 6 вечера (например, 7 вечера), то это будет засчитываться как час (вместо того, чтобы работать до 8 вечера, засчитываемого как час). Итак, я пытаюсь выполнить итерацию по столбцу ‘variable’, и если строка перед i ‘End’, измените на False, потому что это будет означать, что это начало сдвига. Дайте мне знать, если у вас есть какие-либо идеи в то же время. Ценю помощь Дэвида.

2. @tyleroki В вашем вопросе, можете ли вы поделиться ожидаемым результатом в виде True False логического ряда / ‘, который я показываю на шаге 4? По сути, можете ли вы скопировать вывод шага в свой вопрос и изменить True на False или False на True для всего, что не ожидается?

3. извините за мой поздний ответ. Я обновил свой вопрос, чтобы показать, что мне нужно. По сути, всякий раз, когда «Start» находится под «End» в столбце переменных, тогда bool должно быть False . Это связано с тем, что смена начинается, поэтому время не прошло (например, если смена начинается в 7 вечера, это не следует считать истинным)