INGENIERÍA DE DATOS — Trabajo Grupal

Temas cubiertos: 1 (Introducción y conceptos básicos), 2 (Ingesta de datos), 3 (Limpieza y preprocesado)

Descripción: Este documento realiza la ingesta de datos de Fórmula 1 desde 4 fuentes distintas (CSV, API REST, Excel, JSON), explora su estructura y calidad, y aplica técnicas de limpieza (NAs, duplicados, validación de tipos, normalización de textos). Los datos limpios se guardan en datos/clean/.

Contexto en el ciclo de vida del dato: Este script cubre las fases de ingesta (recopilación de datos desde múltiples fuentes) y limpieza (asegurar la calidad antes de cualquier análisis).


0. Instalación y carga de librerías

install.packages(c("readxl", "jsonlite", "dplyr", "httr2", "ggplot2", "corrplot"))
library(readxl)     # Lectura de archivos Excel
library(jsonlite)   # Lectura de archivos JSON
library(dplyr)      # Manipulación de datos
library(httr2)      # Peticiones HTTP a la API
library(ggplot2)    # Gráficos

Se utilizan diferentes librerías para gestionar la ingesta de datos desde múltiples fuentes y facilitar su limpieza y transformación.

1. Crear estructura de carpetas

# [PROPORCIONADO]
dir.create("datos/clean", recursive = TRUE, showWarnings = FALSE)
dir.create("datos/gold", recursive = TRUE, showWarnings = FALSE)
cat("Estructura de carpetas creada correctamente.\n")
## Estructura de carpetas creada correctamente.

2. Ingesta de datos — Fuente 1: CSV

Referencia: Tema 2 — Ingesta de datos (read.csv)

>>> BLOQUE 1 — A COMPLETAR POR EL GRUPO <<<

Leer el archivo datos/raw/f1_resultados_historicos.csv usando read.csv(). Revisar el separador de campos del fichero (puede ser "," o ";"). Asignar el resultado a df_resultados. Después, mostrar las primeras filas con head() y la estructura con str().

df_resultados <- read.csv("datos/raw/f1_resultados_historicos.csv", sep = ",")

head(df_resultados)
##   season round            race_name    circuit_id
## 1   2022     4   Russian Grand Prix         sochi
## 2   2015     1 Hungarian Grand Prix   hungaroring
## 3   2017    12 Hungarian Grand Prix   hungaroring
## 4   2018     4  Austrian Grand Prix red_bull_ring
## 5   2014     5   Spanish Grand Prix     catalunya
## 6   2022    16  Austrian Grand Prix red_bull_ring
##                     circuit_name       date  driver_id     driver_name
## 1                 Sochi Autodrom 2022-06-01    tsunoda    Yuki Tsunoda
## 2                    Hungaroring 2015-10-18     lawson     Liam Lawson
## 3                    Hungaroring 2017-08-18 hulkenberg Nico Hülkenberg
## 4                  Red Bull Ring 2018-10-23   sargeant  Logan Sargeant
## 5 Circuit de Barcelona-Catalunya 2014-11-05  raikkonen  Kimi Räikkönen
## 6                  Red Bull Ring 2022-04-14   sargeant  Logan Sargeant
##   driver_nationality constructor grid position position_text points laps
## 1           Japanese  Alfa Romeo    6       20            20      0   67
## 2      New Zealander       Manor    1        4             4     12   65
## 3             German       Manor    5       19            19      0   56
## 4           American        Haas    5       NA             R      0   20
## 5            Finnish     Ferrari   10       NA             R      0    5
## 6           American  Alfa Romeo    2        2             2     18   58
##       status fastest_lap_time fastest_lap_speed
## 1    +2 Laps         1:35.727           206.174
## 2   Finished         1:33.721           230.861
## 3   Finished                                 NA
## 4 Hydraulics         1:23.903           231.109
## 5    Gearbox         1:17.230           234.613
## 6   Finished         1:18.126           232.487
str(df_resultados)
## 'data.frame':    3780 obs. of  18 variables:
##  $ season            : int  2022 2015 2017 2018 2014 2022 2014 2023 2015 2022 ...
##  $ round             : int  4 1 12 4 5 16 14 6 17 10 ...
##  $ race_name         : chr  "Russian Grand Prix" "Hungarian Grand Prix" "Hungarian Grand Prix" "Austrian Grand Prix" ...
##  $ circuit_id        : chr  "sochi" "hungaroring" "hungaroring" "red_bull_ring" ...
##  $ circuit_name      : chr  "Sochi Autodrom" "Hungaroring" "Hungaroring" "Red Bull Ring" ...
##  $ date              : chr  "2022-06-01" "2015-10-18" "2017-08-18" "2018-10-23" ...
##  $ driver_id         : chr  "tsunoda" "lawson" "hulkenberg" "sargeant" ...
##  $ driver_name       : chr  "Yuki Tsunoda" "Liam Lawson" "Nico Hülkenberg" "Logan Sargeant" ...
##  $ driver_nationality: chr  "Japanese" "New Zealander" "German" "American" ...
##  $ constructor       : chr  "Alfa Romeo" "Manor" "Manor" "Haas" ...
##  $ grid              : int  6 1 5 5 10 2 9 13 8 9 ...
##  $ position          : int  20 4 19 NA NA 2 NA 5 5 13 ...
##  $ position_text     : chr  "20" "4" "19" "R" ...
##  $ points            : int  0 12 0 0 0 18 0 10 10 0 ...
##  $ laps              : int  67 65 56 20 5 58 23 64 55 70 ...
##  $ status            : chr  "+2 Laps" "Finished" "Finished" "Hydraulics" ...
##  $ fastest_lap_time  : chr  "1:35.727" "1:33.721" "" "1:23.903" ...
##  $ fastest_lap_speed : num  206 231 NA 231 235 ...

