1. Cargar Paquetes

Primero, cargamos los paquetes necesarios para nuestro análisis.

library(tidyverse)
library(knitr)

2. Carga y Exploración Inicial de Datos

El primer paso de cualquier análisis es cargar los datos y entender su estructura.

Carga de datos

# Lee los datos desde la carpeta 'data' en la raíz del proyecto
sales_data <- read.csv(here::here("data", "vgsales.csv"))

Primeras filas del conjunto de datos

head(sales_data)
##   Rank                     Name Platform Year        Genre Publisher NA_Sales
## 1    1               Wii Sports      Wii 2006       Sports  Nintendo    41.49
## 2    2        Super Mario Bros.      NES 1985     Platform  Nintendo    29.08
## 3    3           Mario Kart Wii      Wii 2008       Racing  Nintendo    15.85
## 4    4        Wii Sports Resort      Wii 2009       Sports  Nintendo    15.75
## 5    5 Pokemon Red/Pokemon Blue       GB 1996 Role-Playing  Nintendo    11.27
## 6    6                   Tetris       GB 1989       Puzzle  Nintendo    23.20
##   EU_Sales JP_Sales Other_Sales Global_Sales
## 1    29.02     3.77        8.46        82.74
## 2     3.58     6.81        0.77        40.24
## 3    12.88     3.79        3.31        35.82
## 4    11.01     3.28        2.96        33.00
## 5     8.89    10.22        1.00        31.37
## 6     2.26     4.22        0.58        30.26

Resumen Estadístico del Conjunto de Datos

summary(sales_data)
##       Rank           Name             Platform             Year          
##  Min.   :    1   Length:16598       Length:16598       Length:16598      
##  1st Qu.: 4151   Class :character   Class :character   Class :character  
##  Median : 8300   Mode  :character   Mode  :character   Mode  :character  
##  Mean   : 8301                                                           
##  3rd Qu.:12450                                                           
##  Max.   :16600                                                           
##     Genre            Publisher            NA_Sales          EU_Sales      
##  Length:16598       Length:16598       Min.   : 0.0000   Min.   : 0.0000  
##  Class :character   Class :character   1st Qu.: 0.0000   1st Qu.: 0.0000  
##  Mode  :character   Mode  :character   Median : 0.0800   Median : 0.0200  
##                                        Mean   : 0.2647   Mean   : 0.1467  
##                                        3rd Qu.: 0.2400   3rd Qu.: 0.1100  
##                                        Max.   :41.4900   Max.   :29.0200  
##     JP_Sales         Other_Sales        Global_Sales    
##  Min.   : 0.00000   Min.   : 0.00000   Min.   : 0.0100  
##  1st Qu.: 0.00000   1st Qu.: 0.00000   1st Qu.: 0.0600  
##  Median : 0.00000   Median : 0.01000   Median : 0.1700  
##  Mean   : 0.07778   Mean   : 0.04806   Mean   : 0.5374  
##  3rd Qu.: 0.04000   3rd Qu.: 0.04000   3rd Qu.: 0.4700  
##  Max.   :10.22000   Max.   :10.57000   Max.   :82.7400

Estructura del Conjunto de Datos

str(sales_data)
## 'data.frame':    16598 obs. of  11 variables:
##  $ Rank        : int  1 2 3 4 5 6 7 8 9 10 ...
##  $ Name        : chr  "Wii Sports" "Super Mario Bros." "Mario Kart Wii" "Wii Sports Resort" ...
##  $ Platform    : chr  "Wii" "NES" "Wii" "Wii" ...
##  $ Year        : chr  "2006" "1985" "2008" "2009" ...
##  $ Genre       : chr  "Sports" "Platform" "Racing" "Sports" ...
##  $ Publisher   : chr  "Nintendo" "Nintendo" "Nintendo" "Nintendo" ...
##  $ NA_Sales    : num  41.5 29.1 15.8 15.8 11.3 ...
##  $ EU_Sales    : num  29.02 3.58 12.88 11.01 8.89 ...
##  $ JP_Sales    : num  3.77 6.81 3.79 3.28 10.22 ...
##  $ Other_Sales : num  8.46 0.77 3.31 2.96 1 0.58 2.9 2.85 2.26 0.47 ...
##  $ Global_Sales: num  82.7 40.2 35.8 33 31.4 ...

