1 Introducción

La limpieza de datos es una etapa crítica dentro de cualquier proceso de análisis estadístico, ciencia de datos o modelado predictivo. Una base de datos rara vez llega en condiciones perfectas para ser analizada. En muchos casos aparecen valores inválidos, categorías mal registradas, fechas inconsistentes, errores aritméticos, campos vacíos y variables numéricas almacenadas como texto. Por esta razón, antes de realizar análisis descriptivos, inferenciales o construir modelos, es indispensable desarrollar un flujo de depuración riguroso y verificable.

En este documento se trabaja con la base dirty_cafe_sales.csv, la cual contiene registros de ventas de una cafetería. El propósito es mostrar un proceso de limpieza de datos claro, estructurado y reproducible en RStudio, explicando la lógica y la importancia de cada bloque de código.

A lo largo del proceso se siguen normalmente tres fases fundamentales:

  1. Diagnóstico y transformación.
  2. Verificación y validación.
  3. Construcción de la base final.

El valor de este enfoque es que permite conservar simultáneamente tres niveles del trabajo:

2 Fase 1. Diagnóstico y transformación

2.1 1. Cargar los datos

En primer lugar se carga la base de datos. El argumento stringsAsFactors = FALSE evita que las variables de texto se conviertan automáticamente en factores, lo que facilita la limpieza posterior.

# -------------------------------------------------
# 1. Cargar los datos
# -------------------------------------------------
datos <- read.csv("dirty_cafe_sales.csv", stringsAsFactors = FALSE)

# Exploración básica
dim(datos)
## [1] 10000     8
names(datos)
## [1] "Transaction.ID"   "Item"             "Quantity"         "Price.Per.Unit"  
## [5] "Total.Spent"      "Payment.Method"   "Location"         "Transaction.Date"
str(datos)
## 'data.frame':    10000 obs. of  8 variables:
##  $ Transaction.ID  : chr  "TXN_1961373" "TXN_4977031" "TXN_4271903" "TXN_7034554" ...
##  $ Item            : chr  "Coffee" "Cake" "Cookie" "Salad" ...
##  $ Quantity        : chr  "2" "4" "4" "2" ...
##  $ Price.Per.Unit  : chr  "2.0" "3.0" "1.0" "5.0" ...
##  $ Total.Spent     : chr  "4.0" "12.0" "ERROR" "10.0" ...
##  $ Payment.Method  : chr  "Credit Card" "Cash" "Credit Card" "UNKNOWN" ...
##  $ Location        : chr  "Takeaway" "In-store" "In-store" "UNKNOWN" ...
##  $ Transaction.Date: chr  "2023-09-08" "2023-05-16" "2023-07-19" "2023-04-27" ...
head(datos)
##   Transaction.ID     Item Quantity Price.Per.Unit Total.Spent Payment.Method
## 1    TXN_1961373   Coffee        2            2.0         4.0    Credit Card
## 2    TXN_4977031     Cake        4            3.0        12.0           Cash
## 3    TXN_4271903   Cookie        4            1.0       ERROR    Credit Card
## 4    TXN_7034554    Salad        2            5.0        10.0        UNKNOWN
## 5    TXN_3160411   Coffee        2            2.0         4.0 Digital Wallet
## 6    TXN_2602893 Smoothie        5            4.0        20.0    Credit Card
##   Location Transaction.Date
## 1 Takeaway       2023-09-08
## 2 In-store       2023-05-16
## 3 In-store       2023-07-19
## 4  UNKNOWN       2023-04-27
## 5 In-store       2023-06-11
## 6                2023-03-31

2.1.1 Importancia de este bloque

Este bloque es esencial porque permite realizar un diagnóstico inicial de la base. Las funciones utilizadas tienen los siguientes propósitos:

  • read.csv(...): importa el archivo CSV.
  • dim(datos): muestra el número de filas y columnas.
  • names(datos): permite identificar los nombres de las variables.
  • str(datos): muestra la estructura y el tipo de dato de cada variable.
  • head(datos): permite observar rápidamente los primeros registros.

Sin esta revisión inicial no sería posible decidir qué variables requieren transformación.

2.2 2. Copia de seguridad y versión de trabajo

Se crean dos objetos adicionales. Uno conserva la base tal como fue importada y el otro será el espacio de trabajo donde se aplicarán las transformaciones.

# -------------------------------------------------
# 2. Copia de seguridad y versión de trabajo
# -------------------------------------------------
datos_raw <- datos
datos_clean <- datos

2.2.1 Importancia de este bloque

Este paso responde a una buena práctica profesional: nunca limpiar directamente la base original. Mantener datos_raw permite auditar todo el proceso y comparar resultados. Trabajar con datos_clean permite transformar sin perder la referencia inicial.

2.3 3. Limpieza de variables numéricas

Las variables Quantity, Price.Per.Unit y Total.Spent deberían ser numéricas, pero con frecuencia llegan almacenadas como texto. En consecuencia, primero se limpian y luego se convierten a formato numérico.

