Una cadena de hoteles con sedes en Cali, Bogotá, Medellín, Cartagena y Santa Marta quiere entender mejor el comportamiento de sus huéspedes: qué canales de reserva generan más cancelaciones, cómo cambian las tarifas según la ciudad y el tipo de habitación, y qué factores se relacionan con la satisfacción. Este reporte retoma el análisis del Laboratorio 2 (limpieza de datos y preguntas de negocio) y lo presenta de forma informe, para compartirlo con el equipo mediante un enlace.
Objetivo general: explorar la relación entre las variables de las reservas del hotel, con el fin de identificar patrones relevantes para la gestión de la cadena hotelera.
Objetivos específicos:
Se utilizó una base de datos de reservas de la cadena hotelera, con las siguientes variables:
| Variable | Descripcion |
|---|---|
| id_reserva | Identificador de la reserva |
| ciudad_hotel | Ciudad donde está el hotel |
| tipo_habitacion | Tipo de habitación (Sencilla/Doble/Suite) |
| canal_reserva | Canal por el que se hizo la reserva |
| cliente_frecuente | Si el cliente es frecuente (Si/No) |
| cancelada | Si la reserva fue cancelada (Si/No) |
| anticipacion_dias | Días de anticipación con que se hizo la reserva |
| noches | Número de noches reservadas |
| tarifa_por_noche_miles | Tarifa por noche, en miles de pesos |
| calificacion_satisfaccion | Calificación de satisfacción del huésped |
Para cada par de variables se utilizó la técnica adecuada según su tipo:
Se importó el archivo CSV indicando que los campos vacíos se
reconozcan como NA.
library(dplyr)
library(stringr)
library(janitor)
library(readr)
library(stringi)
library(ggplot2)
library(corrplot)
basedatos2 <- read.csv("reservas_hotel_laboratorio.csv",
na.strings = c("", "NA"),
stringsAsFactors = FALSE)
La base contiene 668 reservas y 10 variables en total.
Se exploró la estructura del dataset para identificar si alguna columna no quedó con el tipo de dato que debería tener.
str(basedatos2)
'data.frame': 668 obs. of 10 variables:
$ id_reserva : int 1468 1524 1410 1215 1149 1402 1049 1316 1171 1092 ...
$ ciudad_hotel : chr "Bogota" "Medellin" "Bogota" "Medellin" ...
$ tipo_habitacion : chr "Doble" "Doble" "Sencilla" "Sencilla" ...
$ canal_reserva : chr "Web" "Telefonico" "Telefonico" "Agencia" ...
$ cliente_frecuente : chr "No" "No" "no" "No" ...
$ anticipacion_dias : num 4 9 8 1 17 4 34 4 9 19 ...
$ noches : int 5 4 3 1 1 5 3 4 4 1 ...
$ tarifa_por_noche_miles : num 227 277 140 176 162 159 218 156 161 173 ...
$ cancelada : chr "No" "No" "No" "SI" ...
$ calificacion_satisfaccion: num 7.2 8.1 6.1 NA NA 7 6.3 7.6 NA 6.4 ...
kable(summary(basedatos2), caption = "Resumen de los datos originales")
| id_reserva | ciudad_hotel | tipo_habitacion | canal_reserva | cliente_frecuente | anticipacion_dias | noches | tarifa_por_noche_miles | cancelada | calificacion_satisfaccion | |
|---|---|---|---|---|---|---|---|---|---|---|
| Min. :1001 | Length :668 | Length :668 | Length :668 | Length :668 | Min. :-10.00 | Min. : 1.000 | Min. :-208.0 | Length :668 | Min. : 4.00 | |
| 1st Qu.:1163 | N.unique : 21 | N.unique : 12 | N.unique : 4 | N.unique : 9 | 1st Qu.: 5.00 | 1st Qu.: 3.000 | 1st Qu.: 177.0 | N.unique : 6 | 1st Qu.: 6.60 | |
| Median :1326 | N.blank : 0 | N.blank : 0 | N.blank : 0 | N.blank : 0 | Median : 12.00 | Median : 4.000 | Median : 230.0 | N.blank : 0 | Median : 7.40 | |
| Mean :1326 | Min.nchar: 4 | Min.nchar: 5 | Min.nchar: 3 | Min.nchar: 1 | Mean : 17.38 | Mean : 4.555 | Mean : 262.2 | Min.nchar: 2 | Mean : 7.42 | |
| 3rd Qu.:1488 | Max.nchar: 11 | Max.nchar: 9 | Max.nchar: 10 | Max.nchar: 2 | 3rd Qu.: 25.00 | 3rd Qu.: 5.000 | 3rd Qu.: 311.2 | Max.nchar: 2 | 3rd Qu.: 8.20 | |
| Max. :1650 | NAs : 39 | Max. :113.00 | Max. :88.000 | Max. :3942.0 | Max. :10.00 | |||||
| NAs :139 |
Se buscaron filas idénticas ignorando la columna
id_reserva, porque un mismo registro repetido puede tener
un identificador distinto.
sum(duplicated(basedatos2[, -1])) # duplicados ignorando la columna id
[1] 18
kable(basedatos2[duplicated(basedatos2[, -1]) | duplicated(basedatos2[, -1], fromLast = TRUE), ],
caption = "Registros duplicados encontrados")
| id_reserva | ciudad_hotel | tipo_habitacion | canal_reserva | cliente_frecuente | anticipacion_dias | noches | tarifa_por_noche_miles | cancelada | calificacion_satisfaccion | |
|---|---|---|---|---|---|---|---|---|---|---|
| 4 | 1215 | Medellin | Sencilla | Agencia | No | 1 | 1 | 176 | SI | |
| 5 | 1149 | Sta Marta | Sencilla | Telefonico | Si | 17 | 1 | 162 | Si | |
| 36 | 1601 | Bogota | Doble | Agencia | No | 9 | 2 | 225 | no | 8.8 |
| 67 | 1024 | Santa Marta | Doble | Web | Sí | 4 | 2 | 313 | No | 8.6 |
| 96 | 1476 | Cali | Doble | Agencia | No | 1 | 2 | 223 | No | 5.8 |
| 101 | 1102 | bogota | Suite | Web | S | 7 | 4 | 414 | No | 7.0 |
| 123 | 1590 | Medellin | Doble | Agencia | No | 12 | 4 | 276 | No | 8.9 |
| 141 | 1117 | medellin | Doble | Web | No | 26 | 3 | 256 | No | 7.5 |
| 170 | 1067 | bogota | Doble | Web | Si | 5 | 2 | 172 | No | 7.8 |
| 237 | 1359 | Cali | Sencilla | Web | NO | 35 | 2 | 191 | NO | 8.6 |
| 246 | 1437 | Medellin | Sencilla | Web | No | 15 | 4 | 167 | No | 7.4 |
| 265 | 1243 | Bogota | Sencilla | Agencia | no | 2 | 4 | 209 | No | 6.9 |
| 266 | 1067 | bogota | Doble | Web | Si | 5 | 2 | 172 | No | 7.8 |
| 270 | 1117 | medellin | Doble | Web | No | 26 | 3 | 256 | No | 7.5 |
| 275 | 1386 | Bogota | Suite | Agencia | Si | 19 | 1 | 353 | No | 6.8 |
| 314 | 1024 | Santa Marta | Doble | Web | Sí | 4 | 2 | 313 | No | 8.6 |
| 327 | 1237 | Cartagena | Doble | Web | N | 0 | 4 | 314 | No | 6.2 |
| 330 | 1466 | Bogota | Doble | Agencia | No | 26 | 4 | 188 | No | 7.4 |
| 333 | 1590 | Medellin | Doble | Agencia | No | 12 | 4 | 276 | No | 8.9 |
| 335 | 1215 | Medellin | Sencilla | Agencia | No | 1 | 1 | 176 | SI | |
| 349 | 1648 | CALI | Sencilla | Agencia | NO | 37 | 5 | 124 | No | 7.0 |
| 398 | 1466 | Bogota | Doble | Agencia | No | 26 | 4 | 188 | No | 7.4 |
| 409 | 1237 | Cartagena | Doble | Web | N | 0 | 4 | 314 | No | 6.2 |
| 411 | 1601 | Bogota | Doble | Agencia | No | 9 | 2 | 225 | no | 8.8 |
| 428 | 1648 | CALI | Sencilla | Agencia | NO | 37 | 5 | 124 | No | 7.0 |
| 453 | 1437 | Medellin | Sencilla | Web | No | 15 | 4 | 167 | No | 7.4 |
| 463 | 1635 | Santa Marta | Doble | Agencia | No | 2 | 5 | 272 | Si | |
| 499 | 1359 | Cali | Sencilla | Web | NO | 35 | 2 | 191 | NO | 8.6 |
| 513 | 1580 | Bogota | Sencilla | Web | N | 7 | 3 | 128 | No | 8.8 |
| 538 | 1476 | Cali | Doble | Agencia | No | 1 | 2 | 223 | No | 5.8 |
| 554 | 1580 | Bogota | Sencilla | Web | N | 7 | 3 | 128 | No | 8.8 |
| 573 | 1149 | Sta Marta | Sencilla | Telefonico | Si | 17 | 1 | 162 | Si | |
| 578 | 1243 | Bogota | Sencilla | Agencia | no | 2 | 4 | 209 | No | 6.9 |
| 663 | 1386 | Bogota | Suite | Agencia | Si | 19 | 1 | 353 | No | 6.8 |
| 664 | 1635 | Santa Marta | Doble | Agencia | No | 2 | 5 | 272 | Si | |
| 668 | 1102 | bogota | Suite | Web | S | 7 | 4 | 414 | No | 7.0 |
Se elimina el duplicado, conservando la primera aparición:
# Se elimina el duplicado, conservando la primera aparición
basedatos3 <- basedatos2 %>% distinct(across(-id_reserva), .keep_all = TRUE)
dim(basedatos3) # ya debe tener una fila menos
[1] 650 10
Las variables de texto tenían la misma categoría escrita de varias formas (mayúsculas, espacios sobrantes, tildes y abreviaturas). En cada una se quitaron los espacios, se pasó a minúsculas, se eliminaron las tildes y se recodificó a una etiqueta única.
#ciudad_hotel
kable(table(basedatos3$ciudad_hotel, useNA = "ifany"),
caption = "Ciudad del hotel antes de limpiar") # ver todas las formas en que aparece
| Var1 | Freq |
|---|---|
| Bogota | 7 |
| Cali | 10 |
| bogota | 8 |
| Bogota | 154 |
| BOGOTA | 8 |
| Bogotá | 4 |
| cali | 5 |
| Cali | 79 |
| CALI | 6 |
| Cartagena | 108 |
| CARTAGENA | 9 |
| cartagena | 9 |
| Cartagena | 6 |
| medellin | 13 |
| Medellin | 120 |
| MEDELLIN | 8 |
| Medellín | 6 |
| santa marta | 7 |
| Santa Marta | 72 |
| SANTA MARTA | 7 |
| Sta Marta | 4 |
basedatos3 <- basedatos3 %>%
mutate(
ciudad_hotel = str_trim(ciudad_hotel), # quita espacios sobrantes
ciudad_hotel = str_to_lower(ciudad_hotel), # todo a minúscula
ciudad_hotel = stri_trans_general(ciudad_hotel, "Latin-ASCII"), #quitar las tildes
ciudad_hotel = recode(ciudad_hotel,
"cali" = "Cali",
"bogota" = "Bogota", "bogota" = "Bogotá",
"cartagena" = "Cartagena",
"medellin" = "Medellin", "medellin" = "Medellín", "medellin" = "medellín",
"santa marta" = "Santa Marta", "sta marta" = "Santa Marta")
)
kable(table(basedatos3$ciudad_hotel, useNA = "ifany"),
caption = "Ciudad del hotel después de limpiar")
| Var1 | Freq |
|---|---|
| Bogota | 181 |
| Cali | 100 |
| Cartagena | 132 |
| Medellin | 147 |
| Santa Marta | 90 |
| Var1 | Freq |
|---|---|
| Doble | 21 |
| doble | 20 |
| Doble | 234 |
| DOBLE | 8 |
| sencilla | 9 |
| Sencilla | 223 |
| SENCILLA | 13 |
| Sencilla | 15 |
| suite | 3 |
| Suite | 96 |
| SUITE | 5 |
| Suite | 3 |
| Var1 | Freq |
|---|---|
| Doble | 283 |
| Sencilla | 260 |
| Suite | 107 |
Esta variable ya venía con categorías consistentes, por lo que no requirió transformaciones.
#canal_reserva
kable(table(basedatos3$canal_reserva, useNA = "ifany"),
caption = "Canal de reserva")
| Var1 | Freq |
|---|---|
| Agencia | 152 |
| Telefonico | 101 |
| Walk-in | 100 |
| Web | 258 |
| 39 |
Venía escrita de varias formas (s, si,
n, no, con distintas mayúsculas y tildes). Se
unificó en “Si” y “No”.
| Var1 | Freq |
|---|---|
| N | 40 |
| no | 25 |
| No | 371 |
| NO | 34 |
| S | 5 |
| si | 5 |
| Si | 149 |
| SI | 6 |
| Sí | 15 |
| Var1 | Freq |
|---|---|
| No | 470 |
| Si | 180 |
Se aplicó el mismo procedimiento a la variable
cancelada.
| Var1 | Freq |
|---|---|
| no | 35 |
| No | 449 |
| NO | 45 |
| si | 6 |
| Si | 104 |
| SI | 11 |
| Var1 | Freq |
|---|---|
| No | 529 |
| Si | 121 |
Con las variables ya estandarizadas, se revisó de nuevo la estructura y el resumen del dataset para buscar valores imposibles o atípicos.
#1.1.5 Revisar datos para buscar imposibles o atípicos
str(basedatos3)
'data.frame': 650 obs. of 10 variables:
$ id_reserva : int 1468 1524 1410 1215 1149 1402 1049 1316 1171 1092 ...
$ ciudad_hotel : chr "Bogota" "Medellin" "Bogota" "Medellin" ...
$ tipo_habitacion : chr "Doble" "Doble" "Sencilla" "Sencilla" ...
$ canal_reserva : chr "Web" "Telefonico" "Telefonico" "Agencia" ...
$ cliente_frecuente : chr "No" "No" "No" "No" ...
$ anticipacion_dias : num 4 9 8 1 17 4 34 4 9 19 ...
$ noches : int 5 4 3 1 1 5 3 4 4 1 ...
$ tarifa_por_noche_miles : num 227 277 140 176 162 159 218 156 161 173 ...
$ cancelada : chr "No" "No" "No" "Si" ...
$ calificacion_satisfaccion: num 7.2 8.1 6.1 NA NA 7 6.3 7.6 NA 6.4 ...
kable(summary(basedatos3), caption = "Resumen del dataset después de estandarizar")
| id_reserva | ciudad_hotel | tipo_habitacion | canal_reserva | cliente_frecuente | anticipacion_dias | noches | tarifa_por_noche_miles | cancelada | calificacion_satisfaccion | |
|---|---|---|---|---|---|---|---|---|---|---|
| Min. :1001 | Length :650 | Length :650 | Length :650 | Length :650 | Min. :-10.00 | Min. : 1.0 | Min. :-208.0 | Length :650 | Min. : 4.000 | |
| 1st Qu.:1163 | N.unique : 5 | N.unique : 3 | N.unique : 4 | N.unique : 2 | 1st Qu.: 5.00 | 1st Qu.: 3.0 | 1st Qu.: 178.0 | N.unique : 2 | 1st Qu.: 6.600 | |
| Median :1326 | N.blank : 0 | N.blank : 0 | N.blank : 0 | N.blank : 0 | Median : 12.00 | Median : 4.0 | Median : 231.0 | N.blank : 0 | Median : 7.400 | |
| Mean :1326 | Min.nchar: 4 | Min.nchar: 5 | Min.nchar: 3 | Min.nchar: 2 | Mean : 17.51 | Mean : 4.6 | Mean : 263.0 | Min.nchar: 2 | Mean : 7.416 | |
| 3rd Qu.:1488 | Max.nchar: 11 | Max.nchar: 8 | Max.nchar: 10 | Max.nchar: 2 | 3rd Qu.: 25.00 | 3rd Qu.: 5.0 | 3rd Qu.: 311.8 | Max.nchar: 2 | 3rd Qu.: 8.200 | |
| Max. :1650 | NAs : 39 | Max. :113.00 | Max. :88.0 | Max. :3942.0 | Max. :10.000 | |||||
| NAs :136 |
Algunas variables numéricas tenían valores negativos, que no tienen
sentido en este contexto: no se puede reservar con anticipación negativa
ni cobrar una tarifa negativa. Se convirtieron en NA para
no distorsionar los análisis.
#anticipacion_dias
kable(basedatos3 %>% filter(anticipacion_dias < 0 | is.na(anticipacion_dias)),
caption = "Registros con anticipación negativa o faltante")
| id_reserva | ciudad_hotel | tipo_habitacion | canal_reserva | cliente_frecuente | anticipacion_dias | noches | tarifa_por_noche_miles | cancelada | calificacion_satisfaccion |
|---|---|---|---|---|---|---|---|---|---|
| 1510 | Cali | Doble | Web | No | -4 | 4 | 260 | No | 7.4 |
| 1439 | Santa Marta | Sencilla | Web | No | -3 | 5 | 224 | Si | |
| 1239 | Cartagena | Sencilla | Web | No | -7 | 6 | 276 | No | 8.4 |
| 1014 | Bogota | Doble | Agencia | Si | -7 | 2 | 292 | Si | |
| 1532 | Bogota | Doble | Agencia | No | -10 | 6 | 248 | Si | |
| 1628 | Cartagena | Sencilla | Agencia | No | -9 | 5 | 237 | No | 6.7 |
basedatos3 <- basedatos3 %>%
mutate(anticipacion_dias = if_else(anticipacion_dias < 0, NA_real_, anticipacion_dias))
kable(t(unclass(summary(basedatos3$anticipacion_dias))), digits = 2,
caption = "Resumen de anticipacion_dias después de corregir")
| Min. | 1st Qu. | Median | Mean | 3rd Qu. | Max. | NAs |
|---|---|---|---|---|---|---|
| 0 | 5 | 12 | 17.74 | 25 | 113 | 6 |
#tarifa_por_noche_miles
kable(basedatos3 %>% filter(tarifa_por_noche_miles < 0 | is.na(tarifa_por_noche_miles)),
caption = "Registros con tarifa negativa o faltante")
| id_reserva | ciudad_hotel | tipo_habitacion | canal_reserva | cliente_frecuente | anticipacion_dias | noches | tarifa_por_noche_miles | cancelada | calificacion_satisfaccion |
|---|---|---|---|---|---|---|---|---|---|
| 1346 | Medellin | Sencilla | Walk-in | No | 2 | 5 | -125 | No | 6.6 |
| 1556 | Bogota | Sencilla | Agencia | Si | 6 | 63 | -65 | No | 8.0 |
| 1623 | Medellin | Doble | Telefonico | No | 3 | 3 | -208 | No | 6.9 |
basedatos3 <- basedatos3 %>%
mutate(tarifa_por_noche_miles = if_else(tarifa_por_noche_miles < 0, NA_real_, tarifa_por_noche_miles))
kable(t(unclass(summary(basedatos3$tarifa_por_noche_miles))), digits = 2,
caption = "Resumen de tarifa_por_noche_miles después de corregir")
| Min. | 1st Qu. | Median | Mean | 3rd Qu. | Max. | NAs |
|---|---|---|---|---|---|---|
| 60 | 178 | 231 | 264.85 | 312.5 | 3942 | 3 |
kable(summary(basedatos3), caption = "Resumen general después de corregir valores imposibles")
| id_reserva | ciudad_hotel | tipo_habitacion | canal_reserva | cliente_frecuente | anticipacion_dias | noches | tarifa_por_noche_miles | cancelada | calificacion_satisfaccion | |
|---|---|---|---|---|---|---|---|---|---|---|
| Min. :1001 | Length :650 | Length :650 | Length :650 | Length :650 | Min. : 0.00 | Min. : 1.0 | Min. : 60.0 | Length :650 | Min. : 4.000 | |
| 1st Qu.:1163 | N.unique : 5 | N.unique : 3 | N.unique : 4 | N.unique : 2 | 1st Qu.: 5.00 | 1st Qu.: 3.0 | 1st Qu.: 178.0 | N.unique : 2 | 1st Qu.: 6.600 | |
| Median :1326 | N.blank : 0 | N.blank : 0 | N.blank : 0 | N.blank : 0 | Median : 12.00 | Median : 4.0 | Median : 231.0 | N.blank : 0 | Median : 7.400 | |
| Mean :1326 | Min.nchar: 4 | Min.nchar: 5 | Min.nchar: 3 | Min.nchar: 2 | Mean : 17.74 | Mean : 4.6 | Mean : 264.9 | Min.nchar: 2 | Mean : 7.416 | |
| 3rd Qu.:1488 | Max.nchar: 11 | Max.nchar: 8 | Max.nchar: 10 | Max.nchar: 2 | 3rd Qu.: 25.00 | 3rd Qu.: 5.0 | 3rd Qu.: 312.5 | Max.nchar: 2 | 3rd Qu.: 8.200 | |
| Max. :1650 | NAs : 39 | Max. :113.00 | Max. :88.0 | Max. :3942.0 | Max. :10.000 | |||||
| NAs :6 | NAs :3 | NAs :136 |
#revisar los atípicos
#anticipacion dias
kable(psych::describe(basedatos3$anticipacion_dias), digits = 2,
caption = "Estadísticos descriptivos de anticipacion_dias")
| vars | n | mean | sd | median | trimmed | mad | min | max | range | skew | kurtosis | se | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| X1 | 1 | 644 | 17.74 | 17.43 | 12 | 14.89 | 13.34 | 0 | 113 | 113 | 1.78 | 4.39 | 0.69 |
kable(t(quantile(basedatos3$anticipacion_dias, probs = c(0.25, 0.5, 0.75), na.rm = TRUE)),
caption = "Cuartiles de anticipacion_dias")
| 25% | 50% | 75% |
|---|---|---|
| 5 | 12 | 25 |
hist(basedatos3$anticipacion_dias, breaks = 8, col = "lightblue",
main = "Distribución de Anticipación Días", xlab = "Anticipación dias")
boxplot(
basedatos3$anticipacion_dias,
main = "Boxplot anticipación días",
ylab = "Días"
)
Sí hay datos atípicos en esta variable, pero no son extraños en el contexto, pues es viable que una persona reserve con muchos días de anticipación una habitación de hotel. Por eso se conservan.
#noches
kable(psych::describe(basedatos3$noches), digits = 2,
caption = "Estadísticos descriptivos de noches")
| vars | n | mean | sd | median | trimmed | mad | min | max | range | skew | kurtosis | se | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| X1 | 1 | 650 | 4.6 | 6.05 | 4 | 3.98 | 1.48 | 1 | 88 | 87 | 10.17 | 114.06 | 0.24 |
kable(t(quantile(basedatos3$noches, probs = c(0.25, 0.5, 0.75), na.rm = TRUE)),
caption = "Cuartiles de noches")
| 25% | 50% | 75% |
|---|---|---|
| 3 | 4 | 5 |
hist(basedatos3$noches, breaks = 8, col = "lightblue",
main = "Distribución de Noches por cliente", xlab = "Noches")
boxplot(
basedatos3$noches,
main = "Boxplot Noches por cliente",
ylab = "Noches"
)
Se decidió eliminar los datos mayores a 60 noches porque se
consideran datos imposibles y pueden entorpecer el análisis posterior de
la variable. Por tal razón, se convierten en NA.
basedatos3 <- basedatos3 %>%
mutate(noches = if_else(noches > 60, NA_real_, noches))
kable(t(unclass(summary(basedatos3$noches))), digits = 2,
caption = "Resumen de noches después de corregir")
| Min. | 1st Qu. | Median | Mean | 3rd Qu. | Max. | NAs |
|---|---|---|---|---|---|---|
| 1 | 3 | 4 | 4.1 | 5 | 11 | 5 |
boxplot(
basedatos3$noches,
main = "Boxplot Noches por cliente",
ylab = "Noches"
)
#tarifa_por_noche_miles
kable(psych::describe(basedatos3$tarifa_por_noche_miles), digits = 2,
caption = "Estadísticos descriptivos de tarifa_por_noche_miles")
| vars | n | mean | sd | median | trimmed | mad | min | max | range | skew | kurtosis | se | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| X1 | 1 | 647 | 264.85 | 216.16 | 231 | 242.58 | 91.92 | 60 | 3942 | 3882 | 10.72 | 155.22 | 8.5 |
kable(t(quantile(basedatos3$tarifa_por_noche_miles, probs = c(0.25, 0.5, 0.75), na.rm = TRUE)),
caption = "Cuartiles de tarifa_por_noche_miles")
| 25% | 50% | 75% |
|---|---|---|
| 178 | 231 | 312.5 |
hist(basedatos3$tarifa_por_noche_miles, breaks = 8, col = "lightblue",
main = "Distribución de Tarifa por noche", xlab = "Precio en miles")
boxplot(
basedatos3$tarifa_por_noche_miles,
main = "Boxplot Tarifa por noche",
ylab = "Precio en miles"
)
Se considera posible un valor de hasta 1.000 (miles) por noche en
casos de suites de lujo. Sin embargo, los valores superiores a ese rango
resultan extraños en el contexto de la variable y pueden entorpecer los
análisis posteriores. Por tal razón, los valores mayores a 1.000 se
convierten en NA.
basedatos3 <- basedatos3 %>%
mutate(tarifa_por_noche_miles = if_else(tarifa_por_noche_miles > 1000, NA_real_, tarifa_por_noche_miles))
kable(t(unclass(summary(basedatos3$tarifa_por_noche_miles))), digits = 2,
caption = "Resumen de tarifa_por_noche_miles después de corregir")
| Min. | 1st Qu. | Median | Mean | 3rd Qu. | Max. | NAs |
|---|---|---|---|---|---|---|
| 60 | 178 | 230.5 | 249.65 | 309 | 837 | 8 |
boxplot(
basedatos3$tarifa_por_noche_miles,
main = "Boxplot Tarifa por noche",
ylab = "Precio en miles"
)
Después de la limpieza, el dataset quedó con 650 reservas y 10 variables.
¿La probabilidad de que una reserva se cancele depende del canal por el que se hizo? ¿Hay algún canal que debería revisar la cadena hotelera por su alta tasa de cancelación?
Como cancelada y canal_reserva son
cualitativas, se realiza un análisis con barras apiladas.
#Relacionar: cancelada con canal_reserva, como son cualitativas realizaremos un análisis con barras apiladas
tabla_gc <- table(basedatos3$cancelada, basedatos3$canal_reserva)
kable(tabla_gc, caption = "Reservas según cancelación (filas) y canal de reserva (columnas)")
| Agencia | Telefonico | Walk-in | Web | |
|---|---|---|---|---|
| No | 96 | 83 | 90 | 227 |
| Si | 56 | 18 | 10 | 31 |
kable(round(prop.table(tabla_gc, margin = 1), 3),
caption = "Frecuencias condicionales por fila")
| Agencia | Telefonico | Walk-in | Web | |
|---|---|---|---|---|
| No | 0.194 | 0.167 | 0.181 | 0.458 |
| Si | 0.487 | 0.157 | 0.087 | 0.270 |
ggplot(dplyr::filter(basedatos3, !is.na(canal_reserva)), aes(x = canal_reserva, fill = cancelada)) +
geom_bar(position = "fill", na.rm = TRUE) +
geom_text(
aes(label = after_stat(
scales::percent(..count.. / tapply(..count.., ..x.., sum)[..x..])
)),
stat = "count",
position = position_fill(vjust = 0.5)
) +
scale_y_continuous(
labels = scales::percent,
limits = c(0, 1)
) +
labs(
title = "Cancelaciones según Canal de Reserva",
x = "Canal de reserva",
y = "Porcentaje",
fill = "Cancelaciones"
) +
scale_fill_manual(
values = c(
"No" = "#4C78A8",
"Si" = "#F58518")
) +
theme_minimal()
No tenemos suficiente información para asegurar que un canal de reserva influye del todo en que una reserva se cancele o no. Sin embargo, es notorio que las reservas que más se cancelan son las que se hicieron por el canal “Agencia”, mientras que las que menos se cancelan son las que se hacen por el canal “Web”.
La modalidad “Walk-in” es la que tiene menos porcentaje de cancelaciones porque tiene sentido que si un cliente se acerca directamente a tomar el servicio, no lo cancele.
En conclusión, se recomienda fortalecer el canal “Web” y revisar el canal “Agencia” para descubrir si hay alguna razón por la cual el 37% de las reservas realizadas se han cancelado. Por ejemplo, un tema de servicio al cliente.
¿Reservar con mucha anticipación se relaciona con pagar una tarifa distinta por noche? ¿Y con una mayor probabilidad de cancelar?
Primero se relaciona la anticipación con la tarifa; luego, la anticipación con la cancelación. Se empieza con la matriz de correlación entre las variables cuantitativas.
vars_cuant <- c("anticipacion_dias", "noches",
"tarifa_por_noche_miles", "calificacion_satisfaccion")
matriz_cor <- cor(basedatos3[, vars_cuant], use = "complete.obs")
kable(round(matriz_cor, 3), caption = "Matriz de correlación entre variables cuantitativas")
| anticipacion_dias | noches | tarifa_por_noche_miles | calificacion_satisfaccion | |
|---|---|---|---|---|
| anticipacion_dias | 1.000 | 0.011 | 0.014 | 0.005 |
| noches | 0.011 | 1.000 | 0.012 | 0.073 |
| tarifa_por_noche_miles | 0.014 | 0.012 | 1.000 | 0.299 |
| calificacion_satisfaccion | 0.005 | 0.073 | 0.299 | 1.000 |
corrplot(matriz_cor,
method = "color", type = "upper", order = "hclust",
addCoef.col = "black", tl.col = "black", tl.srt = 45,
title = "Matriz de correlación (orden por clustering)",
mar = c(0, 0, 2, 0))
Este gráfico nos mostró que la correlación entre las variables cuantitativas no es muy fuerte. Ahora se revisa la relación entre anticipación y tarifa.
ggplot(basedatos3, aes(x = anticipacion_dias, y = tarifa_por_noche_miles)) +
geom_point(alpha = 0.5, color = "#028090") +
geom_smooth(method = "lm", se = FALSE, color = "firebrick") +
geom_smooth(method = "loess", se = FALSE, color = "darkorange", linetype = "dashed") +
labs(title = "Anticipacion de Reserva vs. Tarifa por noche: ajuste lineal (rojo) vs. loess (naranja)",
x = "Días de anticipación", y = "Tarifa por noche (miles)") +
theme_minimal()
cor(basedatos3$anticipacion_dias, basedatos3$tarifa_por_noche_miles,
method = "pearson", use = "complete.obs")
[1] -0.02546293
cor(basedatos3$anticipacion_dias, basedatos3$tarifa_por_noche_miles,
method = "spearman", use = "complete.obs")
[1] -0.03321859
De acuerdo con el gráfico, se puede concluir que no hay correlación entre las variables días de anticipación para la reserva y tarifa por noche.
A continuación se revisa la relación entre los días de anticipación y la cancelación. Al ser una cuantitativa y una cualitativa, se propone el siguiente gráfico.
ggplot(basedatos3, aes(x = cancelada, y = anticipacion_dias, fill = cancelada)) +
geom_violin(alpha = 0.6, trim = FALSE) +
geom_boxplot(width = 0.12, fill = "white", outlier.shape = NA) +
labs(title = "Cancelaciones según anticipación de la reserva",
x = "Cancelaciones", y = "Días de anticipación de la reserva") +
theme_minimal() +
theme(legend.position = "none")
kable(basedatos3 %>%
group_by(cancelada) %>%
summarise(
media = mean(anticipacion_dias, na.rm = TRUE),
mediana = median(anticipacion_dias, na.rm = TRUE),
de = sd(anticipacion_dias, na.rm = TRUE),
n = n(),
n_validos = sum(!is.na(anticipacion_dias))
), digits = 2, caption = "Anticipación (días) según cancelación")
| cancelada | media | mediana | de | n | n_validos |
|---|---|---|---|---|---|
| No | 16.51 | 12 | 15.94 | 529 | 526 |
| Si | 23.19 | 16 | 22.20 | 121 | 118 |
El gráfico muestra mayor dispersión en los datos del “sí”; además, muestra que la mayoría de reservas se hacen con poca anticipación. Después de ver este gráfico, se puede concluir que no hay una diferencia entre los días de anticipación de la reserva con el hecho de cancelar o no una reserva.
¿La tarifa por noche cambia según el tipo de habitación y según la ciudad del hotel? ¿En qué ciudad(es) y tipo(s) de habitación se concentran las tarifas más altas?
media_ct <- basedatos3 %>%
filter(!is.na(tarifa_por_noche_miles)) %>%
group_by(ciudad_hotel, tipo_habitacion) %>%
summarise(media = mean(tarifa_por_noche_miles),
n = n(), .groups = "drop")
kable(media_ct, digits = 1, caption = "Tarifa promedio por noche según ciudad y tipo de habitación")
| ciudad_hotel | tipo_habitacion | media | n |
|---|---|---|---|
| Bogota | Doble | 216.0 | 78 |
| Bogota | Sencilla | 155.2 | 71 |
| Bogota | Suite | 386.5 | 31 |
| Cali | Doble | 210.2 | 41 |
| Cali | Sencilla | 163.7 | 40 |
| Cali | Suite | 365.6 | 18 |
| Cartagena | Doble | 316.4 | 52 |
| Cartagena | Sencilla | 242.9 | 54 |
| Cartagena | Suite | 468.1 | 23 |
| Medellin | Doble | 222.3 | 67 |
| Medellin | Sencilla | 164.6 | 54 |
| Medellin | Suite | 391.1 | 23 |
| Santa Marta | Doble | 303.5 | 42 |
| Santa Marta | Sencilla | 218.3 | 37 |
| Santa Marta | Suite | 430.8 | 11 |
ggplot(media_ct, aes(x = tipo_habitacion, y = ciudad_hotel, fill = media)) +
geom_tile(color = "white") +
geom_text(aes(label = round(media, 1),
color = media > mean(media)), size = 4) +
scale_color_manual(values = c("TRUE" = "white", "FALSE" = "black"),
guide = "none") +
scale_fill_gradient(low = "#F0F9F4", high = "#028090") +
labs(title = "Tarifa promedio por noche según ciudad y tipo de habitación",
x = "Tipo de habitación", y = "Ciudad",
fill = "Tarifa media (miles)") +
theme_minimal()
El gráfico permite observar que sí hay un incremento en la tarifa dependiendo del tipo de habitación en todas las ciudades. Sin embargo, Cartagena tiene una tarifa superior respecto a las otras sedes para todos los tipos de habitación.
Para responder la primera pregunta, la tarifa sí cambia de acuerdo a la habitación y a la ciudad. Sobre la segunda pregunta, las tarifas más altas se observan en Cartagena, seguida por Santa Marta. Respecto al tipo de habitación, las Suites son las más costosas en todas las ciudades.
En la limpieza se entendió que la variable de satisfacción tenía
muchos valores faltantes, seguramente por quienes cancelan la reserva.
Para esta pregunta, se eliminan los valores NA del gráfico
y de las medias, mostrando solo los datos que sí existen.
media_scl <- basedatos3 %>%
filter(!is.na(calificacion_satisfaccion), !is.na(tipo_habitacion), !is.na(canal_reserva)) %>%
group_by(tipo_habitacion, canal_reserva) %>%
summarise(media = mean(calificacion_satisfaccion), n = n(), .groups = "drop")
kable(media_scl, digits = 2,
caption = "Satisfacción promedio según tipo de habitación y canal de reserva")
| tipo_habitacion | canal_reserva | media | n |
|---|---|---|---|
| Doble | Agencia | 7.06 | 36 |
| Doble | Telefonico | 7.19 | 35 |
| Doble | Walk-in | 7.55 | 34 |
| Doble | Web | 7.77 | 95 |
| Sencilla | Agencia | 6.73 | 44 |
| Sencilla | Telefonico | 7.00 | 30 |
| Sencilla | Walk-in | 6.62 | 38 |
| Sencilla | Web | 7.13 | 91 |
| Suite | Agencia | 8.46 | 12 |
| Suite | Telefonico | 8.53 | 15 |
| Suite | Walk-in | 8.21 | 16 |
| Suite | Web | 8.64 | 35 |
ggplot(media_scl, aes(x = tipo_habitacion, y = canal_reserva, fill = media)) +
geom_tile(color = "white", linewidth = 0.8) +
geom_text(aes(label = round(media, 1),
color = media > mean(media)), size = 4) +
scale_color_manual(values = c("TRUE" = "white", "FALSE" = "#08306B"),
guide = "none") +
scale_fill_gradient(low = "#DEEBF7", high = "#08519C") +
labs(title = "Satisfacción promedio según canal de reserva y tipo de habitación",
x = "Tipo de habitación", y = "Canal de reserva",
fill = "Satisfacción media") +
theme_minimal()
El gráfico muestra una relación entre la calificación de la satisfacción y el tipo de habitación. Sin embargo, no hay diferencias entre la calificación de la satisfacción y los canales de reserva, puesto que no parecen influir en las opiniones de los clientes.
Se explora si cancelan con menor frecuencia y si reportan una satisfacción distinta.
Primero se explora la relación entre el tipo de cliente y la cancelación.
tabla_cf <- table(Cliente_frecuente = basedatos3$cliente_frecuente,
Cancelada = basedatos3$cancelada)
kable(tabla_cf, caption = "Reservas según tipo de cliente y cancelación")
| No | Si | |
|---|---|---|
| No | 381 | 89 |
| Si | 148 | 32 |
prop_cf <- as.data.frame(prop.table(tabla_cf, margin = 1))
names(prop_cf) <- c("Cliente_frecuente", "Cancelada", "Proporcion")
kable(prop_cf, digits = 3, caption = "Proporción de cancelación según tipo de cliente")
| Cliente_frecuente | Cancelada | Proporcion |
|---|---|---|
| No | No | 0.811 |
| Si | No | 0.822 |
| No | Si | 0.189 |
| Si | Si | 0.178 |
ggplot(prop_cf, aes(x = Cancelada, y = Cliente_frecuente,
size = Proporcion, color = Proporcion)) +
geom_point() +
geom_text(aes(label = scales::percent(Proporcion, accuracy = 1)),
color = "black", size = 3.5, vjust = -2.2) +
scale_size_continuous(range = c(4, 22), name = "Proporción") +
scale_color_gradient(low = "#B7E4C7", high = "#028090") +
guides(color = "none") +
labs(title = "Proporción de cancelaciones según tipo de cliente",
x = "¿Reserva cancelada?", y = "Cliente frecuente") +
theme_minimal()
De acuerdo con el gráfico, no hay relación entre la cancelación de la reserva y el tipo de cliente.
Ahora se explora la relación entre “calificación” y “cancelación”.
basedatos3 %>%
mutate(tiene_calificacion = ifelse(is.na(calificacion_satisfaccion),
"Sin calificación (NA)", "Con calificación")) %>%
count(cancelada, tiene_calificacion) %>%
ggplot(aes(x = cancelada, y = n, fill = tiene_calificacion)) +
geom_col(position = "dodge", alpha = 0.85) +
geom_text(aes(label = n), position = position_dodge(width = 0.9), vjust = -0.5) +
scale_fill_manual(values = c("Con calificación" = "#08519C",
"Sin calificación (NA)" = "#9ECAE1")) +
labs(title = "Disponibilidad de la calificación de satisfacción según cancelación",
x = "¿Reserva cancelada?", y = "N° de reservas", fill = "") +
theme_minimal()
El gráfico muestra que la mayoría de las personas que SI cancelaron la reserva, efectivamente no tienen registro de calificación de satisfacción. Sin embargo, 15 de los clientes que NO cancelaron la reserva, decidieron NO calificar.
Teniendo en cuenta esta información, para explorar las tres variables se propone el siguiente gráfico:
media_cfc <- basedatos3 %>%
filter(!is.na(cliente_frecuente), !is.na(cancelada)) %>%
group_by(cliente_frecuente, cancelada) %>%
summarise(n_total = n(),
n_calif = sum(!is.na(calificacion_satisfaccion)),
media = mean(calificacion_satisfaccion, na.rm = TRUE),
.groups = "drop") %>%
mutate(media = ifelse(n_calif == 0, NA_real_, media),
etiqueta = ifelse(is.na(media),
paste0("Sin calificaciones\n(", n_total, " reservas)"),
paste0(round(media, 2), "\n(n = ", n_calif, " de ", n_total, ")")),
color_texto = case_when(is.na(media) ~ "gris",
media > mean(media, na.rm = TRUE) ~ "claro",
TRUE ~ "oscuro"))
kable(media_cfc, digits = 2,
caption = "Satisfacción promedio según tipo de cliente y cancelación")
| cliente_frecuente | cancelada | n_total | n_calif | media | etiqueta | color_texto |
|---|---|---|---|---|---|---|
| No | No | 381 | 369 | 7.43 | 7.43 | |
| (n = 369 de 381) | claro | |||||
| No | Si | 89 | 0 | Sin calificaciones | ||
| (89 reservas) | gris | |||||
| Si | No | 148 | 145 | 7.38 | 7.38 | |
| (n = 145 de 148) | oscuro | |||||
| Si | Si | 32 | 0 | Sin calificaciones | ||
| (32 reservas) | gris |
ggplot(media_cfc, aes(x = cancelada, y = cliente_frecuente, fill = media)) +
geom_tile(color = "white", linewidth = 0.8) +
geom_text(aes(label = etiqueta, color = color_texto), size = 4.5) +
scale_color_manual(values = c("claro" = "white",
"oscuro" = "#08306B",
"gris" = "grey30"),
guide = "none") +
scale_fill_gradient(low = "#DEEBF7", high = "#08519C",
na.value = "grey85") +
labs(title = "Satisfacción promedio según tipo de cliente y cancelación",
subtitle = "Gris: combinaciones sin calificaciones registradas",
x = "¿Reserva cancelada?", y = "Cliente frecuente",
fill = "Satisfacción media") +
theme_minimal() +
theme(panel.grid = element_blank())
Este último gráfico permite observar que no solo no hay relación entre el tipo de cliente y si cancela o no su reserva, sino que además tampoco hay relación entre el tipo de cliente y la calificación de la satisfacción.
¿Se observa algo parecido a un descuento por estadías largas, el efecto contrario, o ninguna relación?
Se busca entender si hay correlación entre las noches y la tarifa. Se propone:
ggplot(basedatos3, aes(x = noches, y = tarifa_por_noche_miles)) +
geom_point(alpha = 0.5, color = "#028090") +
geom_smooth(method = "lm", se = FALSE, color = "firebrick") +
geom_smooth(method = "loess", se = FALSE, color = "darkorange", linetype = "dashed") +
labs(title = "No. Noches vs. Tarifa por noche: ajuste lineal (rojo) vs. loess (naranja)",
x = "No. Noches", y = "Tarifa por noche (miles)") +
theme_minimal()
cor(basedatos3$anticipacion_dias, basedatos3$tarifa_por_noche_miles,
method = "pearson", use = "complete.obs")
[1] -0.02546293
cor(basedatos3$anticipacion_dias, basedatos3$tarifa_por_noche_miles,
method = "spearman", use = "complete.obs")
[1] -0.03321859
De acuerdo con las pruebas realizadas, no hay correlación entre las variables. El gráfico permite observar que la mayoría de los clientes se quedan menos de 6 noches y que los menores precios se agrupan ahí.
Al ser variables cualitativas, se propone el siguiente gráfico.
tabla_cc <- table(Ciudad = basedatos3$ciudad, Cancelada = basedatos3$cancelada)
kable(tabla_cc, caption = "Reservas según ciudad y cancelación")
| No | Si | |
|---|---|---|
| Bogota | 138 | 43 |
| Cali | 87 | 13 |
| Cartagena | 116 | 16 |
| Medellin | 120 | 27 |
| Santa Marta | 68 | 22 |
prop_cc <- as.data.frame(prop.table(tabla_cc, margin = 1))
names(prop_cc) <- c("Ciudad", "Cancelada", "Proporcion")
kable(prop_cc, digits = 3, caption = "Proporción de reservas canceladas y no canceladas por ciudad")
| Ciudad | Cancelada | Proporcion |
|---|---|---|
| Bogota | No | 0.762 |
| Cali | No | 0.870 |
| Cartagena | No | 0.879 |
| Medellin | No | 0.816 |
| Santa Marta | No | 0.756 |
| Bogota | Si | 0.238 |
| Cali | Si | 0.130 |
| Cartagena | Si | 0.121 |
| Medellin | Si | 0.184 |
| Santa Marta | Si | 0.244 |
ggplot(prop_cc, aes(x = Ciudad, y = Proporcion, fill = Cancelada)) +
geom_col(color = "white") +
geom_text(aes(label = scales::percent(Proporcion, accuracy = 1)),
position = position_stack(vjust = 0.5), color = "white", fontface = "bold") +
scale_fill_manual(values = c("No" = "#9E9AC8", "Si" = "#54278F")) +
scale_y_continuous(labels = scales::percent) +
labs(title = "Proporción de reservas canceladas y no canceladas por ciudad",
x = "Ciudad", y = "Frecuencia relativa", fill = "Cancelada") +
theme_minimal()
De acuerdo con el gráfico, se puede observar que las reservas que SI cancelan son similares en proporción para las ciudades Bogotá y Santa Marta (con un porcentaje bajo ambas). De las reservas que NO cancelan, la mayoría se reportan en Cartagena y Cali; sin embargo, no hay diferencia significativa con las otras ciudades.
En conclusión, estos datos muestran que no hay un porcentaje de mayor cancelación en ninguna ciudad, por lo que no se deben hacer recomendaciones específicas por área geográfica a la cadena hotelera.
tabla_ch <- table(basedatos3$cliente_frecuente, basedatos3$tipo_habitacion)
kable(addmargins(tabla_ch),
caption = "Tabla de contingencia: cliente frecuente y tipo de habitación")
| Doble | Sencilla | Suite | Sum | |
|---|---|---|---|---|
| No | 205 | 193 | 72 | 470 |
| Si | 78 | 67 | 35 | 180 |
| Sum | 283 | 260 | 107 | 650 |
kable(round(prop.table(tabla_ch, margin = 1), 3),
caption = "Frecuencias condicionales por fila (dado el tipo de cliente)")
| Doble | Sencilla | Suite | |
|---|---|---|---|
| No | 0.436 | 0.411 | 0.153 |
| Si | 0.433 | 0.372 | 0.194 |
A continuación se propone un gráfico:
ggplot(dplyr::filter(basedatos3, !is.na(cliente_frecuente), !is.na(tipo_habitacion)),
aes(x = cliente_frecuente, fill = tipo_habitacion)) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent) +
labs(title = "Tipo de habitación según cliente frecuente",
x = "Cliente frecuente", y = "Porcentaje", fill = "Tipo de habitación") +
theme_minimal()
De acuerdo con el gráfico, se puede observar que no hay relación entre
el tipo de habitación y si es cliente frecuente o no.
vars_cuant <- c("anticipacion_dias", "noches",
"tarifa_por_noche_miles", "calificacion_satisfaccion")
matriz_cor <- cor(basedatos3[, vars_cuant], use = "complete.obs")
kable(round(matriz_cor, 3), caption = "Matriz de correlación entre las cuatro variables cuantitativas")
| anticipacion_dias | noches | tarifa_por_noche_miles | calificacion_satisfaccion | |
|---|---|---|---|---|
| anticipacion_dias | 1.000 | 0.011 | 0.014 | 0.005 |
| noches | 0.011 | 1.000 | 0.012 | 0.073 |
| tarifa_por_noche_miles | 0.014 | 0.012 | 1.000 | 0.299 |
| calificacion_satisfaccion | 0.005 | 0.073 | 0.299 | 1.000 |
corrplot(matriz_cor,
method = "color", type = "upper", order = "hclust",
addCoef.col = "black", tl.col = "black", tl.srt = 45,
title = "Matriz de correlación entre variables cuantitativas",
mar = c(0, 0, 2, 0))
De acuerdo con el gráfico, no existen correlaciones entre las variables
cuantitativas de la base de datos. No hay suficiente información para
realizar recomendaciones en este sentido.
| Tema | Hallazgo |
|---|---|
| Canal de reserva | “Agencia” tiene la mayor tasa de cancelación; “Web” la menor. |
| Anticipación | No se relaciona con la tarifa ni con la cancelación. |
| Tarifas | Cartagena es la ciudad más cara; las Suites son el tipo de habitación más costoso. |
| Satisfacción | Varía según el tipo de habitación, no según el canal. |
| Cliente frecuente | Se comporta igual que el ocasional en cancelación y satisfacción. |
| Noches | No hay descuento por estadías largas ni efecto contrario. |
| Ciudad | Ninguna ciudad destaca por mayor cancelación. |
Recomendación 1: canales de reserva. Se recomienda fortalecer el canal “Web”, que combina un volumen importante de reservas con una baja cancelación, y revisar el canal “Agencia” para entender por qué sus reservas se cancelan. Algunas hipótesis a investigar son las condiciones de cancelación pactadas con las agencias, la calidad del servicio al cliente y la ausencia de penalizaciones o anticipos.
Recomendación 2: tipo de habitación. Las suites son el tipo de habitación con mejor calificación de satisfacción promedio. Se recomienda enfocar la oferta en las suites, reforzando un servicio más premium (por ejemplo, atención personalizada y servicios adicionales), para aprovechar la buena percepción de los huéspedes.Adicionalmente, se recomienda fortalecer la oferta por tipo de habitación en todos los públicos, teniendo en cuenta que no hay relaciones con el tipo de habitación y si es cliente frecuente o no.
Estos resultados son de naturaleza descriptiva y correlacional: no permiten afirmar que una variable cause el comportamiento de otra.