1 Introducción

Esta guía presenta un flujo completo de limpieza y análisis exploratorio aplicado a la base de datos dirty_cafe_sales.csv, correspondiente a ventas de una cafetería.
El objetivo es mostrar, paso a paso, cómo:

Todo el trabajo se realiza con funciones de Base R.

2 Carga de datos y exploración inicial

# Cargar los datos
datos <- read.csv("dirty_cafe_sales.csv")

datos <- as.data.frame(unclass(datos), stringsAsFactors = TRUE)

# Exploración básica
dim(datos)          # Número de filas y columnas
## [1] 10000     8
names(datos)        # Nombres de las variables
## [1] "Transaction.ID"   "Item"             "Quantity"         "Price.Per.Unit"  
## [5] "Total.Spent"      "Payment.Method"   "Location"         "Transaction.Date"
str(datos)          # Estructura interna
## 'data.frame':    10000 obs. of  8 variables:
##  $ Transaction.ID  : Factor w/ 10000 levels "TXN_1000555",..: 1057 4474 3699 6755 2437 1836 3874 6379 4212 1190 ...
##  $ Item            : Factor w/ 11 levels "","Cake","Coffee",..: 3 2 4 7 3 9 11 8 1 8 ...
##  $ Quantity        : Factor w/ 8 levels "","1","2","3",..: 3 5 5 3 3 6 4 5 6 6 ...
##  $ Price.Per.Unit  : Factor w/ 9 levels "","1.0","1.5",..: 4 5 2 7 4 6 5 6 5 6 ...
##  $ Total.Spent     : Factor w/ 20 levels "","1.0","1.5",..: 12 5 19 4 12 9 18 7 6 9 ...
##  $ Payment.Method  : Factor w/ 6 levels "","Cash","Credit Card",..: 3 2 3 6 4 3 5 2 1 1 ...
##  $ Location        : Factor w/ 5 levels "","ERROR","In-store",..: 4 3 3 5 3 1 4 5 4 3 ...
##  $ Transaction.Date: Factor w/ 368 levels "","2023-01-01",..: 252 137 201 118 163 91 280 302 210 366 ...
head(datos, 10)     # Primeras observaciones
##    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
## 7     TXN_4433211  UNKNOWN        3            3.0         9.0          ERROR
## 8     TXN_6699534 Sandwich        4            4.0        16.0           Cash
## 9     TXN_4717867                 5            3.0        15.0               
## 10    TXN_2064365 Sandwich        5            4.0        20.0               
##    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
## 7  Takeaway       2023-10-06
## 8   UNKNOWN       2023-10-28
## 9  Takeaway       2023-07-28
## 10 In-store       2023-12-31
summary(datos)      # Resumen estadístico
##      Transaction.ID       Item         Quantity    Price.Per.Unit
##  TXN_1000555:   1   Juice   :1171   5      :2013   3.0    :2429  
##  TXN_1001832:   1   Coffee  :1165   2      :1974   4.0    :2331  
##  TXN_1002457:   1   Salad   :1148   4      :1863   2.0    :1227  
##  TXN_1003246:   1   Cake    :1139   3      :1849   5.0    :1204  
##  TXN_1004184:   1   Sandwich:1131   1      :1822   1.0    :1143  
##  TXN_1004563:   1   Smoothie:1096   UNKNOWN: 171   1.5    :1133  
##  (Other)    :9994   (Other) :3150   (Other): 308   (Other): 533  
##   Total.Spent          Payment.Method     Location      Transaction.Date
##  6.0    : 979                 :2579           :3265             : 159   
##  12.0   : 939   Cash          :2258   ERROR   : 358   UNKNOWN   : 159   
##  3.0    : 930   Credit Card   :2273   In-store:3017   ERROR     : 142   
##  4.0    : 923   Digital Wallet:2291   Takeaway:3022   2023-02-06:  40   
##  20.0   : 746   ERROR         : 306   UNKNOWN : 338   2023-06-16:  40   
##  15.0   : 734   UNKNOWN       : 293                   2023-03-13:  39   
##  (Other):4749                                         (Other)   :9421

