edushinka commited on
Commit
839df80
·
verified ·
1 Parent(s): 44b4b5f

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +128 -17
index.html CHANGED
@@ -1,19 +1,130 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
  <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Reliability table builder: check whether your probabilities mean what they say</title>
7
+ <meta name="description" content="Paste forecast probabilities with their outcomes and get a reliability table by band, the Brier score and a plain-English verdict on calibration. Runs in the browser, nothing is uploaded.">
8
+ <style>
9
+ :root{--bg:#fbfbfd;--ink:#1a1c22;--mute:#5f6573;--line:#e1e4ea;--accent:#1f5fbf;--chip:#eaf1fc;--card:#fff}
10
+ @media (prefers-color-scheme:dark){:root{--bg:#121318;--ink:#e9ebf0;--mute:#a0a6b4;--line:#2b2e37;--accent:#7fb0ff;--chip:#1b2433;--card:#171a21}}
11
+ *{box-sizing:border-box}
12
+ body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.6 system-ui,-apple-system,"Segoe UI",sans-serif}
13
+ main{max-width:800px;margin:0 auto;padding:40px 16px 64px}
14
+ h1{font-size:1.9rem;line-height:1.2;margin:0 0 10px}
15
+ h2{font-size:1.15rem;margin:34px 0 8px}
16
+ p{margin:0 0 14px}
17
+ .lead{color:var(--mute);font-size:1.08rem}
18
+ .card{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:18px 20px;margin:22px 0}
19
+ label{display:block;font-size:.9rem;color:var(--mute);margin-bottom:6px}
20
+ textarea{width:100%;min-height:150px;font:14px/1.5 ui-monospace,Consolas,monospace;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:var(--bg);color:var(--ink)}
21
+ .row{display:flex;gap:10px;flex-wrap:wrap;margin-top:10px;align-items:center}
22
+ button{font:inherit;padding:8px 14px;border-radius:10px;border:1px solid var(--line);background:var(--bg);color:var(--ink);cursor:pointer}
23
+ button.primary{background:var(--accent);color:#fff;border-color:var(--accent)}
24
+ select{font:inherit;padding:7px 10px;border-radius:10px;border:1px solid var(--line);background:var(--bg);color:var(--ink)}
25
+ table{width:100%;border-collapse:collapse;margin-top:14px;font-variant-numeric:tabular-nums}
26
+ th,td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--line)}
27
+ th{font-size:.82rem;text-transform:uppercase;letter-spacing:.04em;color:var(--mute)}
28
+ tr:last-child td{border-bottom:0}
29
+ .verdict{background:var(--chip);border-radius:12px;padding:12px 16px;margin-top:14px}
30
+ .err{color:#b3261e}
31
+ a{color:var(--accent)}
32
+ footer{margin-top:48px;color:var(--mute);font-size:.88rem;border-top:1px solid var(--line);padding-top:16px}
33
+ </style>
34
+ </head>
35
+ <body>
36
+ <main>
37
+ <h1>Reliability table builder</h1>
38
+ <p class="lead">A forecaster that says "70%" should be right about 70 times in 100. Paste your forecasts and what happened, and this page builds the table that checks it.</p>
39
+
40
+ <div class="card">
41
+ <label for="data">One line per forecast: probability (0 to 1, or a percent), then 1 if it happened or 0 if it did not. Commas, spaces or tabs between them. Extra columns are ignored.</label>
42
+ <textarea id="data" spellcheck="false">0.72,1
43
+ 0.65,1
44
+ 0.65,0
45
+ 0.30,0
46
+ 0.28,1
47
+ 0.81,1
48
+ 0.55,0
49
+ 0.47,1
50
+ 0.22,0
51
+ 0.90,1
52
+ 0.61,0
53
+ 0.35,0
54
+ 0.15,0
55
+ 0.77,1
56
+ 0.52,1
57
+ 0.44,0
58
+ 0.68,1
59
+ 0.25,0
60
+ 0.83,1
61
+ 0.58,0</textarea>
62
+ <div class="row">
63
+ <button class="primary" id="go">Build the table</button>
64
+ <label for="bands" style="margin:0">Bands</label>
65
+ <select id="bands"><option value="5">5 (20-point bands)</option><option value="10" selected>10 (10-point bands)</option></select>
66
+ <button id="clear">Clear</button>
67
+ </div>
68
+ <p class="err" id="msg" style="margin:10px 0 0"></p>
69
+ <div id="out"></div>
70
+ </div>
71
+
72
+ <h2>How to read it</h2>
73
+ <p>Each row groups the forecasts that claimed a similar chance. <strong>Average claim</strong> is the mean probability stated in that band; <strong>observed</strong> is the share of those forecasts whose outcome happened. When the two are close in every band the forecaster is well calibrated. A band where observed sits well below the claim is overconfident; well above, underconfident.</p>
74
+ <p>The <strong>Brier score</strong> is the mean squared gap between each probability and its outcome (1 or 0). Lower is better; always guessing 50% scores 0.25 on balanced data. It rewards both calibration and sharpness, so a forecaster can be perfectly calibrated and still have a poor Brier score if it never commits.</p>
75
+ <p>Small samples swing. With 20 forecasts a band may hold two or three cases, so its observed share can only be 0%, 33%, 50% or 100%. Treat anything under about 100 forecasts per band as a sketch, not a verdict.</p>
76
+
77
+ <h2>A worked example on real data</h2>
78
+ <p>The <a href="https://betbetter.world/studies/market-calibration">market calibration study</a> ran this exact table on both sides of 18,238 settled sports markets. Every band landed within about two points of its claim, with outcomes priced below 50% happening slightly more often than claimed and those above 50% slightly less. Its open data set, one row per outcome with the margin-free probability, is a good file to paste here if you want to see what a large, well-calibrated sample looks like. Margin-free model probabilities to test the same way are available from the <a href="https://betbetter.world/api/">Bet Better API</a> with no key.</p>
79
+
80
+ <footer>Built by Bet Better Pty Ltd, Melbourne, as a teaching tool for forecast evaluation. Everything runs in your browser; nothing is uploaded. Not betting advice.</footer>
81
+ </main>
82
+ <script>
83
+ (function(){
84
+ var data=document.getElementById('data'), out=document.getElementById('out'), msg=document.getElementById('msg'), bandsSel=document.getElementById('bands');
85
+ function pct(x){return (x*100).toFixed(1)+'%';}
86
+ function run(){
87
+ out.innerHTML=''; msg.textContent='';
88
+ var rows=[], bad=0;
89
+ data.value.split(/\r?\n/).forEach(function(line){
90
+ var parts=line.trim().split(/[\s,;\t]+/).filter(Boolean);
91
+ if(parts.length<2) return;
92
+ var p=parseFloat(parts[0]), y=parseFloat(parts[1]);
93
+ if(isNaN(p)||isNaN(y)){bad++;return;}
94
+ if(p>1) p=p/100;
95
+ if(p<0||p>1||(y!==0&&y!==1)){bad++;return;}
96
+ rows.push([p,y]);
97
+ });
98
+ if(rows.length<2){msg.textContent='Need at least two valid lines of "probability, outcome".';return;}
99
+ var k=parseInt(bandsSel.value,10), w=1/k, bands=[];
100
+ for(var i=0;i<k;i++) bands.push({lo:i*w,hi:(i+1)*w,n:0,sp:0,sy:0});
101
+ var brier=0;
102
+ rows.forEach(function(r){
103
+ var i=Math.min(k-1,Math.floor(r[0]/w)); var b=bands[i]; b.n++; b.sp+=r[0]; b.sy+=r[1];
104
+ brier+=(r[0]-r[1])*(r[0]-r[1]);
105
+ });
106
+ brier/=rows.length;
107
+ var html='<table><thead><tr><th>Claimed</th><th>Forecasts</th><th>Average claim</th><th>Observed</th><th>Gap</th></tr></thead><tbody>';
108
+ var maxGap=0, nBig=0;
109
+ bands.forEach(function(b){
110
+ if(!b.n) return;
111
+ var claim=b.sp/b.n, obs=b.sy/b.n, gap=(obs-claim)*100;
112
+ if(b.n>=10){ if(Math.abs(gap)>maxGap) maxGap=Math.abs(gap); if(Math.abs(gap)>5) nBig++; }
113
+ html+='<tr><td>'+Math.round(b.lo*100)+' to '+Math.round(b.hi*100)+'%</td><td>'+b.n+'</td><td>'+pct(claim)+'</td><td>'+pct(obs)+'</td><td>'+(gap>=0?'+':'')+gap.toFixed(1)+' pts</td></tr>';
114
+ });
115
+ html+='</tbody></table>';
116
+ var base=rows.reduce(function(a,r){return a+r[1];},0)/rows.length;
117
+ var verdict;
118
+ if(rows.length<50) verdict='Only '+rows.length+' forecasts: the table is a sketch. Gaps of 10 points or more are expected from noise alone at this size.';
119
+ else if(nBig===0) verdict='Every band with at least 10 forecasts sits within 5 points of its claim. On this sample the forecasts are well calibrated.';
120
+ else verdict=nBig+' band'+(nBig>1?'s':'')+' with at least 10 forecasts miss'+(nBig>1?'':'es')+' by more than 5 points (largest gap '+maxGap.toFixed(1)+'). Look at whether the misses are all in one direction: below the claim means overconfident, above means underconfident.';
121
+ out.innerHTML='<div class="verdict"><strong>'+rows.length+' forecasts.</strong> Brier score '+brier.toFixed(4)+' (always guessing the base rate of '+pct(base)+' would score '+(base*(1-base)).toFixed(4)+'). '+verdict+(bad?' '+bad+' line'+(bad>1?'s':'')+' skipped.':'')+'</div>'+html;
122
+ }
123
+ document.getElementById('go').addEventListener('click',run);
124
+ document.getElementById('clear').addEventListener('click',function(){data.value='';out.innerHTML='';msg.textContent='';});
125
+ bandsSel.addEventListener('change',run);
126
+ run();
127
+ })();
128
+ </script>
129
+ </body>
130
  </html>