Conclusiones de la Exploración Inicial

Al ver la salida de summary() y str(), notamos varios puntos clave:

  • Tipos de Datos Incorrectos: Year se cargó como texto (chr). Esto se debe a que, como veremos, contiene valores no numéricos. Platform, Genre y Publisher también son chr y serían más útiles como factor.

  • Outliers: Global_Sales tiene una media de 0.53 pero un máximo de 82.74. Esto indica que hay valores atípicos muy extremos que podrían sesgar los promedios.

3. Limpieza de Datos

3.1 Investigación de chr() en Year

Al ejecutar summary() y str() en el paso anterior, notamos que la columna Year fue cargada como texto (character) y no como un número. Esto sugiere que hay valores no numéricos mezclados en esa columna que debemos investigar.

El siguiente paso lógico es preguntar: “¿Qué valores no numéricos hay?” Para hacer eso, usamos el siguiente comando:

print( head(unique(sales_data$Year), 100) )
##  [1] "2006" "1985" "2008" "2009" "1996" "1989" "1984" "2005" "1999" "2007"
## [11] "2010" "2013" "2004" "1990" "1988" "2002" "2001" "2011" "1998" "2015"
## [21] "2012" "2014" "1992" "1997" "1993" "1994" "1982" "2003" "1986" "2000"
## [31] "N/A"  "1995" "2016" "1991" "1981" "1987" "1980" "1983" "2020" "2017"

Ahora que sabemos que el culpable es el texto “N/A”, vamos a contarlo para ver qué tan grande es el problema.

# 1. Contamos cuántas filas tienen el TEXTO "N/A"
conteo_string_na <- sum(sales_data$Year == "N/A", na.rm = TRUE)
cat("Filas con el TEXTO 'N/A':", conteo_string_na, "\n")
## Filas con el TEXTO 'N/A': 271
# 2. Contamos cuántas filas tienen el VALOR NA de R (que es diferente)
conteo_valor_na <- sum(is.na(sales_data$Year))
cat("Filas con el VALOR NA:", conteo_valor_na, "\n")
## Filas con el VALOR NA: 0

Conclusión del Diagnóstico

El análisis confirma que la columna Year contiene 271 filas con el texto "N/A". Estos no son NAs estándar de R, por lo que debemos filtrarlos manualmente antes de poder convertir la columna a numérica.

3.1.1 Proceso de limpieza y conversión

Filtraremos las filas con "N/A", convertiremos la columna a número y luego filtraremos cualquier NA que se haya generado en el proceso. También filtraremos los juegos lanzados antes de 1990 para centrar el análisis en la era más moderna.

# 1. Toma los datos originales
sales_data_clean <- sales_data %>%
  # 2. Elimina las filas donde Year es el texto "N/A"
  filter(Year != "N/A") %>%
  # 3. Convierte la columna Year a número (esto crea NAs si hay otros textos)
  mutate(Year = as.numeric(as.character(Year))) %>%
  # 4. Elimina cualquier fila con NA y filtra por año
  filter(!is.na(Year), Year >= 1990)

cat(sprintf("Se eliminaron %d filas con datos de año no válidos o antiguos.\n", nrow(sales_data) - nrow(sales_data_clean)))
## Se eliminaron 476 filas con datos de año no válidos o antiguos.
cat(sprintf("El conjunto de datos ahora contiene %d registros.\n", nrow(sales_data_clean)))
## El conjunto de datos ahora contiene 16122 registros.
cat(sprintf("Y como podemos comprobar el Year ahora esta en: %s", class(sales_data_clean$Year)))
## Y como podemos comprobar el Year ahora esta en: numeric

