【问题标题】:Calculate Events Duration in a day using pandas使用 pandas 计算一天中的事件持续时间
【发布时间】:2021-10-14 08:31:51
【问题描述】:

我有以下熊猫数据框,持续时间以分钟为单位:

Start Date             Event    Duration
2021.01.01 00:00 AM     2        540
2021.01.01 9:00 AM      1        180 
2021.01.01 12:00 PM     2        20
2021.01.01 12:20 PM     1        1440
2021.01.02 12:20 PM     2        60
2021.01.02 1:20 PM      1        20

我想计算一天中每个事件的持续时间。问题是有一些事件,如第 3 行中的事件,跨越数天。 我想得到的是这样的:

Date           Event    Duration
2021.01.01       1        880 
2021.01.01       2        560
2021.01.02       1        760
2021.01.02       2        60

一般情况下,特定日期内所有事件的总和不能超过 1440,即 24 小时 * 60 分钟。事件是连续的,所以总是有事件,从来没有没有事件的时候。

【问题讨论】:

  • 预期输出中的持续时间是否正确?应该是[880, 560, 760, 60]

标签: python pandas datetime group-by


【解决方案1】:

由于某些奇怪的原因,我无法立即转换您的日期,但需要替换空格。尽管如此,让我们先将您的 Date 列转换为 pandas 日期并将其设置为索引:

>>> df['Start Date'] = pd.to_datetime(df['Start Date'].str.replace(r'\s+', ' ', regex=True))
>>> df = df.set_index('Start Date')
>>> df
                     Event  Duration
2021-01-01 00:00:00      2       540
2021-01-01 09:00:00      1       180
2021-01-01 12:00:00      2        20
2021-01-01 12:20:00      1      1440
2021-01-02 12:20:00      2        60
2021-01-02 13:20:00      1        20

然后我们可以计算需要进行哪些拆分,即日期发生变化但未显示为 Start Date 的时间戳,并将其添加到索引中:

>>> splits = pd.date_range(df.index.min().floor(freq='D') + pd.Timedelta(days=1), df.index.max().ceil(freq='D') - pd.Timedelta(days=1), freq='D')
>>> df = df.reindex(df.index.append(splits).drop_duplicates().sort_values())
>>> df
                     Event  Duration
2021-01-01 00:00:00    2.0     540.0
2021-01-01 09:00:00    1.0     180.0
2021-01-01 12:00:00    2.0      20.0
2021-01-01 12:20:00    1.0    1440.0
2021-01-02 00:00:00    NaN       NaN
2021-01-02 12:20:00    2.0      60.0
2021-01-02 13:20:00    1.0      20.0

此时我们知道索引之间的差异才是我们想要的时间。填写Duration的空白,然后我们可以简单地按天/事件分组并求和,而不会出现任何意外行为:

>>> minutes = df.index.to_series().diff().shift(-1).astype('timedelta64[m]').fillna(df['Duration'])
>>> minutes
2021-01-01 00:00:00    540.0
2021-01-01 09:00:00    180.0
2021-01-01 12:00:00     20.0
2021-01-01 12:20:00    700.0
2021-01-02 00:00:00    740.0
2021-01-02 12:20:00     60.0
2021-01-02 13:20:00     20.0
dtype: float64
>>> minutes.groupby([df.index.date, df['Event'].ffill()]).sum()
            Event
2021-01-01  1.0      880.0
            2.0      560.0
2021-01-02  1.0      760.0
            2.0       60.0
dtype: float64

请注意,我们还确保使用 .ffill() 将事件 ID 传播到分割线

此解决方案的优点是不会生成每分钟 1 个条目的巨大数据帧,并且对单个 Duration 值中可以包含多少天没有限制。

【讨论】:

  • 您好,您的解决方案部分有效,主要问题是您可以在 2021-01-02 看到,事件 1 Duration 应该是 760,因为前一天的事件在下一天继续天
  • @MarcoMiglionico .ffill()df['Event'] 上丢失了,我会修复它
  • 已修复@MarcoMiglionico。
【解决方案2】:

您可以通过使用pd.date_rangeexplodegroupby 创建日期范围来实现:

df["Start Date"] = pd.to_datetime(df["Start Date"])

df["TimeRange"] = [
    pd.date_range(s, periods=m, freq="T")
    for s, m in zip(df["Start Date"], df["Duration"])
]

df_out = (
    df.explode("TimeRange")
      .groupby(["Event", pd.Grouper(key="TimeRange", freq="D")]['Event']
      .count().rename('Duration').reset_index()
)

df_out

输出:

   Event  TimeRange  Duration
0      1 2021-01-01       880
1      1 2021-01-02       760
2      2 2021-01-01       560
3      2 2021-01-02        60

从开始日期开始按分钟创建记录,然后计算记录并按事件和日期分组。

【讨论】:

    【解决方案3】:

    这不是最优雅的解决方案,但它是一个起点

    # convert to datetime
    df['Start Date'] = pd.to_datetime(df['Start Date'])
    # calculate the end date
    df['End Date'] = df['Start Date'] + df['Duration'].apply(pd.Timedelta, unit='min')
    # create a mask to filter your frame where start date is not the same as end date
    same_day_mask = df['Start Date'].dt.date != df['End Date'].dt.date
    # create two new frames
    same_day_df = df[~same_day_mask].copy()
    not_same_day_df = df[same_day_mask].copy()
    # calculate the time it takes to get to midnight the next day
    not_same_day_df['day1'] = (not_same_day_df['End Date'].dt.normalize() - not_same_day_df['Start Date']).dt.total_seconds()/60
    # Calculate the remaining time from duration 
    not_same_day_df['day2'] = not_same_day_df['Duration'] - not_same_day_df['day1']
    # reasign value to duration
    not_same_day_df['Duration'] = not_same_day_df['day1']
    not_same_day_df['Event2'] = not_same_day_df['Event']
    new = not_same_day_df[['End Date', 'day2', 'Event2']].rename(columns={'End Date': 'Start Date',
                                                                          'day2': 'Duration',
                                                                          'Event2': 'Event'})
    # append the data frames together
    final_df = same_day_df.append(not_same_day_df[not_same_day_df.columns[:3]].append(new))
    # groupby and sum
    print(final_df.groupby([final_df['Start Date'].dt.normalize(), 'Event'])['Duration'].sum().reset_index())
    
      Start Date  Event  Duration
    0 2021-01-01      1     880.0
    1 2021-01-01      2     560.0
    2 2021-01-02      1     760.0
    3 2021-01-02      2      60.0
    

    【讨论】:

      【解决方案4】:

      看看DataFrame.groupby

      例如,您可以像这样计算一天中所有持续时间的总和:

      import pandas as pd
      import io
      
      
      df = """
      Date Time             Event    Duration
      2021.01.01 00:00 AM     2        540
      2021.01.01 9:00 AM      1        180 
      2021.01.01 12:00 PM     2        20
      2021.01.01 12:20 PM     1        1440
      2021.01.02 12:20 PM     2        60
      2021.01.02 1:20 PM      1        20
      """
      
      df = df = pd.read_csv(io.StringIO(df), sep=r"\s+")
      
      df.reset_index().groupby(["index", "Event"]).sum()
      
      
      >>>                   Duration
      >>> index      Event          
      >>> 2021.01.01 1          1620
      >>>            2           560
      >>> 2021.01.02 1            20
      >>>            2            60
      

      【讨论】:

        猜你喜欢
        • 2019-07-21
        • 1970-01-01
        • 2021-09-02
        • 1970-01-01
        • 2019-01-16
        • 2018-08-27
        • 2021-11-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多