# -------------------------------------------------
# 3. Limpieza de variables numéricas
# -------------------------------------------------
vars_num <- c("Quantity", "Price.Per.Unit", "Total.Spent")

for (v in vars_num) {

  # Convertir a carácter
  x <- as.character(datos_clean[[v]])

  # Quitar espacios
  x <- trimws(x)

  # Eliminar comas
  x <- gsub(",", "", x, fixed = TRUE)

  # Estandarizar para detectar inválidos
  x_upper <- toupper(x)

  # Reemplazar valores inválidos por NA
  x[x_upper == ""] <- NA
  x[x_upper == "ERROR"] <- NA
  x[x_upper == "UNKNOWN"] <- NA

  # Convertir a numérico
  datos_clean[[paste0(v, "_num")]] <- as.numeric(x)
}

# Verificación de variables numéricas
summary(datos_clean$Quantity_num)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   1.000   2.000   3.000   3.029   4.000   5.000     479
summary(datos_clean$Price.Per.Unit_num)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##    1.00    2.00    3.00    2.95    4.00    5.00     533
summary(datos_clean$Total.Spent_num)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   1.000   4.000   8.000   8.924  12.000  25.000     502
sum(is.na(datos_clean$Quantity_num))
## [1] 479
sum(is.na(datos_clean$Price.Per.Unit_num))
## [1] 533
sum(is.na(datos_clean$Total.Spent_num))
## [1] 502

2.3.1 Importancia de este bloque

Este bloque es clave dentro de la fase de transformación. Cada línea cumple una función específica:

  • vars_num <- c(...): define las variables que deben convertirse a formato numérico.
  • for (v in vars_num): evita repetir tres veces el mismo procedimiento.
  • as.character(...): garantiza que la variable pueda limpiarse como texto.
  • trimws(x): elimina espacios al inicio y al final.
  • gsub(",", "", ...): elimina comas que podrían impedir la conversión.
  • toupper(x): permite identificar valores inválidos sin importar si vienen en mayúsculas o minúsculas.
  • x[x_upper == "ERROR"] <- NA: transforma errores textuales en valores faltantes reales.
  • as.numeric(x): convierte la columna limpia en numérica.
  • paste0(v, "_num"): crea columnas auxiliares para no alterar todavía las variables originales.

Este enfoque es importante porque todavía estamos en una fase de diagnóstico y transformación, por lo que conviene mantener columnas auxiliares antes de construir la base final.

2.4 4. Limpieza de fecha

La variable Transaction.Date también requiere revisión. Se limpia como texto, se convierten valores inválidos a NA y luego se transforma a formato fecha.

# -------------------------------------------------
# 4. Limpieza de fecha
# -------------------------------------------------
d <- as.character(datos_clean$Transaction.Date)
d <- trimws(d)

d_upper <- toupper(d)
d[d_upper == ""] <- NA
d[d_upper == "UNKNOWN"] <- NA
d[d_upper == "ERROR"] <- NA

datos_clean$Transaction.Date <- d
datos_clean$Date <- as.Date(d)

# Verificación de fecha
summary(datos_clean$Date)
##         Min.      1st Qu.       Median         Mean      3rd Qu.         Max. 
## "2023-01-01" "2023-04-01" "2023-07-02" "2023-07-01" "2023-10-02" "2023-12-31" 
##         NA's 
##        "460"
sum(is.na(datos_clean$Date))
## [1] 460

2.4.1 Importancia de este bloque

Las fechas son fundamentales en análisis de ventas, tendencias y series temporales. Por tanto:

  • as.character(...) evita problemas si la variable entró con otro formato.
  • trimws(...) elimina espacios sobrantes.
  • la conversión de "", "UNKNOWN" y "ERROR" a NA evita errores de interpretación.
  • as.Date(d) transforma la variable a un formato que R puede usar para análisis temporal.

La nueva columna Date es una variable auxiliar validada que luego se conservará en la base final.

2.5 5. Verificación de consistencia aritmética

En una base de ventas, una verificación crítica consiste en comprobar si el gasto total coincide con la multiplicación entre cantidad y precio unitario.

# -------------------------------------------------
# 5. Verificación de consistencia aritmética
# -------------------------------------------------
datos_clean$Total_teorico <- datos_clean$Quantity_num * datos_clean$Price.Per.Unit_num
datos_clean$Dif_Total <- datos_clean$Total.Spent_num - datos_clean$Total_teorico

summary(datos_clean$Dif_Total)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##       0       0       0       0       0       0    1456
# Filas con inconsistencias
errores_total <- datos_clean[
  abs(datos_clean$Dif_Total) > 0.01 & !is.na(datos_clean$Dif_Total),
]

nrow(errores_total)
## [1] 0
errores_total[, c("Transaction.ID",
                  "Quantity_num",
                  "Price.Per.Unit_num",
                  "Total.Spent_num",
                  "Total_teorico",
                  "Dif_Total")]
## [1] Transaction.ID     Quantity_num       Price.Per.Unit_num Total.Spent_num   
## [5] Total_teorico      Dif_Total         
## <0 rows> (or 0-length row.names)
# Corregir Total.Spent cuando haya inconsistencia
datos_clean$Total.Spent_corregido <- ifelse(
  abs(datos_clean$Dif_Total) > 0.01,
  datos_clean$Total_teorico,
  datos_clean$Total.Spent_num
)