3.2 Conversión de tipos

Aqui vemos de convertir Platform, Genre y Publisher a factor para los futuros análisis.

sales_data_clean <- sales_data_clean %>%
  mutate(
    Platform  = as.factor(Platform),
    Genre     = as.factor(Genre),
    Publisher = as.factor(Publisher)
  )

4. Análisis Descriptivo y Visualizaciones

4.1 Verificación de Datos Limpios

Ahora que hemos limpiado y transformado nuestros datos, vamos a hacer una última verificación con str() y summary() sobre nuestro nuevo dataframe sales_data_clean.

Esto nos permitirá confirmar dos cosas: 1. Que los tipos de datos son correctos (ej. Year es num, Genre es Factor). 2. Que ya no tenemos los NAs en Year.

str(sales_data_clean)
## 'data.frame':    16122 obs. of  11 variables:
##  $ Rank        : int  1 3 4 5 7 8 9 11 12 13 ...
##  $ Name        : chr  "Wii Sports" "Mario Kart Wii" "Wii Sports Resort" "Pokemon Red/Pokemon Blue" ...
##  $ Platform    : Factor w/ 30 levels "3DO","3DS","DC",..: 25 25 25 5 4 25 25 4 4 5 ...
##  $ Year        : num  2006 2008 2009 1996 2006 ...
##  $ Genre       : Factor w/ 12 levels "Action","Adventure",..: 11 7 11 8 5 4 5 10 7 8 ...
##  $ Publisher   : Factor w/ 557 levels "10TACLE Studios",..: 356 356 356 356 356 356 356 356 356 356 ...
##  $ NA_Sales    : num  41.5 15.8 15.8 11.3 11.4 ...
##  $ EU_Sales    : num  29.02 12.88 11.01 8.89 9.23 ...
##  $ JP_Sales    : num  3.77 3.79 3.28 10.22 6.5 ...
##  $ Other_Sales : num  8.46 3.31 2.96 1 2.9 2.85 2.26 2.75 1.92 0.71 ...
##  $ Global_Sales: num  82.7 35.8 33 31.4 30 ...
summary(sales_data_clean)
##       Rank           Name              Platform         Year     
##  Min.   :    1   Length:16122       DS     :2132   Min.   :1990  
##  1st Qu.: 4232   Class :character   PS2    :2127   1st Qu.:2003  
##  Median : 8388   Mode  :character   PS3    :1304   Median :2007  
##  Mean   : 8357                      Wii    :1290   Mean   :2007  
##  3rd Qu.:12490                      X360   :1235   3rd Qu.:2010  
##  Max.   :16600                      PSP    :1197   Max.   :2020  
##                                     (Other):6837                 
##           Genre                             Publisher        NA_Sales      
##  Action      :3187   Electronic Arts             : 1339   Min.   : 0.0000  
##  Sports      :2281   Activision                  :  940   1st Qu.: 0.0000  
##  Misc        :1702   Ubisoft                     :  918   Median : 0.0800  
##  Role-Playing:1462   Namco Bandai Games          :  916   Mean   : 0.2542  
##  Adventure   :1274   Konami Digital Entertainment:  817   3rd Qu.: 0.2300  
##  Shooter     :1252   THQ                         :  712   Max.   :41.4900  
##  (Other)     :4964   (Other)                     :10480                    
##     EU_Sales          JP_Sales        Other_Sales       Global_Sales    
##  Min.   : 0.0000   Min.   : 0.0000   Min.   : 0.0000   Min.   : 0.0100  
##  1st Qu.: 0.0000   1st Qu.: 0.0000   1st Qu.: 0.0000   1st Qu.: 0.0600  
##  Median : 0.0200   Median : 0.0000   Median : 0.0100   Median : 0.1700  
##  Mean   : 0.1475   Mean   : 0.0733   Mean   : 0.0485   Mean   : 0.5237  
##  3rd Qu.: 0.1100   3rd Qu.: 0.0400   3rd Qu.: 0.0400   3rd Qu.: 0.4600  
##  Max.   :29.0200   Max.   :10.2200   Max.   :10.5700   Max.   :82.7400  
## 

