-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGesetz_kleine_Zahlen.html
More file actions
343 lines (305 loc) · 26.8 KB
/
Copy pathGesetz_kleine_Zahlen.html
File metadata and controls
343 lines (305 loc) · 26.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
<!DOCTYPE html>
<html lang="de" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Gesetz der kleinen Zahlen — MethodsLab</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,400&family=Fraunces:ital,opsz,wght@0,9..144,200;0,9..144,300;0,9..144,600;1,9..144,300;1,9..144,400&display=swap');
:root{
--bg:#f0ead8;--paper:#f8f3e8;--ink:#18130e;--ink2:#5a4f42;--ink3:#8a7f72;
--a1:#c94a2a;--a2:#2a6e8a;--a3:#5a8a2a;--a4:#7a3a8a;--a5:#b85c00;--a6:#0e7c7b;
--grid:#ddd5c5;--panel:#e8e0ce;--border:#c8bfaa;--accent:var(--a2);--topbar-h:54px;
}
[data-theme="dark"]{
--bg:#0c0f14;--paper:#111520;--ink:#ece6d8;--ink2:#8a8070;--ink3:#857c68;
--a1:#e8614a;--a2:#4ab0d8;--a3:#80cb52;--a4:#c87ad8;--a5:#e07830;--a6:#2bc7c4;
--grid:#1e242e;--panel:#171d28;--border:#2a3040;
}
*{box-sizing:border-box;margin:0;padding:0}
html{font-size:18px}
body{background:var(--bg);color:var(--ink);font-family:'Fraunces',Georgia,serif;font-size:1rem;min-height:100vh}
#topbar{position:sticky;top:0;z-index:300;background:var(--paper);border-bottom:1px solid var(--grid);padding:.6rem 1.2rem;display:flex;align-items:center;gap:.8rem}
#topbar h1{font-size:1.05rem;font-weight:300;letter-spacing:-.01em;flex:1}
#topbar h1 em{color:var(--accent);font-style:italic}
.tbtn{font-family:'DM Mono',monospace;font-size:.72rem;padding:.28rem .7rem;border:1.5px solid var(--border);background:transparent;cursor:pointer;color:var(--ink2);transition:all .15s;letter-spacing:.04em}
.tbtn:hover{background:var(--ink);color:var(--bg)}
.tbtn-red{border-color:var(--a1)!important;color:var(--a1)!important}
.tbtn-red:hover{background:var(--a1)!important;color:var(--bg)!important}
#app{display:grid;grid-template-columns:300px 1fr;height:calc(100vh - var(--topbar-h));overflow:hidden}
#sidebar{overflow-y:scroll;height:100%;background:var(--paper);border-right:1px solid var(--grid);padding:1rem .9rem}
#sidebar::-webkit-scrollbar{width:5px}
#sidebar::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
#main{overflow-y:scroll;height:100%;padding:1.2rem 1.5rem 3rem}
.scard{background:var(--panel);border:1px solid var(--grid);padding:.75rem .85rem;margin-bottom:.7rem}
.scard-title{font-family:'DM Mono',monospace;font-size:.75rem;letter-spacing:.1em;text-transform:uppercase;color:var(--accent);margin-bottom:.6rem;padding-bottom:.3rem;border-bottom:1px solid var(--grid)}
.slider-row{margin-bottom:.55rem}
.slider-lbl{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:.18rem}
.slider-name{font-family:'DM Mono',monospace;font-size:.78rem;color:var(--ink)}
.slider-val{font-family:'DM Mono',monospace;font-size:.82rem;color:var(--accent);font-weight:500}
input[type=range]{width:100%;appearance:none;height:3px;background:var(--grid);border-radius:2px;outline:none;cursor:pointer}
input[type=range]::-webkit-slider-thumb{appearance:none;width:13px;height:13px;background:var(--a1);border-radius:50%;cursor:pointer}
.reset-btn{font-family:'DM Mono',monospace;font-size:.74rem;padding:.45rem .7rem;width:100%;margin-top:.2rem;border:1.5px solid var(--a2);background:transparent;cursor:pointer;color:var(--a2);transition:all .15s}
.reset-btn:hover{background:var(--a2);color:var(--bg)}
.legend-row{display:flex;align-items:center;gap:.5rem;font-family:'DM Mono',monospace;font-size:.72rem;color:var(--ink2);margin-bottom:.35rem;line-height:1.3}
.legend-dot{width:12px;height:12px;border-radius:50%;flex-shrink:0}
.sec-h{font-family:'DM Mono',monospace;font-size:.72rem;letter-spacing:.12em;text-transform:uppercase;color:var(--ink3);margin:1.4rem 0 .6rem;padding-bottom:.3rem;border-bottom:1px solid var(--grid)}
.circ{font-size:2em;font-weight:600;vertical-align:-.15em;line-height:1;display:inline-block}
.story-box{background:var(--panel);border:1px solid var(--grid);border-left:3px solid var(--a5);padding:.8rem .95rem;margin-bottom:.85rem;line-height:1.6;font-size:.95rem}
.story-lbl{font-family:'DM Mono',monospace;font-size:.7rem;letter-spacing:.1em;text-transform:uppercase;color:var(--a5);margin-bottom:.45rem}
.story-box strong{color:var(--ink)} .story-box em{font-style:italic;color:var(--ink)}
.metric-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:.7rem;margin-bottom:.85rem}
.metric{background:var(--panel);border:1px solid var(--grid);padding:.7rem .8rem}
.metric-lbl{font-family:'DM Mono',monospace;font-size:.63rem;letter-spacing:.04em;text-transform:uppercase;color:var(--ink3);margin-bottom:.3rem;line-height:1.3}
.metric-val{font-family:'DM Mono',monospace;font-size:1.35rem;font-weight:500;line-height:1}
.metric-sub{font-family:'DM Mono',monospace;font-size:.64rem;color:var(--ink3);margin-top:.3rem;line-height:1.4}
.plot-box{background:var(--panel);border:1px solid var(--grid);padding:.7rem .85rem;margin-bottom:.85rem}
.plot-title{font-family:'DM Mono',monospace;font-size:.72rem;letter-spacing:.08em;text-transform:uppercase;color:var(--ink3);margin-bottom:.45rem;padding-bottom:.28rem;border-bottom:1px solid var(--grid)}
canvas{width:100%;display:block}
.hint{font-family:'DM Mono',monospace;font-size:.72rem;color:var(--ink3);line-height:1.7;border-top:1px solid var(--grid);padding-top:.4rem;margin-top:.4rem}
.verdict{font-family:'DM Mono',monospace;font-size:.9rem;font-weight:500;padding:.7rem .95rem;margin-bottom:1rem;border:1.5px solid;text-align:center;letter-spacing:.01em;line-height:1.5;color:var(--a5);border-color:var(--a5);background:color-mix(in srgb,var(--a5) 10%,transparent)}
.rank-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:.8rem;margin-bottom:1rem}
.rank-col{background:var(--panel);border:1px solid var(--grid);padding:.6rem .7rem}
.rank-h{font-family:'DM Mono',monospace;font-size:.72rem;letter-spacing:.04em;margin-bottom:.5rem;padding-bottom:.3rem;border-bottom:1px solid var(--grid);font-weight:500}
.rank-h.hi{color:var(--a1)} .rank-h.lo{color:var(--a2)} .rank-h.big{color:var(--ink2)}
.rank-item{display:flex;justify-content:space-between;gap:.4rem;font-family:'DM Mono',monospace;font-size:.7rem;padding:.22rem 0;line-height:1.3}
.rank-name{color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rank-meta{color:var(--ink3);white-space:nowrap;flex-shrink:0}
.rank-rate{font-weight:500}
@media(max-width:820px){.rank-grid{grid-template-columns:1fr}.metric-grid{grid-template-columns:1fr 1fr}}
.lcard-grid{display:grid;grid-template-columns:1fr 1fr;gap:.7rem;margin-bottom:1.4rem}
.lcard-grid .lcard:last-child{grid-column:1/-1}
.lcard{background:var(--paper);border:1.5px solid var(--border);padding:.9rem 1rem}
.lcard-h{font-family:'DM Mono',monospace;font-size:.72rem;color:var(--a2);letter-spacing:.07em;text-transform:uppercase;margin-bottom:.4rem;font-weight:500}
.lcard-body{font-size:.83rem;line-height:1.75;color:var(--ink2)} .lcard-body strong{color:var(--ink)}
.lcard-body code{display:block;background:var(--bg);border:1px solid var(--grid);padding:.4rem .7rem;margin:.45rem 0;font-family:'DM Mono',monospace;font-size:.82rem;color:var(--a2)}
.xlink{display:inline-block;margin-top:.5rem;font-family:'DM Mono',monospace;font-size:.73rem;padding:.3rem .7rem;border:1.5px solid var(--a4);color:var(--a4);text-decoration:none;transition:all .15s}
.xlink:hover{background:var(--a4);color:var(--bg)}
@media(max-width:820px){.lcard-grid{grid-template-columns:1fr}}
.help-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:500;overflow-y:auto}
.help-overlay.open{display:block}
.help-box{background:var(--paper);border:1px solid var(--border);margin:2rem auto;max-width:680px;width:93%;padding:1.4rem 1.6rem 1.8rem;box-shadow:0 12px 48px rgba(0,0,0,.4)}
.help-box-h{font-family:'DM Mono',monospace;font-size:.78rem;letter-spacing:.1em;text-transform:uppercase;color:var(--a1);margin-bottom:.7rem;padding-bottom:.5rem;border-bottom:1px solid var(--grid)}
.help-close{float:right;font-family:'DM Mono',monospace;font-size:.78rem;padding:.2rem .6rem;border:1px solid var(--border);background:transparent;cursor:pointer;color:var(--ink2);margin-top:-.2rem}
.help-close:hover{background:var(--ink);color:var(--bg)}
.help-sh{font-family:'DM Mono',monospace;font-size:.72rem;letter-spacing:.1em;text-transform:uppercase;color:var(--accent);margin:1rem 0 .35rem;padding-bottom:.25rem;border-bottom:1px solid var(--grid)}
.help-sh:first-of-type{margin-top:.1rem}
.help-p{font-family:'DM Mono',monospace;font-size:.79rem;color:var(--ink2);line-height:1.82;margin-bottom:.5rem}
.help-p strong{color:var(--ink)}
.help-formula{display:block;background:var(--bg);border:1px solid var(--grid);padding:.4rem .7rem;font-family:'DM Mono',monospace;font-size:.77rem;color:var(--a2);margin:.35rem 0 .55rem;white-space:pre-wrap;line-height:1.6}
</style>
</head>
<body>
<div id="topbar">
<h1>Gesetz der kleinen Zahlen — <em>warum die „Rekord-Gemeinden" winzig sind</em></h1>
<span style="font-family:'DM Mono',monospace;font-size:.7rem;color:var(--ink3);white-space:nowrap">Dr. R. Düsing · Universität Osnabrück</span>
<button class="tbtn tbtn-red" onclick="toggleHelp()">? Hilfe</button>
<button class="tbtn" id="btn-theme" onclick="toggleTheme()">☀ Light</button>
</div>
<!-- HELP -->
<div class="help-overlay" id="help-overlay" onclick="if(event.target===this)toggleHelp()">
<div class="help-box">
<button class="help-close" onclick="toggleHelp()">✕ Schließen</button>
<div class="help-box-h">Gesetz der kleinen Zahlen — Hilfe</div>
<div class="help-sh">Worum geht's</div>
<p class="help-p">Eine Quote (hier: Anteil Linkshänder) ist in <em>allen</em> Gemeinden gleich groß — sagen wir 10 %. Der gemessene Anteil schwankt aber zufällig von Gemeinde zu Gemeinde. <strong>Die entscheidende Frage:</strong> Welche Gemeinden haben die <em>höchste</em> und die <em>niedrigste</em> Quote? Antwort: fast immer die <strong>kleinen</strong> — und zwar an <em>beiden</em> Enden.</p>
<div class="help-sh">Der Mechanismus</div>
<p class="help-p">Der Standardfehler eines Anteils sinkt mit der Wurzel der Stichprobengröße:</p>
<code class="help-formula">SE = √( p·(1−p) / n )</code>
<p class="help-p">Kleine Gemeinden (kleines n) haben einen großen SE → ihre Messwerte streuen weit um die wahre Quote. Große Gemeinden liegen fast exakt bei p. Deshalb stammen sowohl die Spitzenreiter als auch die Schlusslichter aus den kleinen Gemeinden — nicht weil dort etwas Besonderes passiert, sondern weil <strong>kleine Stichproben stärker schwanken</strong>.</p>
<div class="help-sh">Der Funnel-Plot</div>
<p class="help-p">Quote (y) gegen Einwohnerzahl (x, logarithmisch). Der „Trichter" sind die ±2-SE-Grenzen: links (kleine Gemeinden) weit, rechts (große Gemeinden) eng. Die Punkte füllen den Trichter aus — die extremsten liegen ganz links. Genau diese Form heißt <em>Funnel-Plot</em> und dient auch zur Aufdeckung von Publikationsbias.</p>
<div class="help-sh">Berühmtes Beispiel (Kahneman)</div>
<p class="help-p">In US-Daten haben die Bezirke mit der <em>niedrigsten</em> Nierenkrebsrate eines gemeinsam: sie sind ländlich und dünn besiedelt. Verblüffung — bis man sieht, dass die Bezirke mit der <em>höchsten</em> Rate <strong>genauso</strong> klein sind. Reine Stichprobenstreuung, kein Landluft-Effekt.</p>
<div class="help-sh">Was tun?</div>
<p class="help-p">Ranglisten von Raten ohne Berücksichtigung von n sind irreführend. Lösung: Funnel-Plot statt nackter Rangliste, <strong>Shrinkage</strong> (Werte kleiner Einheiten Richtung Gesamtmittel ziehen, → Regression zur Mitte), oder Bayes/Multilevel-Modelle mit partial pooling.</p>
<div class="help-sh">Literatur</div>
<p class="help-p">Tversky, A. & Kahneman, D. (1971). Belief in the law of small numbers. <em>Psychological Bulletin, 76</em>(2), 105–110. · Kahneman, D. (2011). <em>Schnelles Denken, langsames Denken</em>, Kap. 10.</p>
</div>
</div>
<div id="app">
<!-- ══ SIDEBAR ══ -->
<div id="sidebar">
<div class="scard">
<div class="scard-title">Einstellungen</div>
<div class="slider-row">
<div class="slider-lbl"><span class="slider-name">Wahre Quote p <em style="color:var(--ink3);font-style:normal;font-size:.7rem">(überall gleich)</em></span><span class="slider-val" id="v-p">10 %</span></div>
<input type="range" id="s-p" min="3" max="30" step="1" value="10">
</div>
<div class="slider-row" style="margin-bottom:.5rem">
<div class="slider-lbl"><span class="slider-name">Anzahl Gemeinden</span><span class="slider-val" id="v-N">120</span></div>
<input type="range" id="s-N" min="40" max="240" step="10" value="120">
</div>
<button class="reset-btn" onclick="reroll()">🎲 Neue Erhebung würfeln</button>
</div>
<div class="scard">
<div class="scard-title">Legende</div>
<div class="legend-row"><span class="legend-dot" style="background:var(--a1)"></span> höchste Quoten (Top 8)</div>
<div class="legend-row"><span class="legend-dot" style="background:var(--a2)"></span> niedrigste Quoten (Flop 8)</div>
<div class="legend-row"><span class="legend-dot" style="background:color-mix(in srgb,var(--ink2) 55%,transparent)"></span> übrige Gemeinden</div>
<div class="legend-row" style="margin-top:.3rem"><span style="color:var(--ink3)">┈┈</span> wahre Quote · Trichter = ±2 SE</div>
</div>
<div class="scard">
<div class="scard-title">Hinweis</div>
<div style="font-size:.86rem;line-height:1.55;color:var(--ink2)">In <strong style="color:var(--ink)">jeder</strong> Gemeinde ist die wahre Quote gleich. Jeder Unterschied ist <strong style="color:var(--ink)">Zufall</strong> — und der ist bei kleinen Gemeinden größer.</div>
</div>
</div>
<!-- ══ MAIN ══ -->
<div id="main">
<div class="story-box">
<div class="story-lbl">📋 Schlagzeile aus dem Statistik-Amt</div>
„<strong>Gemeinde Hintertupfingen hat anteilig die meisten Linkshänder Deutschlands!</strong>" Klingt nach einer Entdeckung. Dabei ist der Anteil Linkshänder <em>überall</em> gleich (~10 %, reiner Zufall pro Person).
<strong>Frage vorab:</strong> Sind die Rekord-Gemeinden (höchste <em>und</em> niedrigste Quote) eher <em>große</em> oder <em>kleine</em> Gemeinden? Schau in den Funnel-Plot und die Rangliste.
</div>
<div class="sec-h"><span class="circ">①</span> Kennzahlen</div>
<div class="metric-grid">
<div class="metric"><div class="metric-lbl">Wahre Quote (überall)</div><div class="metric-val" id="m-p">—</div></div>
<div class="metric"><div class="metric-lbl">Höchste gemessene Quote</div><div class="metric-val" id="m-hi" style="color:var(--a1)">—</div><div class="metric-sub" id="m-hi-n">—</div></div>
<div class="metric"><div class="metric-lbl">Niedrigste gemessene Quote</div><div class="metric-val" id="m-lo" style="color:var(--a2)">—</div><div class="metric-sub" id="m-lo-n">—</div></div>
<div class="metric"><div class="metric-lbl">Ø Einwohner der 10 Extreme</div><div class="metric-val" id="m-ex">—</div><div class="metric-sub">vs. Gesamt-Ø <span id="m-avg">—</span></div></div>
</div>
<div class="verdict" id="verdict">—</div>
<div class="sec-h"><span class="circ">②</span> Funnel-Plot — Quote gegen Einwohnerzahl</div>
<div class="plot-box">
<div class="plot-title">jeder Punkt = eine Gemeinde · x = Einwohner (log) · y = gemessene Quote · Trichter = ±2 SE um die wahre Quote</div>
<canvas id="cv" height="450"></canvas>
<div class="hint">Links (kleine Gemeinden) ist der Trichter weit — dort liegen die Extreme. Rechts (große Gemeinden) klebt alles an der wahren Quote.</div>
</div>
<div class="sec-h"><span class="circ">③</span> Rangliste — wer ragt heraus?</div>
<div class="rank-grid">
<div class="rank-col"><div class="rank-h hi">🔺 Höchste Quote</div><div id="rank-hi"></div></div>
<div class="rank-col"><div class="rank-h lo">🔻 Niedrigste Quote</div><div id="rank-lo"></div></div>
<div class="rank-col"><div class="rank-h big">🏙 Größte Gemeinden</div><div id="rank-big"></div></div>
</div>
<div class="sec-h">Konzepte</div>
<div class="lcard-grid">
<div class="lcard"><div class="lcard-h">Gesetz der kleinen Zahlen</div><div class="lcard-body">Wir erwarten, dass kleine Stichproben so „brav" sind wie große — sind sie aber nicht. Kleine n schwanken stark (SE ∝ 1/√n), daher tauchen sie an <strong>beiden</strong> Extremen auf.</div></div>
<div class="lcard"><div class="lcard-h">Der Mechanismus: Standardfehler</div><div class="lcard-body">Der Standardfehler eines Anteils sinkt mit der Wurzel der Stichprobengröße:
<code>SE = √( p·(1−p) / n )</code>
Kleine Gemeinden (kleines n) haben einen großen SE → ihre gemessene Quote streut weit um die wahre Quote p. Große Gemeinden (großes n) liegen fast exakt bei p. <strong>Das</strong> ist der eigentliche Grund, warum sowohl die höchste als auch die niedrigste gemessene Quote aus kleinen Gemeinden stammt — nicht weil dort etwas Besonderes passiert, sondern weil kleine Stichproben stärker zufällig schwanken.</div></div>
<div class="lcard"><div class="lcard-h">Warum beide Enden?</div><div class="lcard-body">Die Streuung ist symmetrisch: eine kleine Gemeinde kann zufällig sehr hoch <em>oder</em> sehr niedrig liegen. Deshalb sind Spitzenreiter <strong>und</strong> Schlusslichter klein — ein verräterisches Muster.</div></div>
<div class="lcard"><div class="lcard-h">Funnel-Plot</div><div class="lcard-body">Wert gegen Stichprobengröße, mit ±2-SE-Trichter. Punkte außerhalb des Trichters sind echte Auffälligkeiten; die Trichterform selbst entlarvt reine Größeneffekte. Auch Standard zur Bias-Diagnose in Meta-Analysen.</div></div>
<div class="lcard"><div class="lcard-h">Gegenmittel: Shrinkage</div><div class="lcard-body">Werte kleiner Einheiten Richtung Gesamtmittel ziehen (Empirical Bayes / Multilevel) — extreme Zufalls-Ausreißer werden gedämpft. Das ist <strong>Regression zur Mitte</strong> in Aktion.
<a class="xlink" href="Regression_zur_Mitte.html">→ Regression zur Mitte</a></div></div>
</div>
</div><!-- #main -->
</div><!-- #app -->
<script>
'use strict';
const NAMES=['Hintertupfingen','Kleinkleckersdorf','Oberbödefeld','Niederzwehren','Hasenwinkel','Krähenbach','Sumpfheim','Schilda','Bullerbü','Posemuckel','Dingenskirchen','Kuhdorf','Wolkenkuckau','Trübsbach','Nieselbach','Matschhausen','Bröselberg','Hödingen','Quakenbrück','Wabern','Lützelflüh','Käsedorf','Tribsees','Knatterheim','Murrhardt','Pfützental','Schnarchhausen','Eulenfurt','Maulwurfshöhn','Grünschnabel','Kieselbronn','Buxtehude','Castrop','Hintersehfeld','Entenhausen','Trödelheim','Wanne-Eickel','Brackwede','Lengerich','Datteln','Espelkamp','Gütersloh','Bünde','Coesfeld','Ahaus','Borken','Soest','Unna','Minden','Herford'];
const ST={p:0.10, N:120};
let seed=5, counties=null;
const LOGMIN=Math.log10(80), LOGMAX=Math.log10(750000);
function mulberry32(a){return function(){a|=0;a=a+0x6D2B79F5|0;let t=Math.imul(a^a>>>15,1|a);t=t+Math.imul(t^t>>>7,61|t)^t;return((t^t>>>14)>>>0)/4294967296;};}
// Gemeindenamen nach Größe: Dörfer klingen wie Dörfer, Großstädte wie Städte (Pools disjunkt durch verschiedene Endungen)
function shuf(a,sd){let r=mulberry32(sd);for(let i=a.length-1;i>0;i--){const j=(r()*(i+1))|0;const t=a[i];a[i]=a[j];a[j]=t;}return a;}
function combos(pre,suf,fixed){const a=fixed.slice();for(const p of pre)for(const s of suf)a.push(p+s);return a;}
const VILLAGE=shuf(combos(['Klein','Hinter','Nieder','Unter','Wenig','Ober','Mittel','Vorder','Hohen'],['dorf','bach','winkel','moor','bronn','büttel','rode','horst','au','tal','scheid','wang','beuren','ried'],['Hintertupfingen','Kleinkleckersdorf','Posemuckel','Kuhdorf','Käsedorf','Bullerbü','Schilda','Trübsbach','Nieselbach','Matschhausen','Maulwurfshöhn','Schnarchhausen','Sumpfheim','Hasenwinkel','Krähenbach','Wolkenkuckau','Pfützental','Grünschnabel','Entenhausen']),11);
const TOWN=shuf(combos(['Ober','Alt','Neu','Bad','Markt','Hohen','Mittel','Groß','Nieder'],['stedt','ingen','kirchen','heim','hausen','feld','berg','wig','roda','leben','stein'],['Quakenbrück','Wabern','Tribsees','Murrhardt','Knatterheim','Lengerich','Brackwede','Espelkamp']),22);
const CITY=shuf(combos(['Groß','Neu','Bad','Hohen','Markt','Ober','Nieder','Mittel','West','Ost'],['stadt','burg','hafen','brücken','münster','hagen','horn'],['Buxtehude','Gütersloh','Bünde','Coesfeld','Castrop','Minden','Herford','Unna','Soest','Datteln','Wanne-Eickel','Eulenfurt']),33);
let rng=mulberry32(seed);
function randn(){let u=0,v=0;while(u===0)u=rng();while(v===0)v=rng();return Math.sqrt(-2*Math.log(u))*Math.cos(2*Math.PI*v);}
function cv(n){return getComputedStyle(document.documentElement).getPropertyValue(n).trim();}
function fmtN(n){return n>=1e6?(n/1e6).toFixed(1)+' Mio':n>=1000?Math.round(n/1000)+'k':''+n;}
function build(){
rng=mulberry32(seed);
const p=ST.p, N=ST.N;
counties=[];
let vi=0,ti=0,ci=0;
for(let i=0;i<N;i++){
const lg=LOGMIN+rng()*(LOGMAX-LOGMIN);
const n=Math.round(Math.pow(10,lg));
const frac=(lg-LOGMIN)/(LOGMAX-LOGMIN);
const se=Math.sqrt(p*(1-p)/n);
const rate=Math.max(0,Math.min(1,p+randn()*se));
const name = frac<0.40 ? VILLAGE[vi++%VILLAGE.length] : (frac<0.755 ? TOWN[ti++%TOWN.length] : CITY[ci++%CITY.length]);
counties.push({name,n,rate});
}
// Markierungen: Top/Flop 8 nach Rate
const byRate=[...counties].sort((a,b)=>b.rate-a.rate);
byRate.slice(0,8).forEach(c=>c.mark='hi');
byRate.slice(-8).forEach(c=>c.mark='lo');
}
function render(){
const p=ST.p;
const byRate=[...counties].sort((a,b)=>b.rate-a.rate);
const hi=byRate[0], lo=byRate[byRate.length-1];
const extremes=byRate.slice(0,5).concat(byRate.slice(-5));
const avgEx=Math.round(extremes.reduce((s,c)=>s+c.n,0)/extremes.length);
const avgAll=Math.round(counties.reduce((s,c)=>s+c.n,0)/counties.length);
document.getElementById('m-p').textContent=(p*100).toFixed(0)+' %';
document.getElementById('m-hi').textContent=(hi.rate*100).toFixed(1)+' %';
document.getElementById('m-hi-n').textContent=hi.name+' · '+fmtN(hi.n)+' Einw.';
document.getElementById('m-lo').textContent=(lo.rate*100).toFixed(1)+' %';
document.getElementById('m-lo-n').textContent=lo.name+' · '+fmtN(lo.n)+' Einw.';
document.getElementById('m-ex').textContent=fmtN(avgEx);
document.getElementById('m-avg').textContent=fmtN(avgAll);
document.getElementById('verdict').innerHTML=
`Die 10 Gemeinden mit den extremsten Quoten haben im Schnitt nur <strong>${fmtN(avgEx)}</strong> Einwohner — die größten Gemeinden liegen dagegen praktisch alle bei <strong>${(p*100).toFixed(0)} %</strong>. Kein Linkshänder-Hotspot, nur Stichprobenstreuung (SE ∝ 1/√n).`;
// Ranglisten
const fill=(id,arr,cls)=>{
const el=document.getElementById(id); el.innerHTML='';
arr.forEach(c=>{
const d=document.createElement('div'); d.className='rank-item';
d.innerHTML=`<span class="rank-name" title="${c.name}">${c.name}</span><span class="rank-meta"><span class="rank-rate" style="color:var(${cls})">${(c.rate*100).toFixed(1)}%</span> · ${fmtN(c.n)}</span>`;
el.appendChild(d);
});
};
fill('rank-hi',byRate.slice(0,5),'--a1');
fill('rank-lo',byRate.slice(-5).reverse(),'--a2');
fill('rank-big',[...counties].sort((a,b)=>b.n-a.n).slice(0,5),'--ink2');
draw();
}
function draw(){
const canvas=document.getElementById('cv');
const W=canvas.clientWidth||640; canvas.width=W;
const H=canvas.height, ctx=canvas.getContext('2d');
const ml=52,mr=18,mt=16,mb=48, cW=W-ml-mr, cH=H-mt-mb;
const ink2=cv('--ink2'),ink3=cv('--ink3'),grid=cv('--grid'),bg=cv('--bg'),a1=cv('--a1'),a2=cv('--a2'),paper=cv('--paper');
ctx.clearRect(0,0,W,H);
const p=ST.p, seMax=Math.sqrt(p*(1-p)/Math.pow(10,LOGMIN));
const ylo=Math.max(0,p-3.1*seMax), yhi=p+3.1*seMax;
const lx=n=>ml+(Math.log10(n)-LOGMIN)/(LOGMAX-LOGMIN)*cW;
const ly=r=>mt+cH*(1-(r-ylo)/(yhi-ylo));
// y-Gitter
ctx.font='12px DM Mono,monospace';ctx.textAlign='right';ctx.textBaseline='middle';
const ystep=(yhi-ylo)/5;
for(let k=0;k<=5;k++){const r=ylo+k*ystep;ctx.strokeStyle=grid;ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(ml,ly(r));ctx.lineTo(ml+cW,ly(r));ctx.stroke();ctx.fillStyle=ink3;ctx.fillText((r*100).toFixed(0)+'%',ml-6,ly(r));}
// x-Ticks (log)
ctx.textAlign='center';ctx.textBaseline='top';
[100,1000,10000,100000,1000000].forEach(n=>{ if(n>=Math.pow(10,LOGMIN)&&n<=Math.pow(10,LOGMAX)){ ctx.strokeStyle=grid;ctx.beginPath();ctx.moveTo(lx(n),mt);ctx.lineTo(lx(n),mt+cH);ctx.stroke(); ctx.fillStyle=ink3;ctx.fillText(fmtN(n),lx(n),mt+cH+7);} });
ctx.fillStyle=ink2;ctx.font='13px DM Mono,monospace';ctx.textBaseline='bottom';ctx.fillText('Einwohnerzahl (logarithmisch) →',ml+cW/2,H-6);
// Trichter ±2 SE
ctx.fillStyle=`color-mix(in srgb,${ink2} 8%,transparent)`;
ctx.beginPath();
for(let i=0;i<=60;i++){const lg=LOGMIN+i/60*(LOGMAX-LOGMIN);const n=Math.pow(10,lg);const se=Math.sqrt(p*(1-p)/n);ctx.lineTo(lx(n),ly(Math.min(yhi,p+2*se)));}
for(let i=60;i>=0;i--){const lg=LOGMIN+i/60*(LOGMAX-LOGMIN);const n=Math.pow(10,lg);const se=Math.sqrt(p*(1-p)/n);ctx.lineTo(lx(n),ly(Math.max(ylo,p-2*se)));}
ctx.closePath();ctx.fill();
// Trichter-Ränder
ctx.strokeStyle=ink3;ctx.lineWidth=1.3;ctx.setLineDash([5,4]);
[1,-1].forEach(sgn=>{ctx.beginPath();for(let i=0;i<=60;i++){const lg=LOGMIN+i/60*(LOGMAX-LOGMIN);const n=Math.pow(10,lg);const se=Math.sqrt(p*(1-p)/n);const r=p+sgn*2*se;const y=ly(Math.max(ylo,Math.min(yhi,r)));i?ctx.lineTo(lx(n),y):ctx.moveTo(lx(n),y);}ctx.stroke();});
ctx.setLineDash([]);
// wahre Quote
ctx.strokeStyle=ink2;ctx.lineWidth=1.6;ctx.beginPath();ctx.moveTo(ml,ly(p));ctx.lineTo(ml+cW,ly(p));ctx.stroke();
ctx.fillStyle=ink2;ctx.font='11px DM Mono,monospace';ctx.textAlign='left';ctx.textBaseline='bottom';ctx.fillText('wahre Quote '+(p*100).toFixed(0)+'%',ml+4,ly(p)-3);
// Punkte
counties.forEach(c=>{
const x=lx(c.n),y=ly(Math.max(ylo,Math.min(yhi,c.rate)));
const col=c.mark==='hi'?a1:(c.mark==='lo'?a2:`color-mix(in srgb,${ink2} 55%,transparent)`);
ctx.beginPath();ctx.arc(x,y,c.mark?4.6:3.3,0,2*Math.PI);ctx.fillStyle=col;ctx.fill();
if(c.mark){ctx.strokeStyle=bg;ctx.lineWidth=1.3;ctx.stroke();}
});
}
// ── Controls ──
document.getElementById('s-p').addEventListener('input',function(){ST.p=+this.value/100;document.getElementById('v-p').textContent=this.value+' %';build();render();});
document.getElementById('s-N').addEventListener('input',function(){ST.N=+this.value;document.getElementById('v-N').textContent=ST.N;build();render();});
function reroll(){ seed=(seed*1103515245+12345)>>>0; build(); render(); }
function toggleHelp(){document.getElementById('help-overlay').classList.toggle('open');}
function toggleTheme(){const h=document.documentElement,d=h.getAttribute('data-theme')==='dark';h.setAttribute('data-theme',d?'light':'dark');document.getElementById('btn-theme').textContent=d?'🌙 Dark':'☀ Light';requestAnimationFrame(render);}
document.addEventListener('keydown',e=>{if(e.key==='Escape')document.getElementById('help-overlay').classList.remove('open');});
window.addEventListener('resize',()=>requestAnimationFrame(draw));
// ── Init ──
build(); render();
</script>
</body>
</html>