按天重新采样并分类具有日期时间开始和日期时间结束的 DataFrame
Resampling by day and category a DataFrame that have datetime start and datetime end
问题
给定一个 table (DataFrame) 事件,其中每个事件(行)都有其开始日期时间和停止日期时间以及事件类别。
如何将此 table 转换为 table,其中每一行都是所有日期和类别的组合,以及这一天事件类别的相关小时数?
例子
也许看例子比解释问题更容易:
我想转换这个DataFrame
datetime_start
datetime_end
event_category
2021-01-0110:30:00
2021-01-0316:30:00
'A'
2021-01-0109:00:00
2021-01-0115:30:00
'B'
2021-01-0122:00:00
2021-01-0123:00:00
'B'
进入这个DataFrame
日期
event_category
sum_of_hours_with_event_active
2021-01-01
'A'
13.5
2021-01-01
'B'
7.5
2021-01-02
'A'
24
2021-01-02
'B'
0
2021-01-03
'A'
16.5
2021-01-03
'B'
0
如果您确定同一事件类别中的同一天没有重叠的时间段(或者您想重复计算这些时间段),那么您可以按事件类别创建所有日期的基础并合并您的时间跨度到该 DataFrame。
然后通过剪裁减去,我们可以计算出该事件仅在当天贡献的总时间(生成的负值与当天不对应,因此它们被剪裁为 0)。最后,我们可以在一天内按事件 sum
。
import pandas as pd
# Enumerate all categories for every day.
dfb = pd.merge(pd.DataFrame({'event_category': df['event_category'].unique()}),
pd.DataFrame({'date': pd.date_range(df.datetime_start.dt.normalize().min(),
df.datetime_end.dt.normalize().max(), freq='D')}),
how='cross')
# Merge timespans
m = dfb.merge(df, on='event_category')
# Calculate time for that day
m['sum_hours'] = ((m['datetime_end'].clip(upper=m['date']+pd.offsets.DateOffset(days=1))
- m['datetime_start'].clip(lower=m['date']))
.clip(lower=pd.Timedelta(0)))
# Sum of hours for event by day
m = (m.groupby(['event_category', 'date'])['sum_hours']
.sum().dt.total_seconds().div(3600)
.reset_index())
print(m)
event_category date sum_hours
0 A 2021-01-01 13.5
1 A 2021-01-02 24.0
2 A 2021-01-03 16.5
3 B 2021-01-01 7.5
4 B 2021-01-02 0.0
5 B 2021-01-03 0.0
数据
import pandas as pd
start_times = pd.DatetimeIndex(['2021-01-01 10:30:00', '2021-01-01 09:00:00', '2021-01-01 22:00:00'])
end_times = pd.DatetimeIndex(['2021-01-03 16:30:00', '2021-01-01 15:30:00', '2021-01-01 23:00:00'])
categories = ['A', 'B', 'B']
df = pd.DataFrame({'datetime_start': start_times, 'datetime_end': end_times, 'event_category': categories})
回答
首先我们 groupby
event_category 这样 apply
每个类别都起作用。两个系列的串联表示事件的变化,即事件的开始和结束。如果同一类别中有多个事件同时开始或结束,则需要 apply
内的 groupby
和 sum
。累积总和 (cumsum
) 给出发生变化时的事件总数,即一个或多个事件开始或结束时的事件总数。接下来我们使用 asfreq
上采样到所需的频率。这应该至少等于数据的时间粒度。最后我们再次重新采样(使用 groupby
和 Grouper
对象实现)和 sum
.
本质上,我们是在计算每个类别中所有事件占用的时间段数,然后乘以时间段的长度(示例中为半小时),然后按天分组。 DateOffset
对象用于参数化周期。
step = pd.DateOffset(hours=0.5) # Half hour steps
df.groupby('event_category') \
.apply(lambda x: pd.concat([pd.Series(1, x['datetime_start']),
pd.Series(-1, x['datetime_end'])]) \
.groupby(level=0) \
.sum() \
.cumsum() \
.asfreq(step, method='ffill')
) \
.groupby([pd.Grouper(level=0), pd.Grouper(level=1, freq='D')]) \
.sum() * step.hours
这将适用于同一类别中的重叠事件。
结果
event_category
A 2021-01-01 13.5
2021-01-02 24.0
2021-01-03 16.5
B 2021-01-01 7.5
dtype: float64
问题
给定一个 table (DataFrame) 事件,其中每个事件(行)都有其开始日期时间和停止日期时间以及事件类别。
如何将此 table 转换为 table,其中每一行都是所有日期和类别的组合,以及这一天事件类别的相关小时数?
例子
也许看例子比解释问题更容易:
我想转换这个DataFrame
datetime_start | datetime_end | event_category |
---|---|---|
2021-01-0110:30:00 | 2021-01-0316:30:00 | 'A' |
2021-01-0109:00:00 | 2021-01-0115:30:00 | 'B' |
2021-01-0122:00:00 | 2021-01-0123:00:00 | 'B' |
进入这个DataFrame
日期 | event_category | sum_of_hours_with_event_active |
---|---|---|
2021-01-01 | 'A' | 13.5 |
2021-01-01 | 'B' | 7.5 |
2021-01-02 | 'A' | 24 |
2021-01-02 | 'B' | 0 |
2021-01-03 | 'A' | 16.5 |
2021-01-03 | 'B' | 0 |
如果您确定同一事件类别中的同一天没有重叠的时间段(或者您想重复计算这些时间段),那么您可以按事件类别创建所有日期的基础并合并您的时间跨度到该 DataFrame。
然后通过剪裁减去,我们可以计算出该事件仅在当天贡献的总时间(生成的负值与当天不对应,因此它们被剪裁为 0)。最后,我们可以在一天内按事件 sum
。
import pandas as pd
# Enumerate all categories for every day.
dfb = pd.merge(pd.DataFrame({'event_category': df['event_category'].unique()}),
pd.DataFrame({'date': pd.date_range(df.datetime_start.dt.normalize().min(),
df.datetime_end.dt.normalize().max(), freq='D')}),
how='cross')
# Merge timespans
m = dfb.merge(df, on='event_category')
# Calculate time for that day
m['sum_hours'] = ((m['datetime_end'].clip(upper=m['date']+pd.offsets.DateOffset(days=1))
- m['datetime_start'].clip(lower=m['date']))
.clip(lower=pd.Timedelta(0)))
# Sum of hours for event by day
m = (m.groupby(['event_category', 'date'])['sum_hours']
.sum().dt.total_seconds().div(3600)
.reset_index())
print(m)
event_category date sum_hours
0 A 2021-01-01 13.5
1 A 2021-01-02 24.0
2 A 2021-01-03 16.5
3 B 2021-01-01 7.5
4 B 2021-01-02 0.0
5 B 2021-01-03 0.0
数据
import pandas as pd
start_times = pd.DatetimeIndex(['2021-01-01 10:30:00', '2021-01-01 09:00:00', '2021-01-01 22:00:00'])
end_times = pd.DatetimeIndex(['2021-01-03 16:30:00', '2021-01-01 15:30:00', '2021-01-01 23:00:00'])
categories = ['A', 'B', 'B']
df = pd.DataFrame({'datetime_start': start_times, 'datetime_end': end_times, 'event_category': categories})
回答
首先我们 groupby
event_category 这样 apply
每个类别都起作用。两个系列的串联表示事件的变化,即事件的开始和结束。如果同一类别中有多个事件同时开始或结束,则需要 apply
内的 groupby
和 sum
。累积总和 (cumsum
) 给出发生变化时的事件总数,即一个或多个事件开始或结束时的事件总数。接下来我们使用 asfreq
上采样到所需的频率。这应该至少等于数据的时间粒度。最后我们再次重新采样(使用 groupby
和 Grouper
对象实现)和 sum
.
本质上,我们是在计算每个类别中所有事件占用的时间段数,然后乘以时间段的长度(示例中为半小时),然后按天分组。 DateOffset
对象用于参数化周期。
step = pd.DateOffset(hours=0.5) # Half hour steps
df.groupby('event_category') \
.apply(lambda x: pd.concat([pd.Series(1, x['datetime_start']),
pd.Series(-1, x['datetime_end'])]) \
.groupby(level=0) \
.sum() \
.cumsum() \
.asfreq(step, method='ffill')
) \
.groupby([pd.Grouper(level=0), pd.Grouper(level=1, freq='D')]) \
.sum() * step.hours
这将适用于同一类别中的重叠事件。
结果
event_category
A 2021-01-01 13.5
2021-01-02 24.0
2021-01-03 16.5
B 2021-01-01 7.5
dtype: float64