|
""" |
|
Utilities for generating random numbers, random sequences, and |
|
random selections. |
|
""" |
|
|
|
import networkx as nx |
|
from networkx.utils import py_random_state |
|
|
|
__all__ = [ |
|
"powerlaw_sequence", |
|
"zipf_rv", |
|
"cumulative_distribution", |
|
"discrete_sequence", |
|
"random_weighted_sample", |
|
"weighted_choice", |
|
] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@py_random_state(2) |
|
def powerlaw_sequence(n, exponent=2.0, seed=None): |
|
""" |
|
Return sample sequence of length n from a power law distribution. |
|
""" |
|
return [seed.paretovariate(exponent - 1) for i in range(n)] |
|
|
|
|
|
@py_random_state(2) |
|
def zipf_rv(alpha, xmin=1, seed=None): |
|
r"""Returns a random value chosen from the Zipf distribution. |
|
|
|
The return value is an integer drawn from the probability distribution |
|
|
|
.. math:: |
|
|
|
p(x)=\frac{x^{-\alpha}}{\zeta(\alpha, x_{\min})}, |
|
|
|
where $\zeta(\alpha, x_{\min})$ is the Hurwitz zeta function. |
|
|
|
Parameters |
|
---------- |
|
alpha : float |
|
Exponent value of the distribution |
|
xmin : int |
|
Minimum value |
|
seed : integer, random_state, or None (default) |
|
Indicator of random number generation state. |
|
See :ref:`Randomness<randomness>`. |
|
|
|
Returns |
|
------- |
|
x : int |
|
Random value from Zipf distribution |
|
|
|
Raises |
|
------ |
|
ValueError: |
|
If xmin < 1 or |
|
If alpha <= 1 |
|
|
|
Notes |
|
----- |
|
The rejection algorithm generates random values for a the power-law |
|
distribution in uniformly bounded expected time dependent on |
|
parameters. See [1]_ for details on its operation. |
|
|
|
Examples |
|
-------- |
|
>>> nx.utils.zipf_rv(alpha=2, xmin=3, seed=42) |
|
8 |
|
|
|
References |
|
---------- |
|
.. [1] Luc Devroye, Non-Uniform Random Variate Generation, |
|
Springer-Verlag, New York, 1986. |
|
""" |
|
if xmin < 1: |
|
raise ValueError("xmin < 1") |
|
if alpha <= 1: |
|
raise ValueError("a <= 1.0") |
|
a1 = alpha - 1.0 |
|
b = 2**a1 |
|
while True: |
|
u = 1.0 - seed.random() |
|
v = seed.random() |
|
x = int(xmin * u ** -(1.0 / a1)) |
|
t = (1.0 + (1.0 / x)) ** a1 |
|
if v * x * (t - 1.0) / (b - 1.0) <= t / b: |
|
break |
|
return x |
|
|
|
|
|
def cumulative_distribution(distribution): |
|
"""Returns normalized cumulative distribution from discrete distribution.""" |
|
|
|
cdf = [0.0] |
|
psum = sum(distribution) |
|
for i in range(len(distribution)): |
|
cdf.append(cdf[i] + distribution[i] / psum) |
|
return cdf |
|
|
|
|
|
@py_random_state(3) |
|
def discrete_sequence(n, distribution=None, cdistribution=None, seed=None): |
|
""" |
|
Return sample sequence of length n from a given discrete distribution |
|
or discrete cumulative distribution. |
|
|
|
One of the following must be specified. |
|
|
|
distribution = histogram of values, will be normalized |
|
|
|
cdistribution = normalized discrete cumulative distribution |
|
|
|
""" |
|
import bisect |
|
|
|
if cdistribution is not None: |
|
cdf = cdistribution |
|
elif distribution is not None: |
|
cdf = cumulative_distribution(distribution) |
|
else: |
|
raise nx.NetworkXError( |
|
"discrete_sequence: distribution or cdistribution missing" |
|
) |
|
|
|
|
|
inputseq = [seed.random() for i in range(n)] |
|
|
|
|
|
seq = [bisect.bisect_left(cdf, s) - 1 for s in inputseq] |
|
return seq |
|
|
|
|
|
@py_random_state(2) |
|
def random_weighted_sample(mapping, k, seed=None): |
|
"""Returns k items without replacement from a weighted sample. |
|
|
|
The input is a dictionary of items with weights as values. |
|
""" |
|
if k > len(mapping): |
|
raise ValueError("sample larger than population") |
|
sample = set() |
|
while len(sample) < k: |
|
sample.add(weighted_choice(mapping, seed)) |
|
return list(sample) |
|
|
|
|
|
@py_random_state(1) |
|
def weighted_choice(mapping, seed=None): |
|
"""Returns a single element from a weighted sample. |
|
|
|
The input is a dictionary of items with weights as values. |
|
""" |
|
|
|
rnd = seed.random() * sum(mapping.values()) |
|
for k, w in mapping.items(): |
|
rnd -= w |
|
if rnd < 0: |
|
return k |
|
|