2.5.1 Importancia de este bloque

Este paso es central en la fase de verificación, porque evalúa la consistencia interna del dataset:

  • Total_teorico: calcula cuánto debería ser el total gastado.
  • Dif_Total: mide la diferencia entre el total registrado y el total esperado.
  • abs(datos_clean$Dif_Total) > 0.01: identifica discrepancias significativas.
  • errores_total: almacena las filas con inconsistencias para revisión.
  • ifelse(...): crea una versión corregida del total cuando se detecta error.

Aquí se evidencia claramente la segunda fase: verificación y validación.

2.6 6. Limpieza y estandarización de variables categóricas

Las variables categóricas clave son Item, Payment.Method y Location. Se convierten a minúsculas, se quitan espacios sobrantes y se reemplazan valores inválidos por NA.

# -------------------------------------------------
# 6. Limpieza y estandarización de variables categóricas
# -------------------------------------------------
cols_cat <- c("Item", "Payment.Method", "Location")

for (col in cols_cat) {

  x <- as.character(datos_clean[[col]])
  x <- tolower(x)
  x <- trimws(x)

  # Reducir dobles espacios
  x <- gsub("  ", " ", x, fixed = TRUE)

  # Reemplazar inválidos por NA
  x[x == ""] <- NA
  x[x == "unknown"] <- NA
  x[x == "error"] <- NA

  datos_clean[[col]] <- x
}

# Verificación de categóricas
table(datos_clean$Item, useNA = "ifany")
## 
##     cake   coffee   cookie    juice    salad sandwich smoothie      tea 
##     1139     1165     1092     1171     1148     1131     1096     1089 
##     <NA> 
##      969
table(datos_clean$Payment.Method, useNA = "ifany")
## 
##           cash    credit card digital wallet           <NA> 
##           2258           2273           2291           3178
table(datos_clean$Location, useNA = "ifany")
## 
## in-store takeaway     <NA> 
##     3017     3022     3961
unique(datos_clean$Item)
## [1] "coffee"   "cake"     "cookie"   "salad"    "smoothie" NA         "sandwich"
## [8] "juice"    "tea"
unique(datos_clean$Payment.Method)
## [1] "credit card"    "cash"           NA               "digital wallet"
unique(datos_clean$Location)
## [1] "takeaway" "in-store" NA

2.6.1 Importancia de este bloque

Las variables categóricas suelen contener errores de registro. Este bloque busca estandarizarlas:

  • tolower(...): evita que "Coffee" y "coffee" se traten como categorías diferentes.
  • trimws(...): elimina espacios invisibles que afectan tablas y conteos.
  • gsub(" ", " ", ...): reduce espacios dobles internos.
  • la sustitución por NA convierte valores inválidos en ausencias reales.
  • table(..., useNA = "ifany"): permite revisar si todavía quedan faltantes.
  • unique(...): ayuda a inspeccionar categorías finales.

Con esto se completa la fase de transformación.

3 Fase 2. Verificación y validación

3.1 7. Revisión final de calidad

En este punto se realiza una revisión general para comprobar que la limpieza aplicada sea coherente.

# -------------------------------------------------
# 7. Revisión final de calidad
# -------------------------------------------------

# NA por variable
colSums(is.na(datos_clean))
##        Transaction.ID                  Item              Quantity 
##                     0                   969                     0 
##        Price.Per.Unit           Total.Spent        Payment.Method 
##                     0                     0                  3178 
##              Location      Transaction.Date          Quantity_num 
##                  3961                   460                   479 
##    Price.Per.Unit_num       Total.Spent_num                  Date 
##                   533                   502                   460 
##         Total_teorico             Dif_Total Total.Spent_corregido 
##                   994                  1456                  1456
# Porcentaje de NA
round(colMeans(is.na(datos_clean)) * 100, 2)
##        Transaction.ID                  Item              Quantity 
##                  0.00                  9.69                  0.00 
##        Price.Per.Unit           Total.Spent        Payment.Method 
##                  0.00                  0.00                 31.78 
##              Location      Transaction.Date          Quantity_num 
##                 39.61                  4.60                  4.79 
##    Price.Per.Unit_num       Total.Spent_num                  Date 
##                  5.33                  5.02                  4.60 
##         Total_teorico             Dif_Total Total.Spent_corregido 
##                  9.94                 14.56                 14.56
# Revisar inválidos en columnas de texto
cols_text <- sapply(datos_clean, is.character)

