【问题标题】:Find whether an event (with a start & end time) goes beyond a certain time (e.g. 6pm) in dataframe using Python (pandas, datetime)使用 Python (pandas, datetime) 在数据框中查找事件(具有开始和结束时间)是否超过特定时间(例如下午 6 点)
【发布时间】:2021-03-23 11:48:59
【问题描述】:

我正在使用 pandas 和 datetime 库创建一个 Python 程序,该程序将计算我每周临时工作的工资,因此我可以交叉引用我的银行对帐单,而不是查看工资单。 我正在分析的数据来自与我的工作日程同步的 Google Calendar API。它以这种格式将特定日历中的事件打印到 csv 文件中:

Start End Title Hours
0 02.12.2020 07:00 02.12.2020 16:00 Shift 9.0
1 04.12.2020 18:00 04.12.2020 21:00 Shift 3.0
2 05.12.2020 07:00 05.12.2020 12:00 Shift 5.0
3 06.12.2020 09:00 06.12.2020 18:00 Shift 9.0
4 07.12.2020 19:00 07.12.2020 23:00 Shift 4.0
5 08.12.2020 19:00 08.12.2020 23:00 Shift 4.0
6 09.12.2020 10:00 09.12.2020 15:00 Shift 5.0

由于我是这份工作的临时工,因此我必须考虑一些事情,例如罚款率(基本利率,周一至周五下午 6 点之后,周六和周日都有不同的税率)。我想知道我是否可以使用 datetime 分析这个 csv 并计算下午 6 点之前有多少小时,以及 6 点之后有多少小时。因此,以此为例,输出将如下所示:

Start End Title Hours
1 04.12.2020 15:00 04.12.2020 21:00 Shift 6.0
Start End Title Total Hours Hours before 3pm Hours after 3pm
1 04.12.2020 15:00 04.12.2020 21:00 Shift 6.0 3.0 3.0

我可以使用它来获取星期几,但我只是不确定如何分析某些时间段的罚款率:

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

感谢您在 Python 甚至可以应用于其他编码语言/技术方面的任何帮助:)

编辑: 这就是我的数据框目前的样子

Start End Title Hours day_of_week Pay week_of_year
0 2020-12-02 07:00:00 2020-12-02 16:00:00 Shift 9.0 Wednesday 337.30 49

编辑 回应 David Erickson 的评论。

value variable bool
0 2020-12-02 07:00:00 Start False
1 2020-12-02 08:00:00 Start False
2 2020-12-02 09:00:00 Start False
3 2020-12-02 10:00:00 Start False
4 2020-12-02 11:00:00 Start False
5 2020-12-02 12:00:00 Start False
6 2020-12-02 13:00:00 Start False
7 2020-12-02 14:00:00 Start False
8 2020-12-02 15:00:00 Start False
9 2020-12-02 16:00:00 End 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 End True
14 2020-12-05 07:00:00 Start False
15 2020-12-05 08:00:00 Start False
16 2020-12-05 09:00:00 Start False
17 2020-12-05 10:00:00 Start False
18 2020-12-05 11:00:00 Start False
19 2020-12-05 12:00:00 End False
20 2020-12-06 09:00:00 Start False
21 2020-12-06 10:00:00 Start False
22 2020-12-06 11:00:00 Start False
23 2020-12-06 12:00:00 Start False
24 2020-12-06 13:00:00 Start False
25 2020-12-06 14:00:00 Start False
26 2020-12-06 15:00:00 Start False
27 2020-12-06 6:00:00 Start False
28 2020-12-06 17:00:00 Start False
29 2020-12-06 18:00:00 End False
30 2020-12-07 19:00:00 Start False
31 2020-12-07 20:00:00 Start True
32 2020-12-07 21:00:00 Start True
33 2020-12-07 22:00:00 Start True
34 2020-12-07 23:00:00 End True
35 2020-12-08 19:00:00 Start False
36 2020-12-08 20:00:00 Start True
37 2020-12-08 21:00:00 Start True
38 2020-12-08 22:00:00 Start True
39 2020-12-08 23:00:00 End True
40 2020-12-09 10:00:00 Start False
41 2020-12-09 11:00:00 Start False
42 2020-12-09 12:00:00 Start False
43 2020-12-09 13:00:00 Start False
44 2020-12-09 14:00:00 Start False
45 2020-12-09 15:00:00 End False
46 2020-12-11 19:00:00 Start False
47 2020-12-11 20:00:00 Start True
48 2020-12-11 21:00:00 Start True
49 2020-12-11 22:00:00 Start True

【问题讨论】:

    标签: python pandas datetime time-series google-calendar-api


    【解决方案1】:

    更新:(2020-12-19)

    我只是过滤掉了 Start 行,因为您是正确的,正在计算额外的行 wa。另外,我将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 数据框将 StartEnd 放在同一列中,并使用 resample 填写空白时间,确保 groupby 通过最初打开的“开始”和“结束”值同一行。找出哪些行最初在一起的最简单方法是使用cumcount 获取按“开始”和“结束”分组的新数据框中的值的累积计数。我将在稍后的答案中向您展示这是如何工作的。

    完整代码:

    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 步:融化数据框: 您只需要两列“开始”和“结束”即可获得所需的输出

    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 彼此排成一行,代表 ' Start' 和 'End' 就像他们以前在一起一样

    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 步:按 TrueFalse 过滤数据帧,以计算 Normal Rate 和 Higher Rate 的总小时数并打印值。

    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
    

    【讨论】:

    • 非常感谢您发布此信息!使用融化功能绝对让我走上正轨。我现在唯一要解决的问题是,如果轮班开始时间超过下午 6 点(例如晚上 7 点),那么这将被计为一个小时(而不是工作到晚上 8 点才被计为一个小时)。所以我试图遍历“变量”列,如果 i 之前的行是“结束”,则更改为 False,因为这意味着它是轮班的开始。如果您在此期间有任何想法,请告诉我。感谢大卫的帮助。
    • @tyleroki 在您的问题中,您能否以我在第 4 步中展示的 True / 'False 布尔系列的形式分享预期的输出?从本质上讲,您可以将 Step Output 复制到您的问题中并将 True 更改为 False 或将 False 更改为 True 吗?
    • 抱歉我的回复晚了。我已经更新了我的问题以显示我所追求的。基本上只要变量列中的“开始”位于“结束”下方,则布尔值应为 False。这是因为轮班开始了,所以没有时间过去(例如,如果轮班从晚上 7 点开始,这不应该算作真的)
    猜你喜欢
    • 2020-05-27
    • 2022-12-08
    • 2020-06-03
    • 1970-01-01
    • 1970-01-01
    • 2023-03-09
    • 2021-07-23
    • 2021-11-09
    • 1970-01-01
    相关资源
    最近更新 更多