Se carga el dataset principal desde un archivo CSV que contiene los resultados históricos de Fórmula 1. Se inspecciona su estructura mediante head() y str() para comprender su contenido y tipos de datos.


3. Ingesta de datos — Fuente 2: API REST (Ergast/Jolpica)

Referencia: Tema 2 — Ingesta de datos (APIs)

[PROPORCIONADO] — Llamadas a la API

La API devuelve datos en formato JSON. Se realizan dos llamadas: clasificación de pilotos y de constructores de la temporada 2024.

# [PROPORCIONADO] — Clasificación de pilotos 2024
tryCatch({
  url_pilotos <- "https://api.jolpi.ca/ergast/f1/2024/driverStandings.json"
  
  resp_pilotos <- request(url_pilotos) %>%
    req_headers("Accept" = "application/json") %>%
    req_perform()
  
  json_pilotos <- resp_pilotos %>%
    resp_body_json()
  
  standings_pilotos <- json_pilotos$MRData$StandingsTable$StandingsLists[[1]]$DriverStandings
  
  df_api_pilotos <- data.frame(
    posicion = as.integer(sapply(standings_pilotos, function(x) x$position)),
    puntos = as.numeric(sapply(standings_pilotos, function(x) x$points)),
    victorias = as.integer(sapply(standings_pilotos, function(x) x$wins)),
    driver_id = sapply(standings_pilotos, function(x) x$Driver$driverId),
    nombre = sapply(standings_pilotos, function(x) x$Driver$givenName),
    apellido = sapply(standings_pilotos, function(x) x$Driver$familyName),
    nacionalidad = sapply(standings_pilotos, function(x) x$Driver$nationality),
    constructor = sapply(standings_pilotos, function(x) x$Constructors[[1]]$name),
    stringsAsFactors = FALSE
  )
  
  cat("Clasificación de pilotos 2024 descargada:", nrow(df_api_pilotos), "pilotos\n")
  
}, error = function(e) {
  cat("Error al acceder a la API de pilotos:", e$message, "\n")
  df_api_pilotos <<- data.frame()
})
## Clasificación de pilotos 2024 descargada: 24 pilotos
# [PROPORCIONADO] — Clasificación de constructores 2024
tryCatch({
  url_constructores <- "https://api.jolpi.ca/ergast/f1/2024/constructorStandings.json"
  
  resp_constructores <- request(url_constructores) %>%
    req_headers("Accept" = "application/json") %>%
    req_perform()
  
  json_constructores <- resp_constructores %>%
    resp_body_json()
  
  standings_constructores <- json_constructores$MRData$StandingsTable$StandingsLists[[1]]$ConstructorStandings
  
  df_api_constructores <- data.frame(
    posicion = as.integer(sapply(standings_constructores, function(x) x$position)),
    puntos = as.numeric(sapply(standings_constructores, function(x) x$points)),
    victorias = as.integer(sapply(standings_constructores, function(x) x$wins)),
    constructor_id = sapply(standings_constructores, function(x) x$Constructor$constructorId),
    nombre_constructor = sapply(standings_constructores, function(x) x$Constructor$name),
    nacionalidad = sapply(standings_constructores, function(x) x$Constructor$nationality),
    stringsAsFactors = FALSE
  )
  
  cat("Clasificación de constructores 2024 descargada:", nrow(df_api_constructores), "equipos\n")
  
}, error = function(e) {
  cat("Error al acceder a la API de constructores:", e$message, "\n")
  df_api_constructores <<- data.frame()
})
## Clasificación de constructores 2024 descargada: 10 equipos
# [PROPORCIONADO] — Inspeccionar los datos recibidos de la API
cat("--- Datos de pilotos desde la API ---\n")
## --- Datos de pilotos desde la API ---
head(df_api_pilotos)
##   posicion puntos victorias      driver_id  nombre   apellido nacionalidad
## 1        1    437         9 max_verstappen     Max Verstappen        Dutch
## 2        2    374         4         norris   Lando     Norris      British
## 3        3    356         3        leclerc Charles    Leclerc   Monegasque
## 4        4    292         2        piastri   Oscar    Piastri   Australian
## 5        5    290         2          sainz  Carlos      Sainz      Spanish
## 6        6    245         2        russell  George    Russell      British
##   constructor
## 1    Red Bull
## 2     McLaren
## 3     Ferrari
## 4     McLaren
## 5     Ferrari
## 6    Mercedes
cat("\n--- Datos de constructores desde la API ---\n")
## 
## --- Datos de constructores desde la API ---
head(df_api_constructores)
##   posicion puntos victorias constructor_id nombre_constructor nacionalidad
## 1        1    666         6        mclaren            McLaren      British
## 2        2    652         5        ferrari            Ferrari      Italian
## 3        3    589         9       red_bull           Red Bull     Austrian
## 4        4    468         4       mercedes           Mercedes       German
## 5        5     94         0   aston_martin       Aston Martin      British
## 6        6     65         0         alpine     Alpine F1 Team       French

