计算组合单股风险贡献时遇log方法NaN及矩阵不兼容错误
咱们一步步来排查你遇到的两个问题:
1. 第一个警告:log(pr) : NaNs produced
这个问题的根源是你用错了Return.calculate函数。你已经通过percent_change2计算出了月度简单收益率(last(x)/first(x)-1),但又把这个收益率序列传给了Return.calculate——而这个函数的设计初衷是从价格序列计算收益率,不是对已有的收益率做二次处理。
当你用method="log"时,Return.calculate会执行log(1 + 简单收益率)来转换为对数收益率,但如果你的月度收益率里存在<= -1的值(比如价格出现负数,或者填充NA时出错导致收益异常),log(1 + r)就会遇到非正数,进而产生NaNs。
修复方案:
直接把你已经计算好的monthly_return处理成对数收益率就行,不用再调用Return.calculate:
# 直接将简单收益率转换为对数收益率,注意要确保1 + 收益率 > 0 portfolioComponentReturns <- na.omit(log(1 + monthly_return))
或者如果你本来就想用简单收益率计算风险贡献,那完全可以跳过对数转换,直接用monthly_return(记得先处理NA):
portfolioComponentReturns <- na.omit(monthly_return)
另外,建议检查你的monthly_return里是否有异常值(比如<= -1的收益率),这可能是填充NA时的逻辑问题导致的——你用前后两天的价格平均填充NA,但如果前后价格本身有问题,可能会算出不合理的月度收益。可以用summary(monthly_return)查看数据分布。
2. 第二个错误:non-conformable arguments
这个错误说明矩阵乘法的维度不匹配:t(w)的列数和covariance_matrix的行数不一致。通常有两个原因:
- 你的权重向量
w的长度,和portfolioComponentReturns中的资产数量(列数)不相等; covariance_matrix的维度因为NA处理或数据错误发生了变化(比如某列全是NA被na.omit删掉了)。
修复方案:
先确认
w的长度:
运行ncol(portfolioComponentReturns)查看资产数量,确保length(w)和这个数字完全一致。比如如果你的monthly_return有18列(对应df的2:19列),那w必须是长度为18的向量,且元素和为1(投资组合权重的基本要求)。检查
covariance_matrix的维度:
运行dim(covariance_matrix),看它是否是n×n的矩阵(n是资产数量)。如果不是,说明portfolioComponentReturns里有列被na.omit删掉了(因为整列都是NA),这时候需要重新检查数据填充的逻辑,确保每列都有有效数据。确保
w是列向量(或者在矩阵乘法时正确转换):
有时候w如果是普通向量,t(w)会变成行向量,但如果covariance_matrix是n×n,t(w) %*% covariance_matrix需要行向量×n×n矩阵,结果是行向量,再乘以w(列向量)才能得到标量。可以显式把w转成矩阵:w_matrix <- matrix(w, ncol = 1) sd_portfolio <- sqrt(t(w_matrix) %*% covariance_matrix %*% w_matrix)
完整修正后的代码示例
# 先处理月度收益率转换为对数收益率(或直接用简单收益率) portfolioComponentReturns <- na.omit(log(1 + monthly_return)) # 检查数据是否正常,避免出现-Inf值 stopifnot(all(apply(portfolioComponentReturns, 2, function(x) all(x != -Inf)))) # 计算协方差矩阵 covariance_matrix <- cov(portfolioComponentReturns) # 确保权重向量维度匹配资产数量 w_matrix <- matrix(w, ncol = 1) stopifnot(ncol(covariance_matrix) == length(w)) # 计算组合标准差 sd_portfolio <- sqrt(t(w_matrix) %*% covariance_matrix %*% w_matrix) # 边际风险贡献 marginal_contribution <- (covariance_matrix %*% w_matrix) / sd_portfolio[1, 1] # 组件风险贡献(权重×边际贡献,确保维度匹配) component_contribution <- w_matrix * marginal_contribution # 验证总和是否等于组合波动率 components_summed <- sum(component_contribution) # 百分比风险贡献 component_percentages <- component_contribution / sd_portfolio[1, 1] # 整理成表格 percentage_tibble_by_hand <- tibble( asset = colnames(portfolioComponentReturns), `portfolio weight` = as.vector(w_matrix), `risk contribution` = as.vector(component_percentages) ) percentage_tibble_by_hand
另外,补充一点:你在填充NA的时候用的df[nas, -1] <- (df[nas-1,-1] + df[nas+1,-1])/2,如果nas是第一行或者最后一行,nas-1或nas+1会超出索引范围,导致错误。建议用更稳健的填充方法,比如zoo::na.approx(df[, -1])来线性插值填充NA,避免边界问题。
内容的提问来源于stack exchange,提问作者Revolucion for Monica

