File size: 2,830 Bytes
52467f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python3
"""Gepaarter Vergleich der HumanEval+-Laeufe (McNemar, exakt).

WARUM: Bisher habe ich die Varianten ueber Binomial-Konfidenzintervalle
verglichen (+-4,4 bis +-4,9 Punkte) und daraus geschlossen, es sei nichts
unterscheidbar. Dieser Vergleich ist zu schwach, denn er behandelt die Laeufe
als unabhaengige Stichproben. Sie sind aber GEPAART: jede Variante bearbeitet
exakt dieselben 164 Aufgaben. Aufgaben, die alle loesen oder alle verfehlen,
tragen keine Information ueber den Unterschied — nur die Faelle, in denen genau
eine der beiden Varianten besteht (die "diskordanten Paare"), zaehlen.

Der exakte McNemar-Test schaut ausschliesslich auf diese Paare: unter der
Nullhypothese "beide gleich gut" ist ein diskordantes Paar ein Muenzwurf.
"""
import json
import glob
import math
import itertools
import os

PREP = os.path.dirname(os.path.abspath(__file__))


def lade(pfad):
    d = json.load(open(pfad))
    return d["modell"], {a["task"]: a["ok"] for a in d["aufgaben"]}


def binom_zweiseitig(k, n):
    """Exakter Zweiseitentest, p=0.5. k Erfolge aus n."""
    if n == 0:
        return 1.0
    k = min(k, n - k)
    einseitig = sum(math.comb(n, i) for i in range(k + 1)) / 2 ** n
    return min(1.0, 2 * einseitig)


laeufe = [lade(f) for f in sorted(glob.glob(f"{PREP}/he-humanevalplus/*.json"))]

print(f"{len(laeufe)} Laeufe, je {len(laeufe[0][1])} Aufgaben\n")
print("Gepaarte Vergleiche — nur diskordante Paare tragen Information:\n")
print(f"{'A':38} {'B':38} {'nur A':>6} {'nur B':>6} {'p':>8}")
print("-" * 100)

zeilen = []
for (na, ra), (nb, rb) in itertools.combinations(laeufe, 2):
    gemeinsam = set(ra) & set(rb)
    nur_a = sum(1 for t in gemeinsam if ra[t] and not rb[t])
    nur_b = sum(1 for t in gemeinsam if rb[t] and not ra[t])
    p = binom_zweiseitig(nur_a, nur_a + nur_b)
    zeilen.append((p, na, nb, nur_a, nur_b))

for p, na, nb, a, b in sorted(zeilen):
    stern = " *" if p < 0.05 else ""
    print(f"{na[:38]:38} {nb[:38]:38} {a:6} {b:6} {p:8.3f}{stern}")

print()
print("Kleinstes p:", f"{min(z[0] for z in zeilen):.3f}")
print("Signifikant bei 0,05:", sum(1 for z in zeilen if z[0] < 0.05), "von", len(zeilen))

# Wie gross muesste ein echter Unterschied sein, damit wir ihn saehen?
print()
print("Trennschaerfe: welche Aufteilung der diskordanten Paare waere signifikant?")
for n in (6, 10, 15, 20, 25, 30, 40):
    # kleinstes k > n/2, das noch p < 0,05 liefert
    kand = [k for k in range(n // 2 + 1, n + 1) if binom_zweiseitig(k, n) < 0.05]
    if kand:
        k = min(kand)
        print(f"  {n:2} diskordante Paare: ab {k}:{n-k} "
              f"(p={binom_zweiseitig(k, n):.3f}) — Vorsprung {2*k-n} Aufgaben")
    else:
        print(f"  {n:2} diskordante Paare: NIE signifikant, selbst bei {n}:0 "
              f"(p={binom_zweiseitig(n, n):.3f})")