Semana 06 — Limpieza de Datos y Resumen Analítico con dplyr

Introducción al Lenguaje de Programación y Estadística R — ITLA

Author

Angelo Pérez

Published

June 5, 2026

library(tidyverse)

1 Dataset Original

ventas_crudas <- tibble(
  id_venta   = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 3),
  vendedor   = c("ana", "PEDRO", "María ", NA, "carmen", "ana",
                 "PEDRO", "luis", "carmen", "luis", "María "),
  region     = c("norte", "SUR", "Norte", "sur", "NORTE", "norte",
                 "sur", "Norte", "sur", "norte", "Norte"),
  monto      = c(15000, 22000, 18500, NA, 31000, 16000,
                 19500, 9500000, 21000, 17500, 18500),
  mes        = c(1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 1),
  completada = c("SI", "SI", "NO", "SI", NA, "SI", "NO", "SI", "SI", "NO", "NO")
)

print(ventas_crudas)
# A tibble: 11 × 6
   id_venta vendedor region   monto   mes completada
      <dbl> <chr>    <chr>    <dbl> <dbl> <chr>     
 1        1 "ana"    norte    15000     1 SI        
 2        2 "PEDRO"  SUR      22000     1 SI        
 3        3 "María " Norte    18500     1 NO        
 4        4  <NA>    sur         NA     2 SI        
 5        5 "carmen" NORTE    31000     2 <NA>      
 6        6 "ana"    norte    16000     2 SI        
 7        7 "PEDRO"  sur      19500     3 NO        
 8        8 "luis"   Norte  9500000     3 SI        
 9        9 "carmen" sur      21000     3 SI        
10       10 "luis"   norte    17500     3 NO        
11        3 "María " Norte    18500     1 NO        

2 Parte 1 — Diagnóstico Inicial

2.1 glimpse() — Estructura del dataset

glimpse(ventas_crudas)
Rows: 11
Columns: 6
$ id_venta   <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 3
$ vendedor   <chr> "ana", "PEDRO", "María ", NA, "carmen", "ana", "PEDRO", "lu…
$ region     <chr> "norte", "SUR", "Norte", "sur", "NORTE", "norte", "sur", "N…
$ monto      <dbl> 15000, 22000, 18500, NA, 31000, 16000, 19500, 9500000, 2100…
$ mes        <dbl> 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 1
$ completada <chr> "SI", "SI", "NO", "SI", NA, "SI", "NO", "SI", "SI", "NO", "…

2.2 summary() — Resumen estadístico

summary(ventas_crudas)
    id_venta           vendedor        region       monto              mes   
 Min.   : 1.000   Length   :11   Length   :11   Min.   :  15000   Min.   :1  
 1st Qu.: 3.000   N.unique : 5   N.unique : 5   1st Qu.:  17750   1st Qu.:1  
 Median : 5.000   N.blank  : 0   N.blank  : 0   Median :  19000   Median :2  
 Mean   : 5.273   Min.nchar: 3   Min.nchar: 3   Mean   : 967900   Mean   :2  
 3rd Qu.: 7.500   Max.nchar: 6   Max.nchar: 5   3rd Qu.:  21750   3rd Qu.:3  
 Max.   :10.000   NAs      : 1                  Max.   :9500000   Max.   :3  
                                                NAs    :1                    
     completada
 Length   :11  
 N.unique : 2  
 N.blank  : 0  
 Min.nchar: 2  
 Max.nchar: 2  
 NAs      : 1  
               

2.3 Conteo de NAs por columna

cat("=== NAs por columna ===\n")
=== NAs por columna ===
colSums(is.na(ventas_crudas))
  id_venta   vendedor     region      monto        mes completada 
         0          1          0          1          0          1 

2.4 Diagnóstico completo

# ── Duplicados ──────────────────────────────────────────────────────────────
n_filas_total   <- nrow(ventas_crudas)
n_ids_unicos    <- n_distinct(ventas_crudas$id_venta)
n_duplicados    <- n_filas_total - n_ids_unicos

cat("=== DIAGNÓSTICO DEL DATASET ===\n\n")
=== DIAGNÓSTICO DEL DATASET ===
cat("Filas totales:          ", n_filas_total, "\n")
Filas totales:           11 
cat("IDs únicos:             ", n_ids_unicos,  "\n")
IDs únicos:              10 
cat("Filas duplicadas:       ", n_duplicados,  "\n")
Filas duplicadas:        1 
# ── NAs ─────────────────────────────────────────────────────────────────────
cat("\nNAs por columna:\n")

NAs por columna:
na_conteo <- colSums(is.na(ventas_crudas))
for (col in names(na_conteo)) {
  if (na_conteo[col] > 0)
    cat("  •", col, "→", na_conteo[col], "NA(s)\n")
}
  • vendedor → 1 NA(s)
  • monto → 1 NA(s)
  • completada → 1 NA(s)
