Search code examples
pythonpandasdataframepandas-groupby

How to calculate the sum of time difference for every entry and exit per employee for each day?


i'm using this dataframe , each employee have a unique ID and in column E/X , 6 represent the time he entered and 1 represent the time he left

Emp E/X             DateTime        Date     Time
107 6 2022-01-04  10:04:18 0 2022-01-04 10:04:18
107 6 2022-01-04  11:32:52 0 2022-01-04 11:32:52
107 6 2022-01-04  11:39:59 0 2022-01-04 11:39:59
107 1 2022-01-04  12:05:26 0 2022-01-04 12:05:26
107 6 2022-01-04  18:02:18 0 2022-01-04 18:02:18
107 6 2022-01-04  18:30:38 0 2022-01-04 18:30:38
107 1 2022-01-04  19:06:58 0 2022-01-04 19:06:58
107 1 2022-01-05  12:22:10 0 2022-01-05 12:22:10
107 6 2022-01-05  19:22:15 0 2022-01-05 19:22:15
122 1 2022-01-03  08:57:40 0 2022-01-03 08:57:40
122 6 2022-01-03  12:49:33 0 2022-01-03 12:49:33
122 1 2022-01-03  13:22:28 0 2022-01-03 13:22:28
122 6 2022-01-03  16:29:51 0 2022-01-03 16:29:51
122 1 2022-01-03  16:40:06 0 2022-01-03 16:40:06

I was wondering if it was possible to calculate how much the employee worked each day and change the E/X column so that each day has a successive in/out because it has errors sometimes there's multiple entries successively for exemple im gonna take the first two rows and change the second one into exit :

    Emp E/X           DateTime        Date     Time
    107 6 2022-01-04  10:04:18 0 2022-01-04 10:04:18
    107 1 2022-01-04  11:32:52 0 2022-01-04 11:32:52

    122 6 2022-01-03  08:57:40 0 2022-01-03 08:57:40
    122 1 2022-01-03  12:49:33 0 2022-01-03 12:49:33
    122 6 2022-01-03  13:22:28 0 2022-01-03 13:22:28
    122 1 2022-01-03  16:29:51 0 2022-01-03 16:29:51 this line is going to be deleted 
    122 1 2022-01-03  16:40:06 0 2022-01-03 16:40:06

desired result :

    Emp E/X             DateTime        Date     Time
    107 6 2022-01-04  10:04:18 0 2022-01-04 10:04:18
    107 1 2022-01-04  11:32:52 0 2022-01-04 11:32:52
    107 6 2022-01-04  11:39:59 0 2022-01-04 11:39:59
    107 1 2022-01-04  12:05:26 0 2022-01-04 12:05:26
    107 6 2022-01-04  18:02:18 0 2022-01-04 18:02:18
    107 1 2022-01-04  19:06:58 0 2022-01-04 19:06:58
    107 6 2022-01-05  12:22:10 0 2022-01-05 12:22:10
    107 1 2022-01-05  19:22:15 0 2022-01-05 19:22:15
    122 6 2022-01-03  08:57:40 0 2022-01-03 08:57:40
    122 1 2022-01-03  12:49:33 0 2022-01-03 12:49:33
    122 6 2022-01-03  13:22:28 0 2022-01-03 13:22:28

    122 1 2022-01-03  16:40:06 0 2022-01-03 16:40:06

and once the E/X is fixed i want to calculate the sum of every difference between 6 and 1 per employee for each day

Desired Result:

    EMP           Date  WorkHours    
4   107     2022-01-03  2
5   107     2022-01-04  8
6   122     2022-01-03  4

Solution

  • Going to use my own test data and assume it is already clean, i.e. alternating start/end datetimes

    setup

    df = pd.concat(
        [
            pd.DataFrame(
                {
                    "employee":[107]*6,
                    "E/X":[6,1,6,1,6,1],
                    "datetime":pd.Timestamp("2022") + pd.Series([0,4,14,26,40,50]).apply(pd.Timedelta, unit="hours")
                }
            ),
            pd.DataFrame(
                {
                    "employee":[122]*8,
                    "E/X":[6,1,6,1,6,1,6,1],
                    "datetime":pd.Timestamp("2022") + pd.Series([3,20,30,35,45,55,56,60]).apply(pd.Timedelta, unit="hours")
                }
            ),
        ]
    ).reset_index(drop=True)
    

    df looks like this

        employee  E/X            datetime
    0        107    6 2022-01-01 00:00:00
    1        107    1 2022-01-01 04:00:00
    2        107    6 2022-01-01 14:00:00
    3        107    1 2022-01-02 02:00:00
    4        107    6 2022-01-02 16:00:00
    5        107    1 2022-01-03 02:00:00
    6        122    6 2022-01-01 03:00:00
    7        122    1 2022-01-01 20:00:00
    8        122    6 2022-01-02 06:00:00
    9        122    1 2022-01-02 11:00:00
    10       122    6 2022-01-02 21:00:00
    11       122    1 2022-01-03 07:00:00
    12       122    6 2022-01-03 08:00:00
    13       122    1 2022-01-03 12:00:00
    

    solution

    This will use a package called piso (pandas interval set operations). In particular it will follow the last example of piso.coverage

    # create day range required and convert to interval index
    days = pd.date_range("2022", freq="D", periods=4)
    day_intervals = pd.IntervalIndex.from_breaks(days)
    
    # creates an interval index from start and end times for an employee
    # then calculates the sum of each interval for each bin in day_intervals
    def calc_employee(d):
        ii = pd.IntervalIndex.from_arrays(d.loc[d["E/X"] == 6, "datetime"], d.loc[d["E/X"] == 1, "datetime"])
        return piso.coverage(ii, domain=day_intervals, bins=True, how="sum")
    
    # apply the function for each employee
    hours_worked = df.groupby("employee").apply(calc_employee)
    
    # columns will be day_intervals, let's change it to the start of each day
    hours_worked.columns = hours_worked.columns.left
    
    # melt it into tidy data format
    hours_worked = hours_worked.melt(var_name="date", value_name="timedelta", ignore_index=False).reset_index()
    
    # calculate hours from timedelta value (optional)
    hours_worked["hours"] = hours_worked["timedelta"]/pd.Timedelta("1hr")
    

    hours_worked looks like this:

       employee       date       timedelta  hours
    0       107 2022-01-01 0 days 14:00:00   14.0
    1       122 2022-01-01 0 days 17:00:00   17.0
    2       107 2022-01-02 0 days 10:00:00   10.0
    3       122 2022-01-02 0 days 08:00:00    8.0
    4       107 2022-01-03 0 days 02:00:00    2.0
    5       122 2022-01-03 0 days 11:00:00   11.0
    

    note: I am the creator of piso. Please feel free to reach out with feedback or questions if you have any.