sum(datos_clean[, cols_text] == "error", na.rm = TRUE)
## [1] 0
sum(datos_clean[, cols_text] == "unknown", na.rm = TRUE)
## [1] 0
# Revisar numéricas
summary(datos_clean$Quantity_num)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   1.000   2.000   3.000   3.029   4.000   5.000     479
summary(datos_clean$Price.Per.Unit_num)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##    1.00    2.00    3.00    2.95    4.00    5.00     533
summary(datos_clean$Total.Spent_num)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   1.000   4.000   8.000   8.924  12.000  25.000     502
summary(datos_clean$Total.Spent_corregido)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   1.000   4.000   8.000   8.929  12.000  25.000    1456
# Revisar fecha
summary(datos_clean$Date)
##         Min.      1st Qu.       Median         Mean      3rd Qu.         Max. 
## "2023-01-01" "2023-04-01" "2023-07-02" "2023-07-01" "2023-10-02" "2023-12-31" 
##         NA's 
##        "460"
# Revisar duplicados
sum(duplicated(datos_clean))
## [1] 0
sum(duplicated(datos_clean$Transaction.ID))
## [1] 0

3.1.1 Importancia de este bloque

Este bloque resume la fase de validación:

  • colSums(is.na(...)): cuantifica faltantes.
  • colMeans(is.na(...)): muestra el porcentaje de faltantes.
  • sapply(..., is.character): selecciona solo columnas de texto para buscar inválidos.
  • summary(...): revisa rangos y distribuciones básicas.
  • duplicated(...): detecta filas duplicadas e identificadores repetidos.

Si este bloque arroja resultados coherentes, entonces la base ya está lista para consolidarse.

4 Fase 3. Construcción de la base final

4.1 8. Construcción de la base final limpia

En esta fase se seleccionan solo las variables limpias y relevantes para análisis.

# -------------------------------------------------
# 8. Construcción de la base final limpia
# -------------------------------------------------
datos_final <- datos_clean[, c(
  "Transaction.ID",
  "Item",
  "Quantity_num",
  "Price.Per.Unit_num",
  "Total.Spent_corregido",
  "Payment.Method",
  "Location",
  "Date"
)]

# Renombrar variables
names(datos_final) <- c(
  "Transaction_ID",
  "Item",
  "Quantity",
  "Price_Per_Unit",
  "Total_Spent",
  "Payment_Method",
  "Location",
  "Date"
)

# Verificación inicial de datos_final
str(datos_final)
## 'data.frame':    10000 obs. of  8 variables:
##  $ Transaction_ID: chr  "TXN_1961373" "TXN_4977031" "TXN_4271903" "TXN_7034554" ...
##  $ Item          : chr  "coffee" "cake" "cookie" "salad" ...
##  $ Quantity      : num  2 4 4 2 2 5 3 4 5 5 ...
##  $ Price_Per_Unit: num  2 3 1 5 2 4 3 4 3 4 ...
##  $ Total_Spent   : num  4 12 NA 10 4 20 9 16 15 20 ...
##  $ Payment_Method: chr  "credit card" "cash" "credit card" NA ...
##  $ Location      : chr  "takeaway" "in-store" "in-store" NA ...
##  $ Date          : Date, format: "2023-09-08" "2023-05-16" ...
summary(datos_final)
##  Transaction_ID         Item              Quantity     Price_Per_Unit
##  Length:10000       Length:10000       Min.   :1.000   Min.   :1.00  
##  Class :character   Class :character   1st Qu.:2.000   1st Qu.:2.00  
##  Mode  :character   Mode  :character   Median :3.000   Median :3.00  
##                                        Mean   :3.029   Mean   :2.95  
##                                        3rd Qu.:4.000   3rd Qu.:4.00  
##                                        Max.   :5.000   Max.   :5.00  
##                                        NA's   :479     NA's   :533   
##   Total_Spent     Payment_Method       Location              Date           
##  Min.   : 1.000   Length:10000       Length:10000       Min.   :2023-01-01  
##  1st Qu.: 4.000   Class :character   Class :character   1st Qu.:2023-04-01  
##  Median : 8.000   Mode  :character   Mode  :character   Median :2023-07-02  
##  Mean   : 8.929                                         Mean   :2023-07-01  
##  3rd Qu.:12.000                                         3rd Qu.:2023-10-02  
##  Max.   :25.000                                         Max.   :2023-12-31  
##  NA's   :1456                                           NA's   :460
colSums(is.na(datos_final))
## Transaction_ID           Item       Quantity Price_Per_Unit    Total_Spent 
##              0            969            479            533           1456 
## Payment_Method       Location           Date 
##           3178           3961            460
head(datos_final)
##   Transaction_ID     Item Quantity Price_Per_Unit Total_Spent Payment_Method
## 1    TXN_1961373   coffee        2              2           4    credit card
## 2    TXN_4977031     cake        4              3          12           cash
## 3    TXN_4271903   cookie        4              1          NA    credit card
## 4    TXN_7034554    salad        2              5          10           <NA>
## 5    TXN_3160411   coffee        2              2           4 digital wallet
## 6    TXN_2602893 smoothie        5              4          20    credit card
##   Location       Date
## 1 takeaway 2023-09-08
## 2 in-store 2023-05-16
## 3 in-store 2023-07-19
## 4     <NA> 2023-04-27
## 5 in-store 2023-06-11
## 6     <NA> 2023-03-31

4.1.1 Importancia de este bloque