# ── Inconsistencias de texto ─────────────────────────────────────────────────
cat("\nValores únicos en 'vendedor' (antes de limpiar):\n")

Valores únicos en 'vendedor' (antes de limpiar):
cat(" ", paste(unique(ventas_crudas$vendedor), collapse = " | "), "\n")
  ana | PEDRO | María  | NA | carmen | luis 
cat("\nValores únicos en 'region' (antes de limpiar):\n")

Valores únicos en 'region' (antes de limpiar):
cat(" ", paste(unique(ventas_crudas$region), collapse = " | "), "\n")
  norte | SUR | Norte | sur | NORTE 
cat("\nValores únicos en 'completada' (antes de limpiar):\n")

Valores únicos en 'completada' (antes de limpiar):
cat(" ", paste(unique(ventas_crudas$completada), collapse = " | "), "\n")
  SI | NO | NA 
# ── Outliers (IQR) ───────────────────────────────────────────────────────────
q1  <- quantile(ventas_crudas$monto, 0.25, na.rm = TRUE)
q3  <- quantile(ventas_crudas$monto, 0.75, na.rm = TRUE)
iqr <- q3 - q1
lim_sup <- q3 + 1.5 * iqr

cat("\nDetección de outliers en 'monto' (método IQR):\n")

Detección de outliers en 'monto' (método IQR):
cat("  Q1          = RD$", format(q1,      big.mark = ","), "\n")
  Q1          = RD$ 17,750 
cat("  Q3          = RD$", format(q3,      big.mark = ","), "\n")
  Q3          = RD$ 21,750 
cat("  IQR         = RD$", format(iqr,     big.mark = ","), "\n")
  IQR         = RD$ 4,000 
cat("  Límite sup  = RD$", format(lim_sup, big.mark = ","), "\n")
  Límite sup  = RD$ 27,750 
cat("  Outliers detectados:",
    sum(ventas_crudas$monto > lim_sup, na.rm = TRUE), "\n")
  Outliers detectados: 2 
cat("  Valor(es) outlier:",
    ventas_crudas$monto[ventas_crudas$monto > lim_sup], "\n")
  Valor(es) outlier: NA 31000 9500000 

Resumen del diagnóstico:

  • 1 fila duplicada: id_venta = 3 aparece dos veces (filas 3 y 11).
  • NAs: vendedor tiene 1 NA (fila 4), monto tiene 1 NA (fila 4), completada tiene 1 NA (fila 5).
  • Inconsistencias de texto: vendedor mezcla minúsculas, mayúsculas y espacios al final. region tiene el mismo problema.
  • Outlier: el valor 9,500,000 en monto (fila 8) supera el límite superior del IQR ampliamente; los demás valores rondan entre 15,000 y 31,000.

3 Parte 2 — Limpieza

# ── Paso 1: Calcular mediana del mes 3 (para imputar monto NA en mes 2) ──────
# El NA de monto está en la fila con mes = 2
mediana_mes2 <- ventas_crudas |>
  filter(mes == 2, !is.na(monto)) |>
  pull(monto) |>
  median()

cat("Mediana de monto en mes 2 (para imputación):", mediana_mes2, "\n")
Mediana de monto en mes 2 (para imputación): 23500 
# ── Paso 2: Pipeline de limpieza completo ────────────────────────────────────
ventas_limpias <- ventas_crudas |>

  # 1. Eliminar duplicados — conserva la primera ocurrencia de cada id_venta
  distinct(id_venta, .keep_all = TRUE) |>

  # 2. Limpiar texto: trim de espacios + estandarizar capitalización
  mutate(
    vendedor = str_trim(vendedor),             # quita espacios al inicio/fin
    vendedor = str_to_title(vendedor),         # Title Case: "Ana", "Pedro"…
    vendedor = replace_na(vendedor, "Desconocido"), # imputa NA con "Desconocido"

    region   = str_trim(region),
    region   = str_to_lower(region),           # lower case: "norte", "sur"
  ) |>

  # 3. Imputar NA de monto con mediana del mes correspondiente
  mutate(
    monto = if_else(is.na(monto), mediana_mes2, monto)
  ) |>

  # 4. Convertir 'completada' a factor, reemplazando NA con "NO"
  mutate(
    completada = replace_na(completada, "NO"),
    completada = factor(completada, levels = c("NO", "SI"))
  ) |>

  # 5. Marcar outliers usando IQR sobre el dataset ya sin duplicados
  mutate(
    q1_m      = quantile(monto, 0.25),
    q3_m      = quantile(monto, 0.75),
    iqr_m     = q3_m - q1_m,
    es_outlier = monto > (q3_m + 1.5 * iqr_m) | monto < (q1_m - 1.5 * iqr_m)
  ) |>
  select(-q1_m, -q3_m, -iqr_m)   # elimina columnas auxiliares del IQR