Conclusión de la Verificación:

¡Éxito! La salida de str() ahora muestra Year como num y Platform, Genre y Publisher como Factor. El summary() de Year ahora muestra estadísticas (Min., Median, Mean) en lugar de “Class :character”, confirmando que la limpieza fue exitosa.

4.2 Análisis de Género

Con los datos listos, podemos empezar a hacernos preguntas. La primera es: ¿Cuáles son los géneros de videojuegos más vendidos?

# Agrupamos por Género, sumamos las ventas globales y ordenamos
genre_sales <- sales_data_clean %>%
  group_by(Genre) %>%
  summarise(Total_Global_Sales = sum(Global_Sales)) %>%
  arrange(desc(Total_Global_Sales))

# Usamos kable() para una tabla bien formateada
kable(genre_sales)
Genre Total_Global_Sales
Action 1671.70
Sports 1276.96
Shooter 966.41
Role-Playing 908.59
Misc 789.17
Racing 714.67
Platform 706.88
Fighting 436.81
Simulation 389.65
Adventure 230.02
Puzzle 179.49
Strategy 173.43
# Gráfico: Top 10 Géneros por Ventas Globales
ggplot(head(genre_sales, 10), aes(x = reorder(Genre, Total_Global_Sales), y = Total_Global_Sales)) +
  geom_bar(stat = "identity", fill = "steelblue") +
  coord_flip() + # Voltear los ejes hace que los nombres largos sean legibles
  labs(title = "Top 10 Géneros por Ventas Globales (desde 1990)",
       x = "Género",
       y = "Ventas Globales (en millones)") +
  theme_minimal()

Conclusión:

Se observa que ‘Action’ y ‘Sports’ son, por mucho, los géneros más vendidos en la historia moderna de los videojuegos.

4.3 Análisis de Editores (Publishers)

A continuación, investigamos: ¿Qué compañías dominan el mercado?

De forma similar a como hicimos con Year, primero debemos investigar la columna Publisher. En nuestra exploración inicial, vimos que era chr y en el paso 3.2 la convertimos a factor. Ahora, veamos cuáles son los editores más comunes para detectar cualquier dato “sucio” o faltante.

# summary() en un factor nos muestra los conteos de los niveles más frecuentes.
# Usamos maxsum=50 para limitar la salida y no inundar el reporte
# con los 500+ editores.
publisher_summary <- summary(sales_data_clean$Publisher, maxsum = 50)
kable(publisher_summary)
x
Electronic Arts 1339
Activision 940
Ubisoft 918
Namco Bandai Games 916
Konami Digital Entertainment 817
THQ 712
Sony Computer Entertainment 682
Nintendo 649
Sega 631
Take-Two Interactive 412
Capcom 368
Tecmo Koei 338
Atari 305
Square Enix 231
Warner Bros. Interactive Entertainment 217
Disney Interactive Studios 214
Eidos Interactive 196
Midway Games 196
505 Games 192
Microsoft Game Studios 189
Acclaim Entertainment 184
D3Publisher 183
Vivendi Games 161
Codemasters 150
Idea Factory 128
Deep Silver 121
Nippon Ichi Software 104
Zoo Digital Publishing 104
Unknown 99
Majesco Entertainment 90
LucasArts 89
Rising Star Games 85
Hudson Soft 75
Banpresto 73
Crave Entertainment 71
Bethesda Softworks 69
Atlus 67
Virgin Interactive 62
5pb 61
Infogrames 61
Ignition Entertainment 59
Focus Home Interactive 56
Marvelous Interactive 56
Empire Interactive 51
Kadokawa Shoten 50
SquareSoft 49
Destineer 45
GT Interactive 45
DTP Entertainment 44
(Other) 3168
# Contemos específicamente cuántos "Unknown" hay
conteo_unknown <- sum(sales_data_clean$Publisher == "Unknown")
cat(sprintf("Número total de registros con Publisher 'Unknown': %d\n", conteo_unknown))
## Número total de registros con Publisher 'Unknown': 99

