Videospiele-Reviews ausgewertet mit R

Ich habe mir mal die Mühe gemacht, eine etwas größere Anzahl an Videospiele-Reviews (etwa 40.000) über mehrere Konsolen hinweg auszuwerten. Die Daten liegen als CSV-Datei vor und enthalten mehrere Spalten: Name des Spiels, Release-Datum des Spiels, Plattform, Bewertung.

Hier ein Auszug:

Baja: Edge of Control,"Sep 22, 2008",playstation-3,61
Asphalt: Urban GT,"Nov 17, 2004",ds,60
NCAA March Madness 08,"Dec 11, 2007",xbox-360,69
GripShift,"Dec 12, 2007",xbox-360,72
[...]

Manche Spiele haben keine Bewertung, diese sind trotzdem aufgelistet (mit leerem Bewertungseintrag).

Wie viele Spiele erschienen unter welcher Plattform

In der Hoffnung, dass die Übersicht relativ vollständig ist, kann man sich zum Einstieg die Frage stellen, wie viele Spiele auf welcher Plattform erschienen sind.

Hierzu müssen wir lediglich ein Balkendiagramm über die Plattform ausgeben lassen. Dieses können wir mit ggplot auch noch ein wenig sortieren, in meinem Fall z.B. grob nach den Generationen der Konsolen (portable Konsolen und PC separat zum Schluss).

library('ggplot2')
 
t <- read.csv('data.csv')
 
m <- c('nintendo-64', 'playstation', 'gamecube', 'playstation-2', 'xbox', 'w    ii', 'playstation-3', 'xbox-360', 'wii-u', 'playstation-4', 'xbox-one', 'ds'    , '3ds', 'playstation-vita', 'pc')
 
t$system2 <- factor(t$system, levels=m)
ggplot(t, aes(factor(system2))) + geom_bar() + theme(axis.text.x = element_text(angle=90)) + xlab('Platforms') + ylab('No of reviews')
Anzahl der Spiele auf verschiedenen Konsolen

Anzahl der Spiele auf verschiedenen Konsolen

Die Playstation 2 scheint ihrerzeit eine enorme Vielfalt an Spielen gehabt zu haben. Zur nächsten Generation haben Nintendo und Microsoft hier ziemlich aufgeholt und in der aktuellen Generation sieht man bisher kaum Unterschiede (aber immer hat Sony die Nase etwas vorne).

Aus eigener Erfahrung gehe ich jedoch davon aus, dass hier bei manchen älteren Konsolen nicht alle Spiele aufgelistet sind. In meinen Daten sind für den Nintendo 64 lediglich 95 Spiele verzeichnet. Laut Wikipedia müsste es in Europa aber 254 offizielle Spiele geben:

Of the console’s 388 official releases, 84 are region-locked to Japan, 50 to North America, and 4 to Europe.

Welche Konsole hat die besten Bewertungen?

Nun nützt einem die größte Anzahl an Spielen nichts, wenn die Spiele dann entsprechend schlecht sind. Wie schneiden die Konsolen also durchschnittlich in etwa bei den Reviews ab?

Hierfür ist ein Whisker-Plot eine gute Wahl, da wir so den Median der Bewertungen auf jedem System sehen, aber auch die Verteilung der Bewertungen. Nachteil ist jedoch, dass Whisker-Plots üblicherweise den Durchschitt (mean) nicht beinhalten. Der Standard-Whisker-Plot lässt sich mit ggplot sehr leicht erstellen, aber den Durchschnitt müsste man manuell dazumalen. Ich habe vorerst nur den Standard-Whisker-Plot genommen.

library('ggplot2')
 
t <- read.csv('data.csv')
 
m <- c('nintendo-64', 'playstation', 'gamecube', 'playstation-2', 'xbox', 'w    ii', 'playstation-3', 'xbox-360', 'wii-u', 'playstation-4', 'xbox-one', 'ds'    , '3ds', 'playstation-vita', 'pc')
 
t$system2 <- factor(t$system, levels=m)
ggplot(t, aes(x=system2, y=rating)) + geom_boxplot() + theme(axis.text.x = element_text(angle=90)) + xlab('Platforms') + ylab('Rating')
Bewertungen der Spiele verschiedener Konsolen

Bewertungen der Spiele verschiedener Konsolen

