# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

import numpy as np
from numba import jit, prange
from dask import delayed, compute
import matplotlib.pyplot as plt
# %matplotlib inline

# ## Fast boostrap resampling when there are multiple datasets (of same size) to boostrap

def multi_bootstrap(data, boots):
    """
    Keyword arguments:
    data -- numpy multi-dimentional array 
    boot -- number of bootstraps  
    
    """
    designs = data.shape[0]
    samples = data.shape[1]
    
    to_return = np.empty((designs, boots))
    
    for design in range(designs):
        
        to_return[design:design+1] = bootstrap(data[design], boots)
        
    return to_return


@jit(nopython=False)
def multi_bootstrap_jit(data, boots):
    """
    Keyword arguments:
    data -- numpy multi-dimentional array 
    boot -- number of bootstraps  
    
    """
    designs = data.shape[0]
    samples = data.shape[1]
    
    to_return = np.zeros((designs, boots))
    
    for design in range(designs):

        to_return[design:design+1] = bootstrap(data[design], boots)
        
    return to_return


@jit(nopython=False)
def multi_bootstrap_par(data, boots):
    """
    Keyword arguments:
    data -- numpy multi-dimentional array 
    boot -- number of bootstraps  
    
    """
    designs = data.shape[0]
    samples = data.shape[1]
    
    to_return = np.empty((designs, boots))
    
    for design in range(designs):
        
        to_return[design:design+1] = bootstrap_par(data[design], boots)
        
    return to_return


@jit(nopython=True)
def bootstrap(data, boots):
    """
    Create bootstrap datasets that represent the distribution of the mean.
    Returns a numpy array containing the bootstrap datasets 
    
    Keyword arguments:
    data -- numpy array of systems to boostrap
    boots -- number of bootstrap (default = 1000)
    """
    
    bs_data = np.empty(boots)
    
    for b in range(boots):
        
        total=0
        
        for s in range(data.shape[0]):
        
            total += data[np.random.randint(0, data.shape[0])]

        bs_data[b] = total / data.shape[0]

    return bs_data


@jit(nopython=True, parallel=True)
def bootstrap_par(data, boots):
    """
    Create bootstrap datasets that represent the distribution of the mean.
    Returns a numpy array containing the bootstrap datasets 
    
    Keyword arguments:
    data -- numpy array of systems to boostrap
    boots -- number of bootstrap (default = 1000)
    """
    
    bs_data = np.empty(boots)
    
    for b in prange(boots):
        
        total=0
        
        for s in range(data.shape[0]):
        
            total += data[np.random.randint(0, data.shape[0])]

        bs_data[b] = total / data.shape[0]

    return bs_data


# ## Small Problem 10 initial datasets

sample = np.arange(100).reshape(10, 10)
sample.shape

# %timeit x = multi_bootstrap(sample, 1000)

# %timeit x = multi_bootstrap_jit(sample, 1000)

# %time x = multi_bootstrap_par(sample, 1000)

# multi-core processing improves performance even with a problem as small as 10 initial datasets

# ## Larger Problem.  1000 initial datasets

sample = np.arange(10000).reshape(1000, 10)
sample.shape

# NumPy Implementation

# %%timeit
x = multi_bootstrap(sample, 1000)

# NumPy and Numba JIT implementation

# %%timeit
x = multi_bootstrap_jit(sample, 1000)

# NumPy, JIT and Parallel (multi-core) Implementation

# %%timeit
x = multi_bootstrap_par(sample, 1000)

StarcoderdataJupyterScriptsDedupFiltered