auditstat 0.1.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- auditstat/__init__.py +6 -0
- auditstat/_formatting.py +32 -0
- auditstat/sso/__init__.py +6 -0
- auditstat/sso/evalueer_SSO_klant_gestratificeerd.py +56 -0
- auditstat/sso/max_fout_benaderd_SSO.py +19 -0
- auditstat/sso/max_fout_exact_SSO.py +48 -0
- auditstat/sso/max_fout_exact_continu_SSO.py +107 -0
- auditstat/sso/max_fout_heterogeen_SSO.py +164 -0
- auditstat/sso/moment_match_SSO.py +110 -0
- auditstat/sso/n_klant_k_klant_SSO.py +38 -0
- auditstat/sso/plot_kanskromme_SSO.py +197 -0
- auditstat/sso/plot_max_fout_vergelijking_SSO.py +160 -0
- auditstat/sso/plot_posterior_heterogeen_SSO.py +137 -0
- auditstat/sso/script_SSO.py +106 -0
- auditstat/sso/simuleer_SSO.py +28 -0
- auditstat/sso/strata_SSO_klant.py +116 -0
- auditstat/stratified/__init__.py +19 -0
- auditstat/stratified/analyseer_klimheuristiek.py +319 -0
- auditstat/stratified/eval_stratified.py +925 -0
- auditstat/stratified/plan_stratified.py +574 -0
- auditstat/stratified/plot_kanskromme.py +149 -0
- auditstat/stratified/utils.py +268 -0
- auditstat/stratified/valideer_granulariteit.py +171 -0
- auditstat-0.1.9.dist-info/METADATA +651 -0
- auditstat-0.1.9.dist-info/RECORD +28 -0
- auditstat-0.1.9.dist-info/WHEEL +5 -0
- auditstat-0.1.9.dist-info/licenses/LICENSE +287 -0
- auditstat-0.1.9.dist-info/top_level.txt +1 -0
auditstat/__init__.py
ADDED
auditstat/_formatting.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Gedeelde hulpfuncties voor Nederlandse getalnotatie in plots.
|
|
2
|
+
|
|
3
|
+
Nederlandse notatie gebruikt een komma als decimaalteken en een punt als
|
|
4
|
+
duizendtal-scheidingsteken (bijv. "1.234,56"), het omgekeerde van de
|
|
5
|
+
Engelse/Amerikaanse notatie die matplotlib standaard gebruikt voor
|
|
6
|
+
astabels, legenda's en titels.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import matplotlib.pyplot as plt
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def nl_getal(x, decimalen=2):
|
|
13
|
+
"""Formatteert een getal met Nederlandse notatie (vast aantal
|
|
14
|
+
decimalen, komma als decimaalteken, punt als duizendtal-
|
|
15
|
+
scheidingsteken)."""
|
|
16
|
+
fmt = f"{x:,.{decimalen}f}"
|
|
17
|
+
return fmt.replace(",", "X").replace(".", ",").replace("X", ".")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def nl_getal_variabel(x):
|
|
21
|
+
"""Formatteert een getal met een door Python zelf gekozen, compact
|
|
22
|
+
aantal decimalen (zoals %g), maar met komma als decimaalteken.
|
|
23
|
+
Geschikt voor labels waar de precisie per waarde sterk uiteenloopt
|
|
24
|
+
(bijv. 0,5 / 0,05 / 0,005)."""
|
|
25
|
+
return f"{x:g}".replace(".", ",")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def nl_tick_formatter(decimalen=2):
|
|
29
|
+
"""Geeft een matplotlib-tickformatter terug die getallen weergeeft
|
|
30
|
+
met Nederlandse notatie (komma als decimaalteken, punt als
|
|
31
|
+
duizendtal-scheidingsteken)."""
|
|
32
|
+
return plt.FuncFormatter(lambda x, _pos=None: nl_getal(x, decimalen))
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from auditstat.sso.strata_SSO_klant import bouw_strata_SSO_klant
|
|
2
|
+
from auditstat.stratified.eval_stratified import eval_stratified
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def evalueer_SSO_klant_gestratificeerd(
|
|
6
|
+
post_typen,
|
|
7
|
+
model="binomiaal",
|
|
8
|
+
zekerheid=0.95,
|
|
9
|
+
methode="FFT samen",
|
|
10
|
+
granulariteit=None,
|
|
11
|
+
):
|
|
12
|
+
"""
|
|
13
|
+
Evalueert een klant gestratificeerd (één stratum per type post), op
|
|
14
|
+
basis van SSO-steekproeven per type post.
|
|
15
|
+
|
|
16
|
+
Bouwt eerst de stratatabel via ``bouw_strata_SSO_klant`` en roept
|
|
17
|
+
daarna ``eval_stratified`` aan. Zie ``bouw_strata_SSO_klant`` voor het
|
|
18
|
+
formaat van ``post_typen``, en ``eval_stratified`` voor de overige
|
|
19
|
+
parameters en het resultaatformaat.
|
|
20
|
+
|
|
21
|
+
Per type post wordt een effectieve steekproefomvang en een effectief
|
|
22
|
+
aantal fouten bij de klant gekalibreerd via ``moment_match_SSO``
|
|
23
|
+
(gemiddelde- en variantie-matching op het ``model``-kansmodel), zodat
|
|
24
|
+
de gestratificeerde evaluatie zowel het juiste gemiddelde als (voor
|
|
25
|
+
zover mogelijk binnen de Beta/Gamma-familie) de juiste variantie van
|
|
26
|
+
de werkelijke, continue exacte SSO-onzekerheid draagt — zie
|
|
27
|
+
``bouw_strata_SSO_klant`` en ``moment_match_SSO`` voor de details.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
post_typen : list[dict]
|
|
32
|
+
Zie ``bouw_strata_SSO_klant``.
|
|
33
|
+
model : {"binomiaal", "poisson"}
|
|
34
|
+
Kansmodel voor de gestratificeerde convolutieberekening.
|
|
35
|
+
zekerheid : float
|
|
36
|
+
Gewenste statistische zekerheid, tussen 0 en 1.
|
|
37
|
+
methode : {"FFT paarsgewijs", "FFT samen", "direct", "Monte Carlo"}
|
|
38
|
+
Methode voor de convolutieberekening.
|
|
39
|
+
granulariteit : int, optioneel
|
|
40
|
+
Zie ``eval_stratified``; standaard ``None`` (automatisch bepaald).
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
dict
|
|
45
|
+
Het resultaat van ``eval_stratified``, inclusief de kanskromme
|
|
46
|
+
(bruikbaar met ``auditstat.stratified.plot_kanskromme.plot_kanskromme``)
|
|
47
|
+
en de opgebouwde stratatabel onder ``"steekproeven"``.
|
|
48
|
+
"""
|
|
49
|
+
steekproeven = bouw_strata_SSO_klant(post_typen, model=model)
|
|
50
|
+
return eval_stratified(
|
|
51
|
+
steekproeven=steekproeven,
|
|
52
|
+
model=model,
|
|
53
|
+
zekerheid=zekerheid,
|
|
54
|
+
methode=methode,
|
|
55
|
+
granulariteit=granulariteit,
|
|
56
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from scipy import stats
|
|
2
|
+
|
|
3
|
+
from auditstat.sso.n_klant_k_klant_SSO import n_klant_k_klant_SSO
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def max_fout_benaderd_SSO(n, k, N_klant, zekerheid=0.95, verdeling="binomiaal"):
|
|
7
|
+
"""
|
|
8
|
+
Berekent de benaderde maximale foutfractie voor een SSO-klant.
|
|
9
|
+
"""
|
|
10
|
+
n_klant, k_klant = n_klant_k_klant_SSO(n, k, N_klant)
|
|
11
|
+
|
|
12
|
+
if verdeling == "binomiaal":
|
|
13
|
+
# Vlakke prior (Beta(1,1)), posterior is Beta(k_klant+1, n_klant-k_klant+1)
|
|
14
|
+
return stats.beta.ppf(zekerheid, k_klant + 1, n_klant - k_klant + 1)
|
|
15
|
+
elif verdeling == "poisson":
|
|
16
|
+
# Vlakke prior Gamma(1,0), posterior voor de fractie is Gamma(k_klant+1, schaal=1/n_klant)
|
|
17
|
+
return stats.gamma.ppf(zekerheid, a=k_klant + 1, scale=1.0 / n_klant)
|
|
18
|
+
else:
|
|
19
|
+
raise ValueError("Kies 'binomiaal' of 'poisson'")
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy import stats
|
|
3
|
+
from scipy.interpolate import interp1d
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _continue_kwantiel(cdf_func, max_val, target_p):
|
|
7
|
+
"""Interpolatie helper om discrete verdelingen continu te maken."""
|
|
8
|
+
x_vals = np.arange(0, int(max_val) + 3)
|
|
9
|
+
y_vals = cdf_func(x_vals)
|
|
10
|
+
|
|
11
|
+
# Verwijder duplicaten om interpolatie fouten te voorkomen (bij staarten)
|
|
12
|
+
unique_y, indices = np.unique(y_vals, return_index=True)
|
|
13
|
+
unique_x = x_vals[indices]
|
|
14
|
+
|
|
15
|
+
if target_p <= unique_y[0]:
|
|
16
|
+
return float(unique_x[0])
|
|
17
|
+
|
|
18
|
+
interp_func = interp1d(
|
|
19
|
+
unique_y, unique_x, kind="linear", bounds_error=False, fill_value=(0, max_val)
|
|
20
|
+
)
|
|
21
|
+
return float(interp_func(target_p))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def max_fout_exact_SSO(n, k, N_klant, zekerheid=0.95, verdeling="binomiaal"):
|
|
25
|
+
"""
|
|
26
|
+
Berekent de exacte maximale foutfractie voor een SSO-klant,
|
|
27
|
+
met continue doortrekking van de discrete voorspellende verdelingen.
|
|
28
|
+
"""
|
|
29
|
+
if verdeling == "binomiaal":
|
|
30
|
+
# Voorspellende verdeling: Beta-Binomiaal (N_klant, alpha=k+1, beta=n-k+1)
|
|
31
|
+
def cdf_func(x):
|
|
32
|
+
return stats.betabinom.cdf(x, N_klant, k + 1, n - k + 1)
|
|
33
|
+
|
|
34
|
+
aantal_fouten = _continue_kwantiel(cdf_func, N_klant, zekerheid)
|
|
35
|
+
return aantal_fouten / N_klant
|
|
36
|
+
|
|
37
|
+
elif verdeling == "poisson":
|
|
38
|
+
# Voorspellende verdeling: Negatief-Binomiaal (r=k+1, p=n/(n+N_klant))
|
|
39
|
+
# scipy.stats.nbinom hanteert het aantal mislukkingen, we passen parameters hierop aan.
|
|
40
|
+
p_succes = n / (n + N_klant)
|
|
41
|
+
|
|
42
|
+
def cdf_func(x):
|
|
43
|
+
return stats.nbinom.cdf(x, k + 1, p_succes)
|
|
44
|
+
|
|
45
|
+
aantal_fouten = _continue_kwantiel(cdf_func, N_klant, zekerheid)
|
|
46
|
+
return aantal_fouten / N_klant
|
|
47
|
+
else:
|
|
48
|
+
raise ValueError("Kies 'binomiaal' of 'poisson'")
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.optimize import brentq
|
|
3
|
+
from scipy.special import betainc, betaln, gammaln
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _betabinom_continu_pdf(x, N_klant, alpha, beta):
|
|
7
|
+
"""Continue (reële) uitbreiding van de Beta-Binomiaal kansmassafunctie.
|
|
8
|
+
|
|
9
|
+
Vervangt de binomiaalcoëfficiënt C(N_klant, x) door zijn continue
|
|
10
|
+
uitbreiding via de Gammafunctie, zodat x reële waarden in [0, N_klant]
|
|
11
|
+
mag aannemen in plaats van uitsluitend gehele getallen. Op gehele x
|
|
12
|
+
komt dit exact overeen met scipy.stats.betabinom.pmf(x, N_klant,
|
|
13
|
+
alpha, beta); ertussenin levert het een analytisch vloeiende
|
|
14
|
+
dichtheid op in plaats van een trapsgewijze interpolatie.
|
|
15
|
+
"""
|
|
16
|
+
log_coef = gammaln(N_klant + 1) - gammaln(x + 1) - gammaln(N_klant - x + 1)
|
|
17
|
+
log_dichtheid = (
|
|
18
|
+
log_coef + betaln(x + alpha, N_klant - x + beta) - betaln(alpha, beta)
|
|
19
|
+
)
|
|
20
|
+
return np.exp(log_dichtheid)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _betabinom_continu_cdf_grid(N_klant, alpha, beta, aantal_punten=4000):
|
|
24
|
+
"""Geeft (x_grid, cdf_grid) terug: een genormaliseerde cumulatieve
|
|
25
|
+
verdeling van de continue Beta-Binomiaal-dichtheid op [0, N_klant]."""
|
|
26
|
+
x = np.linspace(0, N_klant, aantal_punten)
|
|
27
|
+
dichtheid = _betabinom_continu_pdf(x, N_klant, alpha, beta)
|
|
28
|
+
cdf = np.concatenate((
|
|
29
|
+
[0.0],
|
|
30
|
+
np.cumsum((dichtheid[1:] + dichtheid[:-1]) / 2 * np.diff(x)),
|
|
31
|
+
))
|
|
32
|
+
# Normaliseren: numerieke kwadratuur over een eindig grid benadert de
|
|
33
|
+
# (exacte) integraal van 1 niet perfect; dit forceert cdf(N_klant) = 1.
|
|
34
|
+
cdf = cdf / cdf[-1]
|
|
35
|
+
return x, dichtheid, cdf
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def max_fout_exact_continu_SSO(
|
|
39
|
+
n, k, N_klant, zekerheid=0.95, verdeling="binomiaal", aantal_punten=4000
|
|
40
|
+
):
|
|
41
|
+
"""
|
|
42
|
+
Berekent de maximale foutfractie bij de klant via een continue
|
|
43
|
+
uitbreiding van het exacte model (Beta-Binomiaal resp.
|
|
44
|
+
Negatief-Binomiaal), zonder de twee onzekerheidslagen samen te
|
|
45
|
+
voegen zoals bij max_fout_benaderd_SSO.
|
|
46
|
+
|
|
47
|
+
max_fout_exact_SSO rekent met de werkelijke, discrete voorspellende
|
|
48
|
+
verdeling en trekt alleen de resulterende kwantielfunctie lineair
|
|
49
|
+
door tussen gehele waarden. Hier wordt in plaats daarvan de
|
|
50
|
+
kansmassafunctie zélf analytisch doorgetrokken naar reële x: voor
|
|
51
|
+
"binomiaal" via de Gammafunctie-uitbreiding van de
|
|
52
|
+
binomiaalcoëfficiënt (zie _betabinom_continu_pdf), voor "poisson"
|
|
53
|
+
via de gesloten-vorm-identiteit tussen de Negatief-Binomiale CDF en
|
|
54
|
+
de regularized incomplete beta-functie (die zelf al analytisch is
|
|
55
|
+
in het aantal "fouten"). Beide onzekerheidslagen blijven behouden:
|
|
56
|
+
de posterior-onzekerheid over de ware foutkans (Beta) én de
|
|
57
|
+
steekproefvariabiliteit van die foutkans toegepast op de (hier
|
|
58
|
+
continu benaderde) klantpopulatie van omvang N_klant.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
n, k : int
|
|
63
|
+
Omvang en aantal fouten in de steekproef bij SSO.
|
|
64
|
+
N_klant : int
|
|
65
|
+
Aantal posten bij de klant.
|
|
66
|
+
zekerheid : float, standaard 0.95
|
|
67
|
+
Gewenste statistische zekerheid.
|
|
68
|
+
verdeling : {"binomiaal", "poisson"}, standaard "binomiaal"
|
|
69
|
+
aantal_punten : int, standaard 4000
|
|
70
|
+
Resolutie van het grid voor de numerieke integratie (alleen
|
|
71
|
+
gebruikt voor verdeling="binomiaal"; "poisson" gebruikt een
|
|
72
|
+
gesloten-vorm CDF en heeft dit niet nodig).
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
float
|
|
77
|
+
Maximale foutfractie (aantal fouten / N_klant) bij de gekozen
|
|
78
|
+
zekerheid.
|
|
79
|
+
"""
|
|
80
|
+
if N_klant <= 0:
|
|
81
|
+
raise ValueError("N_klant moet groter zijn dan 0.")
|
|
82
|
+
if not (0 < zekerheid < 1):
|
|
83
|
+
raise ValueError("zekerheid moet tussen 0 en 1 liggen.")
|
|
84
|
+
|
|
85
|
+
if verdeling == "binomiaal":
|
|
86
|
+
alpha, beta = k + 1, n - k + 1
|
|
87
|
+
x, _, cdf = _betabinom_continu_cdf_grid(N_klant, alpha, beta, aantal_punten)
|
|
88
|
+
aantal_fouten = float(np.interp(zekerheid, cdf, x))
|
|
89
|
+
return aantal_fouten / N_klant
|
|
90
|
+
|
|
91
|
+
elif verdeling == "poisson":
|
|
92
|
+
r = k + 1
|
|
93
|
+
p_succes = n / (n + N_klant)
|
|
94
|
+
|
|
95
|
+
def cdf_func(x):
|
|
96
|
+
return betainc(r, x + 1, p_succes)
|
|
97
|
+
|
|
98
|
+
if cdf_func(0.0) >= zekerheid:
|
|
99
|
+
aantal_fouten = 0.0
|
|
100
|
+
elif cdf_func(N_klant) <= zekerheid:
|
|
101
|
+
aantal_fouten = float(N_klant)
|
|
102
|
+
else:
|
|
103
|
+
aantal_fouten = brentq(lambda x: cdf_func(x) - zekerheid, 0.0, N_klant)
|
|
104
|
+
return aantal_fouten / N_klant
|
|
105
|
+
|
|
106
|
+
else:
|
|
107
|
+
raise ValueError("Kies 'binomiaal' of 'poisson'")
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.special import comb
|
|
3
|
+
from scipy.stats import poisson
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _posterior_theta_klant_binomiaal(n, k, f, theta_grid):
|
|
7
|
+
"""Niet-genormaliseerde posterior-dichtheid van theta_klant (binomiaal).
|
|
8
|
+
|
|
9
|
+
Model: n_X (het aantal van de n SSO-brede steekproefposten dat bij
|
|
10
|
+
klant X hoort) ~ Binomiaal(n, f). Klant X heeft een eigen, van de
|
|
11
|
+
rest van de SSO-populatie onafhankelijke foutkans theta_klant met
|
|
12
|
+
vlakke Beta(1, 1)-prior; de rest van de SSO-populatie heeft een
|
|
13
|
+
eigen, eveneens vlakke Beta(1, 1)-prior op theta_rest. Alleen de
|
|
14
|
+
som k = k_X + k_rest wordt waargenomen, niet de opsplitsing.
|
|
15
|
+
|
|
16
|
+
Omdat een Beta-Binomiaal-marginale verdeling met vlakke prior
|
|
17
|
+
discreet uniform is (P(l | m) = 1 / (m + 1)), marginaliseert
|
|
18
|
+
theta_rest weg tot een constante gewichtsfactor per (n_X, k_X)-
|
|
19
|
+
combinatie. Wat overblijft is een eindige dubbele som die hieronder
|
|
20
|
+
direct wordt opgeteld.
|
|
21
|
+
"""
|
|
22
|
+
post = np.zeros_like(theta_grid, dtype=float)
|
|
23
|
+
m_waarden = np.arange(0, n + 1)
|
|
24
|
+
gewicht_m = comb(n, m_waarden) * f**m_waarden * (1 - f) ** (n - m_waarden)
|
|
25
|
+
for m, w_m in zip(m_waarden, gewicht_m, strict=False):
|
|
26
|
+
if w_m == 0.0:
|
|
27
|
+
continue
|
|
28
|
+
marginaal_rest = 1.0 / (n - m + 1)
|
|
29
|
+
j_min = max(0, k - (n - m))
|
|
30
|
+
j_max = min(m, k)
|
|
31
|
+
if j_min > j_max:
|
|
32
|
+
continue
|
|
33
|
+
bijdrage = np.zeros_like(theta_grid)
|
|
34
|
+
for j in range(j_min, j_max + 1):
|
|
35
|
+
bijdrage += comb(m, j) * theta_grid**j * (1 - theta_grid) ** (m - j)
|
|
36
|
+
post += w_m * marginaal_rest * bijdrage
|
|
37
|
+
return post
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _posterior_theta_klant_poisson(n, k, f, theta_grid):
|
|
41
|
+
"""Niet-genormaliseerde posterior-dichtheid van theta_klant (poisson).
|
|
42
|
+
|
|
43
|
+
Analoge constructie als bij _posterior_theta_klant_binomiaal, maar
|
|
44
|
+
met een Poisson/Gamma-model in plaats van Binomiaal/Beta: n_X ~
|
|
45
|
+
Binomiaal(n, f) (de opsplitsing van de steekproef over klant en
|
|
46
|
+
rest is een eigenschap van het steekproefontwerp en verandert niet
|
|
47
|
+
met het foutmodel), en gegeven n_X = m, k_X | theta_klant ~
|
|
48
|
+
Poisson(m * theta_klant) met vlakke (oneigenlijke) prior op
|
|
49
|
+
theta_klant; analoog voor de rest met expositie n - m.
|
|
50
|
+
|
|
51
|
+
Een vlakke prior op een Poisson-snelheid marginaliseert tot een
|
|
52
|
+
(oneigenlijke, maar voor elk vast expositieaantal constante)
|
|
53
|
+
gewicht 1 / (n - m) voor elk mogelijk aantal fouten in de rest. Bij
|
|
54
|
+
n_X = n is er geen rest-expositie: dan ligt k_X = k vast en is er
|
|
55
|
+
geen marginalisatie nodig.
|
|
56
|
+
"""
|
|
57
|
+
post = np.zeros_like(theta_grid, dtype=float)
|
|
58
|
+
m_waarden = np.arange(0, n + 1)
|
|
59
|
+
gewicht_m = comb(n, m_waarden) * f**m_waarden * (1 - f) ** (n - m_waarden)
|
|
60
|
+
for m, w_m in zip(m_waarden, gewicht_m, strict=False):
|
|
61
|
+
if w_m == 0.0:
|
|
62
|
+
continue
|
|
63
|
+
if m == n:
|
|
64
|
+
bijdrage = poisson.pmf(k, mu=m * theta_grid)
|
|
65
|
+
else:
|
|
66
|
+
marginaal_rest = 1.0 / (n - m)
|
|
67
|
+
bijdrage = np.zeros_like(theta_grid)
|
|
68
|
+
for j in range(0, k + 1):
|
|
69
|
+
bijdrage += poisson.pmf(j, mu=m * theta_grid)
|
|
70
|
+
bijdrage = marginaal_rest * bijdrage
|
|
71
|
+
post += w_m * bijdrage
|
|
72
|
+
return post
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _kwantiel_van_theta_posterior(theta_grid, dichtheid, p):
|
|
76
|
+
"""Kwantiel van een dichtheid gegeven op een vast grid, via lineaire
|
|
77
|
+
interpolatie van de (genormaliseerde) cumulatieve verdeling."""
|
|
78
|
+
cdf = np.concatenate((
|
|
79
|
+
[0.0],
|
|
80
|
+
np.cumsum((dichtheid[1:] + dichtheid[:-1]) / 2 * np.diff(theta_grid)),
|
|
81
|
+
))
|
|
82
|
+
cdf = cdf / cdf[-1]
|
|
83
|
+
return float(np.interp(p, cdf, theta_grid))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def max_fout_heterogeen_SSO(
|
|
87
|
+
n, k, f, zekerheid=0.95, verdeling="binomiaal", aantal_theta=4001
|
|
88
|
+
):
|
|
89
|
+
"""
|
|
90
|
+
Berekent de maximale foutfractie bij een SSO-klant onder een
|
|
91
|
+
heterogeniteitsmodel: klant X heeft een eigen, van de rest van de
|
|
92
|
+
SSO-populatie onafhankelijke foutkans, in plaats van de impliciete
|
|
93
|
+
aanname van max_fout_exact_SSO / max_fout_exact_continu_SSO dat
|
|
94
|
+
elke post (van elke klant) verwisselbaar is met dezelfde,
|
|
95
|
+
SSO-brede foutkans (de "uitwisselbare" aanname).
|
|
96
|
+
|
|
97
|
+
Alleen de relatieve omvang van klant X binnen de totale
|
|
98
|
+
SSO-populatie is nodig (f = N_klant / N_totaal), niet N_klant of
|
|
99
|
+
N_totaal afzonderlijk: de totale SSO-populatie wordt verondersteld
|
|
100
|
+
zeer groot (effectief oneindig) te zijn, zowel in aantal posten als
|
|
101
|
+
in waarde, zodat trekken van de steekproef de populatie niet
|
|
102
|
+
noemenswaardig uitput. Onder diezelfde aanname convergeert de
|
|
103
|
+
werkelijke foutfractie bij klant X (die zelf ook een grote
|
|
104
|
+
populatie heeft) naar zijn eigen foutkans theta_klant; het
|
|
105
|
+
teruggegeven kwantiel is dus het kwantiel van de posterior van
|
|
106
|
+
theta_klant zelf, niet van een voorspellende telverdeling over een
|
|
107
|
+
eindige N_klant.
|
|
108
|
+
|
|
109
|
+
Model
|
|
110
|
+
-----
|
|
111
|
+
Van de n SSO-brede steekproefposten horen er n_X ~ Binomiaal(n, f)
|
|
112
|
+
toevallig bij klant X. Klant X heeft een eigen vlakke prior op zijn
|
|
113
|
+
foutkans theta_klant, onafhankelijk van de (eveneens vlakke) prior
|
|
114
|
+
op de foutkans van de rest van de SSO-populatie. Alleen de totale
|
|
115
|
+
som k = k_X + k_rest wordt waargenomen; de posterior van
|
|
116
|
+
theta_klant marginaliseert over alle mogelijke opsplitsingen.
|
|
117
|
+
|
|
118
|
+
Grenswaarden: bij f = 1 (alle steekproefposten horen bij klant X)
|
|
119
|
+
komt dit exact overeen met het bestaande uitwisselbare model
|
|
120
|
+
(Beta(k + 1, n - k + 1) resp. Gamma(k + 1, n), het laatste
|
|
121
|
+
getrunceerd naar [0, 1]). Bij f = 0 bevat de SSO-brede steekproef
|
|
122
|
+
geen informatie over klant X en blijft de posterior gelijk aan de
|
|
123
|
+
vlakke prior (uniform op [0, 1]).
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
n, k : int
|
|
128
|
+
Omvang en aantal fouten in de SSO-brede steekproef.
|
|
129
|
+
f : float
|
|
130
|
+
Relatieve omvang van klant X binnen de totale SSO-populatie
|
|
131
|
+
(N_klant / N_totaal), tussen 0 en 1.
|
|
132
|
+
zekerheid : float, standaard 0.95
|
|
133
|
+
Gewenste statistische zekerheid.
|
|
134
|
+
verdeling : {"binomiaal", "poisson"}, standaard "binomiaal"
|
|
135
|
+
aantal_theta : int, standaard 4001
|
|
136
|
+
Resolutie van het grid voor theta_klant op [0, 1].
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
float
|
|
141
|
+
Maximale foutfractie bij klant X bij de gekozen zekerheid,
|
|
142
|
+
onder het heterogeniteitsmodel.
|
|
143
|
+
"""
|
|
144
|
+
if not isinstance(n, (int, np.integer)) or n <= 0:
|
|
145
|
+
raise ValueError("n moet een positief geheel getal zijn.")
|
|
146
|
+
if not isinstance(k, (int, np.integer)) or k < 0:
|
|
147
|
+
raise ValueError("k moet een niet-negatief geheel getal zijn.")
|
|
148
|
+
if k > n:
|
|
149
|
+
raise ValueError("k mag niet groter zijn dan n.")
|
|
150
|
+
if not (0 <= f <= 1):
|
|
151
|
+
raise ValueError("f moet tussen 0 en 1 liggen.")
|
|
152
|
+
if not (0 < zekerheid < 1):
|
|
153
|
+
raise ValueError("zekerheid moet tussen 0 en 1 liggen.")
|
|
154
|
+
|
|
155
|
+
theta_grid = np.linspace(0.0, 1.0, aantal_theta)
|
|
156
|
+
|
|
157
|
+
if verdeling == "binomiaal":
|
|
158
|
+
dichtheid = _posterior_theta_klant_binomiaal(n, k, f, theta_grid)
|
|
159
|
+
elif verdeling == "poisson":
|
|
160
|
+
dichtheid = _posterior_theta_klant_poisson(n, k, f, theta_grid)
|
|
161
|
+
else:
|
|
162
|
+
raise ValueError("Kies 'binomiaal' of 'poisson'")
|
|
163
|
+
|
|
164
|
+
return _kwantiel_van_theta_posterior(theta_grid, dichtheid, zekerheid)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def moment_match_SSO(n, k, N_klant, verdeling="binomiaal"):
|
|
5
|
+
"""
|
|
6
|
+
Kalibreert een effectieve steekproefomvang (n_eff) en een effectief
|
|
7
|
+
aantal fouten (k_eff) bij de klant, zodanig dat de resulterende
|
|
8
|
+
Beta- (binomiaal) resp. Gamma-verdeling (poisson) dezelfde
|
|
9
|
+
gemiddelde en variantie heeft als de werkelijke, continue exacte
|
|
10
|
+
samengestelde verdeling van ``max_fout_exact_continu_SSO``
|
|
11
|
+
(Beta-Binomiaal resp. Negatief-Binomiaal).
|
|
12
|
+
|
|
13
|
+
Achtergrond
|
|
14
|
+
-----------
|
|
15
|
+
``n_klant_k_klant_SSO`` (gebruikt door ``max_fout_benaderd_SSO``)
|
|
16
|
+
poolt de SSO-steekproef en de klantpopulatie tot één kleinere
|
|
17
|
+
"as-if"-steekproef. Dat voegt de twee onzekerheidslagen samen
|
|
18
|
+
(posterior-onzekerheid over de ware foutkans, én
|
|
19
|
+
steekproefvariabiliteit van die foutkans bij de klant) tot één
|
|
20
|
+
enkele laag, en levert daardoor zowel een vertekend gemiddelde als
|
|
21
|
+
een te lage variantie op (zie ``max_fout_exact_continu_SSO`` voor de
|
|
22
|
+
precieze, continue versie van het exacte tweelagen-model).
|
|
23
|
+
|
|
24
|
+
Deze functie kiest in plaats daarvan (n_eff, k_eff) zó dat de
|
|
25
|
+
Beta(k_eff + 1, n_eff - k_eff + 1)- resp. Gamma(k_eff + 1, schaal=1 /
|
|
26
|
+
n_eff)-verdeling dezelfde eerste twee momenten heeft als de
|
|
27
|
+
werkelijke samengestelde verdeling van de foutfractie bij de klant.
|
|
28
|
+
Dit is bedoeld om als (n_laag, k_laag)-paar te dienen voor
|
|
29
|
+
``eval_stratified``, dat zelf intern een Beta- resp.
|
|
30
|
+
Gamma-verdeling per stratum opbouwt uit (n_laag, k_laag) — zo
|
|
31
|
+
draagt de gestratificeerde evaluatie zowel het juiste gemiddelde als
|
|
32
|
+
(voor zover binnen de Beta/Gamma-familie mogelijk) de juiste
|
|
33
|
+
variantie van de werkelijke tweelagen-onzekerheid.
|
|
34
|
+
|
|
35
|
+
Omdat de Beta- en Gamma-familie een beperkte spreiding kunnen
|
|
36
|
+
weergeven bij een gegeven gemiddelde, kan dit bij een kleine
|
|
37
|
+
N_klant (t.o.v. de SSO-steekproef) resulteren in een (n_eff, k_eff)
|
|
38
|
+
buiten het toegestane bereik (k_eff < 0 of k_eff > n_eff). In dat
|
|
39
|
+
geval wordt geklemd aan de dichtstbijzijnde geldige grens en wordt
|
|
40
|
+
een ``RuntimeWarning`` afgevuurd.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
n : int of float
|
|
45
|
+
Steekproefomvang bij de SSO.
|
|
46
|
+
k : int of float
|
|
47
|
+
Aantal fouten in de steekproef bij de SSO.
|
|
48
|
+
N_klant : int of float
|
|
49
|
+
Aantal posten bij de klant.
|
|
50
|
+
verdeling : {"binomiaal", "poisson"}, standaard "binomiaal"
|
|
51
|
+
Moet overeenkomen met het ``model``-argument waarmee het
|
|
52
|
+
resulterende (n_eff, k_eff)-paar straks aan ``eval_stratified``
|
|
53
|
+
wordt doorgegeven.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
tuple[float, float]
|
|
58
|
+
(n_eff, k_eff)
|
|
59
|
+
"""
|
|
60
|
+
if n <= 0:
|
|
61
|
+
raise ValueError("n moet groter zijn dan 0.")
|
|
62
|
+
if N_klant <= 0:
|
|
63
|
+
raise ValueError("N_klant moet groter zijn dan 0.")
|
|
64
|
+
if k < 0:
|
|
65
|
+
raise ValueError("k mag niet negatief zijn.")
|
|
66
|
+
if k > n:
|
|
67
|
+
raise ValueError("k mag niet groter zijn dan n.")
|
|
68
|
+
|
|
69
|
+
if verdeling == "binomiaal":
|
|
70
|
+
alpha, beta_ = k + 1, n - k + 1
|
|
71
|
+
s = alpha + beta_
|
|
72
|
+
gemiddelde = alpha / s
|
|
73
|
+
variantie = alpha * beta_ * (s + N_klant) / (s**2 * (s + 1) * N_klant)
|
|
74
|
+
s_eff = gemiddelde * (1 - gemiddelde) / variantie - 1
|
|
75
|
+
n_eff = s_eff - 2
|
|
76
|
+
k_eff = gemiddelde * s_eff - 1
|
|
77
|
+
|
|
78
|
+
elif verdeling == "poisson":
|
|
79
|
+
r = k + 1
|
|
80
|
+
p_succes = n / (n + N_klant)
|
|
81
|
+
gemiddelde_aantal = r * (1 - p_succes) / p_succes
|
|
82
|
+
variantie_aantal = r * (1 - p_succes) / p_succes**2
|
|
83
|
+
gemiddelde = gemiddelde_aantal / N_klant
|
|
84
|
+
variantie = variantie_aantal / N_klant**2
|
|
85
|
+
n_eff = gemiddelde / variantie
|
|
86
|
+
k_eff = gemiddelde**2 / variantie - 1
|
|
87
|
+
|
|
88
|
+
else:
|
|
89
|
+
raise ValueError("Kies 'binomiaal' of 'poisson'")
|
|
90
|
+
|
|
91
|
+
geklemd = False
|
|
92
|
+
if k_eff < 0:
|
|
93
|
+
k_eff = 0.0
|
|
94
|
+
geklemd = True
|
|
95
|
+
if n_eff < k_eff:
|
|
96
|
+
n_eff = k_eff
|
|
97
|
+
geklemd = True
|
|
98
|
+
if geklemd:
|
|
99
|
+
warnings.warn(
|
|
100
|
+
"De werkelijke spreiding van de foutfractie bij de klant kon niet "
|
|
101
|
+
"exact gekalibreerd worden binnen de Beta/Gamma-familie (N_klant is "
|
|
102
|
+
"klein t.o.v. de SSO-steekproef n). Het (n_eff, k_eff)-paar is "
|
|
103
|
+
"geklemd aan de dichtstbijzijnde geldige grens; de resulterende "
|
|
104
|
+
"gestratificeerde evaluatie onderschat in dit geval mogelijk de "
|
|
105
|
+
"werkelijke onzekerheid.",
|
|
106
|
+
RuntimeWarning,
|
|
107
|
+
stacklevel=2,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return n_eff, k_eff
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
def n_klant_k_klant_SSO(n, k, N_klant):
|
|
2
|
+
"""
|
|
3
|
+
Herleidt een effectieve steekproefomvang (n_klant) en een effectief
|
|
4
|
+
aantal fouten (k_klant) bij de klant, op basis van een steekproef bij
|
|
5
|
+
de SSO (n, k) en de omvang van de klantpopulatie (N_klant).
|
|
6
|
+
|
|
7
|
+
Dit combineert de bewijskracht van de SSO-steekproef met de omvang
|
|
8
|
+
van de klantpopulatie tot een enkele, kleinere effectieve steekproef
|
|
9
|
+
bij de klant zelf (n_klant <= min(n, N_klant)), met k_klant naar rato
|
|
10
|
+
verdeeld. Dit is dezelfde benadering als gebruikt in
|
|
11
|
+
max_fout_benaderd_SSO.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
n : int of float
|
|
16
|
+
Steekproefomvang bij de SSO.
|
|
17
|
+
k : int of float
|
|
18
|
+
Aantal fouten in de steekproef bij de SSO.
|
|
19
|
+
N_klant : int of float
|
|
20
|
+
Aantal posten bij de klant.
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
tuple[float, float]
|
|
25
|
+
(n_klant, k_klant)
|
|
26
|
+
"""
|
|
27
|
+
if n <= 0:
|
|
28
|
+
raise ValueError("n moet groter zijn dan 0.")
|
|
29
|
+
if N_klant <= 0:
|
|
30
|
+
raise ValueError("N_klant moet groter zijn dan 0.")
|
|
31
|
+
if k < 0:
|
|
32
|
+
raise ValueError("k mag niet negatief zijn.")
|
|
33
|
+
if k > n:
|
|
34
|
+
raise ValueError("k mag niet groter zijn dan n.")
|
|
35
|
+
|
|
36
|
+
n_klant = (n * N_klant) / (n + N_klant)
|
|
37
|
+
k_klant = (n_klant / n) * k
|
|
38
|
+
return n_klant, k_klant
|