如何用Pandas计算指数成分不变的连续天数均值?
解决指数成分连续不变天数的平均值计算问题
我明白你的需求啦——要从这个日期索引的市值DataFrame里,找出成分(非零列集合)连续不变的时间段,统计每个时间段的天数,再求这些天数的平均值。下面给你一步步拆解实现方法,附带示例代码:
步骤1:构造示例数据(方便演示)
先模拟一个和你描述一致的DataFrame,这样你可以直接跑代码验证:
import pandas as pd # 生成日期索引 dates = pd.date_range('2023-01-01', periods=10) # 公司列名 companies = ['a', 'b', 'c', 'd'] # 市值数据:0代表未加入/已退出指数 data = [ [100, 200, 0, 0], # 成分:a、b [150, 250, 0, 0], # 成分:a、b [0, 300, 150, 0], # 成分:b、c [0, 350, 200, 0], # 成分:b、c [0, 400, 250, 0], # 成分:b、c [200, 0, 300, 100], # 成分:a、c、d [250, 0, 350, 150], # 成分:a、c、d [300, 100, 400, 200],# 成分:a、b、c、d [350, 150, 450, 250],# 成分:a、b、c、d [0, 200, 500, 300] # 成分:b、c、d ] df = pd.DataFrame(data, index=dates, columns=companies)
步骤2:给每行标记唯一的成分标识
我们需要给每行的非零列集合生成一个可哈希的唯一标识,这样才能判断两行的成分是否完全相同。这里有两种常用方式:
方式A:用frozenset(无序集合,适合不关心列顺序的场景)
# 对每行,提取非零列的名称,转成不可变的frozenset(可作为分组键) component_sets = df.apply(lambda row: frozenset(row[row > 0].index), axis=1)
方式B:用排序后的字符串拼接(更直观,适合需要查看成分的场景)
# 对每行,提取非零列名称并排序,拼接成字符串 component_keys = df.apply(lambda row: ','.join(sorted(row[row > 0].index)), axis=1)
两种方式效果等价,选你习惯的就行。
步骤3:识别连续相同成分的分组
通过比较当前行和上一行的成分标识,生成分组ID——连续相同的成分会被分到同一个组:
# 以方式A为例,方式B只需把component_sets换成component_keys即可 group_ids = (component_sets != component_sets.shift()).cumsum()
解释:shift()把上一行的成分标识移到当前行位置,比较是否和当前行不同;cumsum()会在每次成分变化时累加1,这样连续相同的行就会拥有同一个group_id。
步骤4:计算连续天数的平均值
统计每个分组的行数(即连续天数),然后求这些天数的平均值:
# 统计每个分组的行数 continuous_days = group_ids.value_counts() # 计算平均值 average_continuous_days = continuous_days.mean() print(f"指数成分不变的连续天数平均值:{average_continuous_days:.2f}")
在示例数据中,输出结果是2.00,因为各分组的天数分别是2、3、2、2、1,平均值为(2+3+2+2+1)/5=2。
简化版链式代码
如果你不想保留中间变量,可以把步骤2-4合并成一行:
average_days = (df.apply(lambda row: frozenset(row[row > 0].index), axis=1) .pipe(lambda x: (x != x.shift()).cumsum()) .value_counts() .mean())
边界情况说明
- 如果所有日期的成分完全相同,那么平均值就是DataFrame的总行数;
- 如果每行成分都不一样,平均值就是1;
- 空DataFrame(无数据)的话需要额外处理,不过一般你的场景不会遇到这种情况。
内容的提问来源于stack exchange,提问作者bbarroso
相关产品推荐
相关产品推荐

