Packages

Basic statistics for comparing datasets from benchmarks

Current section

Files

Jump to
eministat src eministat_resample.erl
Raw

src/eministat_resample.erl

%%% @doc Bootstrap a sample by resampling in the data structure
%%% @end
-module(eministat_resample).
-include("eministat.hrl").
-export([resample/3, bootstrap_bca/3]).
-compile(export_all).
%% @doc resample/3 is the main resampler of eministat
%% @end
resample(Estimators, Resamples, #dataset { n = N, points = Ps }) ->
ResultSets = boot(Resamples, N, list_to_tuple(Ps)),
estimate(Estimators, ResultSets).
boot(Resamples, N, Points) ->
boot(Resamples, N, Points, []).
boot(0, _, _, Acc) -> Acc;
boot(K, N, Ps, Acc) ->
Points = draw(N, N, Ps),
boot(K-1, N, Ps, [eministat_ds:from_list(K, Points) | Acc]).
draw(0, _, _) -> [];
draw(K, N, Tuple) ->
[element(rand:uniform(N), Tuple) | draw(K-1, N, Tuple)].
estimate([], _Results) -> [];
estimate([Name | Next], Results) ->
Resamples = lists:sort([estimator(Name, D) || D <- Results]),
Rs = eministat_ds:from_list(Name, Resamples),
[{Name, Rs} | estimate(Next, Results)].
%% Bias-correct accelerated bootstrap, taken from Bryan O'Sullivan's Criterion
bootstrap_bca(CLevel, Sample, Bootstraps) when CLevel > 0 andalso CLevel < 1 ->
[{Est, e(CLevel, Sample, Est, Resample)} || {Est, Resample} <- Bootstraps].
estimator(mean, Ds) -> eministat_ds:mean(Ds);
estimator(variance, Ds) -> eministat_ds:variance(Ds);
estimator(std_dev, Ds) -> eministat_ds:std_dev(Ds).
e(CLevel, Sample, Est, #dataset { n = N, points = Ps } = Rs) ->
PT = estimator(Est, Sample),
Mean = eministat_ds:mean(Rs),
StdDev = eministat_ds:std_dev(Rs),
Z1 = quantile(standard(), (1 - CLevel) / 2),
CumN = fun(X) -> round(N * cumulative(standard(), X)) end,
ProbN = count(fun(X) -> X < PT end, Ps),
Bias = quantile(standard(), ProbN / N),
#dataset { points = JackPs } = Jack = jackknife(Est, Sample),
JackMean = eministat_ds:mean(Jack),
F = fun(J, {S, C}) ->
D = JackMean - J,
D2 = D * D,
{S + D2, C + D2 * D}
end,
{SumSquares, SumCubes} = lists:foldl(F, {0.0,0.0}, JackPs),
%% io:format("JackMean: ~p, Jack: ~p~n", [JackMean, Jack]),
Accel = SumCubes / (6 * (math:pow(SumSquares, 1.5))),
B1 = Bias + Z1,
A1 = Bias + B1 / (1.0 - Accel * B1),
Lo = max(0, CumN(A1)),
B2 = Bias - Z1,
A2 = Bias + B2 / (1.0 - Accel * B2),
Hi = min(N - 1, CumN(A2)),
%% io:format("Points found: ~p~n", [#{ pt => PT, lo => Lo, hi => Hi, n => N, z1 => Z1, prob_n => ProbN, bias => Bias,
%% accel => Accel, b1 => B1, a1 => A1, b2 => B2, a2 => A2 }]),
true = Lo =< Hi,
true = CLevel > 0 andalso CLevel < 1,
#{ pt => PT, mean => Mean, std_dev => StdDev, lo => lists:nth(Lo+1, Ps), hi => lists:nth(Hi+1, Ps), cl => CLevel }.
jackknife(Ty, #dataset{ name = N } = Ds) ->
eministat_ds:from_list({jack, N}, jackknife_(Ty, Ds)).
jackknife_(mean, #dataset { n = N, points = Ps }) when N > 1 ->
L = N-1,
[(X + Y) / L || {X, Y} <- zip(prefix_sum_l(Ps), prefix_sum_r(Ps))];
jackknife_(variance, Ds) -> jackknife_variance(0, Ds);
%jackknife_(unbiased_variance, Ds) -> jackknife_variance(1, Ds);
jackknife_(std_dev, Ds) -> [math:sqrt(X) || X <- jackknife_variance(1, Ds)].
jackknife_variance(C, #dataset { n = N, points = Ps } = Ds) when N > 1 ->
M = eministat_ds:mean(Ds),
GOA = fun(X) ->
V = X - M,
V*V
end,
ALs = prefix_sum_l([GOA(P) || P <- Ps]),
ARs = prefix_sum_r([GOA(P) || P <- Ps]),
BLs = prefix_sum_l([P - M || P <- Ps]),
BRs = prefix_sum_r([P - M || P <- Ps]),
Q = N - 1,
[begin B = BL + BR, (AL + AR - (B * B) / Q) / (Q - C) end ||
{AL, AR, BL, BR} <- zip4(ALs, ARs, BLs, BRs)].
prefix_sum_l(Points) -> scanl(fun erlang:'+'/2, 0.0, Points).
prefix_sum_r(Points) -> tl(scanr(fun erlang:'+'/2, 0.0, Points)).
%% -- NORMAL DISTRIBUTION ------------------------------
%% Constants
sqrt2() -> math:sqrt(2).
sqrt2pi() -> math:sqrt(2 * math:pi()).
standard() ->
#{ mean => 0.0, std_dev => 1.0, pdf_denom => math:log(sqrt2pi()), cdf_denom => sqrt2() }.
cumulative(#{ mean := M, cdf_denom := CDF}, X) ->
math:erfc((M - X) / CDF) / 2.
quantile(#{ mean := M }, 0.5) -> M;
quantile(#{ mean := M, cdf_denom := CDF }, P) when P > 0 andalso P < 1 ->
X = inv_erfc(2 * (1 - P)),
X * CDF + M.
%% -- STANDARD LIBRARY ROUTINES -----------------------------------------
%% Things which should have been in a standard library but isn't, one way or the other.
%% @doc count/2 counts how many times a predicate returns `true'
%% @end
count(F, Ps) -> count(F, Ps, 0).
count(F, [P | Ps], K) ->
case F(P) of
true -> count(F, Ps, K+1);
false -> count(F, Ps, K)
end;
count(_F, [], K) -> K.
%% @doc scanl/3 is like foldl/3 but returns the accumulator for each iteration
%% @end
scanl(F, Q, Ls) ->
case Ls of
[] -> [Q];
[X|Xs] -> [Q|scanl(F, F(X, Q), Xs)]
end.
%% @doc scanr/3 is like foldr/3 but returns the accumulator for each iteration
%% @end
scanr(_F, Q0, []) -> [Q0];
scanr(F, Q0, [X|Xs]) ->
Qs = [Q|_] = scanr(F, Q0, Xs),
[F(X, Q) | Qs].
%% These variants of zip ignore extra arguments
zip([X|Xs], [Y|Ys]) -> [{X,Y} | zip(Xs, Ys)];
zip(_, _) -> [].
zip4([A|As], [B|Bs], [C|Cs], [D|Ds]) -> [{A,B,C,D} | zip4(As, Bs, Cs, Ds)];
zip4(_, _, _, _) -> [].
inv_erfc(P) when P > 0 andalso P < 2 ->
PP = case P =< 1 of
true -> P;
false -> 2 - P
end,
T = math:sqrt(-2 * math:log(0.5 * PP)),
%% Initial guess for searching
X0 = -0.70711 * ((2.30753 + T * 0.27061) / (1 + T * (0.99229 + T * 0.04481)) - T),
R = inv_erfc_loop(PP, 0, X0),
case P =< 1 of
true -> R;
false -> -R
end.
inv_erfc_loop(_PP, J, X) when J >= 2 -> X;
inv_erfc_loop(PP, J, X) ->
Err = math:erfc(X) - PP,
XP = X + Err / (1.12837916709551257 * math:exp(-X * X) - X * Err), %% // Halley
inv_erfc_loop(PP, J+1, XP).