如何用Pandas按动态24小时间隔为DataFrame时间列分配组值?
问题描述
现有如下Pandas DataFrame,其中列B为datetime类型数据:
import pandas as pd data = {'A': ['XYZ', 'XYZ', 'XYZ', 'XYZ', 'PQR', 'PQR', 'PQR', 'PQR', 'CVB', 'CVB', 'CVB', 'CVB'], 'B': ['2022-02-16 14:00:31', '2022-02-16 16:11:26', '2022-02-16 17:31:26', '2022-02-16 22:47:46', '2022-02-17 07:11:11', '2022-02-17 10:43:36', '2022-02-17 15:05:11', '2022-02-18 18:06:12', '2022-02-19 09:05:46', '2022-02-19 13:02:16', '2022-02-19 18:05:26', '2022-02-19 22:05:26']} df = pd.DataFrame(data) df['B'] = pd.to_datetime(df['B'])
需求为按动态24小时间隔分组:第一个组的起始时间为数据集首个时间戳,后续数据若在该起始时间的24小时范围内则归为同一组;若超出范围则开启新组,新组起始时间设为当前数据的时间,以此类推,最终生成Group列。预期输出如下:
| A | B | Group | |
|---|---|---|---|
| 0 | XYZ | 2022-02-16 14:00:31 | 1 |
| 1 | XYZ | 2022-02-16 16:11:26 | 1 |
| 2 | XYZ | 2022-02-16 17:31:26 | 1 |
| 3 | XYZ | 2022-02-16 22:47:46 | 1 |
| 4 | PQR | 2022-02-17 07:11:11 | 1 |
| 5 | PQR | 2022-02-17 10:43:36 | 1 |
| 6 | PQR | 2022-02-17 15:05:11 | 2 |
| 7 | PQR | 2022-02-18 18:06:12 | 3 |
| 8 | CVB | 2022-02-19 09:05:46 | 3 |
| 9 | CVB | 2022-02-19 13:02:16 | 3 |
| 10 | CVB | 2022-02-19 18:05:26 | 3 |
| 11 | CVB | 2022-02-19 22:05:26 | 4 |
当前尝试的代码仅标记了第一个组,其余组值为NaN,无法满足需求。
解决方案
方案1:Numba加速循环(高效处理百万级数据)
该方案通过Numba编译循环逻辑,将Python循环转换为机器码执行,在处理百万级时间戳时性能优异,避免了纯Pandas操作的额外开销。
import pandas as pd import numpy as np from numba import jit # 初始化数据 data = {'A': ['XYZ', 'XYZ', 'XYZ', 'XYZ', 'PQR', 'PQR', 'PQR', 'PQR', 'CVB', 'CVB', 'CVB', 'CVB'], 'B': ['2022-02-16 14:00:31', '2022-02-16 16:11:26', '2022-02-16 17:31:26', '2022-02-16 22:47:46', '2022-02-17 07:11:11', '2022-02-17 10:43:36', '2022-02-17 15:05:11', '2022-02-18 18:06:12', '2022-02-19 09:05:46', '2022-02-19 13:02:16', '2022-02-19 18:05:26', '2022-02-19 22:05:26']} df = pd.DataFrame(data) df['B'] = pd.to_datetime(df['B']) # 将时间转换为纳秒数值,方便Numba处理 timestamps = df['B'].values.astype(np.int64) # 24小时对应的纳秒数 one_day_ns = pd.Timedelta(days=1).value @jit(nopython=True) def assign_groups(timestamps): n = len(timestamps) groups = np.ones(n, dtype=np.int64) current_group_start = timestamps[0] current_group = 1 for i in range(1, n): if timestamps[i] > current_group_start + one_day_ns: current_group += 1 current_group_start = timestamps[i] groups[i] = current_group return groups # 生成Group列 df['Group'] = assign_groups(timestamps) print(df)
方案2:纯Pandas矢量化实现(无需额外依赖)
如果不想引入Numba依赖,可使用Pandas的扩展窗口操作实现,虽效率略低于Numba方案,但也能处理大规模数据:
import pandas as pd # 初始化数据 data = {'A': ['XYZ', 'XYZ', 'XYZ', 'XYZ', 'PQR', 'PQR', 'PQR', 'PQR', 'CVB', 'CVB', 'CVB', 'CVB'], 'B': ['2022-02-16 14:00:31', '2022-02-16 16:11:26', '2022-02-16 17:31:26', '2022-02-16 22:47:46', '2022-02-17 07:11:11', '2022-02-17 10:43:36', '2022-02-17 15:05:11', '2022-02-18 18:06:12', '2022-02-19 09:05:46', '2022-02-19 13:02:16', '2022-02-19 18:05:26', '2022-02-19 22:05:26']} df = pd.DataFrame(data) df['B'] = pd.to_datetime(df['B']) # 初始化组起始时间序列 group_starts = pd.Series(index=df.index, dtype='datetime64[ns]') group_starts.iloc[0] = df['B'].iloc[0] # 判断每个位置是否需要开启新组 new_group = df['B'] > group_starts.expanding().max() + pd.Timedelta(days=1) # 更新组起始时间:新组用当前时间,旧组沿用之前的起始时间 group_starts = group_starts.where(~new_group, df['B']) # 生成Group列:新组标志的累积和+1 df['Group'] = new_group.cumsum() + 1 print(df)
核心逻辑说明
两种方案遵循同一逻辑:
- 第一个组的起始时间为数据集的首个时间戳
- 遍历每个时间戳,若当前时间超出当前组起始时间的24小时范围,则开启新组,新组起始时间设为当前时间
- 通过累积计数生成最终组号
内容的提问来源于stack exchange,提问作者user3046211
相关产品推荐
相关产品推荐