Hier sehen wir z.B., dass die Median-Bewertung des Nintendo 64 wesentlich besser ist als die von Playstation. Insgesamt nahmen die Nintendo-Bewertungen aber bis zur Wii wesentlich ab und wurden erst mit der Wii-U wieder besser (ein für mich sehr interessanter Effekt, weil die Meinung zur Wii-U anfangs sehr skeptisch war). Seitdem Microsoft auf den Markt eingestiegen ist, liefern sich die Microsoft- und Sony-Konsolen ein heißes Rennen. Mal hatte die X-Box die besseren Bewertungen, mal die Playstation.

Was man aus einem Whisker-Plot auch ablesen kann – er enthält wirklich viel Information in einer kompakten Darstellung – ist der Maximal- und Minimalwert. Und zudem noch die Information, welche Werte bereits als Ausreißer gelten könnten (diese sind durch einzelne Punkte dargestellt). Den Maximalwert sehen wir als oberes Ende der Linie, die aus der Box hervorgeht. Demnach hatte ein Nintendo-64-Spiel die beste Spielebewertung von allen Konsolen.

Generelle Verteilung von Bewertungen

Der bisherigen Grafik nach scheinen die Bewertungen grob um den Wert 70 (von 100) herum orientiert. Allerdings kann man das noch nicht so genau ablesen. Hierzu brauchen wir ein weiteres Diagramm. Auch das ist wieder sehr einfach, entweder als Balkendiagramm oder als Dichtediagram (density plot).

library('ggplot2')
 
t <- read.csv('data.csv')
 
g_tmp <- ggplot(t, aes(rating)) + xlim(0, 100) + xlab('Rating')
 
g_tmp + geom_density()
g_tmp + geom_histogram(binwidth=1)
Histogramm der Bewertungen von Videospielen

Histogramm der Bewertungen von Videospielen

Hier sieht man, dass die meisten Spiele mit 70 Punkten bewertet werden. Wenn man sich solche Histogramme ansieht, ist man oft jedoch nicht am tatsächlichen Höhepunkt (bei 70 Punkten) interessiert, sondern an dem Wert, der bei einer glatten Wahrscheinlichkeitsverteilung der Höchstwert wäre (Modus). In diesem Fall wäre der Modus bei etwa 72 oder 73 Punkten.

Die schlechteste Bewertung eines Spiels liegt bei 8 Punkten, die beste bei 99. Das sieht man in dieser Grafik relativ schlecht, kann man aber in R mit summary(t) (t ist die eingelesene Tabelle) sehr schnell einsehen.

Veränderung von Bewertungen über die Zeit

Nun hat man ja manchmal den Eindruck, wir seien eine kritikunfähige Gesellschaft geworden. Sehen wir uns also mal an, wie die Bewertungen sich über die Zeit verändert haben. Sind diese im Durchschnitt eventuell immer besser geworden?

Aufgrund des obigen Datumsformats erkennt R dies nicht automatisch als Datum. Das kann man jedoch korrigieren:

t$game_release <- as.Date(t$game_release, "%b %d, %Y")

Um genügend Daten pro Gruppe zu haben, gruppiere ich außerdem nur nach Jahren. Der Rest ist ähnlich wie bereits zuvor, auch hier eignet sich wieder ein Whisker-Plot.

library('ggplot2')
 
t <- read.csv('data.csv')
t$game_release <- as.Date(t$game_release, "%b %d, %Y")
t$year <- format(t$game_release, "%Y")
 
ggplot(t, aes(x=year, y=rating)) + geom_boxplot() + theme(axis.text.x = element_text(angle=90)) + xlab('Year') + ylab('Rating')
Bewertung von Videospielen nach Jahren

Bewertung von Videospielen nach Jahren

Auffällig ist, dass die Bewertungen sprunghaft schlechter geworden sind vom Jahr 1999 auf das Jahr 2000. Es wäre möglich, dass sich hier etwas am Datenstand geändert hat. Möglicherweise sind bis 1999 nur sehr gut Spiele in der Datenbank erfasst worden. Ab 2000 sind die Daten recht kontinuierlich, sodass man diese erstmal vergleichen kann. Hier sieht man, dass die Bewertungen von 2006-2009 etwas schlechter waren und danach wieder besser wurden. Nur ist jetzt noch offen, ob dies an den Bewertenden liegt und auf ein gesellschaftliches Phänomen zurückzuführen ist oder ob die Spieleindustrie in diesen Jahren etwas schlechter war.

Wie Sportwettquoten funktionieren und wie man damit rechnet

Da ich heute darüber nachdachte, algorithmisch an Sportwetten teilzunehmen, habe ich ein wenig mit den Quoten herumgerechnet.

Schauen wir zunächst in echte Daten

