web-dev-qa-db-ja.com

Pythonで特定の平均と中央値を満たすために乱数を生成する方法は?

N個の乱数を生成したいと思います(例:n=200、可能な値の範囲は2〜40で、平均値は12で、中央値は6.5です。

私はどこでも検索しましたが、これに対する解決策を見つけることができませんでした。次のスクリプトを試してみたところ、20などの小さな数値で機能するため、大きな数値の場合は時間がかかり、結果が返されます。

n=200
x = np.random.randint(0,1,size=n) # initalisation only
while True:
        if x.mean() == 12 and np.median(x) == 6.5:
            break
        else:
            x=np.random.randint(2,40,size=n)

誰かがこれを改善して、n = 5000程度の場合でも迅速な結果を得るのを手伝ってくれませんか?

9
MWH

期待する結果に非常に近い結果を得る1つの方法は、中央の制約を満たし、すべての望ましい範囲の数値を含む、長さ100の2つの個別のランダム範囲を生成することです。次に、配列を連結することにより、平均は約12になりますが、12とは完全に等しくありません。しかし、これは単に処理しているだけなので、これらの配列の1つを調整することで、期待どおりの結果を生成できます。

In [162]: arr1 = np.random.randint(2, 7, 100)    
In [163]: arr2 = np.random.randint(7, 40, 100)

In [164]: np.mean(np.concatenate((arr1, arr2)))
Out[164]: 12.22

In [166]: np.median(np.concatenate((arr1, arr2)))
Out[166]: 6.5

以下は、ランダムシーケンスの作成を制限することにより、forループまたはpythonレベルのコードを使用する他のソリューションに対して非常に最適化されたベクトル化されたソリューションです。

import numpy as np
import math

def gen_random(): 
    arr1 = np.random.randint(2, 7, 99)
    arr2 = np.random.randint(7, 40, 99)
    mid = [6, 7]
    i = ((np.sum(arr1 + arr2) + 13) - (12 * 200)) / 40
    decm, intg = math.modf(i)
    args = np.argsort(arr2)
    arr2[args[-41:-1]] -= int(intg)
    arr2[args[-1]] -= int(np.round(decm * 40))
    return np.concatenate((arr1, mid, arr2))

デモ:

arr = gen_random()
print(np.median(arr))
print(arr.mean())

6.5
12.0

関数の背後にあるロジック:

その基準を持つランダムな配列を作成するために、arr1midarr2の3つの配列を連結できます。 arr1arr2はそれぞれ99アイテムを保持し、midは2アイテム6と7を保持するため、最終結果は中央値として6.5になります。次に、それぞれ長さが99の2つのランダム配列を作成します。結果を12の平均値にするために必要なことは、現在の合計と12 * 200の差を見つけて、Nの最大数から結果を引くことです。この場合、arr2から選択してN=50を使用できます。

編集:

結果に浮動小数点数が含まれていても問題ない場合は、実際に次のように関数を短縮できます。

import numpy as np
import math

def gen_random(): 
    arr1 = np.random.randint(2, 7, 99).astype(np.float)
    arr2 = np.random.randint(7, 40, 99).astype(np.float)
    mid = [6, 7]
    i = ((np.sum(arr1 + arr2) + 13) - (12 * 200)) / 40
    args = np.argsort(arr2)
    arr2[args[-40:]] -= i
    return np.concatenate((arr1, mid, arr2))
5
Kasramvd

さて、あなたは4つ以上のパラメータを持つ分布を見ています-2つは範囲を定義し、2つは必要な平均と中央値に責任があります。

頭の上から2つの可能性について考えることができます。

  1. 正規分布が切り捨てられています。詳細は here を参照してください。すでに範囲が定義されており、平均と中央値からμとσを回復する必要があります。いくつかの非線形方程式を解く必要がありますが、Pythonではかなり可能です。サンプリングは https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.truncnorm.html を使用して実行できます

  2. 4パラメータのベータ分布。詳細は こちら を参照してください。この場合も、平均値と中央値からベータ分布のαとβを復元するには、いくつかの非線形方程式を解く必要があります。それらをサンプリングすることを知ることは https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.beta.html によって簡単になります

更新

ここでは、平均からmuへの切り捨てられた法線に対してどのようにそれを行うことができますか: 指定された平均で切り捨てられた法線

1

適切な中央値と平均値を持つ小さな配列がたくさんある場合は、それらを組み合わせてより大きな配列を作成できます。

したがって...現在実行しているように、より小さな配列を事前に生成し、それらをランダムに組み合わせて、より大きなnにすることができます。もちろん、これはランダムなサンプルに偏りをもたらしますが、ほぼランダムなものを求めているように思えます。

これは、サイズ4、6、8、10、...、18の小さなサンプルから構築された、必要なプロパティを備えたサイズ5000のサンプルを生成する作業(py3)コードです。

小さいランダムサンプルの作成方法を変更したことに注意してください。中央値を6.5にする場合は、数値の半分を<= 6および半分> = 7にする必要があるため、これらの半分を個別に生成します。これにより、処理速度が大幅に向上します。

import collections
import numpy as np
import random

rs = collections.defaultdict(list)
for i in range(50):
    n = random.randrange(4, 20, 2)
    while True:
        x=np.append(np.random.randint(2, 7, size=n//2), np.random.randint(7, 41, size=n//2))
        if x.mean() == 12 and np.median(x) == 6.5:
            break
    rs[len(x)].append(x)

def random_range(n):
    if n % 2:
        raise AssertionError("%d must be even" % n)
    r = []
    while n:
        i = random.randrange(4, min(20, n+1), 2)
        # Don't be left with only 2 slots left.
        if n - i == 2: continue
        xs = random.choice(rs[i])
        r.extend(xs)
        n -= i
    random.shuffle(r)
    return r

xs = np.array(random_range(5000))
print([(i, list(xs).count(i)) for i in range(2, 41)])
print(len(xs))
print(xs.mean())
print(np.median(xs))

出力:

[(2, 620), (3, 525), (4, 440), (5, 512), (6, 403), (7, 345), (8, 126), (9, 111), (10, 78), (11, 25), (12, 48), (13, 61), (14, 117), (15, 61), (16, 62), (17, 116), (18, 49), (19, 73), (20, 88), (21, 48), (22, 68), (23, 46), (24, 75), (25, 77), (26, 49), (27, 83), (28, 61), (29, 28), (30, 59), (31, 73), (32, 51), (33, 113), (34, 72), (35, 33), (36, 51), (37, 44), (38, 25), (39, 38), (40, 46)]
5000
12.0
6.5

出力の最初の行は、最終的な配列に2が620、3が52、4が440などあることを示しています。

0
Paul Hankin

この投稿にはすでに認められた回答がありますが、私は一般的な非整数アプローチを提供したいと思います。ループやテストは必要ありません。アイデアは、PDFをコンパクトにサポートして取得することです。Kasrâmvdの受け入れられた回答のアイデアを使用して、左と右の間隔で2つの分布を作成します。平均が次のようになるような形状パラメーターを選択します。ここで興味深いのは、連続したPDFを作成できることです。つまり、間隔がつながっているジャンプはありません。

例として、私はベータ分布を選択しました。境界でゼロ以外の有限値を使用するために、左にベータ= 1、右にアルファ= 1を選択しました。 PDFの定義と平均の要件を見ると、連続性は2つの方程式を与えます。

  • _4.5 / alpha = 33.5 / beta_
  • 2 + 6.5 * alpha / ( alpha + 1 ) + 6.5 + 33.5 * 1 / ( 1 + beta ) = 24

これは解くのがかなり簡単な二次方程式です。 _scipy.stat.beta_を使用するだけ

_from scipy.stats import beta

import matplotlib.pyplot as plt
import numpy as np

x1 = np.linspace(2, 6.5, 200 )
x2 = np.linspace(6.5, 40, 200 )

# i use s and t not alpha and beta
s = 1./737 *(np.sqrt(294118) - 418 )
t = 1./99 *(np.sqrt(294118) - 418 )

data1 = beta.rvs(s, 1, loc=2, scale=4.5, size=20000)
data2 = beta.rvs(1, t, loc=6.5, scale=33.5, size=20000)
data = np.concatenate( ( data1, data2 ) )
print np.mean( data1 ), 2 + 4.5 * s/(1.+s)
print np.mean( data2 ), 6.5 + 33.5/(1.+t) 
print np.mean( data )
print np.median( data )

fig = plt.figure()
ax = fig.add_subplot( 1, 1, 1 )
ax.hist(data1, bins=13, density=True )
ax.hist(data2, bins=67, density=True )
ax.plot( x1, beta.pdf( x1, s, 1, loc=2, scale=4.5 ) )
ax.plot( x2, beta.pdf( x2, 1, t, loc=6.5, scale=33.5 ) )
ax.set_yscale( 'log' )
plt.show()
_

提供する

_>> 2.661366939244768 2.6495436216856976
>> 21.297348804473618 21.3504563783143
>> 11.979357871859191
>> 6.5006779033245135
_

結果は必要に応じて次のようになります enter image description here

0
mikuszefski