Conclusión de la Investigación:

Efectivamente, el resumen muestra que “Unknown” es una de las categorías más frecuentes (con 99 registros en nuestros datos limpios). Al igual que “N/A” era un placeholder para un año faltante, “Unknown” lo es para un editor faltante.

Filtramos y sumamos ventas

Para que nuestro análisis de “Top Editores” sea significativo y compare compañías reales, filtraremos estos 99 registros “Unknown” antes de sumarizar.

# Agrupamos por Editor, sumamos ventas y filtramos "Unknown"
publisher_sales <- sales_data_clean %>%
  group_by(Publisher) %>%
  summarise(Total_Global_Sales = sum(Global_Sales)) %>%
  filter(Publisher != "Unknown") %>% 
  arrange(desc(Total_Global_Sales))

kable(head(publisher_sales, 10))
Publisher Total_Global_Sales
Nintendo 1549.51
Electronic Arts 1093.39
Activision 703.00
Sony Computer Entertainment 607.28
Ubisoft 473.54
Take-Two Interactive 399.30
THQ 340.44
Konami Digital Entertainment 271.79
Sega 270.30
Microsoft Game Studios 245.79
# Gráfico: Top 10 Editores por Ventas Globales
ggplot(head(publisher_sales, 10), aes(x = reorder(Publisher, Total_Global_Sales), y = Total_Global_Sales)) +
  geom_bar(stat = "identity", fill = "darkgreen") +
  coord_flip() +
  labs(title = "Top 10 Editores por Ventas Globales (desde 1990)",
       x = "Editor",
       y = "Ventas Globales (en millones)") +
  theme_minimal()

Conclusión:

Tras filtrar los datos desconocidos, vemos que Nintendo, Electronic Arts y Activision son los tres gigantes indiscutibles, con Nintendo liderando significativamente.

4.4 Análisis Temporal

Veamos: ¿Cómo ha evolucionado el número de lanzamientos de juegos a lo largo del tiempo?

# Contamos el número de juegos (filas) por año
games_per_year <- sales_data_clean %>%
  group_by(Year) %>%
  summarise(Count = n())

# Gráfico: Lanzamientos de Juegos por Año
# (Usamos linewidth=1 en lugar de size=1 para evitar advertencias)
ggplot(games_per_year, aes(x = Year, y = Count)) +
  geom_line(color = "red", linewidth = 1) +
  geom_point(color = "red") +
  labs(title = "Número de Juegos Lanzados por Año",
       x = "Año",
       y = "Cantidad de Juegos") +
  theme_minimal()

Conclusión:

El gráfico de líneas muestra un “boom” claro en la industria, con un crecimiento masivo en lanzamientos que llega a su pico alrededor de 2008-2009, seguido de una contracción.

5. Análisis Comparativo

5.1 Comparación de Ventas por Región

¿Qué región del mundo compra más videojuegos?

# Sumamos el total de ventas para cada región
regional_sales <- sales_data_clean %>%
  summarise(
    NA_Sales = sum(NA_Sales),
    EU_Sales = sum(EU_Sales),
    JP_Sales = sum(JP_Sales),
    Other_Sales = sum(Other_Sales)
  ) %>%
  gather(key = "Region", value = "Total_Sales") # 'gather' pone los datos en formato largo para ggplot