Aquí se materializa la tercera fase del proceso: construcción de la base final.

  • Se eliminan columnas auxiliares que ya no son necesarias.
  • Se seleccionan únicamente variables limpias.
  • Se renombran las variables para que sean más claras y consistentes.

Esta fase permite pasar de un dataset transformado (datos_clean) a un dataset analítico (datos_final).

4.2 9. Imputación de variables numéricas con la mediana

Se reemplazan los valores faltantes en Quantity y Price_Per_Unit usando la mediana. Luego se recalcula Total_Spent.

# -------------------------------------------------
# 9. Imputación de variables numéricas con la mediana
# -------------------------------------------------
mediana_quantity <- median(datos_final$Quantity, na.rm = TRUE)
datos_final$Quantity[is.na(datos_final$Quantity)] <- mediana_quantity

mediana_price <- median(datos_final$Price_Per_Unit, na.rm = TRUE)
datos_final$Price_Per_Unit[is.na(datos_final$Price_Per_Unit)] <- mediana_price

# Recalcular Total_Spent a partir de Quantity y Price_Per_Unit
datos_final$Total_Spent <- datos_final$Quantity * datos_final$Price_Per_Unit

4.2.1 Importancia de este bloque

La imputación con la mediana es apropiada en variables numéricas cuando se desea conservar registros y reducir el efecto de valores extremos. Este paso:

  • evita perder observaciones,
  • mantiene la estructura de la base,
  • y garantiza que Total_Spent se mantenga consistente tras la imputación.

4.3 10. Imputación de variables categóricas

En variables categóricas, una estrategia común consiste en reemplazar NA por la categoría "unknown".

# -------------------------------------------------
# 10. Imputación de variables categóricas
# -------------------------------------------------
datos_final$Payment_Method[is.na(datos_final$Payment_Method)] <- "unknown"
datos_final$Location[is.na(datos_final$Location)] <- "unknown"
datos_final$Item[is.na(datos_final$Item)] <- "unknown"

4.3.1 Importancia de este bloque

Esta estrategia permite:

  • conservar la observación,
  • no inventar categorías inexistentes,
  • y marcar explícitamente que el valor era desconocido.

4.4 11. Eliminar filas con fecha faltante

En este caso, se opta por eliminar filas donde no se dispone de la fecha, ya que una fecha ausente afecta análisis temporal, agregaciones por día, mes o tendencias.

# -------------------------------------------------
# 11. Eliminar filas con fecha faltante
# -------------------------------------------------
datos_final <- datos_final[!is.na(datos_final$Date), ]

4.4.1 Importancia de este bloque

La fecha es una variable estructural importante para el análisis de ventas. En lugar de imputar fechas artificialmente, se opta por eliminar esos registros para preservar la validez del análisis temporal.

4.5 12. Revisión final de la base lista para análisis

Finalmente se revisa la base resultante.

# -------------------------------------------------
# 12. Revisión final de la base lista para análisis
# -------------------------------------------------
colSums(is.na(datos_final))
## Transaction_ID           Item       Quantity Price_Per_Unit    Total_Spent 
##              0              0              0              0              0 
## Payment_Method       Location           Date 
##              0              0              0
summary(datos_final)
##  Transaction_ID         Item              Quantity     Price_Per_Unit 
##  Length:9540        Length:9540        Min.   :1.000   Min.   :1.000  
##  Class :character   Class :character   1st Qu.:2.000   1st Qu.:2.000  
##  Mode  :character   Mode  :character   Median :3.000   Median :3.000  
##                                        Mean   :3.024   Mean   :2.953  
##                                        3rd Qu.:4.000   3rd Qu.:4.000  
##                                        Max.   :5.000   Max.   :5.000  
##   Total_Spent     Payment_Method       Location              Date           
##  Min.   : 1.000   Length:9540        Length:9540        Min.   :2023-01-01  
##  1st Qu.: 4.000   Class :character   Class :character   1st Qu.:2023-04-01  
##  Median : 8.000   Mode  :character   Mode  :character   Median :2023-07-02  
##  Mean   : 8.936                                         Mean   :2023-07-01  
##  3rd Qu.:12.000                                         3rd Qu.:2023-10-02  
##  Max.   :25.000                                         Max.   :2023-12-31
str(datos_final)
## 'data.frame':    9540 obs. of  8 variables:
##  $ Transaction_ID: chr  "TXN_1961373" "TXN_4977031" "TXN_4271903" "TXN_7034554" ...
##  $ Item          : chr  "coffee" "cake" "cookie" "salad" ...
##  $ Quantity      : num  2 4 4 2 2 5 3 4 5 5 ...
##  $ Price_Per_Unit: num  2 3 1 5 2 4 3 4 3 4 ...
##  $ Total_Spent   : num  4 12 4 10 4 20 9 16 15 20 ...
##  $ Payment_Method: chr  "credit card" "cash" "credit card" "unknown" ...
##  $ Location      : chr  "takeaway" "in-store" "in-store" "unknown" ...
##  $ Date          : Date, format: "2023-09-08" "2023-05-16" ...
head(datos_final)
##   Transaction_ID     Item Quantity Price_Per_Unit Total_Spent Payment_Method
## 1    TXN_1961373   coffee        2              2           4    credit card
## 2    TXN_4977031     cake        4              3          12           cash
## 3    TXN_4271903   cookie        4              1           4    credit card
## 4    TXN_7034554    salad        2              5          10        unknown
## 5    TXN_3160411   coffee        2              2           4 digital wallet
## 6    TXN_2602893 smoothie        5              4          20    credit card
##   Location       Date
## 1 takeaway 2023-09-08
## 2 in-store 2023-05-16
## 3 in-store 2023-07-19
## 4  unknown 2023-04-27
## 5 in-store 2023-06-11
## 6  unknown 2023-03-31

