Pandas不规则时间序列分组后过滤DataFrame的技术问询
解决时序DataFrame分组后过滤指定时段及空分组的问题
看起来你在处理时序数据分组时遇到了两个头疼的问题:一是pd.Grouper生成了一堆不需要的空分组,二是尝试用apply过滤14点后时段时触发了KeyError: True。我来一步步帮你拆解解决。
先明确你的场景
先把你的示例数据用可运行的代码还原出来,方便后续验证:
import pandas as pd data = { 'timestamp': ['2018-11-12 14:03:53', '2018-11-12 14:03:58', '2018-11-12 14:04:09', '2018-11-12 14:04:19', '2018-11-12 14:04:29'], 'A': [9.45, 73.8, 4.25, 62.39, 15.98], 'B': [501.0, 108.0, 215.0, 551.0, 113.0] } df = pd.DataFrame(data) df['timestamp'] = pd.to_datetime(df['timestamp']) df.set_index('timestamp', inplace=True)
先解答你的附带问题:为什么之前的apply写法会报错?
你尝试的代码:
df.groupby(pd.Grouper(freq='30Min')).apply(lambda x: x[x.index.min().hour >= 14])
触发KeyError: True的核心原因是**groupby.apply()对返回值的规则要求**:
- 它要求每个分组的返回值要么是标量,要么是和输入分组同结构的DataFrame/Series,或者是能被组合成统一结果的对象。
- 你的lambda逻辑里,当分组时段符合条件时返回整个分组DataFrame,不符合时返回
x[False]——也就是空DataFrame。pandas在拼接这些结果时,空DataFrame的索引处理会混乱,最终抛出KeyError。简单说,这种写法不符合apply的返回规范。
正确的解决方案:两种高效过滤方式
方式1:先过滤原始数据,再分组(推荐!)
既然你只关注每日14点之后的数据,最高效的做法是先把原始数据中14点前的行过滤掉,再分组。这样分组时自然不会生成14点前的分箱,从根源避免空分组:
# 第一步:过滤出14点及之后的行 filtered_df = df[df.index.hour >= 14] # 第二步:正常分组聚合 result = filtered_df.groupby(pd.Grouper(freq='30Min')).apply(custom_agg)
这种方法不需要处理后续的空分组,性能最优,优先推荐使用。
方式2:分组后再过滤(适合必须保留部分空分组的场景)
如果因为业务需求必须先分组再过滤(比如要保留14点后某些空分组,但排除其他时段的),可以用groupby对象的filter方法(注意这和DataFrame的filter不是同一个东西):
def filter_valid_groups(x): # 两个条件:分组非空,且分组的起始时段在14点之后 # 用floor('30Min')确保取的是分箱的起始时间(比如14:03的数据属于14:00的分箱) return not x.empty and x.index.floor('30Min').hour.min() >= 14 # 先分组过滤,再重新分组聚合 result = df.groupby(pd.Grouper(freq='30Min')).filter(filter_valid_groups)\ .groupby(pd.Grouper(freq='30Min')).apply(custom_agg)
或者更简单的:先完成聚合,再过滤掉空行和不符合时段的行:
# 先完成所有分组的聚合 agg_result = df.groupby(pd.Grouper(freq='30Min')).apply(custom_agg) # 过滤:保留14点后且非全NaN的行 final_result = agg_result[(agg_result.index.hour >=14) & (~agg_result.isna().all(axis=1))]
这里~agg_result.isna().all(axis=1)用来排除所有列都是NaN的空分组行,agg_result.index.hour >=14确保只保留目标时段。
示例验证
假设你的自定义聚合函数是计算A列均值和B列总和:
def custom_agg(x): return pd.Series({ 'A_mean': x['A'].mean(), 'B_sum': x['B'].sum() })
用方式1处理后,结果只会包含2018-11-12 14:00:00这个30分钟分箱的聚合值,完全不会出现其他空分组。
内容的提问来源于stack exchange,提问作者mathiascg
相关产品推荐
相关产品推荐

