Fast Python/Numpy Frequency-Severity Distribution Simulation
<p>I'm looking for a away to simulate a classical frequency severity distribution, something like:
X = sum(i = 1..N, Y_i), where N is for example poisson distributed and Y lognormal.</p>

<p>Simple naive numpy script would be:</p>

<pre><code>import numpy as np
SIM_NUM = 3

X = []
for _i in range(SIM_NUM):
    nr_claims = np.random.poisson(1)
    temp = []
    for _j in range(nr_claims):
         temp.append(np.random.lognormal(0, 1))
    X.append(sum(temp))
</code></pre>

<p>Now I try to vectorize that for a performance increase. A bit better is the following:</p>

<pre><code>N = np.random.poisson(1, SIM_NUM)
X = []
for n in N:
    X.append(sum(np.random.lognormal(0, 1, n)))
</code></pre>

<p>My question is if I can still vectorize the second loop? For example by simulating all the losses with:</p>

<pre><code>N = np.random.poisson(1, SIM_NUM)
# print(N) would lead to something like [1 3 0]
losses = np.random.lognormal(0,1, sum(N))
# print(N) would lead to something like 
#[ 0.56750244  0.84161871  0.41567216  1.04311742]

# X should now be [ 0.56750244, 0.84161871 + 0.41567216 + 1.04311742, 0] 
</code></pre>

<p>Ideas that I have are "smart slicing" or "matrix multiplication with A = [[1, 0, 0, 0]],[0,1,1,1],[0,0,0,0]]. But I couldn't make something clever out these ideas. </p>

<p>I'm looking for fastest possible computation of X.</p>

<p>We can use <a href="https://docs.scipy.org/doc/numpy/reference/generated/numpy.bincount.html" rel="nofollow"><code>np.bincount</code></a> , which is quite efficient for such interval/ID based summing operations specially when working with <code>1D</code> arrays. The implementation would look something like this -</p>

<pre><code># Generate all poisson distribution values in one go
pv = np.random.poisson(1,SIM_NUM)

# Use poisson values to get count of total for random lognormal needed.
# Generate all those random numbers again in vectorized way 
rand_arr = np.random.lognormal(0, 1, pv.sum())

# Finally create IDs using pv as extents for use with bincount to do
# ID based and thus effectively interval-based summing
out = np.bincount(np.arange(pv.size).repeat(pv),rand_arr,minlength=SIM_NUM)
</code></pre>

<p>Runtime test -</p>

<p>Function definitions :</p>

<pre><code>def original_app1(SIM_NUM):
    X = []
    for _i in range(SIM_NUM):
        nr_claims = np.random.poisson(1)
        temp = []
        for _j in range(nr_claims):
             temp.append(np.random.lognormal(0, 1))
        X.append(sum(temp))
    return X

def original_app2(SIM_NUM):
    N = np.random.poisson(1, SIM_NUM)
    X = []
    for n in N:
        X.append(sum(np.random.lognormal(0, 1, n)))
    return X

def vectorized_app1(SIM_NUM):
    pv = np.random.poisson(1,SIM_NUM)
    r = np.random.lognormal(0, 1,pv.sum())
    return np.bincount(np.arange(pv.size).repeat(pv),r,minlength=SIM_NUM)
</code></pre>

<p>Timings on large datasets :</p>

<pre><code>In [199]: SIM_NUM = 1000

In [200]: %timeit original_app1(SIM_NUM)
100 loops, best of 3: 2.6 ms per loop

In [201]: %timeit original_app2(SIM_NUM)
100 loops, best of 3: 6.65 ms per loop

In [202]: %timeit vectorized_app1(SIM_NUM)
1000 loops, best of 3: 252 Âµs per loop

In [203]: SIM_NUM = 10000

In [204]: %timeit original_app1(SIM_NUM)
10 loops, best of 3: 26.1 ms per loop

In [205]: %timeit original_app2(SIM_NUM)
10 loops, best of 3: 77.5 ms per loop

In [206]: %timeit vectorized_app1(SIM_NUM)
100 loops, best of 3: 2.46 ms per loop
</code></pre>

<p>So, we are looking at some <strong><code>10x+</code></strong> speedup there.</p>

python performance numpy vectorization