DataFrame按含列表列分组计算新列报错:unhashable type list 求解
问题:DataFrame分组计算时列表列导致"unhashable type list"错误的解决方法
需求与问题
需要对DataFrame按DealType、StartDate、Commodity三列分组,通过公式计算新列FloatPrice:FloatPrice = -(totalValue - totalFP)/totalQuantity
其中:
- totalValue:分组内
Value列的总和 - totalFP:分组内
FixedPrice * Quantity的总和 - totalQuantity:分组内
Quantity列的总和
但由于Commodity列是列表类型,分组时触发unhashable type list错误,以下两种尝试代码均失败。
数据示例
DealType StartDate Commodity FixedPrice Quantity Value Sell 01Dec2023 [stock1, stock2] 1.0 10 10 Sell 01Dec2023 [stock1, stock2] 2.0 10 10 Sell 01Dec2023 [stock1, stock2] 3.0 10 10 Buy 01Dec2023 [stock1, stock2, stock3] 1.0 10 10 Buy 01Dec2023 [stock1, stock2, stock3] 2.0 10 10 Buy 01Dec2023 [stock1, stock2, stock3] 3.0 10 10
预期结果
DealType StartDate Commodity FixedPrice Quantity Value FloatPrice Sell 01Dec2023 [stock1, stock2] 1.0 10 10 1.0 Sell 01Dec2023 [stock1, stock2] 2.0 10 10 1.0 Sell 01Dec2023 [stock1, stock2] 3.0 10 10 1.0 Buy 01Dec2023 [stock1, stock2, stock3] 1.0 10 10 1.0 Buy 01Dec2023 [stock1, stock2, stock3] 2.0 10 10 1.0 Buy 01Dec2023 [stock1, stock2, stock3] 3.0 10 10 1.0
尝试的代码(注释已翻译)
代码1
# 按Commodity、StartDate、DealType分组 grouped_df = df.groupby(['Commodity', 'StartDate', 'DealType']) # 计算每组的总MTMValue、总行权价、总数量 total_values = grouped_df.agg({'MTMValue': 'sum', 'FixedPriceStrike': 'sum', 'Quantity': 'sum'}) # 用公式计算FloatPrice total_values['FloatPrice'] = -(total_values['MTMValue'] - (total_values['FixedPriceStrike'] * total_values['Quantity'])) / total_values['Quantity'] # 将FloatPrice合并回原DataFrame df = pd.merge(df, total_values['FloatPrice'], how='left', on=['Commodity', 'StartDate', 'DealType'])
代码2
df['FloatPrice'] = grouped_df.apply( lambda group: -(group['MTMValue'].sum() - (group['FixedPriceStrike'] * group['Quantity']).sum()) / group['Quantity'].sum() ).reset_index(level=[0, 1, 2], drop=True)
解决方法与最优实现
核心原因
列表是可变类型,不可哈希,而groupby的分组键必须是可哈希类型(如字符串、元组、数值等),因此直接用列表分组会报错。解决思路是将Commodity列转换为可哈希的类型。
方法1:列表转元组(推荐)
元组是不可变可哈希类型,且能完整保留原列表的结构信息,适合后续需要复用分组逻辑的场景。
简洁实现(用reset_index匹配原行)
import pandas as pd # 将Commodity列的列表转为元组 df['Commodity_tuple'] = df['Commodity'].apply(tuple) # 分组计算FloatPrice并匹配到原DataFrame df['FloatPrice'] = df.groupby(['DealType', 'StartDate', 'Commodity_tuple']).apply( lambda g: -(g['Value'].sum() - (g['FixedPrice'] * g['Quantity']).sum()) / g['Quantity'].sum() ).reset_index(level=[0,1,2], drop=True) # 可选:删除临时生成的元组列 df.drop('Commodity_tuple', axis=1, inplace=True)
高效实现(适合大数据量,用agg+合并)
import pandas as pd df['Commodity_tuple'] = df['Commodity'].apply(tuple) # 分组计算所需聚合指标 agg_result = df.groupby(['DealType', 'StartDate', 'Commodity_tuple']).agg( totalValue=('Value', 'sum'), totalFP=('FixedPrice', lambda x: (x * df.loc[x.index, 'Quantity']).sum()), totalQuantity=('Quantity', 'sum') ).reset_index() # 计算FloatPrice agg_result['FloatPrice'] = -(agg_result['totalValue'] - agg_result['totalFP']) / agg_result['totalQuantity'] # 合并回原DataFrame df = pd.merge(df, agg_result[['DealType', 'StartDate', 'Commodity_tuple', 'FloatPrice']], on=['DealType', 'StartDate', 'Commodity_tuple'], how='left') # 删除临时列 df.drop('Commodity_tuple', axis=1, inplace=True)
方法2:列表转字符串(适合无需保留列表结构的场景)
将列表用分隔符拼接成字符串,同样可作为分组键:
import pandas as pd # 将列表转为逗号分隔的字符串 df['Commodity_str'] = df['Commodity'].apply(lambda x: ','.join(x)) # 后续分组、计算、合并逻辑同方法1,最后删除临时列 df.drop('Commodity_str', axis=1, inplace=True)
最优方案推荐
- 中小数据量:优先选择**列表转元组+
groupby+apply+reset_index**的简洁实现,代码量少且易维护。 - 大数据量:选择列表转元组+
agg+合并的方式,避免apply的性能瓶颈,运行更稳定。
内容的提问来源于stack exchange,提问作者iBeMeltin
相关产品推荐
相关产品推荐

