webentwicklung-frage-antwort-db.com.de

Wie kann man Zufallszahlen generieren, um einen bestimmten Mittelwert und Median in Python zu erreichen?

Ich möchte n Zufallszahlen erzeugen, z. B. n=200, wobei der Bereich möglicher Werte zwischen 2 und 40 mit einem Mittelwert von 12 und einem Median von 6,5 liegt.

Ich habe überall gesucht und konnte keine Lösung dafür finden. Ich habe versucht, das folgende Skript funktioniert für kleine Zahlen wie 20, für große Zahlen dauert es Ewigkeiten und das Ergebnis wird zurückgegeben.

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)

Könnte mir jemand helfen, indem er dies verbessert, um ein schnelles Ergebnis zu erzielen, selbst wenn n = 5000 oder so?

7
MWH

Eine Möglichkeit, ein Ergebnis zu erzielen, das dem gewünschten Ergebnis sehr nahe kommt, besteht darin, zwei separate Zufallsbereiche mit der Länge 100 zu generieren, die Ihre Medianeinschränkungen erfüllen und alle gewünschten Zahlenbereiche enthalten. Durch die Verkettung der Arrays liegt der Mittelwert bei etwa 12, aber nicht ganz bei 12. Da es sich jedoch lediglich um einen Mittelwert handelt, mit dem Sie sich befassen, können Sie einfach Ihr erwartetes Ergebnis erzeugen, indem Sie eines dieser Arrays anpassen.

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

Nachfolgend finden Sie eine vektorisierte und stark optimierte Lösung gegen jede andere Lösung, die für Code auf Python-Ebene verwendet wird, indem die Erstellung zufälliger Sequenzen eingeschränkt wird:

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))

Demo:

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

6.5
12.0

Die Logik hinter der Funktion:

Um ein zufälliges Array mit diesen Kriterien zu erhalten, können wir 3 Arrays arr1, mid und arr2 miteinander verketten. arr1 und arr2 enthalten jeweils 99 Elemente, und die mid enthält 2 Elemente 6 und 7, sodass das Endergebnis einen Medianwert von 6,5 ergibt. Jetzt erstellen wir zwei zufällige Arrays mit jeweils einer Länge von 99. Um das Ergebnis mit einem Mittelwert von 12 zu ermitteln, müssen wir nur die Differenz zwischen der aktuellen Summe und 12 * 200 ermitteln und das Ergebnis von unseren N größten Zahlen abziehen Wir können sie aus arr2 auswählen und N=50 verwenden.

Bearbeiten:

Wenn es kein Problem ist, Float-Nummern in Ihrem Ergebnis zu haben, können Sie die Funktion wie folgt abkürzen:

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))
4
Kasrâmvd

Hier möchten Sie einen Medianwert, der unter dem Mittelwert liegt. Das bedeutet, dass eine einheitliche Verteilung nicht angemessen ist: Sie möchten viele kleine und weniger große Werte.

Insbesondere möchten Sie, dass so viele Werte kleiner oder gleich 6 sind, wie die Anzahl der Werte größer oder gleich 7 ist.

Ein einfacher Weg, um sicherzustellen, dass der Median 6,5 ist, besteht darin, dass Sie im Bereich [2 - 6] die gleiche Anzahl von Werten wie in [7 - 40] haben. Wenn Sie in beiden Bereichen einheitliche Verteilungen gewählt haben, haben Sie einen theoretischen Mittelwert von 13,75, was nicht so weit von den erforderlichen 12 ist.

Eine geringfügige Abweichung der Gewichte kann das theoretische Mittel noch näher bringen: Wenn wir [5, 4, 3, 2, 1, 1, ..., 1] für die relativen Gewichte des random.choices der [7, 8, ..., 40] finden wir einen theoretischen Mittelwert von 19,98 für diesen Bereich, der nahe an den erwarteten 20 liegt.

Beispielcode:

>>> pop1 = list(range(2, 7))
>>> pop2 = list(range(7, 41))
>>> w2 = [ 5, 4, 3, 2 ] + ( [1] * 30)
>>> r1 = random.choices(pop1, k=2500)
>>> r2 = random.choices(pop2, w2, k=2500)
>>> r = r1 + r2
>>> random.shuffle(r)
>>> statistics.mean(r)
12.0358
>>> statistics.median(r)
6.5
>>>

Wir haben also jetzt eine Verteilung von 5000 Werten, die einen Median von genau 6,5 und einen Mittelwert von 12,0358 hat (dieses ist zufällig, und ein anderer Test ergibt einen etwas anderen Wert). Wenn wir einen exakten Mittelwert von 12 wünschen, müssen wir nur einige Werte anpassen. Hier ist sum(r) 60179, wenn es 60000 sein sollte, also müssen wir 175 Werte verringern, die weder 2 sind (sich außerhalb des Bereichs befinden) als 7 (würde den Median ändern).

Am Ende könnte eine mögliche Generatorfunktion sein:

def gendistrib(n):
    if n % 2 != 0 :
        raise ValueError("gendistrib needs an even parameter")
    n2 = n//2     # n / 2 in Python 2
    pop1 = list(range(2, 7))               # lower range
    pop2 = list(range(7, 41))              # upper range
    w2 = [ 5, 4, 3, 2 ] + ( [1] * 30)      # weights for upper range
    r1 = random.choices(pop1, k=n2)        # lower part of the distrib.
    r2 = random.choices(pop2, w2, k=n2)    # upper part
    r = r1 + r2
    random.shuffle(r)                      # randomize order
    # time to force an exact mean
    tot = sum(r)
    expected = 12 * n
    if tot > expected:                     # too high: decrease some values
        for i, val in enumerate(r):
            if val != 2 and val != 7:
                r[i] = val - 1
                tot -= 1
                if tot == expected:
                    random.shuffle(r)      # shuffle again the decreased values
                    break
    Elif tot < expected:                   # too low: increase some values
        for i, val in enumerate(r):
            if val != 6 and val != 40:
                r[i] = val + 1
                tot += 1
                if tot == expected:
                    random.shuffle(r)      # shuffle again the increased values
                    break
    return r

Es ist wirklich schnell: Ich könnte timeit gendistrib(10000) in weniger als 0,02 Sekunden. Es sollte jedoch nicht für kleine Distributionen (weniger als 1000) verwendet werden.

2
Serge Ballesta

Ok, Sie betrachten die Verteilung mit nicht weniger als 4 Parametern - zwei von denen, die den Bereich definieren und zwei, die für den erforderlichen Mittelwert und den Median verantwortlich sind.

Ich könnte über zwei Möglichkeiten von Kopf bis Fuß nachdenken:

  1. Abgeschnittene Normalverteilung, siehe hier für Details. Sie haben bereits den Bereich definiert und müssen μ und σ vom Mittelwert und Medianwert zurückgewinnen. Es wird das Lösen einiger nichtlinearer Gleichungen erforderlich machen, aber in Python durchaus machbar. Die Probenahme könnte mit https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.truncnorm.html durchgeführt werden

  2. 4-Parameter-Beta-Verteilung, siehe hier für Details. Um α und β in der Beta-Verteilung aus dem Mittelwert und dem Medianwert wiederzugewinnen, müssen einige nichtlineare Gleichungen gelöst werden. Sie zu kennen, wäre einfach über https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.beta.html

AKTUALISIEREN

Wie könnten Sie dies tun, wenn Sie von einem Mittelwert zu einem Mu abgeglichen sind: Mit einem gegebenen Mittelwert normal abgeschnitten

1

Wenn Sie eine Reihe kleinerer Arrays mit dem richtigen Median und Mittelwert haben, können Sie diese kombinieren, um ein größeres Array zu erzeugen.

Sie können also kleinere Arrays vorab erzeugen, wie Sie es gerade tun, und diese dann zufällig für ein größeres n kombinieren. Natürlich wird dies zu einem voreingenommenen Zufalls-Sample führen, aber es klingt, als wollten Sie nur etwas, das ungefähr zufällig ist.

Hier ist der Funktionscode (py3), der ein Muster der Größe 5000 mit den gewünschten Eigenschaften generiert, das aus kleineren Mustern der Größe 4, 6, 8, 10, ..., 18 erstellt wird.

Beachten Sie, dass ich den Aufbau der kleineren Zufallsstichproben geändert habe: Die Hälfte der Zahlen muss <= 6 und die Hälfte> = 7 sein, wenn der Median 6,5 sein soll, also generieren wir diese Hälften unabhängig voneinander. Das beschleunigt die Dinge enorm.

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))

Ausgabe:

[(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

Die erste Zeile der Ausgabe zeigt, dass sich im letzten Array 620 2, 52 3, 440 4 usw. befinden.

0
Paul Hankin

Während dieser Beitrag bereits eine akzeptierte Antwort hat, möchte ich einen allgemeinen, nicht ganzzahligen Ansatz beisteuern. Es sind keine Schleifen oder Tests erforderlich. Die Idee ist es, eine PDF mit kompakter Unterstützung. Nehmen Sie die Idee der akzeptierten Antwort von Kasrâmvd, machen Sie zwei Verteilungen im linken und rechten Intervall. Wählen Sie Formparameter so, dass der Mittelwert auf den Wert fällt Die interessante Möglichkeit hierbei ist, dass man ein kontinuierliches PDF erstellen kann, dh ohne Sprünge, bei denen die Intervalle zusammenlaufen.

Als Beispiel habe ich die Betaverteilung gewählt. Um endliche Werte ungleich Null an der Grenze zu haben, habe ich Beta = 1 für links und Alpha = 1 für rechts gewählt. Betrachtet man die Definition des PDF und die Anforderung des Mittelwerts, ergibt die Kontinuität zwei Gleichungen:

  • 4.5 / alpha = 33.5 / beta
  • 2 + 6.5 * alpha / ( alpha + 1 ) + 6.5 + 33.5 * 1 / ( 1 + beta ) = 24

Dies ist eine quadratische Gleichung, die ziemlich einfach zu lösen ist. Das nur mit scipy.stat.beta Gefällt

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()

bietet

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

die Ergebnisse sind also wie gewünscht und es sieht so aus: enter image description here

0
mikuszefski