1. Introducció

Aquest conjunt de dades prove de la pàgina web Kaggle. L’objectiu del projecte es analitzar i entendre la relació entre la mida inicial d’un joc i la seva capacitat de créixer de manera sostinguda.

Es a dir, l’objectiu es respondre la pregunta que ens vam plantejar.

La pregunta que ens vam plantejar va ser la següent: Els jocs que presenten un creixement percentual (gain_percent) positiu i sostingut són generalment jocs que ja tenien una base de jugadors mitjana (avg_players) petita o gran a l’inici del període de creixement?

2. Dimensions del dataset

# dim(datos)
# glimpse(datos)

3. Descripció de les dades i preprocessament

variable tipus Descripció Valors possibles / Rang
month character Mes de mesura i any Format Mes-Any
avg_players numeric Base mitjana de jugadors 0 fins a milions
gain character Creixement de jugadors -∞ fins ∞
gain_percent numeric Creixement percentual -∞ fins ∞
peak_players integer Nombre màxim de jugadors 1 fins a milions
name character Nom del títol del joc 0–100 caràcters
steam_appid integer ID únic del joc 0 fins 1.000.000

Les variables que mes hem fet servir són les següents: avg_players->Mitjana de jugadors simultanis gain_percent->Percentatge de creixement o perdua dels jugadors respecte el mes anterior month->Temporalitat de les dades

Hem netejat la variable gain_percent i hem creat variables per categoritzar el tamany: Molt petit,petit,mitja,gran.

Hem fet tambe variables per dividir per estacions del any, també hem segmentat les dades en èpoques: Pre-Covid,Covid i Post-Covid.

# tibble(
#   variable = names(datos),
#   tipus = sapply(datos, class)
# )

4. Metodologia estadística utilitzada

Per analitzar les dades s’han aplicat les següents tècniques:

Anàlisi Exploratòria (EDA): Ús d’histogrames amb escala logarítmica i corbes de densitat per entendre la distribució de jugadors.

Test de Chi-quadrat d’Independència: Per determinar si existeix una relació entre la mida del joc (popularitat) i la seva tendència de creixement, així com la relació entre les estacions de l’any i el creixement.

Test de Kruskal-Wallis: Com a alternativa no paramètrica a l’ANOVA, utilitzat per comparar si el creixement (gain_percent) difereix significativament entre les tres èpoques definides (COVID-19).

df <- load("steamcharts.RData")

library(ggplot2)
library(dplyr)


str(datos)
## 'data.frame':    612265 obs. of  7 variables:
##  $ month       : chr  "Sep-25" "Aug-25" "Jul-25" "Jun-25" ...
##  $ avg_players : num  7805 6922 7371 8205 9053 ...
##  $ gain        : chr  "883.12" "-449.35" "-833.5" "-847.53" ...
##  $ gain_percent: num  0.1276 -0.061 -0.1016 -0.0936 -0.0495 ...
##  $ peak_players: int  13254 12168 13951 15798 15333 17727 18180 18934 20626 19006 ...
##  $ name        : chr  "Counter-Strike" "Counter-Strike" "Counter-Strike" "Counter-Strike" ...
##  $ steam_appid : int  10 10 10 10 10 10 10 10 10 10 ...
head(datos)
##    month avg_players    gain gain_percent peak_players           name
## 1 Sep-25     7805.25  883.12       0.1276        13254 Counter-Strike
## 2 Aug-25     6922.13 -449.35      -0.0610        12168 Counter-Strike
## 3 Jul-25     7371.48  -833.5      -0.1016        13951 Counter-Strike
## 4 Jun-25     8204.98 -847.53      -0.0936        15798 Counter-Strike
## 5 May-25     9052.51 -471.31      -0.0495        15333 Counter-Strike
## 6 Apr-25     9523.82 -849.53      -0.0819        17727 Counter-Strike
##   steam_appid
## 1          10
## 2          10
## 3          10
## 4          10
## 5          10
## 6          10
ggplot(datos, aes(x = avg_players, y = gain_percent)) +
  geom_point(alpha = 0.6) +
  labs(
    title = "Relació entre avg_players i gain_percent",
    x = "avg_players",
    y = "gain_percent"
  )

