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.
# 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
# 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
datos_raw <- datos # respaldo de los datos originales
datos_clean <- datos # versión que será modificada (limpia)
En esta sección se transforman las columnas que deben ser numéricas: - Quantity - Price.Per.Unit - Total.Spent
# 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
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
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
# 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"
# 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
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)
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
# 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