我正在努力在Python中实现一个基本的蒙特卡罗模拟器,用于我正在尝试的一些项目管理风险建模(基本上是Crystal Ball / @Risk,但是在Python中)。
我有一组n
个随机变量(所有scipy.stats
个实例)。我知道我可以使用rv.rvs(size=k)
从这些k
个变量中生成n
个独立的观察结果。
我想通过指定n x n
正半定相关矩阵来引入变量之间的相关性。
在scipy中有一种干净的方法吗?
我尝试过什么
This answer和this answer似乎表明“copulas”将是一个答案,但我没有看到任何scipy参考。
This link似乎实现了我正在寻找的东西,但我不确定scipy是否已实现此功能。我也希望它适用于非正常变量。
似乎Iman, Conover paper是标准方法。
答案 0 :(得分:10)
如果你只想通过高斯Copula(*)进行相关,那么可以用numpy和scipy的几个步骤来计算它。
使用所需的协方差numpy.random.multivariate_normal
创建多变量随机变量,并创建一个(k_variables的nobs)数组
应用scipy.stats.norm.cdf
将常规变换为均匀随机变量,为每个列/变量获得均匀的边际分布
应用dist.ppf
将统一边距转换为所需的分布,其中dist
可以是scipy.stats
(*)高斯copula只是一种选择,当我们对尾部行为感兴趣时它并不是最好的,但它最容易使用 例如http://archive.wired.com/techbiz/it/magazine/17-03/wp_quant?currentPage=all
两个参考
https://stats.stackexchange.com/questions/37424/how-to-simulate-from-a-gaussian-copula
http://www.mathworks.com/products/demos/statistics/copulademo.html
(我可能刚才在python中做过这个,但是现在没有任何脚本或函数。)
答案 1 :(得分:3)
看起来像基于拒绝的采样方法,例如Metropolis-Hastings算法就是你想要的。 Scipy可以使用scipy.optimize.basinhopping函数实现此类方法。
基于拒绝的抽样方法允许您从任何给定的概率分布中抽取样本。这个想法是你从另一个"提案"中抽取随机样本。 pdf易于采样(如均匀或高斯分布),然后使用随机测试来确定提案分布中的这个样本是否应该被接受"表示所需分布的样本。
剩下的技巧将是:
找出关节N维概率密度函数的形式,它具有您希望沿每个维度形成的边缘,但具有您想要的相关矩阵。这对于高斯分布来说很容易做到,其中所需的相关矩阵和均值向量就是定义分布所需的全部。如果你的边缘有一个简单的表达式,你可以用一些简单而又乏味的代数找到这个pdf。 This论文引用了其他几个与您谈论的内容相符的内容,并且我确定还有更多内容。
为basinhopping
制定一个函数,使其最小化,以便它被接受" minima"相当于您定义的此pdf样本。
鉴于(1)的结果,(2)应该是直截了当的。
答案 2 :(得分:0)
如果您已经有一个正半定相关矩阵R [n x n],则很容易用R作为输入来构建NormalCopula。我将向您展示一个n = 3的示例。该代码基于OpenTURNS library。
import openturns as ot
# you can replace this part by your matrix
dim = 3
R = ot.CorrelationMatrix (dim)
R[0,1] = 0.25
R[0,2] = 0.6
R[1,2] = 0.9
copula = ot.NormalCopula(R)
要获取尺寸样本,只需写
size = 5
print(copula.getSample(size))
>>> [ X0 X1 X2 ]
0 : [ 0.355353 0.76205 0.632379 ]
1 : [ 0.902567 0.984443 0.989552 ]
2 : [ 0.423219 0.811016 0.754304 ]
3 : [ 0.303776 0.471557 0.450188 ]
4 : [ 0.746168 0.918729 0.891347 ]
编辑-按照@Michael_Baudin的评论
当然,如果您要将边际分布设置为例如Beta和LogNormal边际,也可以:
X0 = ot.LogNormal(0.1, 1, 0)
X1 = ot.Beta()
X2 = ot.Uniform(1.0, 2.0)
distribution = ot.ComposedDistribution([X0,X1,X2], Original_copula)
print(distribution.getSample(size))
>>> [ X0 X1 X2 ]
0 : [ 3.97678 0.158823 1.75635 ]
1 : [ 1.18929 -0.554092 1.18952 ]
2 : [ 2.59542 0.0751359 1.68599 ]
3 : [ 1.33363 -0.18407 1.42241 ]
4 : [ 1.34084 0.198019 1.6553 ]