Se realiza la ingesta de datos desde una API REST que proporciona información actualizada sobre la clasificación de pilotos y constructores. Esto permite enriquecer el análisis con datos recientes.


4. Ingesta de datos — Fuente 3: Excel

Referencia: Tema 2 — Ingesta de datos (readxl)

>>> BLOQUE 2 — A COMPLETAR POR EL GRUPO <<<

Leer el archivo datos/raw/f1_equipos_presupuestos.xlsx. Este archivo tiene dos hojas: “Equipos” y “Presupuestos”. Usar read_excel() del paquete readxl con el parámetro sheet para cada hoja. Asignar cada hoja a una variable distinta: df_equipos y df_presupuestos. Mostrar head() y dim() de cada una.

df_equipos <- read_excel("datos/raw/f1_equipos_presupuestos.xlsx", sheet = "Equipos")
df_presupuestos <- read_excel("datos/raw/f1_equipos_presupuestos.xlsx", sheet = "Presupuestos")

head(df_equipos)
## # A tibble: 6 × 5
##   nombre_equipo pais_sede      anio_fundacion num_empleados director_equipo 
##   <chr>         <chr>                   <dbl>         <dbl> <chr>           
## 1 Mercedes      Germany                  1954          1100 Toto Wolff      
## 2 Ferrari       Italy                    1929          1200 Frédéric Vasseur
## 3 Red Bull      Austria                  2005           900 Christian Horner
## 4 McLaren       United Kingdom           1963           850 Andrea Stella   
## 5 Aston Martin  United Kingdom           2021           700 Mike Krack      
## 6 Alpine        France                   2021           750 Bruno Famin
dim(df_equipos)
## [1] 10  5
head(df_presupuestos)
## # A tibble: 6 × 5
##   nombre_equipo temporada presupuesto_millones pct_desarrollo pct_personal
##   <chr>             <dbl>                <dbl>          <dbl>        <dbl>
## 1 Mercedes           2019                 135            47.9         32  
## 2 Mercedes           2020                 149.           36.8         36.5
## 3 Mercedes           2021                 156.           49.9         28.4
## 4 Mercedes           2022                 152            42           30.7
## 5 Mercedes           2023                 159            48           39.9
## 6 Ferrari            2019                 136.           39.1         29
dim(df_presupuestos)
## [1] 50  5

