7: Random number generation

Back to main page

Sampling from the logistic function

The logistic function has the following form

$$F(X)=\frac{e^x}{1+e^x}\quad \Rightarrow \quad f(x)=\frac{e^x}{(1+e^x)^2}.$$
# nbi:hide_in
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact, FloatSlider
plt.rcParams['figure.figsize'] = (16, 5)

def cdf_func(xdata):
    val = np.divide(np.exp(xdata), 1+np.exp(xdata))
    return val
def pdf_func(xdata):
    val = np.divide(np.exp(xdata), (1+np.exp(xdata)**2))
    return val

def logistic_cdf_pdf(x):
    xdata = np.linspace(-10, 10, 1000)
    
    fig, [ax1, ax2] = plt.subplots(1, 2)
    
    ax1.plot(xdata, pdf_func(xdata))
    xshade = xdata[xdata<=x]
    ax1.fill_between(xshade, pdf_func(xshade), alpha=0.3, zorder=1)
    ax1.scatter(x,0, s=30, zorder=2, c="orange")
    ax1.axhline(y=0, color='k', linewidth=0.5, zorder=1)
    ax1.set_xlim(-10, 10)
    ax1.set_ylim(-0.06,0.6)
    ax1.set_xticks([x])
    ax1.set_xticklabels(["x={}".format(x)])
    ax1.set_title("pdf")
    ax1.spines['top'].set_visible(False)
    ax1.spines['right'].set_visible(False)
    
    ax2.plot(xdata, cdf_func(xdata))
    ax2.vlines(x, 0, cdf_func(x), linestyle="dashed", alpha=0.4)
    ax2.scatter(x,0, s=30, zorder=2, c="orange")
    ax2.axhline(y=0, color='k', linewidth=0.5, zorder=1)
    ax2.set_xlim(-10, 10)
    ax2.set_ylim(-0.1,1.1)
    ax2.set_xticks([x])
    ax2.set_xticklabels(["x={}".format(x)])
    ax2.set_title("cdf")
    ax2.spines['top'].set_visible(False)
    ax2.spines['right'].set_visible(False)
    
    plt.show();
    
logistic_cdf_pdf(0.6); 

To apply the inverse transform method we note that

  • $y=\frac{e^x}{1+e^x}\Rightarrow e^x=\frac{1-y}{y}.$
  • $F^{-1}(y)=\log(1-y)-\log(y).$
# The following code generates samples from the logistic distribution
import numpy as np

def logistic_inv(y):
    return np.log(1-y)-np.log(y)
    
#  random sample from uniform distribution on [0,1)
y = np.random.sample(20)    

# random sample from logistic distribution
x = logistic_inv(y)
# nbi:hide_in
# Compute the empirical cdf and compare against the true cdf

# plot setup
plt.figure(figsize=(7,6)) 
plt.gca().spines['top'].set_visible(False)
plt.gca().spines['right'].set_visible(False)
plt.gca().spines['bottom'].set_position(('data',0))

def ecdf(x):
    erange_x, counts = np.unique(x, return_counts=True)
    cdf_emp = np.cumsum(counts)/x.size
    return cdf_emp, erange_x

# add padding 
def padding(cdf_values, range_x):
    edge = (range_x[0]-2, range_x[-1]+2)
    range_x_padded = np.pad(range_x, (1,1), 'constant', constant_values=edge)
    cdf_values_padded = np.pad(cdf_values, (1,1), 'constant', constant_values=(0, 1))
    return cdf_values_padded, range_x_padded

# sort the data
x = np.sort(x)

# compute empirical cdf
ecdf_values, erange_x = ecdf(x)
ecdf_values_padded, erange_x_padded = padding(ecdf_values, erange_x)

# the true cdf
N=100
delta = 1/N
xval = np.linspace(np.min(x)-4, np.max(x)+4, 1000)
    
# plot
plt.plot(xval, cdf_func(xval), label ='True cdf')
plt.step(erange_x_padded, ecdf_values_padded, where='post', 
         color="green", linewidth=1, label='Empirical cdf')
plt.legend()
plt.show();