3 Diagnóstico de valores faltantes

# Conteo de NA por columna
colSums(is.na(datos))
##   Transaction.ID             Item         Quantity   Price.Per.Unit 
##                0                0                0                0 
##      Total.Spent   Payment.Method         Location Transaction.Date 
##                0                0                0                0
# Conteo total de NA
sum(is.na(datos))
## [1] 0

4 Copia de seguridad y versión de trabajo

datos_raw   <- datos       # respaldo de los datos originales
datos_clean <- datos       # versión que será modificada (limpia)

5 Conversión y limpieza de variables numéricas

En esta sección se transforman las columnas que deben ser numéricas: - Quantity - Price.Per.Unit - Total.Spent

5.1 Limpieza de Quantity

# Asegurar que Quantity es texto
q <- as.character(datos_clean$Quantity)

# Eliminar comas si las hubiera
q <- gsub(",", "", q, fixed = TRUE)

# Convertir a numérico
datos_clean$Quantity_num <- suppressWarnings(as.numeric(q))

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

5.2 Limpieza de Price Per Unit

p <- as.character(datos_clean$Price.Per.Unit)

# Eliminar símbolo de dólar (si aparece)
p <- gsub("$", "", p, fixed = TRUE)

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

# Eliminar espacios
p <- gsub(" ", "", p, fixed = TRUE)

# Convertir a numérico
datos_clean$Price_num <- suppressWarnings(as.numeric(p))

# Resumen
summary(datos_clean$Price_num)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##    1.00    2.00    3.00    2.95    4.00    5.00     533

5.3 Limpieza de Total Spent

t <- as.character(datos_clean$Total.Spent)

# Eliminar símbolo de dólar
t <- gsub("$", "", t, fixed = TRUE)

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

# Eliminar espacios
t <- gsub(" ", "", t, fixed = TRUE)

# Convertir a numérico
datos_clean$Total_num <- suppressWarnings(as.numeric(t))

# Resumen
summary(datos_clean$Total_num)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   1.000   4.000   8.000   8.924  12.000  25.000     502

6 Conversión y limpieza de fechas

# Asegurar que Transaction.Date es texto
d <- as.character(datos_clean$Transaction.Date)

# Reemplazar doble espacio por uno (por seguridad)
d <- gsub("  ", " ", d, fixed = TRUE)

# Reemplazar "UNKNOWN" por NA
d[d == "UNKNOWN"] <- NA

# Asignar de nuevo
datos_clean$Transaction.Date <- d

# Convertir a tipo Date (suponiendo formato AAAA-MM-DD)
datos_clean$Date <- as.Date(datos_clean$Transaction.Date)

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"

7 Limpieza y estandarización de texto en variables categóricas

# Variables categóricas clave
cols_cat <- c("Item", "Payment.Method", "Location")

for (col in cols_cat) {
  # Convertir a minúsculas
  datos_clean[[col]] <- tolower(as.character(datos_clean[[col]]))

  # Eliminar espacios al inicio y al final
  datos_clean[[col]] <- trimws(datos_clean[[col]])

  # Reducir múltiples espacios internos a uno solo
  datos_clean[[col]] <- gsub("  ", " ", datos_clean[[col]], fixed = TRUE)
}

# Reemplazar "unknown" por NA en campos clave
datos_clean$Payment.Method[datos_clean$Payment.Method == "unknown"] <- NA
datos_clean$Location[datos_clean$Location == "unknown"]             <- NA

# Frecuencias
table(datos_clean$Item, useNA = "ifany")
## 
##              cake   coffee   cookie    error    juice    salad sandwich 
##      333     1139     1165     1092      292     1171     1148     1131 
## smoothie      tea  unknown 
##     1096     1089      344
table(datos_clean$Payment.Method, useNA = "ifany")
## 
##                          cash    credit card digital wallet          error 
##           2579           2258           2273           2291            306 
##           <NA> 
##            293
table(datos_clean$Location, useNA = "ifany")
## 
##             error in-store takeaway     <NA> 
##     3265      358     3017     3022      338