Se cargan las dos hojas del archivo Excel, que contienen información sobre los equipos y sus presupuestos, utilizando read_excel().


5. Ingesta de datos — Fuente 4: JSON

Referencia: Tema 2 — Ingesta de datos (jsonlite)

>>> BLOQUE 3 — A COMPLETAR POR EL GRUPO <<<

Leer el archivo datos/raw/f1_pilotos_info.json usando fromJSON() del paquete jsonlite. La función devuelve una lista; acceder al elemento $pilotos para obtener el dataframe. Asignar a df_pilotos_bio. Mostrar head(), str() y dim().

json_data <- fromJSON("datos/raw/f1_pilotos_info.json")

df_pilotos_bio <- json_data$pilotos

head(df_pilotos_bio)
##   code  nombre   apellido fecha_nacimiento nacionalidad equipo_actual
## 1  HAM   Lewis   Hamilton       1985-01-07      British      Mercedes
## 2  VER     Max Verstappen       1997-09-30        Dutch      Red Bull
## 3  LEC Charles    Leclerc       1997-10-16   Monegasque       Ferrari
## 4  SAI  Carlos      Sainz       1994-09-01      Spanish       Ferrari
## 5  NOR   Lando     Norris       1999-11-13      British       McLaren
## 6  RUS  George    Russell       1998-02-15      British      Mercedes
##   estatura_cm peso_kg salario_anual_millones titulos_mundiales  driver_id
## 1         174      73                     55                 7   hamilton
## 2         181      72                     55                 3 verstappen
## 3         180      70                     24                 0    leclerc
## 4         178      66                     10                 0      sainz
## 5         170      69                     20                 0     norris
## 6         185      70                      8                 0    russell
str(df_pilotos_bio)
## 'data.frame':    21 obs. of  11 variables:
##  $ code                  : chr  "HAM" "VER" "LEC" "SAI" ...
##  $ nombre                : chr  "Lewis" "Max" "Charles" "Carlos" ...
##  $ apellido              : chr  "Hamilton" "Verstappen" "Leclerc" "Sainz" ...
##  $ fecha_nacimiento      : chr  "1985-01-07" "1997-09-30" "1997-10-16" "1994-09-01" ...
##  $ nacionalidad          : chr  "British" "Dutch" "Monegasque" "Spanish" ...
##  $ equipo_actual         : chr  "Mercedes" "Red Bull" "Ferrari" "Ferrari" ...
##  $ estatura_cm           : int  174 181 180 178 170 185 173 171 178 182 ...
##  $ peso_kg               : int  73 72 70 66 69 70 63 68 70 70 ...
##  $ salario_anual_millones: num  55 55 24 10 20 8 10 20 5 10 ...
##  $ titulos_mundiales     : int  7 3 0 0 0 0 0 2 0 0 ...
##  $ driver_id             : chr  "hamilton" "verstappen" "leclerc" "sainz" ...
dim(df_pilotos_bio)
## [1] 21 11