5 Análisis Exploratorio de Datos (EDA) en RStudio con datos_final

Una vez que la base de datos ha sido limpiada y estructurada en el objeto datos_final, el siguiente paso natural es desarrollar un Análisis Exploratorio de Datos (EDA). Este proceso permite comprender la estructura del dataset, detectar patrones, identificar comportamientos relevantes, revisar distribuciones, observar relaciones entre variables y generar una base sólida para futuros modelos o análisis avanzados.

En este documento se desarrolla un EDA completo sobre datos_final, que corresponde a la versión final del conjunto de datos de ventas de cafetería. El análisis se organiza en varios bloques:

  1. Verificación inicial del dataset limpio.
  2. Estadística descriptiva de variables numéricas.
  3. Distribución de variables numéricas.
  4. Identificación de valores atípicos.
  5. Análisis de variables categóricas.
  6. Relación entre variables numéricas.
  7. Relación entre variables categóricas y numéricas.
  8. Análisis temporal de ventas.
  9. Análisis de ventas por producto, método de pago y ubicación.

6 1. Verificación inicial del dataset limpio

Antes de analizar, es indispensable confirmar que el objeto datos_final está correctamente estructurado.

dim(datos_final)
## [1] 9540    8
str(datos_final)
## 'data.frame':    9540 obs. of  8 variables:
##  $ Transaction_ID: chr  "TXN_1961373" "TXN_4977031" "TXN_4271903" "TXN_7034554" ...
##  $ Item          : chr  "coffee" "cake" "cookie" "salad" ...
##  $ Quantity      : num  2 4 4 2 2 5 3 4 5 5 ...
##  $ Price_Per_Unit: num  2 3 1 5 2 4 3 4 3 4 ...
##  $ Total_Spent   : num  4 12 4 10 4 20 9 16 15 20 ...
##  $ Payment_Method: chr  "credit card" "cash" "credit card" "unknown" ...
##  $ Location      : chr  "takeaway" "in-store" "in-store" "unknown" ...
##  $ Date          : Date, format: "2023-09-08" "2023-05-16" ...
summary(datos_final)
##  Transaction_ID         Item              Quantity     Price_Per_Unit 
##  Length:9540        Length:9540        Min.   :1.000   Min.   :1.000  
##  Class :character   Class :character   1st Qu.:2.000   1st Qu.:2.000  
##  Mode  :character   Mode  :character   Median :3.000   Median :3.000  
##                                        Mean   :3.024   Mean   :2.953  
##                                        3rd Qu.:4.000   3rd Qu.:4.000  
##                                        Max.   :5.000   Max.   :5.000  
##   Total_Spent     Payment_Method       Location              Date           
##  Min.   : 1.000   Length:9540        Length:9540        Min.   :2023-01-01  
##  1st Qu.: 4.000   Class :character   Class :character   1st Qu.:2023-04-01  
##  Median : 8.000   Mode  :character   Mode  :character   Median :2023-07-02  
##  Mean   : 8.936                                         Mean   :2023-07-01  
##  3rd Qu.:12.000                                         3rd Qu.:2023-10-02  
##  Max.   :25.000                                         Max.   :2023-12-31
head(datos_final)
##   Transaction_ID     Item Quantity Price_Per_Unit Total_Spent Payment_Method
## 1    TXN_1961373   coffee        2              2           4    credit card
## 2    TXN_4977031     cake        4              3          12           cash
## 3    TXN_4271903   cookie        4              1           4    credit card
## 4    TXN_7034554    salad        2              5          10        unknown
## 5    TXN_3160411   coffee        2              2           4 digital wallet
## 6    TXN_2602893 smoothie        5              4          20    credit card
##   Location       Date
## 1 takeaway 2023-09-08
## 2 in-store 2023-05-16
## 3 in-store 2023-07-19
## 4  unknown 2023-04-27
## 5 in-store 2023-06-11
## 6  unknown 2023-03-31
colSums(is.na(datos_final))
## Transaction_ID           Item       Quantity Price_Per_Unit    Total_Spent 
##              0              0              0              0              0 
## Payment_Method       Location           Date 
##              0              0              0

6.1 Importancia de este bloque

Este bloque permite confirmar el número de observaciones, revisar tipos de datos, comprobar si quedan valores faltantes e inspeccionar rápidamente los primeros registros.

7 2. Estadística descriptiva de variables numéricas

Las principales variables numéricas en esta base son Quantity, Price_Per_Unit y Total_Spent.