8 Verificación aritmética interna

datos_clean$Total_teorico <- datos_clean$Quantity_num * datos_clean$Price_num
datos_clean$Dif_Total     <- datos_clean$Total_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 discrepancias importantes
errores_total <- datos_clean[which(abs(datos_clean$Dif_Total) > 0.01 &
                                   !is.na(datos_clean$Dif_Total)), ]

nrow(errores_total)
## [1] 0
head(errores_total, 10)
##  [1] Transaction.ID   Item             Quantity         Price.Per.Unit  
##  [5] Total.Spent      Payment.Method   Location         Transaction.Date
##  [9] Quantity_num     Price_num        Total_num        Date            
## [13] Total_teorico    Dif_Total       
## <0 rows> (or 0-length row.names)

9 Manejo de valores faltantes en la versión final

datos_final <- subset(
  datos_clean,
  !is.na(Item) &
  !is.na(Quantity_num) &
  !is.na(Price_num) &
  !is.na(Total_num) &
  !is.na(Date)
)

dim(datos_final)
## [1] 8159   14
colSums(is.na(datos_final))
##   Transaction.ID             Item         Quantity   Price.Per.Unit 
##                0                0                0                0 
##      Total.Spent   Payment.Method         Location Transaction.Date 
##                0              238              269                0 
##     Quantity_num        Price_num        Total_num             Date 
##                0                0                0                0 
##    Total_teorico        Dif_Total 
##                0                0

10 Análisis descriptivo de la base limpia

# Ventas totales
ventas_totales   <- sum(datos_final$Total_num)
ventas_totales
## [1] 72704.5
# Ticket promedio
ticket_promedio  <- mean(datos_final$Total_num)
ticket_promedio
## [1] 8.910957
# Productos más vendidos
tabla_items <- sort(table(datos_final$Item), decreasing = TRUE)
tabla_items
## 
##    juice   coffee     cake    salad sandwich smoothie   cookie      tea 
##      972      969      943      937      915      888      881      877 
##  unknown             error 
##      270      266      241
# Ingreso total por producto
ingreso_por_item <- tapply(datos_final$Total_num,
                           datos_final$Item,
                           sum)
sort(ingreso_por_item, decreasing = TRUE)
##    salad sandwich smoothie    juice     cake   coffee      tea   cookie 
##  14195.0  10992.0  10888.0   8631.0   8577.0   5936.0   3976.5   2630.0 
##           unknown    error 
##   2420.0   2266.0   2193.0
# Ventas por método de pago
ventas_por_pago <- tapply(datos_final$Total_num,
                          datos_final$Payment.Method,
                          sum,
                          na.rm = TRUE)
ventas_por_pago
##                          cash    credit card digital wallet          error 
##        18133.5        16733.0        16748.0        16918.0         2120.5
# Ventas por ubicación
ventas_por_ubicacion <- tapply(datos_final$Total_num,
                               datos_final$Location,
                               sum,
                               na.rm = TRUE)
ventas_por_ubicacion
##             error in-store takeaway 
##  23941.5   2633.5  22240.0  21615.0
# Ventas diarias
ventas_diarias <- tapply(datos_final$Total_num,
                         datos_final$Date,
                         sum)

ventas_diarias_df <- data.frame(
  Date   = as.Date(names(ventas_diarias)),
  Ventas = as.numeric(ventas_diarias)
)

head(ventas_diarias_df)
##         Date Ventas
## 1 2023-01-01  127.5
## 2 2023-01-02  137.5
## 3 2023-01-03  128.0
## 4 2023-01-04  238.5
## 5 2023-01-05  295.5
## 6 2023-01-06  207.0
summary(ventas_diarias_df$Ventas)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    75.5   162.0   197.5   199.2   236.0   342.5