summary(datos[, c("avg_players", "gain_percent")])
##   avg_players         gain_percent       
##  Min.   :0.000e+00   Min.   :     -1.00  
##  1st Qu.:2.530e+00   1st Qu.:     -0.17  
##  Median :1.061e+01   Median :     -0.02  
##  Mean   :5.933e+02   Mean   :     14.71  
##  3rd Qu.:6.046e+01   3rd Qu.:      0.15  
##  Max.   :1.585e+06   Max.   :1622960.34
sd(datos$avg_players, na.rm = TRUE)
## [1] 11226.13
sd(datos$gain_percent, na.rm = TRUE)
## [1] 3085.757
library(ggplot2)

ggplot(datos, aes(avg_players)) +
  geom_histogram(bins = 50, fill = "steelblue", alpha = 0.7) +
  scale_x_continuous(trans = "log10") +
  labs(
    title = "Distribució de la base de jugadors (avg_players)",
    x = "avg_players (log scale)",
    y = "Freqüència"
  )
## Warning in scale_x_continuous(trans = "log10"): log-10 transformation
## introduced infinite values.
## Warning: Removed 1235 rows containing non-finite outside the scale range
## (`stat_bin()`).

ggplot(datos, aes(gain_percent)) +
  geom_histogram(bins = 50, fill = "darkgreen", alpha = 0.7) +
  xlim(-1, 1) +
  labs(
    title = "Distribució de gain_percent (limitat a [-1, 1])",
    x = "gain_percent",
    y = "Freqüència"
  )
