"""Module for using bootstrap in analysis"""
import functools
import multiprocessing
import numpy as np
from gameanalysis import regret
[docs]def game_function(
game, function, num_resamples, num_returned, *, percentiles=None, processes=None
):
"""Bootstrap the value of a function over a sample game
Parameters
----------
game : SampleGame
The sample game to bootstrap the function value over.
function : f(Game) -> float or f(Game) -> [float]
The function of the game to compute. It must be pickleable unless
processes is 1, and it must return either a float or an iterable of
floats. If an iterable of floats, this bootstrap all indices of the
return value independently.
num_resamples : int
The number of bootstrap samples. Higher will take longer but also give
better accuracy.
num_returned : int
The number of float values your function returns.
percentiles : int or [int]
The percentiles to compute on the resulting data in [0, 100]. Standard
percentiles are 95, or [2.5, 97.5]. By default, return all samples.
processes : int (optional)
The number of processes to use for computation. By default this is the
number of cores.
Returns
-------
bootstrap_percentiles : ndarray
An ndarray of the percentiles from bootstrapping. The shape will depend
on the number of percentiles and the number of values returned from
your function.
"""
results = np.empty((num_resamples, num_returned))
chunksize = num_resamples if processes == 1 else 4
with multiprocessing.Pool(processes) as pool:
for i, res in enumerate(
pool.imap_unordered(
functools.partial(_resample_function, function, game),
range(num_resamples),
chunksize=chunksize,
)
):
results[i] = res
if percentiles is None: # pylint: disable=no-else-return
results.sort(0)
return results.T
else:
return np.percentile(results, percentiles, 0).T
def _resample_function(function, game, _):
"""Function for resampling"""
return function(game.resample()) # pragma: no cover
[docs]def profile_function(
game, function, profiles, num_resamples, *, percentiles=None, processes=None
):
"""Compute a function over profiles
Parameters
----------
game : SampleGame
The sample game to bootstrap the function value over.
function : Game, profile -> float
The function of the game profile pair to compute. It must be
pickleable, and it must return a float (e.g. regret.mixture_regret).
profiles : ndarray
The profiles to compute bootstrap bounds over for function.
num_resamples : int
The number of bootstrap samples. Higher will take longer but also give
better accuracy.
percentiles : int or [int]
The percentiles to compute on the resulting data in [0, 100]. Standard
percentiles are 95, or [2.5, 97.5]. By default, return all samples.
processes : int (optional)
The number of processes to use for computation. By default this is the
number of cores.
Returns
-------
bootstrap_percentiles : ndarray
An ndarray of the percentiles from bootstrapping for each profile. The
shape will depend on the number of percentiles and the number of
profiles.
"""
profiles = profiles.reshape((-1, game.num_strats))
return game_function(
game,
functools.partial(_profile_function, function, profiles),
num_resamples,
profiles.shape[0],
percentiles=percentiles,
processes=processes,
)
def _profile_function(function, profiles, game):
"""Map a profile function over profiles"""
return [function(game, prof) for prof in profiles] # pragma: no cover
[docs]def mixture_regret(game, mixtures, num_resamples, *, percentiles=None, processes=None):
"""Compute percentile bounds on mixture regret
Parameters
----------
game : SampleGame
The sample game to bootstrap the function value over.
mixtures : ndararay
The profiles to compute mixture regret bounds for.
num_resamples : int
The number of bootstrap samples. Higher will take longer but also give
better accuracy.
percentiles : int or [int]
The percentiles to compute on the resulting data in [0, 100]. Standard
percentiles are 95, or [2.5, 97.5]. By default, return all samples.
processes : int (optional)
The number of processes to use for computation. By default this is the
number of cores.
Returns
-------
regret_percentiles : ndarray
An ndarray of the percentiles for bootstrap regret for each profile.
"""
return profile_function(
game,
regret.mixture_regret,
mixtures,
num_resamples,
percentiles=percentiles,
processes=processes,
)
[docs]def mixture_welfare(game, mixtures, num_resamples, *, percentiles=None, processes=None):
"""Compute percentile bounds on mixture welfare
Parameters
----------
game : SampleGame
The sample game to bootstrap the function value over.
mixtures : ndarray
The profiles to compute mixture welfare bounds for.
num_resamples : int
The number of bootstrap samples. Higher will take longer but also give
better accuracy.
percentiles : int or [int]
The percentiles to compute on the resulting data in [0, 100]. Standard
percentiles are 95, or [2.5, 97.5]. By default, return all samples.
processes : int (optional)
The number of processes to use for computation. By default this is the
number of cores.
Returns
-------
bootstrap_percentiles : ndarray
An ndarray of the percentiles for bootstrap welfare for each profile.
"""
return profile_function(
game,
regret.mixed_social_welfare,
mixtures,
num_resamples,
percentiles=percentiles,
processes=processes,
)