使用pandas将带起止日期的年度数据按月份拆分为月度行记录
pandas实现跨起止日期的数值按月份分摊拆分
实现逻辑
- 先将原始数据的起止日期转换为datetime格式,支撑日期运算
- 针对单条数据,生成它覆盖的所有月份列表
- 计算整条数据的总覆盖天数,得到日均分摊金额
- 按月份计算当月实际覆盖的天数,乘以日均金额得到当月分摊值
- 所有拆分后的记录合并即为最终结果
完整代码
import pandas as pd # 构造示例原始数据,实际使用时替换为自己的数据源读取逻辑即可 df = pd.DataFrame({ 'id': ['abc'], 'StartDate': ['2018/12/12'], 'EndDate': ['2019/11/30'], # 示例结果对应结束日期为2019/11/30,可根据实际调整 'Annual': [120450] }) # 日期列转datetime格式,指定格式避免解析错误 df['StartDate'] = pd.to_datetime(df['StartDate'], format='%Y/%m/%d') df['EndDate'] = pd.to_datetime(df['EndDate'], format='%Y/%m/%d') def split_to_monthly(row): # 生成覆盖的所有月份的第一天 month_list = pd.date_range( start=row['StartDate'].to_period('M').to_timestamp(), end=row['EndDate'].to_period('M').to_timestamp(), freq='MS' ) # 计算整条记录的总覆盖天数 total_days = (row['EndDate'] - row['StartDate']).days + 1 daily_amount = row['Annual'] / total_days monthly_records = [] for month_start in month_list: # 当月最后一天 month_end = month_start + pd.offsets.MonthEnd(0) # 当月实际覆盖的起止日期 actual_start = max(row['StartDate'], month_start) actual_end = min(row['EndDate'], month_end) # 当月覆盖天数 cover_days = (actual_end - actual_start).days + 1 # 当月分摊金额,示例取整为整数,可按需调整精度 monthly_volume = int(round(cover_days * daily_amount, 0)) monthly_records.append({ 'id': row['id'], 'Month': month_start.month, 'Year': month_start.year, 'Monthly volume': monthly_volume }) return monthly_records # 应用拆分逻辑,展开为最终数据框 result_df = df.apply(split_to_monthly, axis=1).explode().apply(pd.Series).reset_index(drop=True) print(result_df)
注意事项
- 如果业务规则不需要包含结束日期当天,删除所有计算天数时的
+1即可 - 金额精度可按需调整,修改
round函数的第二个参数即可指定保留的小数位数 - 数据量较大时,可提前对日期范围做过滤优化,避免重复运算
内容的提问来源于stack exchange,提问作者Dim Link
相关产品推荐
相关产品推荐