Se carga el archivo JSON con información biográfica de los pilotos y se transforma en un dataframe, facilitando su análisis.

La ingesta desde múltiples fuentes permite integrar información heterogénea en un único pipeline de datos.


6. Exploración inicial de todos los datasets

Referencia: Tema 3 — Limpieza y preprocesado (exploración)

# [PROPORCIONADO] — Resumen de dimensiones
cat("========================================\n")
## ========================================
cat("RESUMEN DE DATASETS CARGADOS\n")
## RESUMEN DE DATASETS CARGADOS
cat("========================================\n")
## ========================================
cat("df_resultados (CSV):", dim(df_resultados)[1], "filas x", dim(df_resultados)[2], "columnas\n")
## df_resultados (CSV): 3780 filas x 18 columnas
cat("df_api_pilotos (API):", dim(df_api_pilotos)[1], "filas x", dim(df_api_pilotos)[2], "columnas\n")
## df_api_pilotos (API): 24 filas x 8 columnas
cat("df_api_constructores (API):", dim(df_api_constructores)[1], "filas x", dim(df_api_constructores)[2], "columnas\n")
## df_api_constructores (API): 10 filas x 6 columnas
cat("df_equipos (Excel):", dim(df_equipos)[1], "filas x", dim(df_equipos)[2], "columnas\n")
## df_equipos (Excel): 10 filas x 5 columnas
cat("df_presupuestos (Excel):", dim(df_presupuestos)[1], "filas x", dim(df_presupuestos)[2], "columnas\n")
## df_presupuestos (Excel): 50 filas x 5 columnas
cat("df_pilotos_bio (JSON):", dim(df_pilotos_bio)[1], "filas x", dim(df_pilotos_bio)[2], "columnas\n")
## df_pilotos_bio (JSON): 21 filas x 11 columnas
# [PROPORCIONADO] — Conteo de NAs y estadísticas del dataset principal
cat("--- Valores faltantes (NAs) en df_resultados ---\n")
## --- Valores faltantes (NAs) en df_resultados ---
print(colSums(is.na(df_resultados)))
##             season              round          race_name         circuit_id 
##                  0                  0                  0                  0 
##       circuit_name               date          driver_id        driver_name 
##                  0                  0                  0                  0 
## driver_nationality        constructor               grid           position 
##                  0                  0                  0               1473 
##      position_text             points               laps             status 
##                  0                 27                  0                  0 
##   fastest_lap_time  fastest_lap_speed 
##                  0                502
cat("\n--- Estadísticas descriptivas ---\n")
## 
## --- Estadísticas descriptivas ---
summary(df_resultados)
##      season         round         race_name          circuit_id       
##  Min.   :2014   Min.   : 1.000   Length:3780        Length:3780       
##  1st Qu.:2016   1st Qu.: 5.000   Class :character   Class :character  
##  Median :2018   Median :10.000   Mode  :character   Mode  :character  
##  Mean   :2019   Mean   : 9.918                                        
##  3rd Qu.:2021   3rd Qu.:15.000                                        
##  Max.   :2023   Max.   :20.000                                        
##                                                                       
##  circuit_name           date            driver_id         driver_name       
##  Length:3780        Length:3780        Length:3780        Length:3780       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##  driver_nationality constructor             grid          position   
##  Length:3780        Length:3780        Min.   : 1.00   Min.   : 1.0  
##  Class :character   Class :character   1st Qu.: 5.00   1st Qu.: 5.0  
##  Mode  :character   Mode  :character   Median :10.00   Median :10.0  
##                                        Mean   :10.44   Mean   :10.4  
##                                        3rd Qu.:15.00   3rd Qu.:15.0  
##                                        Max.   :20.00   Max.   :20.0  
##                                                        NA's   :1473  
##  position_text          points            laps          status         
##  Length:3780        Min.   : 0.000   Min.   : 5.00   Length:3780       
##  Class :character   1st Qu.: 0.000   1st Qu.:37.00   Class :character  
##  Mode  :character   Median : 0.000   Median :54.00   Mode  :character  
##                     Mean   : 3.146   Mean   :48.28                     
##                     3rd Qu.: 4.000   3rd Qu.:62.00                     
##                     Max.   :25.000   Max.   :70.00                     
##                     NA's   :27                                         
##  fastest_lap_time   fastest_lap_speed
##  Length:3780        Min.   :185.0    
##  Class :character   1st Qu.:197.1    
##  Mode  :character   Median :209.6    
##                     Mean   :209.7    
##                     3rd Qu.:222.2    
##                     Max.   :235.0    
##                     NA's   :502

