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 ADDED
@@ -0,0 +1,6 @@
1
+ """
2
+ auditstat: Python-pakket voor statistische auditmethoden.
3
+
4
+ Subpakket:
5
+ stratified: gestratificeerde steekproefaudits (HARo-compatibel)
6
+ """
@@ -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,6 @@
1
+ """
2
+ auditstat: Python-pakket voor statistische auditmethoden.
3
+
4
+ Subpakket:
5
+ sso: (in voorbereiding)
6
+ """
@@ -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