Wie wir 20+ Jahre Tennisdaten analysiert und aufbereitet haben

a.k.a.: Making of 20 Jahre, 20 Titel.
Das ist der erste Beitrag aus einer Making-Of-Reihe, die wir bei
SRF Data in Zukunft schreiben möchten. Weil es sich hier nicht um ein investigatives Projekt handelt, liegt der Hauptfokus auf dem Projektablauf und der Frontend-Umsetzung. Künftige Making-Ofs werden sich wieder stärker auf die Analyse der Daten fokussieren. Den Code und eine Anleitung, um alles zu replizieren, findet ihr wie gewohnt auch auf https://srfdata.github.io/.

Wer schon einmal Tennis geschaut hat weiss: Da wird alles vermessen. Mittels Hawk Eye lässt sich jede noch so kleine Überschreitung der Linie nachweisen. Gekonnt glänzen die Moderatoren in allen Sprachen mit Statistiken über Siegesreihen und Prozentwerte. Da ist es doch bestimmt ein Leichtes, diesen Datenschatz journalistisch auszuwerten.

Oder etwa nicht…?

Leider nein.

FYI: Wer sich nicht für die Vorgehensweise, sondern lediglich für den genauen Code der Visualisierungen interessiert, wird bei uns auf Github fündig.

Die ATP, die Welttennisorganisation, die auch für die Ranglistenführung der Spieler zuständig ist, stellt auf ihrer Website einige Zahlen zur Verfügung.

Da kann man von jedem Match gewisse Dinge nachlesen wie:

  • Wer hat gewonnen?
  • Wer hat wie viele Aufschlagspiele gewonnen?
  • Wer hat wie viele Breakbälle abgewehrt?

Das ist schonmal ein Ausgangspunkt. Kurz ein kleines Tennis-Glossar:

Ein Ballwechsel = ein Punkt. Mehrere Punkte (geht von 15 bis 40) sind ein Game. Mehrere Games (i.d.R. bis einer 6 oder 7 davon hat) sind ein Satz. Mehrere Sätze (entweder Best of 3 oder Best of 5) ergeben ein Match.

Aber zurück zur Datenlage auf der ATP-Website: Die deckt die Bereiche Aufschlag / Return schon einigermassen ab, beantwortet aber auch nicht alle Fragen. Zum Beispiel:

  • In welchem Satz gab es wie viele Breaks? (das ist wenn ein Spieler, der aufschlägt, das Game nicht gewinnt)
  • Wer hat ein Break kassiert, danach aber trotzdem den Satz gewonnen?
  • Wer hat wie oft mit der Vorhand / Backhand gewonnen?

…und viele mehr. Es gibt zwar noch zusätzliches Datenmaterial zu Aufschlägen.

Dieses ist aber leider erst seit 2011 verfügbar und nur für die Masters-Turniere (so mittelwichtig) und die Tour Finals (recht wichtig) verfügbar. Grand Slams (die Wichtigsten) fehlen, was die Aussagekraft der Daten sehr einschränkt.

Wer diese oben erwähnten Daten nun gerne auswerten möchte, steht vor dem nächsten Problem: Die ATP bietet keine API und keine Daten in maschinenlesbarer Form an.

Danke nach Serbien

Zum Glück gibt es Ultimate Tennis Statistics von Mileta Cekovic. In hunderten Stunden Wochenendarbeit hat er sämtliche Informationen von der ATP Website (plus historische Daten von Jeff Sackmann) heruntergeladen und in einer Postgres-Datenbank organisiert. Seine Arbeit ist Open Source und auf Github verfügbar.

Ganz trivial war es nicht, die Postgres-Datenbank auf meinem eigenen Computer nachzubilden. Die Programmiersprache ist Java und gewisse Befehle im Package funktionieren nur auf Windows. Mit viel Hilfe aus Serbien, habe ich es aber letztendlich geschafft, eine genaue Anleitung, findet ihr hier.

Danke auch nach Amerika und Australien

Ein weiteres Dankeschön geht an den vorher erwähnten Guru der Tennisdaten: Jeff Sackmann. Er bietet neben historischen Tennisdaten auch Punkt-für-Punkt-Daten (also z.B. 0:15 Backhand Winner, 15:15 Ass,…) aus seinem Match Charting Project an. Ein Projekt, bei dem Freiwillige jede Sekunde eines Tennis-Spiels von Hand rapportieren. Weltweit die einzige mir bekannte Datenquelle für so detaillierte Daten.

