Primero, cargamos los paquetes necesarios para nuestro análisis.
tidyverse: Es una colección de paquetes (incluyendo
dplyr para manipulación y ggplot2 para
gráficos) que facilitan la ciencia de datos.
knitr: Lo usaremos para mostrar las tablas de forma ordenada con
kable().
library(tidyverse)
library(knitr)
El primer paso de cualquier análisis es cargar los datos y entender su estructura.
Usamos here::here() para leer el archivo. Esta es
una buena práctica porque construye la ruta al archivo vgsales.csv
basándose en la ubicación del archivo .Rproj, lo que evita que las rutas
se rompan si movemos el proyecto.
Luego, usamos head(), summary() y
str() para nuestra primera inspección.
# Lee los datos desde la carpeta 'data' en la raíz del proyecto
sales_data <- read.csv(here::here("data", "vgsales.csv"))
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
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
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 ...
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.
chr() en YearAl 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
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.
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
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)
)
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
##
¡É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.
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()
Se observa que ‘Action’ y ‘Sports’ son, por mucho, los géneros más vendidos en la historia moderna de los videojuegos.
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
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.
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()
Tras filtrar los datos desconocidos, vemos que Nintendo, Electronic Arts y Activision son los tres gigantes indiscutibles, con Nintendo liderando significativamente.
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()
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.
¿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()
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.
¿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))
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'.
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()
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).
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.