Datos originales

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")
)

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

Parte 1 — Diagnóstico inicial

Estructura

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", "…

Estadísticas descriptivas

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  
## 

NAs por columna

colSums(is.na(ventas_crudas))
##   id_venta   vendedor     region      monto        mes completada 
##          0          1          0          1          0          1

Tres columnas tienen valores ausentes: vendedor (1), monto (1) y completada (1).

Duplicados

ventas_crudas |>
  count(id_venta) |>
  filter(n > 1)
id_venta n
3 2

id_venta = 3 aparece dos veces. Los valores en ambas filas son idénticos, por lo que se conserva la primera ocurrencia.

Outlier en monto

ventas_crudas |>
  select(id_venta, monto) |>
  arrange(desc(monto))
id_venta monto
8 9500000
5 31000
2 22000
9 21000
7 19500
3 18500
3 18500
10 17500
6 16000
1 15000
4 NA

id_venta = 8 tiene un monto de 9,500,000. El resto de los registros oscila entre 15,000 y 31,000. La diferencia es de aproximadamente 300 veces la mediana del resto, lo que confirma un outlier.


Parte 2 — Limpieza

La mediana por mes se calcula sobre ventas_crudas antes de cualquier filtro, para preservar la información disponible en los datos originales.

medianas_por_mes <- ventas_crudas |>
  group_by(mes) |>
  summarise(mediana_mes = median(monto, na.rm = TRUE))

ventas_limpias <- ventas_crudas |>

  # Eliminar duplicados: conservar primera ocurrencia de cada id_venta
  distinct(id_venta, .keep_all = TRUE) |>

  # Limpiar vendedor: trim de espacios + title case
  mutate(vendedor = str_to_title(str_trim(vendedor))) |>

  # Imputar NA de vendedor
  mutate(vendedor = replace_na(vendedor, "Desconocido")) |>

  # Limpiar region: trim + lower case consistente
  mutate(region = str_to_lower(str_trim(region))) |>

  # Unir medianas para imputar monto
  left_join(medianas_por_mes, by = "mes") |>

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

  # Eliminar columna auxiliar
  select(-mediana_mes) |>

  # Convertir completada a factor; NAs → "NO"
  mutate(
    completada = replace_na(completada, "NO"),
    completada = factor(completada, levels = c("SI", "NO"))
  ) |>

  # Marcar outlier con criterio IQR
  mutate(
    q1         = quantile(monto, 0.25),
    q3         = quantile(monto, 0.75),
    iqr        = q3 - q1,
    es_outlier = monto < (q1 - 1.5 * iqr) | monto > (q3 + 1.5 * iqr)
  ) |>
  select(-q1, -q3, -iqr)

glimpse(ventas_limpias)
## Rows: 10
## Columns: 7
## $ id_venta   <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
## $ vendedor   <chr> "Ana", "Pedro", "María", "Desconocido", "Carmen", "Ana", "P…
## $ region     <chr> "norte", "sur", "norte", "sur", "norte", "norte", "sur", "n…
## $ monto      <dbl> 15000, 22000, 18500, 23500, 31000, 16000, 19500, 9500000, 2…
## $ mes        <dbl> 1, 1, 1, 2, 2, 2, 3, 3, 3, 3
## $ completada <fct> SI, SI, NO, SI, NO, SI, NO, SI, SI, NO
## $ es_outlier <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, FALS…
ventas_limpias
id_venta vendedor region monto mes completada es_outlier
1 Ana norte 15000 1 SI FALSE
2 Pedro sur 22000 1 SI FALSE
3 María norte 18500 1 NO FALSE
4 Desconocido sur 23500 2 SI FALSE
5 Carmen norte 31000 2 NO FALSE
6 Ana norte 16000 2 SI FALSE
7 Pedro sur 19500 3 NO FALSE
8 Luis norte 9500000 3 SI TRUE
9 Carmen sur 21000 3 SI FALSE
10 Luis norte 17500 3 NO FALSE

Parte 3 — Transformación con dplyr

ventas_transformadas <- ventas_limpias |>

  # Comisión: 5% si la venta está completada, 0 si no
  mutate(comision = if_else(completada == "SI", monto * 0.05, 0)) |>

  # Categoría según monto
  mutate(categoria_venta = case_when(
    monto < 15000                    ~ "Baja",
    monto >= 15000 & monto <= 25000  ~ "Media",
    monto > 25000                    ~ "Alta"
  )) |>

  # Ranking por monto dentro de cada región
  group_by(region) |>
  mutate(ranking_region = rank(desc(monto), ties.method = "min")) |>
  ungroup()
ventas_transformadas |>
  select(id_venta, vendedor, region, monto, completada,
         comision, categoria_venta, ranking_region, es_outlier)
id_venta vendedor region monto completada comision categoria_venta ranking_region es_outlier
1 Ana norte 15000 SI 750 Media 6 FALSE
2 Pedro sur 22000 SI 1100 Media 2 FALSE
3 María norte 18500 NO 0 Media 3 FALSE
4 Desconocido sur 23500 SI 1175 Media 1 FALSE
5 Carmen norte 31000 NO 0 Alta 2 FALSE
6 Ana norte 16000 SI 800 Media 5 FALSE
7 Pedro sur 19500 NO 0 Media 4 FALSE
8 Luis norte 9500000 SI 475000 Alta 1 TRUE
9 Carmen sur 21000 SI 1050 Media 3 FALSE
10 Luis norte 17500 NO 0 Media 4 FALSE

Decisiones de diseño:

  • if_else() sobre case_when() para comision: la condición es binaria.
  • ties.method = "min" en rank(): en caso de empate, ambos vendedores reciben el rango menor.
  • El outlier mantiene su monto original. es_outlier lo señala sin modificar el valor, para que cada análisis posterior decida si incluirlo.

Parte 4 — Resumen analítico

resumen_vendedor <- ventas_transformadas |>
  group_by(vendedor) |>
  summarise(
    ventas_completadas = sum(completada == "SI"),
    monto_total        = sum(monto),
    monto_promedio     = mean(monto),
    comision_total     = sum(comision)
  ) |>
  arrange(desc(monto_total))

resumen_vendedor
vendedor ventas_completadas monto_total monto_promedio comision_total
Luis 1 9517500 4758750 475000
Carmen 1 52000 26000 1050
Pedro 1 41500 20750 1100
Ana 2 31000 15500 1550
Desconocido 1 23500 23500 1175
María 0 18500 18500 0
resumen_vendedor |>
  filter(vendedor != "Desconocido") |>
  mutate(vendedor = fct_reorder(vendedor, monto_total)) |>
  ggplot(aes(x = vendedor, y = monto_total, fill = comision_total)) +
  geom_col(width = 0.6) +
  coord_flip() +
  scale_y_continuous(labels = scales::comma) +
  scale_fill_gradient(low = "#c6dbef", high = "#2171b5", labels = scales::comma) +
  labs(
    title = "Monto total por vendedor",
    x     = NULL,
    y     = "Monto total",
    fill  = "Comisión total"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    panel.grid.major.y = element_blank(),
    plot.title         = element_text(face = "bold")
  )

Nota sobre el outlier. Luis tiene el monto total más alto por el registro de 9,500,000 (id_venta = 8, marcado como es_outlier = TRUE). Si se excluye ese registro, Carmen o Ana lideran el ranking. El gráfico usa todos los datos sin filtrar el outlier.