# Quantity
summary(datos_final$Quantity)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.000   2.000   3.000   3.024   4.000   5.000
mean(datos_final$Quantity)
## [1] 3.024109
median(datos_final$Quantity)
## [1] 3
sd(datos_final$Quantity)
## [1] 1.384186
# Price_Per_Unit
summary(datos_final$Price_Per_Unit)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.000   2.000   3.000   2.953   4.000   5.000
mean(datos_final$Price_Per_Unit)
## [1] 2.952673
median(datos_final$Price_Per_Unit)
## [1] 3
sd(datos_final$Price_Per_Unit)
## [1] 1.243449
# Total_Spent
summary(datos_final$Total_Spent)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.000   4.000   8.000   8.936  12.000  25.000
mean(datos_final$Total_Spent)
## [1] 8.935692
median(datos_final$Total_Spent)
## [1] 8
sd(datos_final$Total_Spent)
## [1] 5.827677

7.1 Importancia de este bloque

Este análisis permite conocer tendencia central, dispersión, posibles valores extremos y el rango general del comportamiento de las ventas.

8 3. Distribución de variables numéricas

Para comprender la forma de distribución de los datos se utilizan histogramas.

library(ggplot2)

8.1 Distribución de Quantity

ggplot(datos_final, aes(x = Quantity)) +
  geom_histogram(bins = 30) +
  labs(
    title = "Distribución de la Cantidad de Productos Vendidos",
    x = "Cantidad",
    y = "Frecuencia"
  ) +
  theme_minimal()

8.2 Distribución de Price_Per_Unit

ggplot(datos_final, aes(x = Price_Per_Unit)) +
  geom_histogram(bins = 30) +
  labs(
    title = "Distribución del Precio por Unidad",
    x = "Precio por unidad",
    y = "Frecuencia"
  ) +
  theme_minimal()

8.3 Distribución de Total_Spent

ggplot(datos_final, aes(x = Total_Spent)) +
  geom_histogram(bins = 30) +
  labs(
    title = "Distribución del Gasto Total",
    x = "Total gastado",
    y = "Frecuencia"
  ) +
  theme_minimal()

8.4 Importancia de este bloque

Los histogramas ayudan a detectar concentración de observaciones, sesgos, asimetrías y comportamientos generales de las ventas.

9 4. Identificación de valores atípicos

Los diagramas de caja permiten detectar posibles valores extremos y revisar la dispersión.

9.1 Boxplot de Quantity

ggplot(datos_final, aes(y = Quantity)) +
  geom_boxplot() +
  labs(
    title = "Boxplot de Quantity",
    y = "Cantidad"
  ) +
  theme_minimal()

9.2 Boxplot de Price_Per_Unit

ggplot(datos_final, aes(y = Price_Per_Unit)) +
  geom_boxplot() +
  labs(
    title = "Boxplot de Price_Per_Unit",
    y = "Precio por unidad"
  ) +
  theme_minimal()

9.3 Boxplot de Total_Spent

ggplot(datos_final, aes(y = Total_Spent)) +
  geom_boxplot() +
  labs(
    title = "Boxplot de Total_Spent",
    y = "Total gastado"
  ) +
  theme_minimal()

9.4 Importancia de este bloque

Los boxplots ayudan a identificar valores atípicos, dispersión general y posibles observaciones extremas.

10 5. Análisis de variables categóricas

Las variables categóricas permiten analizar frecuencias y patrones de compra.

10.1 Frecuencia de productos vendidos

table(datos_final$Item)
## 
##     cake   coffee   cookie    juice    salad sandwich smoothie      tea 
##     1082     1123     1035     1124     1099     1075     1048     1027 
##  unknown 
##      927
ggplot(datos_final, aes(x = Item)) +
  geom_bar() +
  labs(
    title = "Frecuencia de Productos Vendidos",
    x = "Producto",
    y = "Número de ventas"
  ) +
  theme_minimal()

10.2 Distribución de métodos de pago

table(datos_final$Payment_Method)
## 
##           cash    credit card digital wallet        unknown 
##           2158           2170           2197           3015
ggplot(datos_final, aes(x = Payment_Method)) +
  geom_bar() +
  labs(
    title = "Distribución de Métodos de Pago",
    x = "Método de pago",
    y = "Número de transacciones"
  ) +
  theme_minimal()

10.3 Distribución por ubicación

table(datos_final$Location)
## 
## in-store takeaway  unknown 
##     2872     2889     3779
ggplot(datos_final, aes(x = Location)) +
  geom_bar() +
  labs(
    title = "Distribución de Ventas por Ubicación",
    x = "Ubicación",
    y = "Número de ventas"
  ) +
  theme_minimal()

10.4 Importancia de este bloque

Este análisis permite identificar productos más frecuentes, métodos de pago más utilizados y ubicaciones con mayor actividad comercial.

11 6. Relación entre variables numéricas

Las relaciones entre variables cuantitativas permiten explorar dependencias y patrones de comportamiento.

11.1 Quantity vs Total_Spent