Se observa que las variables numéricas presentan diferentes rangos y distribuciones, lo que puede influir en el análisis posterior.


7. Limpieza — Tratamiento de NAs y duplicados

Referencia: Tema 3 — Limpieza y preprocesado

>>> BLOQUE 4 — A COMPLETAR POR EL GRUPO <<<

A) Tratamiento de NAs en df_resultados:

  • Para columnas numéricas con NAs (ej: points, grid, laps, fastest_lap_speed): sustituir los NAs por la media de la columna. PISTA: mean(columna, na.rm = TRUE) y luego df$col[is.na(df$col)] <- valor_media.
  • Para columnas de texto con NAs (ej: status, fastest_lap_time): sustituir por el valor más frecuente. PISTA: usar table() para ver frecuencias.

B) Detección y eliminación de duplicados:

  • Comprobar si hay filas duplicadas con duplicated() y sum().
  • Si hay, eliminarlos con df <- df[!duplicated(df), ].

Mostrar el número de NAs y filas antes y después de cada operación.

# Exploración inicial del dataset
dim(df_resultados)
## [1] 3780   18
colSums(is.na(df_resultados))
##             season              round          race_name         circuit_id 
##                  0                  0                  0                  0 
##       circuit_name               date          driver_id        driver_name 
##                  0                  0                  0                  0 
## driver_nationality        constructor               grid           position 
##                  0                  0                  0               1473 
##      position_text             points               laps             status 
##                  0                 27                  0                  0 
##   fastest_lap_time  fastest_lap_speed 
##                  0                502
summary(df_resultados)
##      season         round         race_name          circuit_id       
##  Min.   :2014   Min.   : 1.000   Length:3780        Length:3780       
##  1st Qu.:2016   1st Qu.: 5.000   Class :character   Class :character  
##  Median :2018   Median :10.000   Mode  :character   Mode  :character  
##  Mean   :2019   Mean   : 9.918                                        
##  3rd Qu.:2021   3rd Qu.:15.000                                        
##  Max.   :2023   Max.   :20.000                                        
##                                                                       
##  circuit_name           date            driver_id         driver_name       
##  Length:3780        Length:3780        Length:3780        Length:3780       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##  driver_nationality constructor             grid          position   
##  Length:3780        Length:3780        Min.   : 1.00   Min.   : 1.0  
##  Class :character   Class :character   1st Qu.: 5.00   1st Qu.: 5.0  
##  Mode  :character   Mode  :character   Median :10.00   Median :10.0  
##                                        Mean   :10.44   Mean   :10.4  
##                                        3rd Qu.:15.00   3rd Qu.:15.0  
##                                        Max.   :20.00   Max.   :20.0  
##                                                        NA's   :1473  
##  position_text          points            laps          status         
##  Length:3780        Min.   : 0.000   Min.   : 5.00   Length:3780       
##  Class :character   1st Qu.: 0.000   1st Qu.:37.00   Class :character  
##  Mode  :character   Median : 0.000   Median :54.00   Mode  :character  
##                     Mean   : 3.146   Mean   :48.28                     
##                     3rd Qu.: 4.000   3rd Qu.:62.00                     
##                     Max.   :25.000   Max.   :70.00                     
##                     NA's   :27                                         
##  fastest_lap_time   fastest_lap_speed
##  Length:3780        Min.   :185.0    
##  Class :character   1st Qu.:197.1    
##  Mode  :character   Median :209.6    
##                     Mean   :209.7    
##                     3rd Qu.:222.2    
##                     Max.   :235.0    
##                     NA's   :502
# NAs antes
print("NAs antes:")
## [1] "NAs antes:"
sum(is.na(df_resultados))
## [1] 2002
# Sustituimos valores NA en variables numéricas por la media