cat("\n=== Dataset limpio ===\n")

=== Dataset limpio ===
print(ventas_limpias)
# A tibble: 10 × 7
   id_venta vendedor    region   monto   mes completada es_outlier
      <dbl> <chr>       <chr>    <dbl> <dbl> <fct>      <lgl>     
 1        1 Ana         norte    15000     1 SI         FALSE     
 2        2 Pedro       sur      22000     1 SI         FALSE     
 3        3 María       norte    18500     1 NO         FALSE     
 4        4 Desconocido sur      23500     2 SI         FALSE     
 5        5 Carmen      norte    31000     2 NO         FALSE     
 6        6 Ana         norte    16000     2 SI         FALSE     
 7        7 Pedro       sur      19500     3 NO         FALSE     
 8        8 Luis        norte  9500000     3 SI         TRUE      
 9        9 Carmen      sur      21000     3 SI         FALSE     
10       10 Luis        norte    17500     3 NO         FALSE     

3.1 Verificación post-limpieza

cat("Filas originales:  ", nrow(ventas_crudas),  "\n")
Filas originales:   11 
cat("Filas limpias:     ", nrow(ventas_limpias), "\n")
Filas limpias:      10 
cat("Duplicados restantes:", sum(duplicated(ventas_limpias$id_venta)), "\n")
Duplicados restantes: 0 
cat("NAs restantes:     ", sum(is.na(ventas_limpias)), "\n")
NAs restantes:      0 
cat("\nNiveles de 'completada':", levels(ventas_limpias$completada), "\n")

Niveles de 'completada': NO SI 
cat("Outliers marcados: ", sum(ventas_limpias$es_outlier), "\n")
Outliers marcados:  1 
cat("\nValores únicos 'vendedor' (limpio):\n")

Valores únicos 'vendedor' (limpio):
cat(" ", paste(sort(unique(ventas_limpias$vendedor)), collapse = " | "), "\n")
  Ana | Carmen | Desconocido | Luis | María | Pedro 
cat("\nValores únicos 'region' (limpio):\n")

Valores únicos 'region' (limpio):
cat(" ", paste(sort(unique(ventas_limpias$region)), collapse = " | "), "\n")
  norte | sur 

4 Parte 3 — Transformación con dplyr

ventas_transformadas <- ventas_limpias |>

  # 1. Columna 'comision': 5% del monto si completada == "SI", 0 si no
  mutate(
    comision = if_else(completada == "SI", monto * 0.05, 0)
  ) |>

  # 2. Columna 'categoria_venta' con case_when()
  mutate(
    categoria_venta = case_when(
      monto <  15000 ~ "Baja",
      monto <= 25000 ~ "Media",
      TRUE           ~ "Alta"
    ),
    categoria_venta = factor(categoria_venta,
                             levels = c("Baja", "Media", "Alta"),
                             ordered = TRUE)
  ) |>

  # 3. Ranking por monto dentro de cada región (mayor monto = rank 1)
  group_by(region) |>
  mutate(
    ranking_region = rank(-monto, ties.method = "min")
  ) |>
  ungroup()

cat("=== Dataset transformado ===\n")
=== Dataset transformado ===
print(ventas_transformadas)
# A tibble: 10 × 10
   id_venta vendedor    region   monto   mes completada es_outlier comision
      <dbl> <chr>       <chr>    <dbl> <dbl> <fct>      <lgl>         <dbl>
 1        1 Ana         norte    15000     1 SI         FALSE           750
 2        2 Pedro       sur      22000     1 SI         FALSE          1100
 3        3 María       norte    18500     1 NO         FALSE             0
 4        4 Desconocido sur      23500     2 SI         FALSE          1175
 5        5 Carmen      norte    31000     2 NO         FALSE             0
 6        6 Ana         norte    16000     2 SI         FALSE           800
 7        7 Pedro       sur      19500     3 NO         FALSE             0
 8        8 Luis        norte  9500000     3 SI         TRUE         475000
 9        9 Carmen      sur      21000     3 SI         FALSE          1050
10       10 Luis        norte    17500     3 NO         FALSE             0
# ℹ 2 more variables: categoria_venta <ord>, ranking_region <int>

4.1 Vista de las columnas nuevas

ventas_transformadas |>
  select(id_venta, vendedor, region, monto, completada,
         comision, categoria_venta, ranking_region) |>
  arrange(region, ranking_region) |>
  print()
