-
Notifications
You must be signed in to change notification settings - Fork 0
/
Analisi.Rmd
2316 lines (1841 loc) · 109 KB
/
Analisi.Rmd
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---
title: "Kaggle Steam Datasets"
author: "Nicolò Rossi"
date: "08/08/2020"
output:
html_document:
df_print: paged
pdf_document: default
---
# Introduzione
In questo documento viene mostrata l'analisi di dati estratti dalla piattaforma per videogiochi "Steam" e di come sia possibile usarla
per realizzare un sistema per il consiglio di nuovi giochi da giocare.
I dataset utilizzati sono disponibili pubblicamente e sono stati ottenuti dal sito [Kaggle](https://www.kaggle.com).
Steam è la più grande piattafroma per giocare, pubblicare ed acquistare giochi per computer.
Attiva dal 2003, conta milioni di utenti attivi e migliaia di titoli disponibili. E' amministrata dalla Valve Corporation, società che
si occupa e si è occupata direttamente dello sviluppo di diversi celebri giochi.
Per rendere la lettura più interessante e per aggiungere profondità alle tematiche trattate, a volte inserirò dei commenti di carattere personale
o che esulano dagli argomenti principali analizzati nella relazione. Per evitare ambiguità, quando ciò accade, il testo viene inserito in una nota, in questo modo:
> Questa è una nota
## Dataset utilizzati
Sono considerati i seguenti due dataset:
* [Steam Store Games (Clean dataset)](https://www.kaggle.com/nikdavis/steam-store-games)
* [Steam Video Games](https://www.kaggle.com/tamber/steam-video-games)
contenenti rispettivamente le informazioni relative ai comportamenti di gioco e di acquisto di 200.000 videogiocatori e
quelle sui giochi disponibili sulla piattaforma Steam. Nel corso di questa relazione per semplicità citeremo il primo chiamandolo "dataset del 2019"
oppure "dei giochi" ed il secondo usando i nomi "200k" o "dataset dei giocatori".
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, fig.align='center', message = FALSE, warning = FALSE)
```
```{r, message=FALSE}
# lista delle librerie utilzzate
library("Rcpp")
library("sets")
library("rmarkdown")
library("circlize")
library('knitr')
library("plyr")
library('dplyr')
library('ggplot2')
library('igraph')
library("purrr")
library("ggraph")
library("tidyr")
library("tidygraph")
library("rlang")
library("netrankr")
library("corrplot")
library("lpSolve")
library("lpSolveAPI")
library("Rglpk")
# sorgenti extra
source("Utils.R")
sourceCpp("NetSimilarity.cpp")
sourceCpp("bitcorr.cpp")
```
# Dati "grezzi"
In questa sezione verranno presentati i dataset utilizzati per l'analisi dati, così come essi sono reperibili dal Web.
## Dati sui giocatori
Per prima cosa consideriamo il dataset riguardante 200.000 operazioni compiute da un campione di giocatori. Il dataset è stato caricato su Kaggle tre anni fa, ma avremo modo di individuare un intervallo più specifico per la sua creazione e prenderemo quindi in
considerazione il problema della "datazione" di questi dati.
Il dataset è inizialmente strutturato come segue:
```{r}
# lettura
players.data <- read.csv(
"./data/steam-200k.csv",
header=FALSE)[, -5]
colnames(players.data) <- c("player", "name", "activity", "time")
# rimozione punteggiatura
players.data <- clean.text(players.data, name, "[-.:™®'’]")
players.data
```
Il tempo viene espresso in ore di gioco ed ha significato solo quando associato all'attività "play".
I seguenti semplici conteggi ci consentono di comprendere la reale dimensione del campionamento effettuato per realizzare questa collezione di dati:
Numero di giocatori considerati:
```{r}
number.of.classes(players.data, player)
```
Numero di giochi giocati:
```{r}
number.of.classes(players.data, name)
```
Attività di acquisto:
```{r}
count.selected.lines(players.data, activity == "purchase" )
```
Attività di gioco:
```{r}
count.selected.lines(players.data, activity == "play" )
```
Questa ripartizione non equa delle attività di gioco e acquisto non è semplicemente dovuta al caso in quanto è invece particolarmente frequente acquistare giochi e non giocarli; è il cosiddetto "problema del backlog".
## Informazioni sui giochi
Il dataset con le informazioni aggiuntive sugli specifici giochi è più ricco e completo rispetto a quello precedente, che invece si limita a un campionamento su un relativamente piccolo numero di giocatori. In esso sono racchiuse tutte le informazioni riguardanti i giochi pubblicati sulla piattaforma Steam fino a Maggio 2019 e ad ogni gioco vengono associati i seguenti attributi:
* **appid**: identificativo unico del gioco, non essendo presente nell'altro dataset non ci è molto utile
* **name**: nome del gioco
* **release_date**: data di uscita
* **english**: se supporta la lingua inglese
* **developer**: casa sviluppatrice (a volte possono essere più di una, in quel caso le consideriamo come un unico team di sviluppo separato)
* **publisher**: chi ha pubblicato il gioco
* **platforms**: piattaforme su cui il gioco è disponibile (Windows, Mac e Linux)
* **required_age**: Indica l'età consigliata per il gioco (se pubblicata)
* **categories**: Lista delle categorie (indicate da Steam) che descrivono alcune caratteristiche di gioco
* **genres**: Lista che descrive la tipologia di gioco
* **steamspy_tags**: Ulteriori tag, più dettagliati
* **achievements**: Indica se il gioco supporta il sistema a obiettivi offerto da Steam (e con quanti)
* **positive_ratings**: Rating positivi per il gioco inviati dagli utenti
* **negative_ratings**: Rating negativi per il gioco inviati dagli utenti
* **average_playtime**: tempo medio di gioco
* **median_playtime**: tempo mediano di gioco
* **owners**: indica un intervallo in cui ricade il numero di persone che posseggono il gioco
* **price**: Prezzo di listino del prodotto
Il dataset offre quindi molte informazioni ma non tutti gli attributi risultano egualmente affidabili; in particolare si sono osservate delle discrepanze molto
significative nel calcolo dei tempi di gioco, per cui si è deciso di escludere questi dati dall'analisi e di utilizzare, quando possibile, le informazioni presenti
nel dataset dei giocatori, considerato in precedenza.
> Il sistema degli *achievements* è stato introdotto nel 2005 da Microsoft per la sua piattaforma Xbox Live e si basa sul proporre sfide con
diversa difficoltà che siano comparabili fra i diversi giochi presenti sulla stessa piattaforma. Lo scopo è quello di aumentare la longevità
dei giochi fornendo sfide che premino il giocatore quando completate e creino leaderboards virtuali per aumentare la competizione fra i
diversi giocatori. Dato il successo di questo sistema, in breve tempo diverse compagnie come Sony, EA, Valve e Ubisoft, hanno
introdotto delle loro versioni degli achievement sulle loro piattaforme con modalità molto simili a quelle proposte da Microsoft.
Il concetto di "Gamification" che viene ora proposto in molti ambiti esterni al modo dei videogiochi, come l'e-learning,
molto spesso presenta elementi analoghi a quelli degli achievement.
Questa è, ad esempio, una porzione di questo dataset:
```{r}
games.data <- read.csv(
"./data/steam.csv",
colClasses = c("numeric", "character", "character", "factor", "factor", "factor",
"character", "factor", "character", "character", "character", "factor",
"integer", "integer", "numeric", "numeric", "factor", "numeric"))
# semplifico i nomi per poter effettuare il join con l'altro dataset
games.data <- clean.text(games.data, name, "[-.:™®'’]")
games.data
```
Per un totale di:
```{r}
nrow(games.data)
```
giochi diversi.
## Rendere i dataset Tidy
Consideriamo dapprima il dataset dei giochi presenti su Steam. Per evitare un incremento sostanziale nella dimensione del dataset manteniamo temporaneamente
i campi lista ma li trasformiamo in vere e proprie liste R (anziché del testo spaziato da ";"). Questo ci consentirà di usare *dplyr* nelle fasi successive
in modo molto semplice per poter operare sui suddetti campi.
```{r}
# trasformo le liste separate da ; in liste R
games.data <- games.data %>% mutate(platforms = strsplit(platforms, ";") ) %>%
mutate(categories = strsplit(categories, ";") ) %>%
mutate(genres = strsplit(genres, ";") ) %>%
mutate(steamspy_tags = strsplit(steamspy_tags, ";") )
```
Interpretiamo correttamente il campo con la data di uscita considerandolo come una data R.
```{r}
# trasformo le date da testo a data
games.data <- games.data %>% mutate(release_date = as.Date(release_date) )
```
Per concludere separiamo il campo owners nel suo lower e upper bound, in modo da poterli usare per operazioni di filtering basate su numeri.
```{r}
# divido le info sul numero di giocatori in lower and upper bounds
games.data <- games.data %>% separate(owners, into=c("owners_lwb","owners_upb"), sep="-") %>%
mutate(owners_lwb = as.integer(owners_lwb)) %>%
mutate(owners_upb = as.integer(owners_upb))
games.data
```
### Separazione delle informazioni su gioco e acquisto
Consideriamo ora il dataset dei giocatori, per renderelo tidy lo suddividiamo in due dataset separati, uno per le operazioni di acquisto dei giochi e uno per le
attività di gioco:
```{r}
players.play <- players.data %>% filter(activity == "play") %>% select(-activity)
players.buy <- players.data %>% filter(activity == "purchase") %>% select(-activity, -time)
players.play
players.buy
```
# Analisi esplorativa
In questa sezione risponderemo ad alcune semplici domande che possiamo possiamo porci sui dataset considerati, in modo di poterli comprendere meglio per le analisi successive.
## Considerazioni sul tempo di gioco
In questa sottosezione consideriamo il dataset sulle attività di gioco dei giocatori, in particolare ci chiediamo quali siano i giochi più giocati nel campione analizzato e
come questi siano distribuiti.
Per prima cosa quindi ordiniamo i giochi per tempo assoluto e cumulativo di gioco:
```{r}
games.mostplayed <- players.play %>%
group_by(name) %>%
summarise(totalTime = sum(time)) %>%
arrange(desc(totalTime))
games.mostplayed
```
> Si noti che i primi quattro prodotti più giocati sono realizzati da Valve, la compagnia responsabile della piattaforma Steam. Questo è piuttosto significativo e mostra come una
piattaforma proprietaria consenta poi di incrementare l'utilizzo dei propri giochi nonostante questi non siano gli unici offerti. E'da considerare inoltre che nel 2018 Steam era
largamente la più grande piattaforma del mercato PC dei videogiochi, in grado di coinvolgere gran parte dell'utenza e degli sviluppatori.
Solo verso la fine dell'anno, con l'apertura di un primo store generalista veramente concorrente, l'Epic Games Store, si è venuta a creare della concorrenza nel
campo del publishing dei giochi PC, prima sostanzialmente monopolizzato da Valve in particolare per la sfera degli sviluppatori indipendenti.
Se da un lato la disponibilità di diverse piattaforme sia un vantaggio per il consumatore e gli sviluppatori dal punto di vista economico, questo crea delle
problematiche non indifferenti nella gestione degli, ormai estremamente diffusi, giochi multi-giocatore, nei quali possono crearsi situazioni di incompatibilità
tra versioni offerte da piattaforme differenti dello stesso gioco, suddividendo così la "userbase" (ossia i giocatori) in insiemi separati e più piccoli.
Questo fenomeno era classicamente invece limitato al mondo dei giochi su console proprio per la loro natura "chiusa", ossia completamente dettata dalla
casa produttrice. In ogni caso gli sviluppatori stanno agendo da diversi anni per mitigare il problema e sono sempre di più i giochi che supportano il
"cross-play" ossia la possibilità, per giocatori di piattaforme diverse, di giocare allo stesso gioco.
Osserviamo subito che molti giochi risultano quindi acquistati e non giocati:
```{r}
# giochi totali
number.of.classes(players.data, name)
# giochi acquistati
number.of.classes(players.buy, name)
# giochi giocati
number.of.classes(players.play, name)
# differenza
number.of.classes(players.buy, name) - number.of.classes(players.play, name)
```
Questo ci permette di verificare che effettivamente il dataset è consistente per la regola "giocato $\rightarrow$ acquistato", come sarebbe atteso.
Consideriamo quindi la distribuzione dei tempi di gioco:
```{r}
# distribuzione del tempo di gioco
temp <- games.mostplayed %>% factorise(totalTime, c(0,100,500,1000,2000,4000,16000,30000,Inf))
levels(temp$totalTime) = paste(c(0,100,500,1000,2000,4000,16000,30000), " a\n", c(100,500,1000,2000,4000,16000,30000,Inf) )
ggplot(temp, legend=FALSE) +
labs(title = "Conteggio dei giochi per tempo giocato") +
ylab("Conteggio") +
xlab("Tempo di gioco in ore") +
geom_bar(aes(x=totalTime, fill=totalTime)) +
theme(legend.position = "none")
# con scala logaritmica
figures.logScale.timeDistribution <- ggplot(temp, legend=FALSE) +
labs(title = "Conteggio dei giochi per tempo giocato (log scale)") +
ylab("Conteggio") +
xlab("Tempo di gioco in ore") +
geom_bar(aes(x=totalTime, fill=totalTime)) +
theme(legend.position = "none") +
scale_y_log10()
figures.logScale.timeDistribution
# concentransosi sull'intervallo 0-100
temp <- games.mostplayed %>% filter(totalTime<100) %>% factorise(totalTime, c(0,10,20,40,60,80,100))
levels(temp$totalTime) = paste(c(0,10,20,40,60,80), " a\n", c(10,20,40,60,80,100) )
ggplot(temp, legend=FALSE) +
labs(title = "Conteggio dei giochi per tempo giocato (intervallo 0-100)") +
ylab("Conteggio") +
xlab("Tempo di gioco in ore") +
geom_bar(aes(x=totalTime, fill=totalTime)) +
theme(legend.position = "none")
```
Viene naturale ora chiedersi se queste distribuzioni assolute si rispecchino passando al tempo medio di gioco.
### Numero di Giocatori
Calcoliamo quindi per prima cosa il numero di giocatori per ogni gioco e ne visualizziamo la distribuzione in modo similare al caso precedente:
```{r}
games.nplayers <- players.play %>%
group_by(name) %>%
summarise(totalPlayers = n()) %>%
arrange(desc(totalPlayers))
games.nplayers
```
Visualizziamo quindi i risultati:
```{r}
# distribuzione del numero di giocatori
temp <- games.nplayers %>% factorise(totalPlayers, c(0,5,10,20,50,100,200,500,1000,Inf))
levels(temp$totalPlayers) = paste(c(0,5,10,20,50,100,200,500,1000), " a\n", c(5,10,20,50,100,200,500,1000,Inf) )
figures.players.distribution <- ggplot(temp, legend=FALSE) +
labs(title = "Conteggio dei giochi per numero di giocatori") +
ylab("Conteggio") +
xlab("Numero di giocatori") +
geom_bar(aes(x=totalPlayers, fill=totalPlayers)) +
theme(legend.position = "none")
figures.players.distribution
```
Anche qui si nota la tendenza ad affermarsi di soli pochi giochi. Possiamo ora valutare il tempo medio di gioco.
## Tempo medio di gioco
```{r}
games.avgplaytime <- players.play %>%
group_by(name) %>%
summarise(avgPlayTime = mean(time), players = n() ) %>%
arrange(desc(avgPlayTime))
games.avgplaytime
```
Si può notare da questa lista che molti dei giochi con tempo medio più elevato sono giocati da pochi giocatori che si appassionano particolarmente a un gioco specifico. Per evitare di prendere in considerazione casi limite eccezionali (come "Eastside Hockey Manager"), filtriamo i risultati per accettare esclusivamente i giochi con almeno 5 giocatori.
```{r}
# con più di cento giocatori
temp <- games.avgplaytime %>% filter(players >= 5) %>%
factorise(avgPlayTime, c(0,5,10,20,40,60,100,200,300,Inf))
levels(temp$avgPlayTime) = paste(c(0,5,10,20,40,60,100,200,300), " a\n", c(5,10,20,40,60,100,200,300,Inf) )
figures.avgTime.distribution <- ggplot(temp, legend=FALSE) +
labs(title = "Conteggio dei giochi per tempo medio di gioco") +
ylab("Conteggio") +
xlab("Tempo medio di gioco in ore") +
geom_bar(aes(x=avgPlayTime, fill=avgPlayTime)) +
theme(legend.position = "none")
figures.avgTime.distribution
```
E' abbastanza probabile, anche se non certo, che i giochi nel range di ore da 0 a 5 siano stati in media provati e poi abbandonati dai giocatori.
Il calo drastico dopo le 40 ore probabilmente è dovuto al fatto che molti giochi a giocatore singolo terminano in meno tempo.
Concludiamo questa sezione valutando quanta correlazione ci sia tra i tempi medi di gioco e il numero di giocatori, per verificare se la selezione effettuata
sul dataset sia o meno risultata sensata.
```{r}
ggplot(games.avgplaytime, legend=FALSE) +
labs(title = "Giocatori vs Tempo Medio") +
ylab("Tempo medio di gioco") +
xlab("Numero di giocatori") +
geom_point(aes(y=avgPlayTime, x=players)) +
theme(legend.position = "none") +
scale_x_log10()+
scale_y_log10()
ggplot(games.avgplaytime, legend=FALSE) +
labs(title = "Giocatori vs Tempo Medio") +
ylab("Tempo medio di gioco") +
xlab("Numero di giocatori") +
geom_point(aes(y=avgPlayTime, x=players)) +
theme(legend.position = "none") +
scale_y_log10()
```
I grafici mostrano chiaramente come con l'aumento del numero di giocatori la variabilità rispetto al tempo medio di gioco risulti diminuita:
```{r}
# completo
var(games.avgplaytime$avgPlayTime)
mean(games.avgplaytime$avgPlayTime)
# >= 5
var((games.avgplaytime %>% filter(players >= 5))$avgPlayTime)
mean((games.avgplaytime %>% filter(players >= 5))$avgPlayTime)
# <= 5
var((games.avgplaytime %>% filter(players < 5))$avgPlayTime)
mean((games.avgplaytime %>% filter(players < 5))$avgPlayTime)
```
Questo conferma l'ipotesi precedente, concentrandosi sui giochi con più di dieci giocatori il trend risulta più chiaro.
```{r}
figures.players.avgTime.log <-
ggplot(games.avgplaytime %>% filter(players >= 10), legend=FALSE) +
labs(title = "Giocatori vs Tempo Medio") +
ylab("Tempo medio di gioco") +
xlab("Numero di giocatori") +
geom_point(aes(y=avgPlayTime, x=players)) +
theme(legend.position = "none") +
geom_smooth(aes(y=avgPlayTime, x=players),method = "lm", formula=y~x) +
scale_x_log10()+
scale_y_log10()
figures.players.avgTime.log
figures.players.avgTime <-
ggplot(games.avgplaytime %>% filter(players >= 10), legend=FALSE) +
labs(title = "Giocatori vs Tempo Medio") +
ylab("Tempo medio di gioco") +
xlab("Numero di giocatori") +
geom_point(aes(y=avgPlayTime, x=players)) +
theme(legend.position = "none") +
geom_smooth(aes(y=avgPlayTime, x=players))
figures.players.avgTime
```
mostrando quindi una tendenza all'aumento del tempo medio di gioco a seconda del numero di giocatori.
> Ritengo questo risultato particolarmente interessante, in quanto
si potrebbe effettivamente pensare che i giochi con più giocatori tendano ad attirare anche molte persone con scarso interesse che successivamente andrebbero ad
abbandonare il gioco, facendo diminuire così notevolmente la media. Potrebbe essere di interesse valutare se questo sia il trend anche nei giochi per dispositivi
mobili (android/iOS) dove il mercato e gli utenti sono solitamente molto diversi.
## Dati congiunti
Dopo questa prima fase di analisi esplorativa, possiamo arricchire le informazioni sui giochi acquistati e giocati con quelle delle caratteristiche dei singoli giochi.
Questo è possibile unendo le due tabelle per nome.
```{r}
# Giochi e tempo di gioco (se disponibile)
play.data <- na.omit(full_join(by=c("name"), players.play, games.data))
number.of.classes(players.play, name)
number.of.classes(play.data, name)
```
Questi numeri ci indicano che non stiamo considerando più di mille giochi che erano stati giocati dai giocatori presenti nel dataset 200k. Questa perdita di informazioni è dovuta in primo luogo al fatto che stiamo cercando innanzitutto di unire due tabelle su un campo "nome" testuale, cosa inevitabile dato che nel dataset 200k non viene riportato l'appid associato ai giochi (un identificativo unico rilasciato da Steam per ogni gioco pubblicato). Certi casi invece riguardano il ritiro di vecchie versioni dei giochi dal
commercio per far spazio a versioni rivisitate o migliorate (spesso chiamate remastered). Consideriamo due casi nello specifico, le due serie "Civilization" e "BioShock" per avere un'idea migliore della problematica:
```{r}
q <- "civilization"
# dataset dei giochi
string.query(games.data, name, q) %>% get.unique(name) %>% arrange(desc(name))
# dataset dei giocatori
string.query(players.play, name, q) %>% get.unique(name) %>% arrange(desc(name))
# dataset uniti
string.query(play.data, name, q) %>% get.unique(name) %>% arrange(desc(name))
q <- "bioshock"
# dataset dei giochi
string.query(games.data, name, q) %>% get.unique(name) %>% arrange(desc(name))
# dataset dei giocatori
string.query(players.play, name, q) %>% get.unique(name) %>% arrange(desc(name))
# dataset uniti
string.query(play.data, name, q) %>% get.unique(name) %>% arrange(desc(name))
```
Nel caso di "Civilization" osserviamo che "sid meier's civilization iv warlords" e "sid meier's civilization iv beyond the sword" erano presenti tra i giochi giocati ma non tra i giochi di Steam noti, questo probabilmente è dato dal fatto che queste due sono espansioni di "civilization iv" e dunque ora non sono più considerate un gioco separato; ovviamente è anche possibile che il dataset con i giochi di Steam del 2019 sia incompleto. "lost" e "idle civilization" sono giochi meno noti, e non sono stati giocati da nessuno dei
giocatori campionati. "Precivilization marble age" è presente solo come gioco giocato, a quanto sembra il suo nome è stato cambiato in solo "Marble age". In totale quindi non
è stato possibile aggiungere le ulteriori informazioni a tre diversi giochi. Il caso della serie "BioShock" è invece emblematico per la seconda problematica citata in precedenza: in questo caso si è passati da versioni "base" a versioni "remastered" dello stesso gioco, con conseguente cambio del nome.
Continueremo le analisi con questo dataset ridotto ma informativo, quantifichiamo ora la perdita di giochi e giocatori rispetto al dataset originale:
```{r}
# giocatori persi
number.of.classes(players.play, player) - number.of.classes(play.data, player)
# giochi persi
number.of.classes(players.play, name) - number.of.classes(play.data, name)
```
Il dataset rimane comunque abbastanza ricco per le successive analisi:
```{r}
# giocatori finali
number.of.classes(play.data, player)
# giochi finali
number.of.classes(play.data, name)
```
#### Datazione del dataset:
Individuare la problematica che riguarda i giochi "BioShock" ci permette di determinare un intervallo all'interno del quale i dati sono stati acquisiti, in quanto i
giochi originali sono stati completamente eliminati da Steam e sostituiti per tutti gli utenti dalle nuove versioni nel Settembre 2016.
```{r}
play.data %>% select(name, release_date) %>% unique() %>% arrange(desc(release_date))
```
come si può vedere, l'analisi sembra mostrarci diversi giochi che effettivamente risultano essere successivi al 2016, questa situazione è dovuta al fatto che molti giochi
approdano su Steam in "Early Access" ossia come giochi incompleti ma già acquistabili e giocabili, che successivamente verranno definitivamente pubblicati quando pronti.
Questo significa che spesso i giocatori giochino ad alcuni giochi prima della loro vera e propria uscita e, quando gli sviluppatori prendono alla lettera il concetto di *beta perpetua*, anche diversi anni prima. Si consideri il caso di "space engineers", è entrato in "Early Access" nel 2013 ma pubblicato solamente all'inizio del 2019.
Quindi è necessario cercare un videogioco che non abbia avuto questa fase di pre-lancio o che l'abbia avuta poco prima del settembre 2016. Scegliamo quindi il gioco
"out there somewhere" pubblicato su Steam nel 2016-03-14 e che non ha avuto una fase ad accesso anticipato in quanto porting per la piattaforma Steam di un
gioco realizzato nel 2012.
> Un porting per un gioco è semplicemente la sua riedizione su un'altra piattaforma.
Se non si è convinti che le considerazioni riguardanti la remasterizzazione di "BioShock" siano attendibili, si consideri che il gioco "Civilization VI" è uscito
nell'ottobre del 2016 e che non è stato individuato nessun giocatore nel campione analizzato che lo abbia giocato. Possiamo calcolare (una sottostima) di quale sia la
probabilità di un tale evento nel caso in cui il campionamento sia stato effettuato dopo l'uscita di questo gioco. Per farlo usiamo due dati non disponibili direttamente
sul dataset:
* Il massimo numero di utenti contemporaneamente giocanti a Civilization VI nel solo mese di Ottobre 2016 è stato di
[162.310](https://www.statista.com/statistics/980486/civilization-vi-number-players-steam/) utenti
(una sottostima degli utenti totali, considerando che in sole due settimane aveva superato il milione di copie vendute)
* Nel 2018 si sono registrati circa [90 Milioni](https://www.statista.com/statistics/733277/number-stream-dau-mau/) di giocatori attivi mensilmente, assumeremo che
i dati siano stati ottenuti da utenti attivi. Se così non fosse si stima che gli utenti Steam complessivi possano aver raggiunto il miliardo nel 2019, ma questo numero
è molto difficile da provare e racchiude certamente molti utenti "inesistenti".
```{r}
# numero di campioni
number.of.classes(players.play, player)
# probabilità di non estrazione (caso realistico)
(1-(162310/90000000))**number.of.classes(players.play, player)
# probabilità di non estrazione (caso estremo)
(1-(162310/1000000000))**number.of.classes(players.play, player)
# probabilità di non estrazione (caso estremo, con milione di copie vendute)
(1-(1000000/1000000000))**number.of.classes(players.play, player)
```
Il che conferma come sia molto difficile pensare che non aver individuato giocatori di "Civilization VI" possa essere dovuto al caso.
Possiamo concludere con una certa convinzione quindi che il dataset 200k risalga a più o meno la metà del 2016.
## Valutazione dei punteggi degli utenti
Per mostrare le possibilità del nuovo dataset creato, consideriamo ora la seguente query che ci mostra la frequenza dei voti positivi per
ogni gioco della serie "Sid Meier's Civilization" giocato nel dataset 200k:
```{r}
figures.civ.table <-
string.query(games.data, name, "sid meiers civilization") %>%
filter.by.tag.or(steamspy_tags, c("Strategy")) %>%
mutate(score = ifelse( positive_ratings > 100,
(positive_ratings)/(positive_ratings+negative_ratings), 0)) %>%
select(name,score) %>%
arrange(desc(score))
figures.civ.table
```
E' interessante notare come, dopo "Civilization V", i due seguiti "beyond earth" e "VI" risultino avere un punteggio decisamente più basso. Possiamo chiederci se
questi siano casi del cosiddetto fenomeno del "Review Bombing", per il quale un gioco viene bersagliato in modo sistematico da valutazioni negative da parte degli utenti.
Questo può accadere per molti motivi diversi: non raggiungimento delle aspettative, politiche aziendali non accettate dai fan, problemi dal punto di vista dell'implementazione
del gioco (per bug o ottimizzazione), eccetera ... . Per quanto questa pratica possa sembrare scorretta in quanto valuta il gioco in un determinato contesto e momento del suo
ciclo di vita, è in realtà una delle poche mosse da parte degli appassionati per poter far effettivamente sentire la loro opinione. La stampa specializzata che si occupa invece
della recensione sistematica dei giochi in uscita normalmente non è affetta da queste situazioni e valuta i diversi giochi con il proprio metro di giudizio normale.
Ovviamente, anche fra la stampa specializzata, possono esserci opinioni diverse nella valutazione dei giochi in quanto questi sono prodotti con una componente
artistica e dunque prettamente soggettiva, similmente a quanto accade normalmente ad esempio in ambito cinematografico.
Per questo motivo, per poter avere una valutazione più oggettiva dei prodotti che risenta in minor modo delle opinioni personali dei vari recensori
si è sviluppato il sito [*metacritic*](https://www.metacritic.com/), che ha lo scopo di raccogliere le recensioni ufficiali di diversi media,
tra i quali anche i videogiochi, e di compararle e mediarle. Il sito fornisce infine un numero per ogni prodotto, detto *metascore*, che
rappresenta la media delle valutazioni che ha ricevuto. Questa media viene pesata anche secondo l'autorità del recensore, associata principalmente
al numero di recensioni redatte. Si noti che il *metascore* non include alcuna valutazione da parte degli utenti, che invece sono trattate in
modo separato nel sito. (per altre informazioni, si consulti [questo link](https://www.metacritic.com/about-metascores)).
Per accedere a questi dati, consideriamo un altro dataset da Kaggle, [Metacritic all time games stats](https://www.kaggle.com/skateddu/metacritic-all-time-games-stats).
Si noti che solamente i giochi con un po' di rilievo riescono ad essere considerati dai recensori e così ad ottenere un *metascore*, quindi ci attendiamo l'assenza di questa
informazione per alcuni dei giochi considerati, in quel caso sarà necessario affidarsi solamente agli utenti.
```{r}
metacritic.data <- read.csv(
"./data/metacritic_games.csv")
metacritic.data
# la colonna user_score deve essere numerica
metacritic.data <- mutate(metacritic.data, user_score = as.numeric(gsub("\\.", "",user_score)))
# semplifico i nomi per poter effettuare il join con l'altro dataset
metacritic.data <- clean.text(metacritic.data, name, "[-.:™®'’]")
# considero solo la piattaforma PC e filtro alcune colonne non necessarie
metacritic.data <- metacritic.data %>% filter(platform == "PC") %>%
select(name, genre.s., players, rating, metascore, user_score, release_date,
critic_positive, critic_neutral, critic_negative, user_positive, user_neutral, user_negative) %>%
mutate(critic_total = critic_positive + critic_neutral + critic_negative, user_total = user_positive + user_neutral + user_negative) %>%
mutate(rating_metacritic = rating) %>% select(-rating)
# gestisco le date
Sys.setlocale("LC_TIME", "C")
metacritic.data <- metacritic.data %>% mutate(release_date = tolower(gsub(",", " ",release_date))) %>%
mutate(release_date = format(as.Date(strptime( release_date, "%b %d %Y")), "%d-%m-%y"))
metacritic.data
```
Soffermiamoci ad analizzare questo dataset prima di continuare rispondendo alla domanda che ci siamo posti.
Il numero di giochi recensiti è:
```{r}
number.of.classes(metacritic.data, name)
```
Il che ci indica come esistano giochi con lo stesso nome, di questi selezioniamo solo il più recente:
```{r}
metacritic.data <- metacritic.data %>% group_by(name, release_date) %>% arrange(name, release_date) %>% group_by(name) %>% slice(1)
```
Per prima cosa proviamo a vedere internamente al sito metacritic la correlazione tra voto degli utenti e dei recensori:
```{r, message=FALSE}
figures.userscore.vs.metascore <-
ggplot(na.omit(metacritic.data)) +
geom_point(aes(x=user_score, y=metascore, col=log(user_total), size=critic_total)) +
geom_smooth(aes(x=user_score, y=metascore), method = "lm", color = "green") +
geom_line(data = data.frame(x = seq(0,100), y = seq(0,100)), aes(x=x,y=y), color = "red")+
labs(title = "Userscore vs Metascore") +
ylab("Metascore") +
xlab("Userscore") +
geom_node_label( aes(x=user_score, y=metascore, label = name, filter = abs(user_score-metascore)>55), color = "black", size = 3, repel=TRUE )
figures.userscore.vs.metascore
```
Si noti che i punti al di sopra della retta rossa sono stati valutati meglio dalla critica che dagli utenti, viceversa per i punti al di sotto.
Visiualizziamo ora la distribuzione della differenza fra punteggi della critica e degli utenti:
```{r}
md.no.NA <- na.omit(metacritic.data)
md.no.NA.filtered.hi <- filter(md.no.NA, metascore >= 80)
md.no.NA.filtered.lo <- filter(md.no.NA, metascore <= 50)
ggplot(md.no.NA) +
geom_density(aes(x=user_score-metascore), col="red", linetype = "dashed") +
geom_area( data = data.frame(x=seq(-100,100,0.1), y=dnorm(seq(-100,100,0.1),
mean=mean(md.no.NA$user_score-md.no.NA$metascore),
sd=sd(md.no.NA$user_score-md.no.NA$metascore))),
aes(x=x,y=y), col="red", fill="red", alpha = 0.1) +
geom_density(data = md.no.NA.filtered.hi, aes(x=user_score-metascore), col="darkgreen", linetype = "dashed") +
geom_area( data = data.frame(x=seq(-100,100,0.1), y=dnorm(seq(-100,100,0.1),
mean=mean(md.no.NA.filtered.hi$user_score-md.no.NA.filtered.hi$metascore),
sd=sd(md.no.NA.filtered.hi$user_score-md.no.NA.filtered.hi$metascore))),
aes(x=x,y=y), col="darkgreen", fill="green", alpha = 0.1) +
geom_density(data = md.no.NA.filtered.lo, aes(x=user_score-metascore), col="blue", linetype = "dashed") +
geom_area(data = data.frame(x=seq(-100,100,0.1), y=dnorm(seq(-100,100,0.1),
mean=mean(md.no.NA.filtered.lo$user_score-md.no.NA.filtered.lo$metascore),
sd=sd(md.no.NA.filtered.lo$user_score-md.no.NA.filtered.lo$metascore))),
aes(x=x,y=y), col="blue", fill="blue", alpha = 0.1) +
labs(title = "Delta Userscore-Metascore") +
ylab("frequenze") +
xlab("delta")
```
Ove verde indica i giochi di maggior successo (metascore >= 80), Blu di minor successo (metascore <= 50), mentre le curve rosse sono associate all'intero dataset.
Per ogni selezione sono state mostrate distribuzione empirica e approssimata a gaussiana.
si osserva una certa tendenza per gli utenti a proporre voti lievemente più negativi rispetto alla critica, le situazioni si estremizzano
nel caso dei giochi con alto punteggio e si invertono con quelli con basso punteggio:
```{r}
# discrepanza media (tutto il dataset)
mean(md.no.NA$user_score-md.no.NA$metascore)
# discrepanza media (metascore >= 80)
mean(md.no.NA.filtered.hi$user_score-md.no.NA.filtered.hi$metascore)
# discrepanza media (metascore <= 50)
mean(md.no.NA.filtered.lo$user_score-md.no.NA.filtered.lo$metascore)
```
Si notano inoltre delle "gobbe" evidenti sulla coda sinistra della distribuzione empirica associata ai giochi con metascore molto positivo, probabilmente possono essere proprio
effetti legati al "Review bombing".
Questo ci mostra come i giochi con uno score basso siano valutati più positivamente dai critici che dagli utenti e vice-versa per i punteggi più alti, questo probabilmente
potrebbe essere dovuto al fatto che il voto degli utenti è meno ragionato (in media) e più basato sulle sensazioni dirette, quindi semplicemente se un gioco è piaciuto
avrà una valutazione alta, altrimenti una bassa.
Concentriamoci sui giochi della serie "Civilization":
```{r}
figures.civ.meta.vs.user <-
ggplot(na.omit(metacritic.data %>% string.query(name, "sid meiers civilization"))) +
geom_point(aes(x=user_score, y=metascore, col=log(user_total), size=critic_total)) +
geom_line(data = data.frame(x = seq(0,100), y = seq(0,100)), aes(x=x,y=y), color = "red") +
geom_node_label( aes(x=user_score, y=metascore, label = gsub("sid meiers civilization","",name)), color = "black", size = 3, repel=TRUE ) +
labs(title = "Userscore vs Metascore per la serie Civilization") +
ylab("Metascore") +
xlab("Userscore") +
coord_cartesian(xlim=c(50,100),ylim=c(50,100))
figures.civ.meta.vs.user
```
Come si può notare, il metascore della serie "Civilization" si attesta a valori piuttosto alti e consistenti, lo userscore invece spazia dall'insufficiente al molto buono.
Uniamo queste informazioni ai dati di gioco:
```{r}
played.civs <- md.no.NA %>% full_join(games.data %>% mutate(
name = unlist(map(name, ~ gsub("sid meiers civilization iii complete","sid meiers civilization iii",.)))),
by=c("name")) %>%
string.query(name, "sid meiers civilization") %>% na.omit()
played.civs
```
Confrontiamo ora le valutazioni degli utenti di Metacritic e Steam:
```{r}
figures.civ.steam.meta.users <-
ggplot(played.civs %>% mutate(steam_user_score = 100*(positive_ratings)/(positive_ratings+negative_ratings)) ) +
geom_point(aes(x=steam_user_score, y=user_score)) +
geom_line(data = data.frame(x = seq(0,100), y = seq(0,100)), aes(x=x,y=y), color = "red") +
geom_node_label( aes(x=steam_user_score, y=user_score,
label = gsub("sid meiers civilization","",name)), color = "black", size = 3, repel=TRUE ) +
labs(title = "Steam Userscore vs Metacritic Userscore per la serie Civilization") +
ylab("Voto medio degli utenti su Metacritic") +
xlab("% valutazioni positive su Steam") +
coord_cartesian(xlim=c(50,100),ylim=c(50,100))
figures.civ.steam.meta.users
```
Come si può vedere, su "Civilization VI" sembra esserci un discreto consenso. Ci si può chiedere come si comparino le distribuzioni dei punteggi basati su
Metascore, utenti Metacritic e utenti Steam.
```{r}
ggplot(md.no.NA %>% full_join(games.data,by=c("name")) %>% na.omit() %>%
mutate(steam_user_score = 100*(positive_ratings)/(positive_ratings+negative_ratings)) ) +
geom_density(aes(x=user_score), col="red", fill="red", linetype = "dashed", alpha=0.1) +
geom_density(aes(x=steam_user_score), col="blue", fill="blue", linetype = "dashed", alpha=0.1) +
geom_density(aes(x=metascore), col="darkgreen", fill="green", linetype = "dashed", alpha=0.1) +
labs(title = "Distribuzioni punteggi utenti Metacritic (rosso) Steam (blu) e Metascore (verde)") +
ylab("frequenze") +
xlab("voto/valutazione")
```
Si osserva come la percentuale di valutazioni di apprezzamento degli utenti di Steam non abbia una distribuzione simile a quella dei voti. Costruiamo un modello di regressione lineare generalizzato per per cercare di rendere voti e valutazioni direttamente comparabili.
```{r}
df <- md.no.NA %>% full_join(games.data,by=c("name")) %>% na.omit() %>%
mutate(steam_user_score = 100*(positive_ratings)/(positive_ratings+negative_ratings)) %>% select(name, user_score, steam_user_score)
figures.fit <-
ggplot( df ) +
geom_point(aes(x=steam_user_score, y=user_score)) +
geom_smooth(aes(y=user_score, x=steam_user_score), color="orange", method = "glm", formula = y~x) +
geom_smooth(aes(y=user_score, x=steam_user_score), color="black", method = "glm", formula = y~x+I(x^2)) +
geom_smooth(aes(y=user_score, x=steam_user_score), color="magenta", method = "glm", formula = y~x+I(x^2)+I(x^3))
figures.fit
l1 <- lm(user_score~steam_user_score , data = df)
l2 <- lm(user_score~steam_user_score + I(steam_user_score^2) , data = df)
l3 <- lm(user_score~steam_user_score + I(steam_user_score^2) + I(steam_user_score^3) , data = df)
AIC(l1,l2,l3)
BIC(l1,l2,l3)
summary(l1)
summary(l2)
summary(l3)
f1 <- function(x) l1$coefficients[1] + l1$coefficients[2]*x
f2 <- function(x) l2$coefficients[1] + l2$coefficients[2]*x + l2$coefficients[3]*(x**2)
f3 <- function(x) l3$coefficients[1] + l3$coefficients[2]*x + l3$coefficients[3]*(x**2) + l3$coefficients[4]*(x**3)
```
Il secondo modello (nero) risulta essere quello migliore secondo BIC, AIC, e significatività dei parametri. Il grafico seguente illustra le nuove distribuzioni ottenute adattando quella delle valutazioni prese da Steam.
```{r}
# funzione scelta sulla base dei risultati precedenti
positive.ratio.to.mark <- f2
figures.fit.result <-
ggplot( df ) +
geom_density(aes(x=steam_user_score), col="blue", fill="blue", linetype = "dashed", alpha=0.1) +
geom_density(aes(x=user_score), col="red", fill="red", linetype = "dashed", alpha=0.1) +
geom_density(aes(x=map_dbl(steam_user_score, f1)), col="orange", alpha=0.1) +
geom_density(aes(x=map_dbl(steam_user_score, f2)), col="black", alpha=0.1) +
geom_density(aes(x=map_dbl(steam_user_score, f3)), col="magenta", alpha=0.1) +
labs(title = "Adattamento valutazioni e voti") +
ylab("frequenze") +
xlab("voto/valutazione")
figures.fit.result
```
Queste nuove distribuzioni saranno utili successivamente per valutare in modo attendibile i giochi che non dispongono di un voto direttamente presente sul portale di Metacritic.
Osservare che le distribuzioni delle valutazioni degli utenti Metacritic e quelle del metascore siano molto simili è molto interessante.
Calcolare un punteggio a partire dal solo rapporto apprezzamenti/valutazioni sembra invece sovrastimare abbondantemente la valutazione.
Procediamo quindi infine a vedere dove si posizionano i giochi della serie "Civilization" rispetto alle distribuzioni empiriche calcolate
per i delta di valutazione. Includiamo in questa fase anche un caso noto e decisamente marcato di "review bombing", quello
del gioco "Star Wars Battlefront II":
```{r}
md.no.NA %>% string.query(name,"battlefront ii")
figures.swb2.civ <-
ggplot(md.no.NA) +
geom_density(aes(x=user_score-metascore), col="darkgreen", linetype = "dashed") +
geom_area( data = data.frame(x=seq(-100,100,0.1), y=dnorm(seq(-100,100,0.1),
mean=mean(md.no.NA$user_score-md.no.NA$metascore),
sd=sd(md.no.NA$user_score-md.no.NA$metascore))),
aes(x=x,y=y), col="darkgreen", fill="green", alpha = 0.1) +
geom_point(data = played.civs, aes(x=user_score-metascore,
y=dnorm(user_score-metascore,
mean=mean(md.no.NA$user_score-md.no.NA$metascore),
sd=sd(md.no.NA$user_score-md.no.NA$metascore)))) +
geom_node_text(data = played.civs, aes(x=user_score-metascore,
y=dnorm(user_score-metascore,
mean=mean(md.no.NA$user_score-md.no.NA$metascore),
sd=sd(md.no.NA$user_score-md.no.NA$metascore)),
label = toupper(gsub("sid meiers civilization","",name))), color = "black", size = 3, repel=TRUE ) +
geom_point(data = md.no.NA %>% string.query(name,"battlefront ii"), aes(x=user_score-metascore,
y=dnorm(user_score-metascore,
mean=mean(md.no.NA$user_score-md.no.NA$metascore),
sd=sd(md.no.NA$user_score-md.no.NA$metascore))), col="red") +
geom_node_text(data = md.no.NA %>% string.query(name,"battlefront ii"), aes(x=user_score-metascore,
y=dnorm(user_score-metascore,
mean=mean(md.no.NA$user_score-md.no.NA$metascore),
sd=sd(md.no.NA$user_score-md.no.NA$metascore)),
label = toupper(gsub("sid meiers civilization","",name))), color = "red", size = 3, repel=TRUE ) +
labs(title = "Delta Userscore vs Metascore per la serie Civilization") +
ylab("frequenze") +
xlab("delta")
figures.swb2.civ
```
Da questo grafico possiamo concludere che effettivamente "Civilization VI" ha ricevuto molte valutazioni negative da parte degli utenti, anche se il gioco della serie
che effettivamente ha subito il trattamento peggiore risulta essere "beyond earth". Calandoci nel contesto reale questo è effettivamente facilmente comprensibile:
"beyond earth" è uno spinoff della serie principale con una visione prettamente fantascientifica, ben diversa da quella degli altri titoli della serie. E' evidente
che un cambio così marcato non sia quindi stato apprezzato dagli appassionati che hanno valutato negativamente il prodotto. Concludiamo mostrando l'effetto
ancora più marcato avuto nel rapporto valutazioni positive/negative su Steam. (Non riportiamo dati su "Star Wars battlefront II" in quanto questo
è un gioco EA e non era disponibile su Steam).
```{r}
df <- md.no.NA %>% full_join(games.data,by=c("name")) %>% na.omit() %>%
mutate(steam_user_score = 100*(positive_ratings)/(positive_ratings+negative_ratings))
df.civ <- played.civs %>%
mutate(steam_user_score = 100*(positive_ratings)/(positive_ratings+negative_ratings))
ggplot( df ) +
geom_density(aes(x=steam_user_score-metascore), col="darkgreen", fill="green", linetype = "dashed", alpha=0.1) +
geom_area( data = data.frame(x=seq(-100,100,0.1),
y=dnorm(seq(-100,100,0.1),
mean=mean(df$steam_user_score-df$metascore),
sd=sd(df$steam_user_score-df$metascore))),
aes(x=x,y=y), col="darkgreen", fill="green", alpha = 0.1) +
geom_point(data = df.civ, aes(x=steam_user_score-metascore,
y=dnorm(steam_user_score-metascore,
mean=mean(df$steam_user_score-df$metascore),
sd=sd(df$steam_user_score-df$metascore)))) +
geom_node_text(data = df.civ, aes(x=steam_user_score-metascore,
y=dnorm(steam_user_score-metascore,
mean=mean(df$steam_user_score-df$metascore),
sd=sd(df$steam_user_score-df$metascore)),
label = toupper(gsub("sid meiers civilization","",name))), color = "black", size = 3, repel=TRUE ) +
labs(title = "Delta % valutazioni positive su Steam vs Metascore per la serie Civilization") +
ylab("frequenze") +
xlab("delta")
```
La distribuzione delle percentuali di valutazioni positive non è normale, qui viene comunque approssimata così per visualizzare più chiaramente la problematica
> "Star Wars Battlefront II" è stato ampliamente criticato dagli utenti in quanto il gioco proponeva una enorme quantità di "microtransazioni", in quel periodo
infatti EA (non da sola) stava spingendo per introdurre delle metodologie legate ai software come servizio
(SaaS) all'interno dei propri giochi, in quanto decisamente remunerative. Questo ha creato gradualmente una situazione di malcontento nei giocatori
che è letteralmente esplosa al lancio di questo gioco. Questa, per così dire, ribellione ha portato a un ridimensionamento notevole della
quantità di microtransazioni nei giochi successivi. Ovviamente la situazione è molto complessa e articolata e necessiterebbe senza dubbio di
una trattazione molto più esaustiva che esula dalle finalità di questa relazione.
### Nota sull'incompletezza unendo i dati di gioco con quelli di metacritic
Calcoliamo quanto impatta accettare solamente dati che abbiano disponibili tutti i campi offerti da Metacritic:
```{r}
# giocatori iniziali
number.of.classes(play.data, player)
# giochi iniziali
number.of.classes(play.data, name)
# delta giocatori finali
number.of.classes(play.data, player) - number.of.classes(play.data %>% full_join(md.no.NA,by=c("name")) %>% na.omit(), player)
# delta giochi finali
number.of.classes(play.data, name) - number.of.classes(play.data %>% full_join(md.no.NA,by=c("name")) %>% na.omit(), name)
```
Si può certamente fare di meglio tenendo conto che non tutti i giochi hanno abbastanza recensioni utente o specializzate da ottenere un metascore. Utilizzeremo
quindi queste informazioni solo quando sono effettivamente disponibili, nei restanti casi operiamo sui giochi e giocatori già selezionati e stimiamo le valutazioni utilizzando il modello presentato in precedenza.
## Reti delle associazioni gioco-giocatore
In questa sezione prenderemo in considerazione le reti descritte dalle relazioni di gioco e di acquisto,
che sono direttamente rappresentate dal dataset 200k.
```{r}
totalTime.game <- play.data %>%
group_by(name) %>%
summarise(totalGameTime = sum(time)) %>%
select(name, totalGameTime) %>% arrange(desc(totalGameTime))
totalTime.player <- play.data %>%
group_by(player) %>%
summarise(totalGameTime = sum(time)) %>%
select(player, totalGameTime) %>% arrange(desc(totalGameTime))
totalTime.game
totalTime.player
```
```{r}
games <- play.data %>% select(name) %>% arrange(name) %>% unique() %>% mutate(is_game = TRUE) %>%
inner_join(games.data, by=c("name")) %>% inner_join(totalTime.game, by=c("name")) %>%
arrange(name, release_date) %>% group_by(name) %>% slice(1)
players <- play.data %>% select(player) %>% arrange(player) %>% unique() %>%
mutate(is_game = FALSE) %>% mutate(name = player) %>% select(-player) %>%
full_join(totalTime.player %>% mutate(name=player) %>% select(-player), by=c("name")) %>%
mutate(name = paste("__",name,"__") )
play.relation <- play.data %>% mutate(player = paste("__",player,"__") ) %>%
mutate(from=name, to=player) %>%
semi_join(rbind.fill(games,players), by=c("name")) %>% select(from, to, time)
players.games.graph <- graph_from_data_frame(play.relation, vertices=rbind.fill(games,players), directed=FALSE) %>%
as_tbl_graph() %>%
mutate(centrality = centrality_authority())
players.games.graph
# verifico se il grafo è bipartito (come dovrebbe essere)
as.data.frame(get.edgelist(players.games.graph)) %>%
full_join(games %>% select(name,is_game),by=c("V1" = "name") ) %>%
full_join(players %>% select(name,is_game),by=c("V2" = "name") ) %>% filter(is_game.x == is_game.y)
# questo dataframe deve essere vuoto
```
```{r}
# considero un grafo più piccolo per la rappresentazione
players.games.graph.subgraph <-
to_subgraph(players.games.graph,
ifelse(is_game, totalGameTime > 15000, totalGameTime > 3000),
subset_by = "nodes")$subgraph
figures.game.player.graph <-
ggraph(players.games.graph.subgraph, layout = 'kk') +
geom_edge_link(aes(alpha=time), color="gray") +
geom_node_point(aes(col=is_game, size=centrality, alpha=ifelse(is_game,log(totalGameTime)+0.5,max(log(totalGameTime))) )) +
geom_node_text( aes( filter = centrality > 0.15, label = name), color = "black", size = 3, repel=TRUE ) +
labs(title = "Rete giochi (>15000 ore di gioco) giocatori (>4000 ore di gioco)",
color = "E' un gioco?", alpha="log(time)") +
scale_size(guide="none")
figures.game.player.graph
```
dove la dimensione dei nodi è data dalla centralità. Valutiamo quindi alcune delle proprietà di questo grafo. Iniziamo dalla distribuzione dei gradi:
```{r}
players.games.graph.degrees <- as.data.frame(degree(players.games.graph)) %>% mutate(name = names(degree(players.games.graph))) %>%
rename(degree = "degree(players.games.graph)") %>% join(as.data.frame(players.games.graph) %>% select(name,is_game), by=c("name"))
players.games.graph.degrees
figures.bara <-
ggplot(players.games.graph.degrees %>% factorise(degree, c(0,5,10,20,50,100,500,Inf))) +
geom_bar(aes(x=degree, fill=is_game), position = "dodge") +
labs(title = "Conteggio dei gradi dei nodi") +
ylab("Conteggio") +
xlab("Range del numero di vicini")
figures.bara
ggplot(players.games.graph.degrees) +
geom_histogram(aes(x=degree, fill=is_game), binwidth = 25, alpha=0.3, position="dodge") +
geom_density(aes(x=degree, y=..count..), linetype = "dotted") +
coord_cartesian(xlim=c(0,100)) +
labs(title = "Andamento della distribuzione dei gradi") +
ylab("Conteggio") +
xlab("Numero di vicini")
```
Questa distribuzione dei gradi segue quella di una distribuzione a coda lunga, possiamo quindi concludere che questo grafo segue il modello a *collegamento preferenziale* di
Barabasi-Albert. Questa ipotesi viene confermata anche dall'istogramma delle distanze tra i nodi:
```{r}
# la computazione è molto pesante
#df <- as.data.frame(as.table(players.games.graph %>% distances())) %>% filter(Freq != Inf & Freq != 0) %>% select(Freq)
#ggplot(df %>% factorise(Freq, c(0,1,2,3,4,5,10,Inf))) +
# geom_bar(aes(x=Freq)) +
# labs(title = "Valutazione delle distanze tra i nodi") +
# ylab("Conteggio") +
# xlab("Distanza")
```
Possiamo facilmente vedere, come intuibile, che i giochi sono più centrali, secondo il criterio di autorità, rispetto ai giocatori:
```{r}
players.games.graph %>% arrange(desc(centrality)) %>% select(name,centrality,is_game) %>% as.data.frame()
players.games.graph %>% filter(is_game == FALSE) %>% arrange(desc(centrality)) %>% select(name,centrality,is_game) %>% as.data.frame()
```
Ovviamente questo è dovuto principalmente al fatto che i giocatori sono molti di più dei giochi e che questo è un grafo bipartito, che ammette quindi solo relazioni
giochi-giocatori. Valutiamo ora la presenza o meno della classica *componente gigante* che viene a formarsi nei grafi non diretti a *collegamento preferenziale* come