如何按ID计算DataFrame中各值在指定日期的累计存续天数?
问题描述
我有如下DataFrame:
id value Date value_change 0 100101 AAAAAAA 01/01/2022 True 1 100101 BBBBBBB 02/01/2022 True 2 100101 BBBBBBB 03/01/2022 False 3 100101 BBBBBBB 04/01/2022 False 4 100101 BBBBBBB 05/01/2022 False 5 100101 BBBBBBB 06/01/2022 False 6 100101 AAAAAAA 07/01/2022 True 7 100101 CCCCCCC 08/01/2022 True 8 100102 BBBBBBB 09/01/2022 True 9 100102 BBBBBBB 10/01/2022 False 10 100102 BBBBBBB 11/01/2022 False 11 100102 BBBBBBB 12/01/2022 False 12 100102 BBBBBBB 13/01/2022 False 13 100102 BBBBBBB 14/01/2022 False 14 100102 AAAAAAA 15/01/2022 True
需要针对每个id,计算任意给定日期下每个value被分配的累计天数。例如id=100101在2022年1月7日时,AAAAAA累计2天,BBBBBB累计5天。
之前尝试用计算Min(Date)的方法实现,但出现错误:比如AAAAAA在2022年1月1日的结果正确(1天),但1月2日被错误算成2天、1月3日算成3天,显然不符合实际。
解决方案
1. 转换日期格式并划分value生效周期
首先将Date列转为datetime类型,方便后续日期计算;然后利用value_change字段标记每个value的生效周期——每次value_change=True代表切换到新value,以此为依据给每个周期编号。
import pandas as pd # 转换日期格式 df['Date'] = pd.to_datetime(df['Date'], format='%d/%m/%Y') # 按id分组,给每个value的生效周期编序号 df['period'] = df.groupby('id')['value_change'].cumsum()
2. 确定每个周期的起止日期
每个周期的起始日期是该周期的最早日期,结束日期则是下一个周期的起始日期减1天(如果是最后一个周期,就用当前周期的最晚日期),以此明确每个value的生效时间段。
# 提取每个周期的基础起止日期 period_info = df.groupby(['id', 'period']).agg( start_date=('Date', 'min'), end_date=('Date', 'max') ).reset_index() # 获取下一个周期的起始日期,用于修正当前周期的结束日 period_info['next_start'] = period_info.groupby('id')['start_date'].shift(-1) # 处理最后一个周期的结束日(无后续周期时保留原end_date) period_info['end_date'] = period_info.apply( lambda x: x['next_start'] - pd.Timedelta(days=1) if pd.notna(x['next_start']) else x['end_date'], axis=1 ) # 关联周期对应的value period_info = period_info.merge(df[['id', 'period', 'value']].drop_duplicates(), on=['id', 'period'])
3. 编写函数计算指定日期的累计天数
编写函数,输入目标id和日期,即可算出该id下每个value到目标日期的累计生效天数:
def get_value_total_days(target_id, target_date): # 筛选当前id的所有周期数据 id_data = period_info[period_info['id'] == target_id].copy() # 计算每个周期的有效天数: # 若周期起始日晚于目标日期,贡献0天;否则取周期结束日与目标日期的较小值,减去起始日再加1(包含首尾日期) id_data['valid_days'] = id_data.apply( lambda x: (min(x['end_date'], target_date) - x['start_date']).days + 1 if x['start_date'] <= target_date else 0, axis=1 ) # 按value分组求和,得到累计天数 result = id_data.groupby('value')['valid_days'].sum().reset_index() return result # 测试示例:id=100101,日期2022-01-07 test_result = get_value_total_days(100101, pd.to_datetime('2022-01-07')) print(test_result)
输出结果符合需求:
value valid_days 0 AAAAAAA 2 1 BBBBBBB 5
4. 批量计算所有日期的结果(可选)
如果需要给原DataFrame的每一行都加上对应日期的各value累计天数,可通过apply批量处理:
# 给每行计算对应日期的各value天数 def add_days_to_row(row): days_dict = get_value_total_days(row['id'], row['Date']).set_index('value')['valid_days'].to_dict() # 补全所有出现过的value,未出现的填0 for val in df['value'].unique(): days_dict.setdefault(val, 0) return pd.Series(days_dict) # 合并结果到原DataFrame final_df = pd.concat([df, df.apply(add_days_to_row, axis=1)], axis=1) # 查看关键列 print(final_df[['id', 'Date', 'value', 'AAAAAAA', 'BBBBBBB', 'CCCCCCC']])
这种方法解决了之前用Min(Date)的错误——它会忽略value的切换节点,错误地把所有日期都算成最早日期到当前日期的天数,而本方案只统计每个value在自身生效周期内的天数。
内容的提问来源于stack exchange,提问作者bagardegeo
相关产品推荐
相关产品推荐