# Imputación en points
media_points <- mean(df_resultados$points, na.rm = TRUE)
df_resultados$points[is.na(df_resultados$points)] <- media_points
# Imputación en otras variables numéricas
df_resultados$grid[is.na(df_resultados$grid)] <- mean(df_resultados$grid, na.rm = TRUE)
df_resultados$laps[is.na(df_resultados$laps)] <- mean(df_resultados$laps, na.rm = TRUE)

# NAs después
print("NAs después:")
## [1] "NAs después:"
sum(is.na(df_resultados))
## [1] 1975
# Duplicados antes
print("Duplicados antes:")
## [1] "Duplicados antes:"
sum(duplicated(df_resultados))
## [1] 20
df_resultados <- df_resultados[!duplicated(df_resultados), ]

# Duplicados después
print("Duplicados después:")
## [1] "Duplicados después:"
sum(duplicated(df_resultados))
## [1] 0

Los valores faltantes en variables numéricas se sustituyen por la media, con el objetivo de mantener la información sin introducir sesgos significativos.

Se detectan y eliminan registros duplicados para evitar distorsiones en el análisis.


8. Limpieza — Validación de tipos y normalización de textos

Referencia: Tema 3 — Limpieza y preprocesado (validaciones)

>>> BLOQUE 5 — A COMPLETAR POR EL GRUPO <<<

A) Validación de tipos:

  • Revisar los tipos con str(df_resultados).
  • Asegurar que points, grid y laps son numéricos (as.numeric()).
  • Convertir constructor y driver_nationality a factor (as.factor()).

B) Normalización de textos:

  • Revisar valores únicos de constructor con table() o unique().
  • Si hay inconsistencias (ej: “mercedes” vs “Mercedes”), corregirlas con ifelse(), tolower() o toupper().
  • Hacer lo mismo con driver_nationality si es necesario.
#Validación de tipos
str(df_resultados)
## 'data.frame':    3760 obs. of  18 variables:
##  $ season            : int  2022 2015 2017 2018 2014 2022 2014 2023 2015 2022 ...
##  $ round             : int  4 1 12 4 5 16 14 6 17 10 ...
##  $ race_name         : chr  "Russian Grand Prix" "Hungarian Grand Prix" "Hungarian Grand Prix" "Austrian Grand Prix" ...
##  $ circuit_id        : chr  "sochi" "hungaroring" "hungaroring" "red_bull_ring" ...
##  $ circuit_name      : chr  "Sochi Autodrom" "Hungaroring" "Hungaroring" "Red Bull Ring" ...
##  $ date              : chr  "2022-06-01" "2015-10-18" "2017-08-18" "2018-10-23" ...
##  $ driver_id         : chr  "tsunoda" "lawson" "hulkenberg" "sargeant" ...
##  $ driver_name       : chr  "Yuki Tsunoda" "Liam Lawson" "Nico Hülkenberg" "Logan Sargeant" ...
##  $ driver_nationality: chr  "Japanese" "New Zealander" "German" "American" ...
##  $ constructor       : chr  "Alfa Romeo" "Manor" "Manor" "Haas" ...
##  $ grid              : num  6 1 5 5 10 2 9 13 8 9 ...
##  $ position          : int  20 4 19 NA NA 2 NA 5 5 13 ...
##  $ position_text     : chr  "20" "4" "19" "R" ...
##  $ points            : num  0 12 0 0 0 18 0 10 10 0 ...
##  $ laps              : num  67 65 56 20 5 58 23 64 55 70 ...
##  $ status            : chr  "+2 Laps" "Finished" "Finished" "Hydraulics" ...
##  $ fastest_lap_time  : chr  "1:35.727" "1:33.721" "" "1:23.903" ...
##  $ fastest_lap_speed : num  206 231 NA 231 235 ...
df_resultados$points <- as.numeric(df_resultados$points)
df_resultados$grid <- as.numeric(df_resultados$grid)
df_resultados$laps <- as.numeric(df_resultados$laps)