Zuerst einmal muss man sich überlegen, was eine Quote bedeutet und wie sie entsteht bzw. wie das Wettbüro damit Geld verdient. Im Gegensatz zu anderen Artikeln im Internet bin ich mir dabei nicht so sicher, ob ein Wettbüro wirklich immer selbst Gewinnvorhersagen für die einzelnen Spiele treffen muss. Es lohnt sich nämlich ebenso, die Verteilung der Wetteinsätze der Spieler exakt zu treffen. Trifft das Wettbüro diese genau, ist der tatsächliche Ausgang des Spiels vollkommen egal.

Bei Tipico kann man neben den Quoten auch die Wettverteilung der Spieler ansehen. Rechnen wir einfach mal damit, um diese Überlegung zu prüfen.

Die 5 obersten Highlights, als ich tipico betrachtete
Begegnung                            Quoten           Wettverteilung  Quoten in % *
England (F) - Kanada (F)             3,4   3,0  2,3   26%  30%  44%   28%  31%  41%
IBV Vestmannaeyjar - Breidablik Kop. 5,0   3,7  1,62  10%  13%  77%   18%  25%  57%
Palmeiras SP - Sao Paulo SP          2,35  3,1  3,0   26%  27%  47%   39%  30%  31%
Fjölnir Reykjavik - FH Hafnarfjördur 4,3   3,5  1,75  16%  22%  62%   21%  26%  52%
Chile - Peru                         1,5   4,0  8,0   79%  14%   7%   64%  24%  12%

* von mir berechnet

Wie ich die Quoten in % berechne, sehen wir gleich noch. Diese spiegeln jedoch (unter bestimmten Annahmen) die Wahrscheinlichkeit wider, die tipico als Grundlage zur Berechnung ihrer Quoten benutzt.

Auffällig ist auf jeden Fall schon einmal, dass gerade bei sehr einseitigen Quoten sehr viel mehr Spieler auf den vermeintlich sicheren Sieger setzen, als dies von der Quote erwartet würde oder gedeckt wäre. Bei ausgeglicheneren Partien liegen die Quoten in der Tat sehr nahe an der Wettverteilung. Ich vermute allerdings hinter den angezeigten Zahlen von tipico die Anzahl an Spielern, nicht die Menge an gesetztem Geld. Geht man davon aus, dass gerade Gelegenheitsspieler mit geringen Beträgen auf die sehr starken Mannschaften setzen (z.B. der Gelegenheitsspieler, der mal auf Bayern München setzt), wären die gesetzten Summen wieder näher an den von mir errechneten Prozenten.

Die Berechnung von Quoten

Schauen wir uns aber mal an, wie Quoten entstehen und warum es für ein Wettbüro auch Sinn machen kann, die Wettverteilung der Spieler exakt zu treffen. Wie bin ich also von den Quoten auf die Quoten in % gekommen? Dazu erkläre ich die Gegenrichtung. Angenommen wir sind ein Wettbüro und nehmen an, dass entweder

  1. das Spiel mit den Wahrscheinlichkeiten 50% Sieg für A, 30% unentschieden und 20% Sieg für B ausgeht, oder
  2. die Wetteinsätze ebendiese Wettverteilung beim Tippen haben werden.

Gerade durch zweitere Variante lässt sich die Sache ein wenig anschaulicher erklären. In der Wahrscheinlichkeitstheorie rechnen wir ansonsten ja immer mit Erwartungswerten. Also nehmen wir mal an die Spieler tippen genauso, wie wir unsere Wahrscheinlichkeiten berechnet haben. Nämlich 50% des Geldes wird auf Sieg für A gesetzt, 30% auf unentschieden und 20% auf Sieg für B.

Insgesamt wurden 10.000 Euro gesetzt. Das macht also 5.000, 3.000 und 2.000 auf die drei verschiedenen Ergebnisse.

Nehmen wir an, A gewinnt. Wieviel könnte das Wettbüro also ohne Verlust an die Gewinner auszahlen? Tippgruppe 2 und 3 haben insgesamt 5000 Euro verloren, also kann das Wettbüro (ohne Verlust, aber auch ohne Gewinn) 5000 Euro auszahlen. Da auf einen Sieg für A insgesamt 5000 Euro eingezahlt wurden, entspräche das einer Quote von 2,0.

Rechnet man Selbiges für Tippgruppe 2 und Tippgruppe 3 aus, kommt man auf Quoten von 3,333 und 5.

