使用Python生成随机转移概率矩阵

4

我想知道在Python中是否有一种简单的方法来生成一个填充了随机数字的方阵,给定以下条件:

  • 行总和必须为1。
  • 对角线上的值应该显著高于其他值。

这是一个随机矩阵,生成一个是可能的,但关键是对角线的条件。例如,对于一个4x4的矩阵,输出应该类似于:

[[0.90, 0.03, 0.03, 0.04],
[0.01, 0.98, 0.005, 0.005],
[0.04, 0.01, 0.92, 0.03],
[0.00, 0.02, 0.03, 0.95]]

有没有好的方法可以为可变大小生成这样的矩阵?

有多高?你有概率分布来量化吗? - Reti43
如果对角线上的所有值都高于0.90,我会很开心!目前还没有量化的概率分布。 - DutchJ
嗯,您可以使用 random.uniform(0.9, 1.0) 来生成对角线值,然后生成一个包含3个其他数字的列表,并简单地标准化它们,以便它们总和为 1 - diagonal_value。但我确定一些统计学家会指出此方法生成了有偏差的数字样本,因此您可能不想这么做。顺便说一下,如果您愿意使用numpy,请考虑添加标签。 - Reti43
1
@DutchJ 我很难想象有什么情况下我想要随机数,却不关心分布是什么。你能为这个问题提供更多的背景吗? - Max
@Reti43 反过来可能可以。首先使用具有适当小均值的分布生成所有非对角线条目,然后填入对角线。 - Paul Panzer
@Max 目前,这只是为了测试我的代码,但如果维度变得太大,手动生成这样的矩阵将变得繁琐。我不关心确切的数字,而是背后的想法。即对购买产品A、产品B等的客户的“随机”行为进行建模... - DutchJ
2个回答

2

这里有一个快速而简单的解决方案

import random

k = 4

result = [[random.uniform(0, 0.1 / k) for i in range(k)] for j in range(k)]
for j, r in enumerate(result):
    r[j] += 1 - sum(r)

您可能需要考虑使用不同的随机分布,同时也要查看numpy。

从均匀分布中求和将得到平均值为(低 + 高)/ 2 = 0.05。这意味着你的对角线将非常接近0.95。 使用np.array(result).diagonal()查看。矩阵越大,这种情况就越明显。 - Reti43
@Reti43 你的意思是它们不太随机吗?人们可能会这样想,但对角线元素的方差仍将是离对角线元素的方差的(n-1)倍,所以在我看来这看起来相当不错。 - Paul Panzer
对角线是随机的,但不是均匀的。请记住,它们受到其他数字之和的限制,您没有那种自由度。至于n个均匀数的总和的分布,您可以查看Irwin-Hall分布。对于极限n->无穷大,它是正态分布。因此,就像从分布N(0.95,σ)生成对角线一样。 - Reti43
我的最初建议是对角线应该是均匀选择的数字。由于剩余的数字只是缩小了,它们仍然保持着均匀分布。这在我看来可能还可以,但我的直觉感觉有些可疑,这就是为什么我把它保留为建议的原因。无论如何,OP已经对你的解决方案感到满意。 - Reti43
@Reti43 哦,我明白了。顺便说一下,我认为你对可疑性的直觉是正确的。因为你必须使用的缩放因子取决于你为该行绘制的所有数字,所以得到的条目不会是独立的,也不会是均匀分布的。我并不是说我的方法是 OP 问题的正确模型,但它在数学上更方便,主要是因为它避免了这些依赖关系。 - Paul Panzer

2
这里有一种使用 numpy.identity 的方法,从一个k x k的单位矩阵开始,加上漂移项,然后进行归一化处理。
import numpy as np

k = 4
result = np.identity(4)

# Add a random drift term.  We can guarantee that the diagonal terms
#     will be larger by specifying a `high` parameter that is < 1.
# How much larger depends on that term.  Here, it is 0.25.
result = result + np.random.uniform(low=0., high=.25, size=(k, k))

# Lastly, divide by row-wise sum to normalize to 1.
result = result / result.sum(axis=1, keepdims=1)

# Check
print(result)
print(result.sum(axis=1))

# [[ 0.80736896  0.00663004  0.06474194  0.12125906]
#  [ 0.03545472  0.79746194  0.10495657  0.06212678]
#  [ 0.08566011  0.02632533  0.79709851  0.09091605]
#  [ 0.07298408  0.05698381  0.1585878   0.71144431]]
#
# [ 1.  1.  1.  1.]

上述内容简化为两行:
result = np.identity(k) + np.random.uniform(low=0., high=.25, size=(k, k))
result /= result.sum(axis=1, keepdims=1)

如果指定一个更大的high参数,将会得到一个更小的“对角线与其余部分”的比率:

result = np.identity(k) + np.random.uniform(low=0., high=.60, size=(k, k))
result /= result.sum(axis=1, keepdims=1)
print(result.round(2))
# [[ 0.53  0.02  0.25  0.2 ]
#  [ 0.05  0.58  0.19  0.18]
#  [ 0.02  0.04  0.72  0.22]
#  [ 0.07  0.23  0.08  0.62]]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接