Sackmann veröffentlicht seine Daten (dutzende von verschiedenen CSVs) gelegentlich in seinen Github-Repos. Nun könnte man sie entweder von dort herunterladen und wieder zusammensetzen oder aber man benutzt das Package deuce von Stephanie Kovalchik, das genau das für einen erledigt.

Konkurrenz-Analyse

Bevor man viel Zeit in die Datenanalyse recherchiert, sollte man unbedingt abklären, was die Konkurrenz auf dem Gebiet bereits gemacht hat. Dies aus zwei Gründen: Einerseits bewahrt es einen davor, etwas zu machen, das gar nicht neu ist und andererseits kommt man so auf Ideen, was man untersuchen könnte, und wie man es darstellen könnte.

Inspiration fanden wir z.B. hier, hier, hier, hier, hier und hier

Da die meisten Medienunternehmen sehr unterschiedliche Zielpublika haben (da sie oft unterschiedliche Sprachräume bedienen), ist es aus unserer Sicht sehr legitim, sich an guten Beispielen der Konkurrenz zu orientieren.

Vorprozessierung in R

Jetzt kommt der Teil, der richtig Spass macht. Innerhalb von R können wir ohne viel Aufwand mittels desRPostgreSQL Pakets auf die Datenbank zugreifen.

Bevor man irgend eine Zeile Code schreibt, sollte man sich stets fragen: Was möchte ich wissen? Was würde ich gerne beweisen? Was wären die interessantesten Thesen, die man einem Datencheck unterziehen möchte?

In unserem Fall hatten wir bereits einen relativ klaren Fokus: Wir wollten einen Rückblick werfen auf die Karriere von Roger Federer. Seit 20 Jahren ist er auf der Profi-Tour.

Unser Anspruch: Die Story soll auch verständlich und interessant sein für Tennis-Laien. Also nicht zu tief ins Detail gehen, sondern bei den Kenngrössen bleiben, die alle verstehen: Punkte, Siege, ATP-Rangliste. Bei der allgemeinen Vorrecherche lasen wir, die Karriere von Federer lasse sich grob in vier Phasen unterteilen: Sein Aufstieg, seine Zeit an der Spitze, sein Kampf gegen Nadal und Djokovic und sein Comeback letztes Jahr. Daraus leiteten wir folgende Fragen ab:

  1. Stimmt es, dass Federer älter war als andere Top-Spieler, als er richtig erfolgreich wurde?
  2. Wie gut spielt Federer unter Druck? Im Vergleich zu anderen Spielern aber auch im Verlauf seiner Karriere?
  3. Wie unbesiegbar war er während seinen stärksten Jahren?
  4. Warum verlor er so oft gegen Rafael Nadal? Gibt es sonst noch Spieler, gegen die Federer Mühe hat?
  5. Man sagt, sein Spiel sei mit steigendem Alter noch offensiver. Kann man das messen?
  6. Wie viele unerzwungene Fehler macht er? Hat sich das verändert?

Die Liste war natürlich noch einiges länger. Die Antworten auf die Fragen suchten wir in den Daten, hier eine Auswahl anggplots, die wir generiert haben:

An dieser Stelle nochmal der Hinweis auf unser Github-Repo, wo ihr den Code für die Visualisierungen findet, die es in den endgültigen Artikel schafften.

Diese Arbeitsweise erlaubte es uns, Thesen sehr rasch auf eine visuelle Art und Weise auf ihren Wahrheitsgehalt zu überprüfen und gleichzeitig ein Gefühl dafür zu entwickeln, welche Darstellungsform sich für eine Aufbereitung für den User eignen könnte.

Denn nicht alle Diagramme sind gleich nutzerfreundlich. (z.B. der sogenannte Violin-Plot in der dritten Reihe ev. eher nicht). Trotzdem sollte man in dieser Phase nicht auf komplexe Visualisierungen verzichten. Schliesslich muss man sich ein möglichst vollständiges Bild der Situation machen. Reduzieren kann man später.