df_resultados$constructor <- as.factor(df_resultados$constructor)
df_resultados$driver_nationality <- as.factor(df_resultados$driver_nationality)

#Normalización de tipos
df_resultados$constructor <- tolower(df_resultados$constructor)
df_resultados$constructor <- ifelse(df_resultados$constructor == "mercedes", "Mercedes", df_resultados$constructor)
df_resultados$constructor <- as.factor(df_resultados$constructor)

table(df_resultados$constructor)
## 
##   alfa romeo   alphatauri       alpine aston martin      ferrari  force india 
##          226          226          114          114          376          150 
##         haas        lotus        manor      mclaren     Mercedes racing point 
##          226          150          150          376          376          112 
##     red bull      renault       sauber   toro rosso     williams 
##          376          112          150          150          376

Se validan y ajustan los tipos de datos para asegurar la coherencia del dataset, convirtiendo variables a formato numérico o categórico según corresponda.

Se normalizan los valores de variables categóricas para evitar inconsistencias en los datos.


9. Guardado de datos limpios

# [PROPORCIONADO]
saveRDS(df_resultados, "datos/clean/resultados_clean.rds")
saveRDS(df_api_pilotos, "datos/clean/api_pilotos_clean.rds")
saveRDS(df_api_constructores, "datos/clean/api_constructores_clean.rds")
saveRDS(df_equipos, "datos/clean/equipos_clean.rds")
saveRDS(df_presupuestos, "datos/clean/presupuestos_clean.rds")
saveRDS(df_pilotos_bio, "datos/clean/pilotos_bio_clean.rds")

cat("\n========================================\n")
## 
## ========================================
cat("Todos los datasets limpios guardados en datos/clean/\n")
## Todos los datasets limpios guardados en datos/clean/
cat("Script 1 completado con éxito.\n")
## Script 1 completado con éxito.
cat("========================================\n")
## ========================================

Este script representa las primeras fases del pipeline de datos, asegurando que la información está limpia y preparada para su transformación y análisis posterior. Este proceso asegura que los datos estén preparados para las fases posteriores del pipeline, evitando errores y mejorando la calidad del análisis.


Pregunta teórica 1

Explica brevemente qué es el ciclo de vida del dato y en qué fase se encuentra la ingesta de datos. ¿Qué riesgos existen si se omite la fase de limpieza?

Tu respuesta aquí (5-10 líneas):

El ciclo de vida del dato describe las fases por las que pasa la información desde su captura hasta su uso en la toma de decisiones. La ingesta se sitúa en la fase inicial, donde se recopilan los datos desde distintas fuentes.

Si se omite la fase de limpieza, los datos pueden contener errores, valores inconsistentes o duplicados, lo que afectaría negativamente a la calidad del análisis y a las decisiones basadas en dichos datos.

La limpieza garantiza la calidad del dato y es clave para evitar errores que se propaguen a lo largo del pipeline.


Pregunta teórica 2

¿Qué diferencia hay entre sustituir un valor faltante (NA) por la media y eliminarlo directamente? ¿En qué situaciones usarías cada estrategia?

Tu respuesta aquí (5-10 líneas):

Sustituir un valor faltante por la media permite mantener el tamaño del dataset y evitar la pérdida de información, aunque puede reducir la variabilidad de los datos.

Por otro lado, eliminar registros con valores faltantes puede ser más adecuado cuando el número de NAs es pequeño o cuando estos afectan a variables críticas. La elección depende del contexto y del impacto que tenga cada estrategia en el análisis.

Eliminar datos puede introducir sesgo si los valores faltantes no son aleatorios.