Mathematisch kann man die Quoten auch als den Kehrwert der Wahrscheinlichkeit berechnen, also , wobei q die Quote und p die Wahrscheinlichkeit ist.

Der Vorteil für das Wettbüro: Der Gebührenfaktor g

Jetzt ist es natürlich so, dass in unserem Beispiel der Wettbürobetreiber keinerlei Gewinn macht – zumindest nicht, solange er nicht besser tippt als die Spieler. Dies ist keine erstrebenswerte Situation für ein Wettbüro. Deshalb verringert das Wettbüro die Quoten um einen gewissen Anteil, den ich im Moment aus meiner Sicht als Spieler “Wettgebühren” nenne. Bei tipico scheint dieser ein wenig variabel zu sein, bei egamingbets lag er ziemlich genau um die 10%.

Damit ergibt sich eine angepasste Quote , die der Wettbürobetreiber dem Spieler bietet: , wobei g den Faktor angibt, der die Gebühren für das Wetten einberechnet. Will das Wettbüro 10% Gebühren festlegen, wäre g = 0,9.

Umgekehrt können wir mit obigen Zahlen einmal durchrechnen, dass das Wettbüro in unserem vereinfachten Beispielfall nun tatsächlich 10% an Gebühren einnimmt. Dazu rechnen wir zunächst die angepassten Quoten aus: q’_1 = 1,8; q’_2 = 3; q’_3 = 4,5.

Nehmen wir nun einmal an, unsere Tippgruppe 1 gewinnt. Es wurden 5000 Euro gesetzt, die übrigen beiden Gruppen hatten ebenso 5000 Euro gesetzt und verloren. Durch die Quote von 1,8 zahlt der Betreiber den Gewinnern nun insgesamt 5000 * 1.8 = 9000 Euro aus, ihm selbst bleiben 1000 Euro. Das sind genau 10% des Gesamteinsatzes.

Aufgrund dieses Faktors g macht es für den Wettbürobetreiber durchaus auch Sinn, auf die Wettverteilung der Spieler zu setzen.

Kleiner Zusatz zum Rechnen

Interessant für mich war eigentlich noch eine andere Rechnung. Ich wollte für einige Portale wissen, wie die Gebühren aussehen – ich spreche immer von Gebühren, da ich es wie Transaktionsgebühren bei Paypal sehe, nur muss ich sie mir eben selbst ausrechnen. Die Höhe der Gebühren eines Wettanbieters ist wichtig um zu errechnen, welche Tippquote ich richtig haben müsste, um überhaupt Gewinn zu erwirtschaften.

Startet man mit dem Wissen (also die Summe aller Wahrscheinlichkeiten auf einem Ereignis muss exakt 1 sein), kommt man durch unsere obigen Überlegungen zur angepassten Quote darauf, dass ebenso gelten muss .

Bringt man das Ganze auf einen Hauptnenner, kommt man auf:

Das kann man noch umstellen und dann bequem den Faktor g berechnen. Voraussetzung hierfür ist allerdings, dass der Betreiber auf alle Quoten einer Partie denselben Faktor anwendet.

150.000 SQLite-INSERTs beschleunigen

Bei Sprakit hatte ich das Problem, dass ich 150.000 Wikipedia-Artikel in eine SQLite-Datenbank eintragen musste (für Kroatisch, d.h. für Englisch oder Deutsch noch mehr). Mit einer normalen SQLite-Datenbank auf der Festplatte dauerte das mind. 1h – etwa so lange war ich weg und das Skript war immer noch nicht fertig.

Ein Ex-Kommilitone erklärte mir, dass ich die Datenbank in ein ramfs schieben solle, dort die Änderungen machen, und dann zurückschreiben – weil SQLite bei jeder Änderung auf die Festplatte zugreift, was natürlich langsam ist.

Ich habe kurzerhand mein Skript angepasst:

# Copy SQLite DB to RAM, so that insert is faster                       
tmpfile = tempfile.NamedTemporaryFile()                                 
shutil.copy2(settings['sqlite'], tmpfile.name)                          
 
sqlite_conn = sqlite3.connect(tmpfile.name)
 
# do some real work
 
shutil.copy2(tmpfile.name, settings['sqlite'])

Bin aus dem Zimmer und das Skript war fertig… Innerhalb von etwa 1-2 Minuten.

Ich habe absichtlich tempfile.NamedTemporaryFile() verwendet, um das Skript plattformunabhängig zu halten. Natürlich ist es damit nur auf Systemen schnell, auf denen der temporäre Ordner, den Python verwendet (bei mir /tmp) im RAM liegt.