scipy - 生成具有相关性的随机变量

时间:2015-01-01 01:44:41

标签: python numpy scipy

我正在努力在Python中实现一个基本的蒙特卡罗模拟器,用于我正在尝试的一些项目管理风险建模(基本上是Crystal Ball / @Risk,但是在Python中)。

我有一组n个随机变量(所有scipy.stats个实例)。我知道我可以使用rv.rvs(size=k)从这些k个变量中生成n 个独立的观察结果。

我想通过指定n x n正半定相关矩阵来引入变量之间的相关性。

在scipy中有一种干净的方法吗?

我尝试过什么

This answerthis answer似乎表明“copulas”将是一个答案,但我没有看到任何scipy参考。

This link似乎实现了我正在寻找的东西,但我不确定scipy是否已实现此功能。我也希望它适用于非正常变量。

似乎Iman, Conover paper是标准方法。

3 个答案:

答案 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易于采样(如均匀或高斯分布),然后使用随机测试来确定提案分布中的这个样本是否应该被接受"表示所需分布的样本。

剩下的技巧将是:

  1. 找出关节N维概率密度函数的形式,它具有您希望沿每个维度形成的边缘,但具有您想要的相关矩阵。这对于高斯分布来说很容易做到,其中所需的相关矩阵和均值向量就是定义分布所需的全部。如果你的边缘有一个简单的表达式,你可以用一些简单而又乏味的代数找到这个pdf。 This论文引用了其他几个与您谈论的内容相符的内容,并且我确定还有更多内容。

  2. basinhopping制定一个函数,使其最小化,以便它被接受" minima"相当于您定义的此pdf样本。

  3. 鉴于(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    ]