Oder warum ich keiner Statistik mehr traue, nicht mal der, welche ich selbst gefälscht habe
Hintergrund
Vor einigen Monaten bekam ich den Auftrag, die vollendete Diplomarbeit eines Data Science Praktikanten in eine produktive Datenbank Umgebung einzubinden. Das genaue Ziel dieser Übung war niemanden so richtig klar und es hatte sofort den Charakter, ein einzelnes Puzzleteil einzusetzen, welches nur vielleicht in das Gesamtbild passen könnte. Dies sollte keine Kritik an der Arbeitsgüte des Praktikanten oder an dessen Berufsstand sein, sondern der Ursprung lag wie so oft an den chaotischen Rahmenbedingungen, unter welcher die Arbeit erledigt wurde.
Nun, es war der erste ernsthafte Berührungspunkt mit R, einer in Data Science beliebte Sprache. Irgendwie war Data Science als Disziplin schon lange unser Aufgabenbereich, allerdings eingeschränkt auf strukturierte Daten in Datenbanken. Trotzdem, das Potenzial war sofort zu erkennen und auf den zweiten, aber immer noch flüchtigen Blick, gab es überraschend viele Gemeinsamkeiten. Jedenfalls war die Intuition da, R genauer anzuschauen, auch um gewisses SQL Gepfusche eventuell sauberer lösen zu können. In Daten zu wühlen macht ein nicht kleiner Teil der täglichen Arbeit aus und R kann da wirklich auch Punkten.
Selbstverständlich auftragslos begannen ich mich mit R auseinanderzusetzen und logischerweise gab es da gewisse Schlaglöcher, in die einfach reingerasselt werden musste und die meine bisherige berufliche Arbeit in Fragen gestellt haben.
Gemeinsamkeiten
Nun als erstes ist zu sagen, beide Sprachen sind sehr nahe an mathematischen Disziplinen anzusiedeln. Statistik bei beiden, Mengenlehre bei SQL und Vektoren bei R. In meiner Erinnerung kam die erste Berührung mit der Mengenlehre in den frühen Achtziger so in der dritten Klasse und das absolut Beste von allem war, das unsere Eltern keine Ahnung hatten und es auch nie kapierten. Emil Steinberger parodierte die Mengenlehre was zum Volksbrüller wurde. Viel später bei den Vektoren war das Thema Hausaufgaben zur Korrektur Mamma vorzulegen schon lange erledigt. Gemeinsam hatten sie allerdings immer noch das nicht zuzugebende Schulterzucken der Lehrpersonen auf die Frage: «Wofür brauch ich das.» Nun, sowohl SQL wie R Schreiberlinge können diese Frage sicherlich beantworten.
Doch zurück zu den Schlaglöchern in den Statistiken.
Tabellen kennt man in SQL und DataFrame / DataTable in R. Tönt sehr gleich und bei den einfachen «Hallo Welt» Beispielen, ist dies auch auf den ersten Blick der Fall. Doch bereits bei besagten Beispielen verhielt ich mich als SQL Schwarzgurtträger wie ein Dilettant in R und vor allem auch im Bereich Statistiken erstellen. Zu Demozwecken habe ich ein paar Daten Aufbereitet.
SQL
select id
, first_name
, gender
, age
, pocket_money
from t_r_compare
order by id;
id | first_name | gender | age | pocket_money |
1 | Sofia | w | 10 | 12 |
2 | Max | m | 8 | 10 |
3 | Nina | w | 13 | 22 |
4 | Moritz | m | 12 | 35 |
5 | Olivia | w | 11 | 14 |
6 | Fritz | m | 9 | 8 |
7 | Julia | w | 11 | {null} |
Das ganze in R:
dt <- fread("pocket_money.csv") print(dt)
id |
first_name | gender | age | pocket_money |
1 | Sofia | w | 10 | 12 |
2 | Max | m | 8 | 10 |
3 | Nina | w | 13 | 22 |
4 | Moritz | m | 12 | 35 |
5 | Olivia | w | 11 | 14 |
6 | Fritz | m | 9 | 8 |
7 | Julia | w | 11 | NA |
Soweit so gut, kein Problem, Datenbank Tabelle und DataTable, egal. Sieht gleich aus, fühlt sich gleich an, muss gleich sein.
Halt, Stop, was ist den das?
Doch bereits der nächste einfache Befehl warf Verwirrung auf. Eine simple Addition über das Taschengeld erwies sich bereits als Stolperstein.
SQL-> select sum(pocket_money) from t_r_compare;
-> 101
R-> sum(dt$pocket_money)
-> NA
Was war denn das? Natürlich hat die Julia kein Taschengeld aber ich wollte doch einfach die Gesamtausgabe fürs Taschengeld wissen. Also irgendein Resultat sollte da schon kommen.
Der Hund liegt bekanntlich im Detail begraben. R benötigt für solch triviale Sachen bereits einen Filter.
R-> sum(dt[!is.na(dt$pocket_money), pocket_money])
-> 101
Wenn man genau darüber nachdenkt, ist dies nicht einmal so verkehrt. Denn die Frage, ob die Julia nun kein Taschengeld hat oder ob wir es nicht wissen sind streng genommen zwei unterschiedliche paar Schuhe. Wenn wir wissen, dass sie kein Taschengeld hat, wäre der Betrag von 0 korrekt. Auch nach vielen Jahren Datawarehouse Entwicklung lassen sich solche Fragen nicht immer beantworten, schon gar nicht nur anhand der Daten. Die Löcher in den Grunddaten automatisch auffüllen, ersetzen von Nulls mit Default Werten ist tägliches Brot. Die Konsequenzen auf die Auswertungen wird dabei kaum Beachtung geschenkt, denn streng genommen wird dadurch eine wichtige Aussage geopfert.
WIR WISSEN ES NICHT!
Noch lustiger wird es mit Analytischen Funktionen wie Average und Median. Wegen des fehlenden Taschengelds wird die Julia nur beim R Code rausgefiltert. Ein solcher Filter ist in SQL nicht nötig.
SQL-> select avg(pocket_money) from t_r_compare;
R-> dt[!is.na(dt$pocket_money), mean(pocket_money)]
-> 16.83333
SQL-> select median(pocket_money) from t_r_compare;
R-> dt[!is.na(dt$pocket_money), median(pocket_money)]
-> 13
Interessanterweise bekommen beide Abfragen das gleiche Ergebnis obwohl SQL mit 7 und R mit 6 Datensätzen rechnet. Doch ist dem wirklich so? Kleines Update auf das unbekannte Taschengeld von Julia mit 0 und dann nachmals gerechnet.
SQL-> update t_r_compare set pocket_money = 0 where pocket_money is null;
R-> dt[is.na(dt$pocket_money), pocket_money := 0]
SQL-> select avg(pocket_money) from t_r_compare;
R-> dt[, mean(pocket_money)]
-> 14.42857
SQL-> select median(pocket_money) from t_r_compare;
R-> dt[, median(pocket_money)]
-> 12
Da bin ich reingerasselt. SQL hat niemals mit 7 Datensätzen gerechnet, sondern die Julia einfach aus der Berechnung ausgeschlossen, dies ohne Hinweis und dies ohne mir bewusst zu sein. Da gefällt mir das Verhalten von R doch irgendwie besser. Natürlich ist SQL ausgelegt, grosse Datenmengen ab Datenbanken auszuwerten und ein solches Verhalten ist sehr angenehm und kapselt Probleme von vielen BI Anwendern.
Doch es kam für mich noch dicker. Typische Fragestellung: Gibt mir die Anzahl Kinder, das gesamte -und das durchschnittliche Taschengeld aufgeteilt nach Geschlecht. Natürlich hat die Julia inzwischen wieder ihr unbekanntes Taschengeld.
SQL->
select gender
, count(*) as anzahl
, sum(pocket_money) as sum_pocket_money
, avg(pocket_money) as avg_pocket_money
from t_r_compare
group by gender;
gender | anzahl | sum_pocket_money | avg_pocket_money |
w | 4 | 48 | 16 |
m | 3 | 53 | 17.66667 |
R->
dt[, c(.(anzahl=.N),
.(sum_pocket_money=sum(pocket_money)),
.(avg_pocket_money=mean(pocket_money)))
, by=gender]
gender | anzahl | sum_pocket_money | avg_pocket_money |
w | 4 | NA | NA |
m | 3 | 53 | 17.66667 |
Wirklich überrascht hat mich dieses Verhalten nicht, mein eingesetzte laue Gefühl in der Magengegend verbessert es aber nicht.
Traue keiner Statistik, die du nicht selbst gefälscht hast.
Dieser Ausdruck wird oft Winston Churchill zugewiesen. An dieser Stelle möchte ich folgendes ergänzen: Ich traue auch den Statistiken nicht mehr, die ich selbst gefälscht habe. Jedenfalls trifft dies auf die SQL Statistiken zu. Um wieder bei der Mengenlehre anzugelangen. Gehört jetzt die Julia zur Menge Mädchen dazu oder nicht. Bei R gehört sie klar dazu und R zwingt uns zu überlegen, wie wir mit dem unbekannten Taschengeld umgehen sollen. Doch bei SQL bin ich mir nicht so sicher. Beim Zählen gehört sie dazu, doch wie ist es mit den anderen beiden Funktionen? Bei solchen Dingen geht Probieren über Studieren. Unbekanntes Taschengeld wieder auf 0 setzen.
SQL-> update t_r_compare set pocket_money = 0 where pocket_money is null;
R-> dt[is.na(dt$pocket_money), pocket_money := 0]
SQL->
select gender
, count(*) as anzahl
, sum(pocket_money) as sum_pocket_money
, avg(pocket_money) as avg_pocket_money
from t_r_compare
group by gender;
gender | anzahl | sum_pocket_money | avg_pocket_money |
w | 4 | 48 | 12 |
m | 3 | 53 | 17.66667 |
R-> dt[, c(.(anzahl=.N), .(sum_pocket_money=sum(pocket_money)), .(avg_pocket_money=mean(pocket_money))) , by=gender]
gender | anzahl | sum_pocket_money | avg_pocket_money |
w | 4 | 48 | 12 |
m | 3 | 53 | 17.66667 |
Uff, mathematisch identisch.
Fazit
Immerhin kommen beide Varianten auf das gleiche Resultat und die Frage von vorhin ist auch beantwortet. Vektoren und Mengenlehre hin oder her, die Julia gehört als armer Schlucker, wie im wirklichen Leben irgendwie dazu und dann aber doch wieder nicht. Bei genaueren nachdenken stellt sich die Frage, bei wie vielen Statistiken werden Leute wie Julia einfach unter den Teppich gekehrt?
Doch es ist nicht so, dass deswegen alle Statistiken falsch sind. Wenn die Datengrundlage vollständig ist gibt es kaum Spielraum für unterschiedliche Interpretationen, weder bei der Software noch beim Menschen. Doch ist dies tatsächlich immer der Fall? Bei wie vielen Statistiken sind die Rahmenbedingungen detailliert auf einer solchen Ebene aufgeführt und werden auch so wahrgenommen und interpretiert? Persönlich neige ich dazu Zahlen zu trauen und Statistiken als harte Facts zu interpretieren. Doch ist die Datengrundlage für solche Facts und die daraus getroffenen Massnahmen auch tatsächlich immer ausreichend und demzufolge richtig? Ein Blick auf die offiziellen COVID Datengrundlagen reicht aus, um herfür ein riesiges Fragezeichen über das Vorgehen und getroffenen Massnahmen zu setzen. Damit will ich hinweisen, dass mein Fragebogen beim Blutspenden deutliche mehr Informationen enthält als die offizielle Datengrundlage, welche ich bis anhin gesehen habe.
Zukünftig dürften Statistiken, welche auf Big Data und IoT Daten beruhen, noch grössere Datenlücken aufweisen, mit entsprechenden Spielraum für Interpretationen. Hoffen wir, dass die darauf getroffenen Entscheidungen immer noch die nötige Portion Gesunden Menschenverstand aufweisen.