SciPy Norm.cdf()在lambda函数中返回数组而非单个观测值问题
问题分析与解决方案
你的核心问题是apply用法错误,原代码用生成器表达式作为apply参数而非合法映射函数,导致返回结果不是预期的二维DataFrame,而是迭代器,进而出现维度、索引混乱。同时没有利用numpy/pandas的向量化特性,错误尝试循环列,导致每个位置返回整组结果。
推荐解决方案:向量化计算(高效简洁)
直接用numpy广播机制一次性计算整个生存矩阵,避免低效的循环或apply操作:
import pandas as pd import numpy as np from scipy.stats import norm df = pd.DataFrame({'qty' : [20, 30, 40], 'price' : [100, 50, 20], 'life_exp' : [5, 4, 3]}) df['sd'] = df['life_exp'] / 4 max_life = df['life_exp'].max() ncols = max_life * 2 + 1 ages = np.arange(ncols) # 生成所有列对应的年龄值(0到max_life*2) # 利用广播计算每行每个年龄的z分数:(年龄 - 寿命预期)/标准差 # 将life_exp和sd转为(n行,1列)的数组,与ages(1行,n列)广播运算得到n行n列的矩阵 z_scores = (ages - df['life_exp'].values[:, None]) / df['sd'].values[:, None] # 计算生存概率并转为DataFrame,列索引设为年龄 lifeleft_2 = pd.DataFrame(1 - norm.cdf(z_scores), columns=ages) print(lifeleft_2)
为什么这个方法有效?
- 广播机制自动将一维的
ages与每行的life_exp/sd配对计算,直接生成完整的n行n列矩阵,不会出现维度错位。 - 列索引直接设为年龄值,完全匹配你的需求:每行对应产品,每列对应年龄。
若坚持使用apply(不推荐,效率较低)
如果一定要用apply,需针对每行计算所有年龄的生存概率,再调整结构:
import pandas as pd import numpy as np from scipy.stats import norm df = pd.DataFrame({'qty' : [20, 30, 40], 'price' : [100, 50, 20], 'life_exp' : [5, 4, 3]}) df['sd'] = df['life_exp'] / 4 max_life = df['life_exp'].max() ncols = max_life * 2 + 1 ages = np.arange(ncols) # 定义每行的计算函数 def calc_row_survival(row): z_scores = (ages - row['life_exp']) / row['sd'] return 1 - norm.cdf(z_scores) # 对每行应用函数,转置后调整索引 lifeleft_2 = df.apply(calc_row_survival, axis=1).T lifeleft_2.columns = df.index lifeleft_2.index = ages lifeleft_2 = lifeleft_2.T
原代码的错误点
lifeleft.apply(lambda x: ... for col in l_cols)是非法写法:apply需要接收一个函数,你传入的是生成器表达式,导致返回迭代器而非DataFrame。- 即使修正lambda写法,循环列再apply会导致每个单元格返回整组结果,而非单个值,这就是你看到“每个观测位置返回整个数组”的原因。
内容的提问来源于stack exchange,提问作者Phil P.
相关产品推荐
相关产品推荐