kable(regional_sales)
Region Total_Sales
NA_Sales 4097.77
EU_Sales 2377.92
JP_Sales 1181.81
Other_Sales 781.88
# Gráfico: Comparación de Ventas Totales por Región
ggplot(regional_sales, aes(x = reorder(Region, -Total_Sales), y = Total_Sales)) +
  geom_bar(stat = "identity", aes(fill = Region)) +
  labs(title = "Ventas Totales por Región (desde 1990)",
       x = "Región",
       y = "Ventas Totales (en millones)") +
  theme_minimal()

Conclusión:

América del Norte (NA_Sales) es el mercado más grande, casi duplicando al de Europa (EU_Sales). El mercado japonés (JP_Sales) es también muy significativo.

5.2 Comparación de Géneros por Región

¿Tienen todas las regiones los mismos gustos?

# Seleccionamos los 5 géneros más populares
top_genres <- head(genre_sales$Genre, 5)

# Filtramos por esos géneros y los resumimos por región
genre_regional_sales <- sales_data_clean %>%
  filter(Genre %in% top_genres) %>%
  group_by(Genre) %>%
  summarise(
    NA_Sales = sum(NA_Sales),
    EU_Sales = sum(EU_Sales),
    JP_Sales = sum(JP_Sales)
  ) %>%
  gather(key = "Region", value = "Sales", -Genre)

# Gráfico: Comparación de Géneros Populares por Región
ggplot(genre_regional_sales, aes(x = Genre, y = Sales, fill = Region)) +
  geom_bar(stat = "identity", position = "dodge") +
  labs(title = "Comparación de Géneros Populares por Región",
       x = "Género",
       y = "Ventas (en millones)") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Conclusión:

Aquí vemos patrones interesantes. Mientras que 'Action' y 'Sports' dominan en Norteamérica y Europa, el mercado japonés (JP_Sales) tiene una preferencia cultural muy marcada por el género 'Role-Playing'.

6. Análisis Estadístico (Prueba de Hipótesis)

Queremos saber si la diferencia que vimos en las ventas de 'Action', 'Sports' y 'Shooter' es estadísticamente significativa, o si podría deberse al azar.

Pregunta: ¿Existe una diferencia significativa en las ventas globales medias entre los géneros 'Action', 'Sports' y 'Shooter'?

Método: Usaremos un ANOVA (Análisis de Varianza).

Hipótesis Nula (H0): Las medias de ventas globales son iguales para los tres géneros.

Hipótesis Alternativa (H1): Al menos una de las medias es diferente.

# Filtrar los datos para los tres géneros de interés
anova_data <- sales_data_clean %>%
  filter(Genre %in% c("Action", "Sports", "Shooter"))

# Realizar la prueba ANOVA
anova_result <- aov(Global_Sales ~ Genre, data = anova_data)

# Mostrar el resumen de la prueba
summary(anova_result)
##               Df Sum Sq Mean Sq F value   Pr(>F)    
## Genre          2     57  28.394   10.58 2.58e-05 ***
## Residuals   6717  18022   2.683                     
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Gráfico Boxplot para visualizar las distribuciones
# Usamos scale_y_log10() porque los datos están muy sesgados por valores atípicos
ggplot(anova_data, aes(x = Genre, y = Global_Sales, fill = Genre)) +
  geom_boxplot() +
  scale_y_log10() + 
  labs(title = "Distribución de Ventas Globales para Géneros Seleccionados",
       x = "Género",
       y = "Ventas Globales (escala logarítmica)") +
  theme_minimal()

Conclusión del ANOVA

El resultado del summary(anova_result) muestra un p-value (columna Pr(>F)) de 2.58e-05.

Este valor (0.0000258) es mucho más pequeño que nuestro nivel de significancia estándar (0.05).

Por lo tanto, rechazamos la hipótesis nula (H0).

Conclusión:

Existe una diferencia estadísticamente significativa en las ventas globales medias entre, al menos, dos de los géneros 'Action', 'Sports' y 'Shooter'. El boxplot sugiere que las medianas son diferentes, validando visualmente nuestro resultado.