ggplot(datos_final, aes(x = Quantity, y = Total_Spent)) +
  geom_point(alpha = 0.5) +
  labs(
    title = "Relación entre Cantidad y Gasto Total",
    x = "Cantidad",
    y = "Total gastado"
  ) +
  theme_minimal()

11.2 Price_Per_Unit vs Total_Spent

ggplot(datos_final, aes(x = Price_Per_Unit, y = Total_Spent)) +
  geom_point(alpha = 0.5) +
  labs(
    title = "Relación entre Precio por Unidad y Gasto Total",
    x = "Precio por unidad",
    y = "Total gastado"
  ) +
  theme_minimal()

11.3 Importancia de este bloque

Los gráficos de dispersión permiten observar relaciones positivas o negativas, patrones lineales y posibles estructuras internas.

12 7. Relación entre variables categóricas y numéricas

Aquí se analiza cómo varía el gasto total en función de categorías.

12.1 Gasto total por producto

ggplot(datos_final, aes(x = Item, y = Total_Spent)) +
  geom_boxplot() +
  labs(
    title = "Distribución del Gasto Total por Producto",
    x = "Producto",
    y = "Total gastado"
  ) +
  theme_minimal()

12.2 Gasto total por método de pago

ggplot(datos_final, aes(x = Payment_Method, y = Total_Spent)) +
  geom_boxplot() +
  labs(
    title = "Distribución del Gasto por Método de Pago",
    x = "Método de pago",
    y = "Total gastado"
  ) +
  theme_minimal()

12.3 Gasto total por ubicación

ggplot(datos_final, aes(x = Location, y = Total_Spent)) +
  geom_boxplot() +
  labs(
    title = "Distribución del Gasto por Ubicación",
    x = "Ubicación",
    y = "Total gastado"
  ) +
  theme_minimal()

12.4 Importancia de este bloque

Este análisis permite evaluar si ciertos productos, métodos de pago o ubicaciones concentran mayores niveles de gasto.

13 8. Análisis temporal de ventas

La dimensión temporal es fundamental en un dataset de ventas. Para ello se agregan los datos por fecha.

ventas_por_dia <- aggregate(
  Total_Spent ~ Date,
  data = datos_final,
  sum
)

head(ventas_por_dia)
##         Date Total_Spent
## 1 2023-01-01       180.0
## 2 2023-01-02       180.5
## 3 2023-01-03       206.0
## 4 2023-01-04       262.5
## 5 2023-01-05       337.5
## 6 2023-01-06       258.0

13.1 Serie temporal de ventas

ggplot(ventas_por_dia, aes(x = Date, y = Total_Spent)) +
  geom_line() +
  labs(
    title = "Ventas Totales por Día",
    x = "Fecha",
    y = "Total de ventas"
  ) +
  theme_minimal()

13.2 Importancia de este bloque

Permite observar la evolución de las ventas en el tiempo, detectar picos o caídas y estudiar la estabilidad del comportamiento diario.

14 9. Top productos por ventas

Además de la frecuencia de transacciones, resulta útil analizar qué productos generan más ingresos.

ventas_producto <- aggregate(
  Total_Spent ~ Item,
  data = datos_final,
  sum
)

ventas_producto
##       Item Total_Spent
## 1     cake      9834.0
## 2   coffee      6999.0
## 3   cookie      3447.0
## 4    juice     10053.0
## 5    salad     16198.0
## 6 sandwich     12892.0
## 7 smoothie     12643.0
## 8      tea      4984.5
## 9  unknown      8196.0

14.1 Gráfico de ventas totales por producto

ggplot(ventas_producto, aes(x = reorder(Item, Total_Spent), y = Total_Spent)) +
  geom_bar(stat = "identity") +
  coord_flip() +
  labs(
    title = "Ventas Totales por Producto",
    x = "Producto",
    y = "Total vendido"
  ) +
  theme_minimal()

14.2 Importancia de este bloque

Permite distinguir entre productos más frecuentes y productos que generan mayor valor económico.

15 10. Indicadores básicos de negocio

15.1 Ticket promedio

mean(datos_final$Total_Spent)
## [1] 8.935692

15.2 Ventas totales

sum(datos_final$Total_Spent)
## [1] 85246.5

15.3 Número de transacciones por producto

aggregate(Transaction_ID ~ Item, data = datos_final, length)
##       Item Transaction_ID
## 1     cake           1082
## 2   coffee           1123
## 3   cookie           1035
## 4    juice           1124
## 5    salad           1099
## 6 sandwich           1075
## 7 smoothie           1048
## 8      tea           1027
## 9  unknown            927

15.4 Ventas por método de pago

aggregate(Total_Spent ~ Payment_Method, data = datos_final, sum)
##   Payment_Method Total_Spent
## 1           cash     19621.0
## 2    credit card     19476.5
## 3 digital wallet     19703.0
## 4        unknown     26446.0

15.5 Ventas por ubicación

aggregate(Total_Spent ~ Location, data = datos_final, sum)
##   Location Total_Spent
## 1 in-store     25906.5
## 2 takeaway     25521.5
## 3  unknown     33818.5