You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何不使用循环结合复杂条件实现Group by分组统计?

问题描述

我有如下DataFrame:

import pandas as pd
from dateutil.relativedelta import relativedelta

df = pd.DataFrame({
    'Company': {0: 'A', 1: 'A', 2: 'A', 3: 'A', 4: 'A', 5: 'A', 6: 'A', 7: 'A', 8: 'A'},
    'Date': {0: pd.Timestamp('2022-10-10 00:00:00'), 1: pd.Timestamp('2022-10-10 00:00:00'), 2: pd.Timestamp('2022-10-10 00:00:00'), 3: pd.Timestamp('2022-10-10 00:00:00'), 4: pd.Timestamp('2023-10-10 00:00:00'), 5: pd.Timestamp('2023-10-10 00:00:00'), 6: pd.Timestamp('2023-10-10 00:00:00'), 7: pd.Timestamp('2023-10-10 00:00:00'), 8: pd.Timestamp('2023-10-10 00:00:00')}, 
    'Year': {0: 2021, 1: 2022, 2: 2023, 3: 2024, 4: 2022, 5: 2023, 6: 2024, 7: 2025, 8: 2026},
    'Sales_1Q': {0: 5.0, 1: float('nan'), 2: float('nan'), 3: 1.0, 4: float('nan'), 5: float('nan'), 6: float('nan'), 7: float('nan'), 8: float('nan')},
    'Sales_2Q': {0: float('nan'), 1: 10.0, 2: float('nan'), 3: float('nan'), 4: 10.0, 5: float('nan'), 6: float('nan'), 7: 10.0, 8: 20.0}, 
    'Sales_3Q': {0: 5.0, 1: float('nan'), 2: float('nan'), 3: 1.0, 4: float('nan'), 5: float('nan'), 6: float('nan'), 7: float('nan'), 8: float('nan')}, 
    'Sales_4Q': {0: float('nan'), 1: 10.0, 2: float('nan'), 3: float('nan'), 4: 10.0, 5: float('nan'), 6: float('nan'), 7: 10.0, 8: 20.0},
    'Actual_date_1Q': {0: pd.Timestamp('2021-01-01 00:00:00'), 1: pd.NaT, 2: pd.NaT, 3: pd.Timestamp('2024-10-12 00:00:00'), 4: pd.NaT, 5: pd.NaT, 6: pd.NaT, 7: pd.NaT, 8: pd.NaT},
    'Actual_date_2Q': {0: pd.NaT, 1: pd.Timestamp('2023-01-11 00:00:00'), 2: pd.NaT, 3: pd.NaT, 4: pd.Timestamp('2023-01-11 00:00:00'), 5: pd.NaT, 6: pd.NaT, 7: pd.Timestamp('2025-10-10 00:00:00'), 8: pd.Timestamp('2026-10-10 00:00:00')},
    'Actual_date_3Q': {0: pd.Timestamp('2021-01-19 00:00:00'), 1: pd.NaT, 2: pd.NaT, 3: pd.Timestamp('2025-01-01 00:00:00'), 4: pd.NaT, 5: pd.NaT, 6: pd.NaT, 7: pd.NaT, 8: pd.NaT},
    'Actual_date_4Q': {0: pd.NaT, 1: pd.Timestamp('2023-07-11 00:00:00'), 2: pd.NaT, 3: pd.NaT, 4: pd.Timestamp('2023-07-11 00:00:00'), 5: pd.NaT, 6: pd.NaT, 7: pd.Timestamp('2026-06-22 00:00:00'), 8: pd.Timestamp('2027-06-22 00:00:00')}
})

任务说明

需按Company和Date列分组,当Actual_date_*Q列的值落在对应年度的季度区间时,统计各季度Sales_*Q列的总和。

我已编写代码生成年份到对应季度区间的字典:

min_year = df.Year[df.Year != 0].min()
max_year = df.Year.max()
intervals = {}

for year in range(min_year, max_year + 1):
    q_count = 0
    start_date = pd.to_datetime(f'{year}-01-01')
    end_date = pd.to_datetime(f'{year}-12-31')
    year_interval = pd.date_range(start=start_date, end=end_date, freq='Q')
    
    list_q_days = {}
    
    for per in range(len(year_interval)):
        q_count += 1 
        if per == 0:
            q_start = year_interval[per] - relativedelta(months=3) + relativedelta(days=1) 
        else:
            q_start = year_interval[per-1] + relativedelta(days=1) 
  
        q_days = pd.date_range(start=q_start, end=year_interval[per], freq='d')
        list_q_days[f'{q_count}Q'] = q_days
 
    intervals[year] = list_q_days

已尝试方案

我用循环实现了需求,但效率极低,大数据场景下耗时过长:

%%time
new_df = pd.DataFrame()
for group, data in df.groupby(['Company','Date']):  
    for date in intervals:
        for q in ['1Q','2Q','3Q', '4Q']: 
            data.loc[(data.Year == date), q] = (
                data.apply(lambda x: x['Sales_1Q'] if x['Actual_date_1Q'] in intervals[date][q] else 0, axis=1).sum() +
                data.apply(lambda x: x['Sales_2Q'] if x['Actual_date_2Q'] in intervals[date][q] else 0, axis=1).sum() +
                data.apply(lambda x: x['Sales_3Q'] if x['Actual_date_3Q'] in intervals[date][q] else 0, axis=1).sum() +
                data.apply(lambda x: x['Sales_4Q'] if x['Actual_date_4Q'] in intervals[date][q] else 0, axis=1).sum()
            )
            
    new_df = pd.concat([new_df, data])
df = new_df.copy()

核心问题是外层的groupby循环,尝试用groupby.apply时总是触发TypeError: unhashable type: 'Series'等错误,不知道如何正确结合两者实现高效计算。

期望输出

最终需要得到如下结构的DataFrame:

target_df = pd.DataFrame( {
    'Company': {0: 'A', 1: 'A', 2: 'A', 3: 'A', 4: 'A', 5: 'A', 6: 'A', 7: 'A', 8: 'A'}, 
    'Date': {0: pd.Timestamp('2022-10-10 00:00:00'), 1: pd.Timestamp('2022-10-10 00:00:00'), 2: pd.Timestamp('2022-10-10 00:00:00'), 3: pd.Timestamp('2022-10-10 00:00:00'), 4: pd.Timestamp('2023-10-10 00:00:00'), 5: pd.Timestamp('2023-10-10 00:00:00'), 6: pd.Timestamp('2023-10-10 00:00:00'), 7: pd.Timestamp('2023-10-10 00:00:00'), 8: pd.Timestamp('2023-10-10 00:00:00')}, 
    'Year': {0: 2021, 1: 2022, 2: 2023, 3: 2024, 4: 2022, 5: 2023, 6: 2024, 7: 2025, 8: 2026},
    'Sales_1Q': {0: 5.0, 1: float('nan'), 2: float('nan'), 3: 1.0, 4: float('nan'), 5: float('nan'), 6: float('nan'), 7: float('nan'), 8: float('nan')},
    'Sales_2Q': {0: float('nan'), 1: 10.0, 2: float('nan'), 3: float('nan'), 4: 10.0, 5: float('nan'), 6: float('nan'), 7: 10.0, 8: 20.0}, 
    'Sales_3Q': {0: 5.0, 1: float('nan'), 2: float('nan'), 3: 1.0, 4: float('nan'), 5: float('nan'), 6: float('nan'), 7: float('nan'), 8: float('nan')}, 
    'Sales_4Q': {0: float('nan'), 1: 10.0, 2: float('nan'), 3: float('nan'), 4: 10.0, 5: float('nan'), 6: float('nan'), 7: 10.0, 8: 20.0},
    'Actual_date_1Q': {0: pd.Timestamp('2021-01-01 00:00:00'), 1: pd.NaT, 2: pd.NaT, 3: pd.Timestamp('2024-10-12 00:00:00'), 4: pd.NaT, 5: pd.NaT, 6: pd.NaT, 7: pd.NaT, 8: pd.NaT},
    'Actual_date_2Q': {0: pd.NaT, 1: pd.Timestamp('2023-01-11 00:00:00'), 2: pd.NaT, 3: pd.NaT, 4: pd.Timestamp('2023-01-11 00:00:00'), 5: pd.NaT, 6: pd.NaT, 7: pd.Timestamp('2025-10-10 00:00:00'), 8: pd.Timestamp('2026-10-10 00:00:00')},
    'Actual_date_3Q': {0: pd.Timestamp('2021-01-19 00:00:00'), 1: pd.NaT, 2: pd.NaT, 3: pd.Timestamp('2025-01-01 00:00:00'), 4: pd.NaT, 5: pd.NaT, 6: pd.NaT, 7: pd.NaT, 8: pd.NaT},
    'Actual_date_4Q': {0: pd.NaT, 1: pd.Timestamp('2023-07-11 00:00:00'), 2: pd.NaT, 3: pd.NaT, 4: pd.Timestamp('2023-07-11 00:00:00'), 5: pd.NaT, 6: pd.NaT, 7: pd.Timestamp('2026-06-22 00:00:00'), 8: pd.Timestamp('2027-06-22 00:00:00')},
    '1Q': {0: 10.0, 1: 0.0, 2: 10.0, 3: 0.0, 4: 0.0, 5: 10.0, 6: 0.0, 7: 0.0, 8: 0.0}, 
    '2Q': {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0, 5: 0.0, 6: 0.0, 7: 0.0, 8: 10.0},
    '3Q': {0: 0.0, 1: 0.0, 2: 10.0, 3: 0.0, 4: 0.0, 5: 10.0, 6: 0.0, 7: 0.0, 8: 0.0}, 
    '4Q': {0: 0.0, 1: 0.0, 2: 0.0, 3: 1.0, 4: 0.0, 5: 0.0, 6: 0.0, 7: 10.0, 8: 20.0}
} )

内容的提问来源于stack exchange,提问作者Princeps Maxima

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 21:20:52