# A tibble: 10 × 8
   id_venta vendedor    region   monto completada comision categoria_venta
      <dbl> <chr>       <chr>    <dbl> <fct>         <dbl> <ord>          
 1        8 Luis        norte  9500000 SI           475000 Alta           
 2        5 Carmen      norte    31000 NO                0 Alta           
 3        3 María       norte    18500 NO                0 Media          
 4       10 Luis        norte    17500 NO                0 Media          
 5        6 Ana         norte    16000 SI              800 Media          
 6        1 Ana         norte    15000 SI              750 Media          
 7        4 Desconocido sur      23500 SI             1175 Media          
 8        2 Pedro       sur      22000 SI             1100 Media          
 9        9 Carmen      sur      21000 SI             1050 Media          
10        7 Pedro       sur      19500 NO                0 Media          
# ℹ 1 more variable: ranking_region <int>

5 Parte 4 — Resumen Analítico por Vendedor

resumen_vendedor <- ventas_transformadas |>
  # Excluir outlier del resumen de montos para no distorsionar promedios
  # (se indica con nota; se puede incluir si el instructor lo prefiere)
  group_by(vendedor) |>
  summarise(
    ventas_completadas  = sum(completada == "SI"),
    monto_total         = sum(monto),
    monto_promedio      = round(mean(monto), 0),
    comision_total      = sum(comision),
    n_ventas            = n(),
    .groups = "drop"
  ) |>
  arrange(desc(monto_total))

cat("=== Resumen analítico por vendedor ===\n")
=== Resumen analítico por vendedor ===
print(resumen_vendedor)
# A tibble: 6 × 6
  vendedor ventas_completadas monto_total monto_promedio comision_total n_ventas
  <chr>                 <int>       <dbl>          <dbl>          <dbl>    <int>
1 Luis                      1     9517500        4758750         475000        2
2 Carmen                    1       52000          26000           1050        2
3 Pedro                     1       41500          20750           1100        2
4 Ana                       2       31000          15500           1550        2
5 Descono…                  1       23500          23500           1175        1
6 María                     0       18500          18500              0        1

5.1 Resumen incluyendo categoría más frecuente

resumen_extendido <- ventas_transformadas |>
  group_by(vendedor) |>
  summarise(
    n_ventas           = n(),
    ventas_completadas = sum(completada == "SI"),
    pct_completadas    = round(mean(completada == "SI") * 100, 1),
    monto_total        = sum(monto),
    monto_promedio     = round(mean(monto), 0),
    comision_total     = sum(comision),
    categoria_frecuente = names(sort(table(categoria_venta), decreasing = TRUE))[1],
    .groups = "drop"
  ) |>
  arrange(desc(monto_total))

cat("=== Resumen extendido por vendedor ===\n")
=== Resumen extendido por vendedor ===
print(resumen_extendido)
# A tibble: 6 × 8
  vendedor    n_ventas ventas_completadas pct_completadas monto_total
  <chr>          <int>              <int>           <dbl>       <dbl>
1 Luis               2                  1              50     9517500
2 Carmen             2                  1              50       52000
3 Pedro              2                  1              50       41500
4 Ana                2                  2             100       31000
5 Desconocido        1                  1             100       23500
6 María              1                  0               0       18500
# ℹ 3 more variables: monto_promedio <dbl>, comision_total <dbl>,
#   categoria_frecuente <chr>

5.2 Resumen por región

cat("=== Resumen por región ===\n")
=== Resumen por región ===
ventas_transformadas |>
  group_by(region) |>
  summarise(
    n_ventas           = n(),
    monto_total        = sum(monto),
    monto_promedio     = round(mean(monto), 0),
    comision_total     = sum(comision),
    pct_completadas    = round(mean(completada == "SI") * 100, 1),
    .groups = "drop"
  ) |>
  arrange(desc(monto_total)) |>
  print()
# A tibble: 2 × 6
  region n_ventas monto_total monto_promedio comision_total pct_completadas
  <chr>     <int>       <dbl>          <dbl>          <dbl>           <dbl>
1 norte         6     9598000        1599667         476550              50
2 sur           4       86000          21500           3325              75

5.3 Distribución por categoría de venta

cat("=== Distribución por categoría de venta ===\n")
=== Distribución por categoría de venta ===
ventas_transformadas |>
  count(categoria_venta, completada) |>
  arrange(categoria_venta, completada) |>
  print()
# A tibble: 4 × 3
  categoria_venta completada     n
  <ord>           <fct>      <int>
1 Media           NO             3
2 Media           SI             5
3 Alta            NO             1
4 Alta            SI             1

Sesión de R:
R version: 4.6.0 
tidyverse: 2.0.0 
Fecha:     2026-06-05 

Práctica Semana 06 — Introducción al Lenguaje de Programación y Estadística R — ITLA T2 2026