Wir druckten alle Visualisierungen aus und versuchten sie in eine sinnvolle Reihenfolge zu bringen. Dabei hat es sich bewährt, dass wir bereits ungefähr wussten, welche Geschichte wir erzählen möchten. Aus über zwanzig Visualisierungen wurden zehn und diese wurden nochmal auf Herz und Nieren überprüft:

  • Ist der Inhalt interessant genug?
  • Kann man es noch weiter reduzieren, um die Kernaussage noch verständlicher zu machen?
  • Gibt es andere Darstellungsformen, die den Inhalt besser transportieren oder gibt es noch andere Daten, die dieselbe Aussage noch besser belegen?

Weiterverarbeitung in Sketch

Die Reduktion haben wir zum Teil in R mitggplotselbst vorgenommen oder aber die Grafiken als *.svg oder *.pdf exportiert und in Sketch importiert. Dort konnten wir die Datenpunkte als einzelne grafische Elemente bearbeiten und umgestalten. Daraus entstand unter anderem das:

Designs von Tania Boa

So haben sich unsere anfänglichen Visualisierungen zum Teil noch einmal stark gewandelt. Hier eine Auswahl von Charts in einer Vorher/Nachher-Gegenüberstellung:

Vorher/Nachher (draufklicken für eine grössere Ansicht)

Umsetzung mit react/d3

Da Roger Federer eine Person von internationalem Interesse ist, war uns klar, dass wir die Story auf möglichst viele Sprachen übersetzen möchten. Gleichzeitig mit SRF hat die SRG-Tochter Swissinfo die Story auf Italienisch, Französisch und Englisch veröffentlicht. Später kamen 6 weitere Sprachen dazu.

Hätte man die Grafiken direkt aus Sketch in die Publikation einbinden wollen (was für statische Grafiken und wenig animierte SVGs schon geht), hätte man die Grafiken also in 40-facher Ausführung speichern müssen: 
10 Sprachen × 4 Grössen (nämlich eine pro Responsive-Breakpoint).

Es liegt auf der Hand, dass das keine gute Lösung ist. Die Grafiken haben wir deshalb mit der Javascript-Library d3 anhand der Sketch-Designs nachgebaut. (Aus R exportierten wir mittels jsonlitefür jedes Diagramm eine einzelne JSON-Datei.)

Die übersetzten Text-Stücke hatten wir abgelegt in einem Google-Spreadsheet:

Hier konnten alle Übersetzer gleichzeitig ihre Textbausteine einfüllen

Um die Textbausteine anschliessend in die Applikation zu bringen, benutzten wir das Package gsheets, welches die Tabelle in ein JSON umwandelt:

Danke Jeremy Stucki für das praktische gsheets-Package

Was wir noch besser machen könnten

Das funktioniert sehr gut, aber weil das Arbeiten innerhalb Google Spreadsheets für die Autoren und Übersetzer nicht optimal ist, überlegen wir uns, in Zukunft umzusteigen aufArchieML, eine Struktursprache der New York Times.

Weiteres Verbesserungspotential sehen wir im Rendering der Single-Page-Application. Unser jetztiges Setup basiert zu 100% auf Javascript. Die HTML-Datei selber beinhaltet kein einziges Wort. Unterdessen können zwar auch Suchmaschinen-Crawler solche Javascript-Setups gut durchsuchen. Es wäre aber trotzdem eine Überlegung wert, ob wirklich der Browser des Besuchers die ganze Seite rendern muss, oder ob man das nicht in Webpack perstatic-render-html vorwegnehmen kann.

Das wäre auch gut gewesen für unsere Social-Media-Optimierung, die wir letztendlich sehr handgestrickt lösen mussten. Wenn man nämlich einen Link auf Facebook oder Twitter teilen möchte, dürfen die Meta-Informationen wieog:title undog:description nicht einfach inreact-helmet o.ä. im Client generiert werden, sondern müssen bereits vom Server korrekt versendet werden.

Als Notlösung haben wir deshalb einfach für jede Sprache ein einzelnes HTML-File abgelegt, das immer dieselbe Datei main.jseinbindet, aber einen angepassten<head>besitzt:

ein kleines Node-Script ersetzt die ___ Stellen durch die Übersetzungen und speichert ein File pro Sprache

Das ist aber wirklich nicht das Gelbe vom Ei. Tipps, wie wir das am elegantesten lösen können, sind willkommen 😊✌🏼


Du arbeitest ebenfalls in einem Datenjournalismus-Team? Wie sieht euer Technology-Stack aus? Wir sind immer an einem Austausch interessiert.