## Warning: Removed 31305 rows containing non-finite outside the scale range
## (`stat_bin()`).
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_bar()`).

datos$size_group <- cut(
  datos$avg_players,
  breaks = c(0, 50, 500, 5000, Inf),
  labels = c("Molt petit", "Petit", "Mitjà", "Gran")
)

df_lim <- datos[datos$gain_percent >= -1 & datos$gain_percent <= 1, ]

ggplot(df_lim, aes(x = gain_percent, color = size_group)) +
  geom_density(linewidth = 1) +
  labs(
    title = "Distribució de 'gain_percent' per mida (Corbes de Densitat)",
    x = "Porcentatge de Guany",
    y = "Densitat",
    color = "Mida del joc"
  )

df_lim <- df_lim %>%
  mutate(
    Creixement_Binary = ifelse(gain_percent > 0, "Positiu", "Negatiu o Zero")
  )
  

desviacio_estandard_global <- sd(df_lim$avg_players, na.rm = TRUE)
print(paste("Desviació Estàndard de Avg Players (GLOBAL):", desviacio_estandard_global))
## [1] "Desviació Estàndard de Avg Players (GLOBAL): 11505.4099917134"
mitjana_global <- mean(df_lim$avg_players, na.rm = TRUE)
mediana_global <- median(df_lim$avg_players, na.rm = TRUE)

print(paste("Mitjana de Avg Players (GLOBAL):", mitjana_global))
## [1] "Mitjana de Avg Players (GLOBAL): 609.034417842881"
print(paste("Mediana de Avg Players (GLOBAL):", mediana_global))
## [1] "Mediana de Avg Players (GLOBAL): 10.73"
dim(datos)
## [1] 612265      8
glimpse(datos)
## Rows: 612,265
## Columns: 8
## $ month        <chr> "Sep-25", "Aug-25", "Jul-25", "Jun-25", "May-25", "Apr-25…
## $ avg_players  <dbl> 7805.25, 6922.13, 7371.48, 8204.98, 9052.51, 9523.82, 103…
## $ gain         <chr> "883.12", "-449.35", "-833.5", "-847.53", "-471.31", "-84…
## $ gain_percent <dbl> 0.1276, -0.0610, -0.1016, -0.0936, -0.0495, -0.0819, -0.1…
## $ peak_players <int> 13254, 12168, 13951, 15798, 15333, 17727, 18180, 18934, 2…
## $ name         <chr> "Counter-Strike", "Counter-Strike", "Counter-Strike", "Co…
## $ steam_appid  <int> 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 1…
## $ size_group   <fct> Gran, Gran, Gran, Gran, Gran, Gran, Gran, Gran, Gran, Gra…
library(tidyverse)

tibble(
  variable = names(datos),
  tipus = sapply(datos, class)
)
## # A tibble: 8 × 2
##   variable     tipus    
##   <chr>        <chr>    
## 1 month        character
## 2 avg_players  numeric  
## 3 gain         character
## 4 gain_percent numeric  
## 5 peak_players integer  
## 6 name         character
## 7 steam_appid  integer  
## 8 size_group   factor
summary(datos)
##     month            avg_players            gain            gain_percent       
##  Length:612265      Min.   :0.000e+00   Length:612265      Min.   :     -1.00  
##  Class :character   1st Qu.:2.530e+00   Class :character   1st Qu.:     -0.17  
##  Mode  :character   Median :1.061e+01   Mode  :character   Median :     -0.02  
##                     Mean   :5.933e+02                      Mean   :     14.71  
##                     3rd Qu.:6.046e+01                      3rd Qu.:      0.15  
##                     Max.   :1.585e+06                      Max.   :1622960.34  
##   peak_players         name            steam_appid          size_group    
##  Min.   :      0   Length:612265      Min.   :    10   Molt petit:443799  
##  1st Qu.:     10   Class :character   1st Qu.:221260   Petit     :120200  
##  Median :     32   Mode  :character   Median :336610   Mitjà     : 37526  
##  Mean   :   1193                      Mean   :346922   Gran      :  9505  
##  3rd Qu.:    162                      3rd Qu.:502800   NA's      :  1235  
##  Max.   :3236027                      Max.   :802870
library(dplyr)

df <- as.data.frame(datos)

df <- df %>%
  mutate(
    avg_players = as.numeric(avg_players),
    gain_percent = as.numeric(gsub("%", "", gain_percent)),
    popularity_category = cut(avg_players,
                              breaks = c(-Inf, 100, 1000, 10000, Inf),
                              labels = c("Low", "Medium", "High", "Very High")),
    trend_category = cut(gain_percent,
                         breaks = c(-Inf, 0, 0.05, Inf),
                         labels = c("Loss", "Stable", "Growth"))
  )

head(df)
##    month avg_players    gain gain_percent peak_players           name
## 1 Sep-25     7805.25  883.12       0.1276        13254 Counter-Strike
## 2 Aug-25     6922.13 -449.35      -0.0610        12168 Counter-Strike
## 3 Jul-25     7371.48  -833.5      -0.1016        13951 Counter-Strike
## 4 Jun-25     8204.98 -847.53      -0.0936        15798 Counter-Strike
## 5 May-25     9052.51 -471.31      -0.0495        15333 Counter-Strike
## 6 Apr-25     9523.82 -849.53      -0.0819        17727 Counter-Strike
##   steam_appid size_group popularity_category trend_category
## 1          10       Gran                High         Growth
## 2          10       Gran                High           Loss
## 3          10       Gran                High           Loss
## 4          10       Gran                High           Loss
## 5          10       Gran                High           Loss
## 6          10       Gran                High           Loss
taula_contingencia <- table(df$popularity_category, df$trend_category)
print(taula_contingencia)
##            
##               Loss Stable Growth
##   Low       273327  37747 181418
##   Medium     48718   9350  32465
##   High       12147   2822   8353
##   Very High   2965    847   2106
resultat_chi <- chisq.test(taula_contingencia)
print(resultat_chi)
## 
##  Pearson's Chi-squared test
## 
## data:  taula_contingencia
## X-squared = 1486.1, df = 6, p-value < 2.2e-16
p_valor <- resultat_chi$p.value
cat("El p-valor obtingut és:", p_valor, "\n")
## El p-valor obtingut és: 5.577774e-318
library(dplyr)
library(lubridate)

df$date <- my(df$month)
df$month_num <- month(df$date)

df <- df %>%
  mutate(season = case_when(
    month_num %in% c(12, 1, 2) ~ "Winter",
    month_num %in% c(3, 4, 5)  ~ "Spring",
    month_num %in% c(6, 7, 8)  ~ "Summer",
    month_num %in% c(9, 10, 11) ~ "Autumn"
  ))

df$season <- factor(df$season, levels = c("Winter", "Spring", "Summer", "Autumn"))

taula_estacions <- table(df$season, df$trend_category)
prop.table(taula_estacions, 1) * 100
##         
##               Loss    Stable    Growth
##   Winter 50.461819  8.949977 40.588204
##   Spring 59.756201  7.714418 32.529381
##   Summer 54.905236  8.014913 37.079851
##   Autumn 55.072330  8.508317 36.419353
library(ggplot2)

ggplot(df, aes(x = season, fill = trend_category)) +
  geom_bar(position = "fill") +
  labs(y = "Proporció", x = "Estació de l'any", title = "Tendència de jugadors per Estació") +
  scale_y_continuous(labels = scales::percent) +
  theme_minimal()

chi_season <- chisq.test(taula_estacions)
print(chi_season)
## 
##  Pearson's Chi-squared test
## 
## data:  taula_estacions
## X-squared = 2711.3, df = 6, p-value < 2.2e-16
Sys.setlocale("LC_TIME", "C")
## [1] "C"
library(dplyr)
library(ggplot2)

df <- df %>%
  mutate(
    data_aux = as.Date(paste0("01-", month), format = "%d-%b-%y"),
    era = case_when(
      data_aux < as.Date("2020-03-01") ~ "Pre-COVID",
      data_aux >= as.Date("2020-03-01") & data_aux <= as.Date("2021-12-01") ~ "COVID Peak",
      data_aux > as.Date("2021-12-01") ~ "Post-COVID"
    ),
    era = factor(era, levels = c("Pre-COVID", "COVID Peak", "Post-COVID"))
  )

resum_era <- df %>%
  group_by(era) %>%
  summarise(
    n_observacions = n(),
    mitjana_avg_players = mean(avg_players, na.rm = TRUE),
    mitjana_gain_percent = mean(gain_percent, na.rm = TRUE)
  )

print(resum_era)
## # A tibble: 3 × 4
##   era        n_observacions mitjana_avg_players mitjana_gain_percent
##   <fct>               <int>               <dbl>                <dbl>
## 1 Pre-COVID          314813                462.                18.9 
## 2 COVID Peak         106709                629.                20.7 
## 3 Post-COVID         190743                790.                 4.40
ggplot(df, aes(x = era, y = avg_players, fill = era)) +
  geom_boxplot() +
  scale_y_log10() +
  labs(
    title = "Distribució de Jugadors Mitjans per Època",
    subtitle = "Escala logarítmica per visualitzar jocs de totes les mides",
    y = "Log(Avg Players)",
    x = "Època"
  ) +
  theme_minimal()
## Warning in scale_y_log10(): log-10 transformation introduced infinite values.
## Warning: Removed 1235 rows containing non-finite outside the scale range
## (`stat_boxplot()`).

ggplot(df, aes(x = era, y = gain_percent, fill = era)) +
  geom_boxplot() +
  coord_cartesian(ylim = c(-0.5, 0.5)) +
  labs(
    title = "Taxa de Creixement (Gain Percent) per Època",
    y = "Gain Percent",
    x = "Època"
  ) +
  theme_minimal()

kruskal.test(gain_percent ~ era, data = df)
## 
##  Kruskal-Wallis rank sum test
## 
## data:  gain_percent by era
## Kruskal-Wallis chi-squared = 816.35, df = 2, p-value < 2.2e-16

En general podem veure que les dades de jugadors presenten una forta asimetria positiva, per aixo fem servir logaritmes i tests no paramètrics.

5. Comprovació de les assumpcions i possibles lineas futures

Interpretació i Conclusions

Impacte del COVID-19: El test de Kruskal-Wallis (p-valor < 2.2e-16) confirma que la pandèmia va ser un factor el qual va augmentar significativament el creixement de jugadors.

Estacionalitat: Es rebutja la hipòtesi nul·la d’independència; l’hivern i l’estiu mostren un creixement superior, probablement per les vacances i les rebaixes de Steam.

Mida: La popularitat està lligada a l’estabilitat. Un joc gran és menys probable que experimenti creixements o caigudes brusques en comparació amb un de petit.

Limitacions

Variables Externes: Podiem haver mirat mes factors com ara preu dels jocs genere entre altres els cuals podrien donar valors mes realistes.

Possibles Línies Futures

Estudiar si els jocs “Indie” tenen comportament diferent als “AAA”, mirar el creixement per generes, preus entre altres.