TP Final Estadistica aplicada a investigación de mercado

Author

Betsy Cohen

Objetivo

Según datos abiertos oficiales, la C.A.B.A. posee 665 alojamientos turísticos registrados de los cuales el 40% corresponden a modelo no tradicionales como hoteles boutique, hostels, aparts y B&B.

Ver código
df_alojamientos_badata %>% 
  mutate("Tipo de alojamiento" = case_when(str_detect(tipo,"estrella")|tipo %in% c("Hotel sindical","Hospedaje") ~ "Hotel tradicional",
                              tipo %in% c("Boutique","Hotel boutique") ~"Hotel boutique",
                              TRUE ~ tipo))%>% 
  tabyl("Tipo de alojamiento") %>%
  arrange(desc(n)) %>% 
  adorn_totals() %>% 
  adorn_pct_formatting(digits = 0) %>% 
  as.data.frame() %>% 
  gt() %>% 
  tab_header(
    title = md('**Tipos de alojamiento CABA**')) %>%
  opt_align_table_header('left') %>%
  tab_source_note(source_note = "Fuente: BA DATA")
Tipos de alojamiento CABA
Tipo de alojamiento n percent
Hotel tradicional 401 60%
Hotel boutique 85 13%
Hostel 80 12%
Apart Hotel 73 11%
Bed & Breakfast 26 4%
Total 665 100%
Fuente: BA DATA

“Casa Angelito” era un antiguo hotel de pasajeros que Don Angel administró durante los años 50, alquilando habitaciones a marineros, mozos y pasajeros. Tras su muerte sus nietos están poniendo en valor la propiedad para rentar las habitaciones de manera temporal por Airbnb.

Ubicado en el epicentro de San Telmo, el espacio espera abrir sus puertas al público en diciembre de 2024.

Preocupados por comenzar a recuperar la inversión en la reforma de la antigua casa, los nietos de Don Angel quieren conocer las principales características de la oferta y conocer, para su segmento en particular, cuál es el precio al cual deberían lanzarse al mercado para optimizar la ocupación.

Metodología

Para dar respuesta a esta pregunta utilizamos algunas de las técnicas aprendidas en la materia “Estadística aplicada a investigación de mercado”. Los pasos a seguir serán

  • Realizar limpieza y categorización de variables
  • Observar la correlación entre las variables utilizando v de cramer y ordenar una primera selección de las mismas en conjuntos de correlaciones por temática.
  • Utilizar análisis correspondencias múltiples (MCA) para comprender cómo se relacionan las categorías de estas variables y volver seleccionar las que más aporten a cada uno de los modelos.
  • Realizar un análisis de conglomerados utilizando utilizando el algoritmo k-modas.
  • Analizar los clusters obtenidos sobre todo en relación al precio y la ocupación utilizando una regresión lineal para estas variables en cada cluster.

Acerca de la base de datos

El sitio http://insideairbnb.com/. es un proyecto que recopila datos de la plataforma colaborativa de alquileres, con el propósito de realizar un seguimiento del impacto de este tipo de alquiler sobre la vivienda en las principales capitales turísticas del mundo. Si bien existen otras plataformas de alquiler, Airbnb ha logrado posicionarse como una de las principales y es por ello que creemos que es un buen punto de partida.

Insideairbnb provee un dataset con publicaciones, comentarios de publicaciones y calendario de anuncios a diciembre de 2022 y se publican en forma de archivos csv:

listing_details.csv - Datos detallados de los anuncios calendar_details.csv - Datos detallados del calendario para anuncios review_details.csv - Datos de los comentarios detallados para los anuncions review_summary.csv - Resumen de datos de comentaris e ID de anuncios neighbourhoods.csv: lista de barrios para el filtro geográfico. barrios.geojson - Archivo GeoJSON de los barrios

Se puede encontrar una copia de los datos en el sitio Inside Airbnb http://insideairbnb.com/

Nosotros vamos a trabajar con publicaciones que tuvieron al menos una publicación en el último año y con una selección inicial de variables de listing_details.csv :

Ver código
selected_cols <- c("id","calculated_host_listings_count","host_response_time", "host_is_superhost", "host_identity_verified", "neighbourhood_cleansed",   "property_type","room_type", "accommodates","bedrooms", "beds", "amenities", "price_US", "bathrooms_text","review_scores_rating", "review_scores_accuracy", "review_scores_cleanliness",   "review_scores_checkin", "review_scores_communication", "review_scores_location",  "review_scores_value","instant_bookable", "reviews_per_month")


df_airbnb <- df_airbnb %>% 
  filter(number_of_reviews_ltm >0) %>% # hacemos una transformacion en precio para que aparezca como numérica y en dolares
  mutate(price_US = as.numeric(gsub("[$,]", "",price))/350) %>% 
  dplyr::select(selected_cols)

Tenemos 13848 publicaciones y 25 variables de las cuales 10 son factoriales y 15 numéricas. Entre las variables numéricas ya vemos que tenemos distribuciónes con casos extremos en casi todas las variables y que ninguna parece tener una distribuición normal. Se excluye las viviendas de lujo por lo que situamos el precio por debajo de los 100 dolares la noche

Ver código
df_airbnb <- df_airbnb %>% 
   filter(price_US <= 80)  # precio hasta 80 dolares
  

skimr::skim(df_airbnb)
Data summary
Name df_airbnb
Number of rows 13275
Number of columns 23
_______________________
Column type frequency:
character 9
numeric 14
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
host_response_time 0 1 3 18 0 5 0
host_is_superhost 0 1 1 1 0 2 0
host_identity_verified 0 1 1 1 0 2 0
neighbourhood_cleansed 0 1 4 17 0 45 0
property_type 0 1 7 34 0 54 0
room_type 0 1 10 15 0 4 0
amenities 0 1 25 1924 0 12896 0
bathrooms_text 0 1 0 17 8 35 0
instant_bookable 0 1 1 1 0 2 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
id 0 1.00 2.706916e+17 3.341409e+17 11508.00 29794879.00 48295211.00 6.60034e+17 7.878161e+17 ▇▁▁▂▃
calculated_host_listings_count 0 1.00 1.322000e+01 2.561000e+01 1.00 1.00 2.00 1.10000e+01 1.350000e+02 ▇▁▁▁▁
accommodates 0 1.00 2.780000e+00 1.260000e+00 1.00 2.00 2.00 4.00000e+00 1.600000e+01 ▇▁▁▁▁
bedrooms 1999 0.85 1.280000e+00 7.800000e-01 1.00 1.00 1.00 1.00000e+00 1.400000e+01 ▇▁▁▁▁
beds 142 0.99 1.830000e+00 1.260000e+00 1.00 1.00 1.00 2.00000e+00 1.600000e+01 ▇▁▁▁▁
price_US 0 1.00 2.598000e+01 1.392000e+01 1.51 16.47 22.65 3.22200e+01 7.999000e+01 ▅▇▂▁▁
review_scores_rating 0 1.00 4.770000e+00 3.500000e-01 1.00 4.69 4.86 5.00000e+00 5.000000e+00 ▁▁▁▁▇
review_scores_accuracy 1 1.00 4.820000e+00 3.300000e-01 1.00 4.76 4.91 5.00000e+00 5.000000e+00 ▁▁▁▁▇
review_scores_cleanliness 1 1.00 4.720000e+00 3.900000e-01 1.00 4.63 4.82 5.00000e+00 5.000000e+00 ▁▁▁▁▇
review_scores_checkin 1 1.00 4.860000e+00 3.000000e-01 1.00 4.84 4.96 5.00000e+00 5.000000e+00 ▁▁▁▁▇
review_scores_communication 1 1.00 4.860000e+00 3.100000e-01 1.00 4.83 4.96 5.00000e+00 5.000000e+00 ▁▁▁▁▇
review_scores_location 1 1.00 4.880000e+00 2.600000e-01 1.00 4.86 4.95 5.00000e+00 5.000000e+00 ▁▁▁▁▇
review_scores_value 0 1.00 4.710000e+00 3.800000e-01 1.00 4.63 4.80 4.94000e+00 5.000000e+00 ▁▁▁▁▇
reviews_per_month 0 1.00 1.510000e+00 1.360000e+00 0.02 0.51 1.08 2.12000e+00 1.217000e+01 ▇▂▁▁▁

Tranformación y exploración

Realizamos algunas transformaciones en las variables

host_response_time

Se llevan los NA a la categoría de menor nivel “a few days or more”

Ver código
# df_airbnb %>%
# tabyl(host_response_time)

df_airbnb <- df_airbnb %>% 
  mutate(host_response_time = ifelse(host_response_time == "N/A","a few days or more",host_response_time))
neighbourhood_cleansed

Armamos una categorizacion para los barrios en función de las posibles funciones turísticas que podrían tener: Palermo, Puerto Madero y San Telmo como típicos barrios turísticos, Otros barrios del corredor norte (Belgrano, Recoleta, Nuñez y Colegiales), los barrios del Microcentro de la ciudad (San Nicolas y Retiro) y un grupo de Otros.

Ver código
df_airbnb <- df_airbnb %>% 
  mutate(neighbourhood_cleansed = case_when(neighbourhood_cleansed == "San Telmo" ~ "San Telmo",
                                             neighbourhood_cleansed == "Puerto Madero" ~ "Puerto Madero",
                                             neighbourhood_cleansed == "Palermo" ~ "Palermo",
                                             neighbourhood_cleansed %in% c("Belgrano","Recoleta","Nuñez","Colegiales")  ~ "Other north",
                                             neighbourhood_cleansed %in% c("San Nicolas","Retiro") ~ "Other downtown",
                                             TRUE ~ "Other not north"))


plot_ly(df_airbnb, x= ~neighbourhood_cleansed, color = ~neighbourhood_cleansed, type = "histogram") %>%
  layout(title = "<b>Distribución de neighbourhood_cleansed<b>")
amenities

amenities es una variable que está como una lista de texto entre brakets. se tokeniza y se busca los tokens más frecuentes. Con unas regex se unen auqellos tokens que parecen tener sentido como definitorios de la ocupacion en tanto brinden confort.

Ver código
# df_airbnb %>%
#   select(amenities) %>%
#   mutate(amenities = str_replace_all(amenities, "[\\[\\]\"]", "")) %>% # Remove brackets and quotes
#   unnest_tokens(amenity, amenities, token = "regex", pattern = ",\\s*") %>% # Split into separate rows
#   tabyl(amenity) %>% 
#   arrange(desc(n)) %>% 
#   adorn_pct_formatting() %>% 
#   filter(n > 5000)

df_airbnb <- df_airbnb %>%
  mutate(amenities = str_replace_all(amenities, "[\\[\\]\"]", "")) %>% # Eliminar brackets y comillas
  mutate(am_tv = ifelse(str_detect(amenities, regex("\\b(TV|HDTV)\\b", ignore_case = TRUE)), "t", "f"),
         am_netflix = ifelse(str_detect(amenities, regex("\\b(netflix|amazon|apple tv|disney+|hbo max|chromecast)\\b", ignore_case = TRUE)), "t", "f"),
         am_wifi = ifelse(str_detect(amenities, regex("\\bWifi\\b",ignore_case = TRUE)), "t", "f"),
         am_AC_heating = ifelse(str_detect(amenities, regex("\\bair conditioning|ac |   
central air conditioning|   portable air conditioning | bheating\\b", ignore_case = T)), "t", "f"),
         am_kitchen_stuff = ifelse(str_detect(amenities,regex("\\bkitchen|dishes and silverware|cooking basics|refrigerator|microwave|coffee maker|dining table|toaster|freezer|hot water kettle|stove|wine glasses|coffee\\b", ignore_case = TRUE)), "t", "f"),
         am_washer = ifelse(str_detect(amenities, regex("\\bwasher|dryer\\b",ignore_case = TRUE)), "t", "f"),
         am_bedroom_stuff = ifelse(str_detect(amenities,regex("\\bextra pillows and blankets|bed linens\\b", ignore_case = TRUE)), "t", "f"),
         am_essentials = ifelse(str_detect(amenities,regex("\\bessentials|hangers|hair dryer|hot wate|iron\\b",ignore_case = T)), "t", "f"),
         am_long_term_stays = ifelse(str_detect(amenities,regex("\\blong term stays allowed|cleaning products\\b",ignore_case = TRUE)), "t", "f"),
         am_patio_balcony = ifelse(str_detect(amenities,regex("\\bprivate patio or balcony|patio or balcony|shared patio or balcony|backyard\\b", ignore_case = TRUE)),"t","f"),
         am_parking = ifelse(str_detect(amenities,"\\bparking\\b"),"t","f"),
         am_elevator = ifelse(str_detect(amenities,regex("\\belevator\\b", ignore_case = T)),"t","f"),
         am_host_att = ifelse(str_detect(amenities,"\\bHost greets you\\b"),"t","f"),
         am_bath_stuff = ifelse(str_detect(amenities,regex("\\bshampoo|conditioner|bathroom essentials",ignore_case = TRUE)),"t","f"),
         am_bathtub = ifelse(str_detect(amenities,regex("\\bbathtub\\b", ignore_case = TRUE)),"t","f"),
         am_smoking_all = ifelse(str_detect(amenities,regex("\\bsmoking allowed\\b",ignore_case= TRUE)),"t","f"),
         am_pet_friendly = ifelse(str_detect(amenities,regex("\\bpets allowed\\b",ignore_case = TRUE)),"t","f"),
         am_fire_security = ifelse(str_detect(amenities,regex("\\bfire extinguisher|smoke alarm|carbon monoxide alarm",ignore_case = TRUE)),"t","f"))


# eliminar amenities original
df_airbnb$amenities <- NULL
tipos de baño

El acceso a baño privado o no suele ser un factor importante en la búsqueda de una estadía. La cantidad la descartamos para dicotomizar las 6 categorías resultantes si es privado o no Los NA los imputamos como privados o compartidos en función del room_type.

Ver código
# PASO 1: separar el tipo y cantidad de baños

df_airbnb <- df_airbnb %>%
  mutate(bathrooms_text = str_replace(bathrooms_text, "Shared half-bath", "1 Shared_half-bath")) %>%
  mutate(bathrooms_text = str_replace(bathrooms_text, "Private half-bath", "1 Private_half-bath")) %>% 
  mutate(bathrooms_text = str_replace(bathrooms_text,"Half-bath","1 Private_half-bath")) %>% #se asume private
  separate(bathrooms_text, into = c("num_baths", "bath_type"), sep = " ", fill = "right", remove = F, convert = T) 

# PASO 2 Dicotomización de bath a privado(Private) o no privado (Shared) asumiendo que los que tienen más de un baño "baths" también son privados.
df_airbnb <- df_airbnb %>% 
  mutate(bath_type = case_when(bath_type %in% c("bath","baths","private","private","Private_half-bath","Private") ~ "Private bath",
                               bath_type %in% c("shared","Shared_half-bath","Shared") ~ "Shared bath")) 

# Imputación de NA  segun el tipo de habitacion 
df_airbnb <- df_airbnb %>% 
  mutate(bath_type = case_when(is.na(bath_type) & room_type == "Entire home/apt" ~ "Private bath",
                               is.na(bath_type) & room_type == "Hotel room" ~ "Private bath",
                               is.na(bath_type) & room_type == "Private room" ~ "Shared bath",
                               is.na(bath_type) & room_type == "Shared room" ~ "Shared bath",
                               TRUE ~ bath_type ))

# df_airbnb %>%
#   tabyl(bath_type)

# Eliminar bathrooms_text
df_airbnb$bathrooms_text<- NULL  
df_airbnb$num_baths<- NULL 

plot_ly(df_airbnb, x= ~bath_type, color = ~bath_type, type = "histogram") %>%
  layout(title = "<b>Distribución de bath_type<b>")
propety_type

Dada la escaza caracterización que brinda esta variable la descartamos del análisis

Ver código
# df_airbnb <- df_airbnb %>%
#   mutate(property_type = case_when(
#     property_type %in% c("Entire condo", "Private room in condo", "Shared room in condo") ~ "Condo",
#     property_type %in% c("Room in bed and breakfast", "Private room in bed and breakfast", "Shared room in bed and breakfast") ~ "B&B",
#     property_type %in% c("Private room in hostel", "Shared room in hostel") ~ "Hostel",
#     property_type %in% c("Room in boutique hotel", "Private room in boutique hotel", "Shared room in boutique hotel") ~ "Bout Hot",
#     property_type %in% c("Room in hotel", "Entire serviced apartment", "Private room in serviced apartment", "Shared room in serviced apartment", "Room in aparthotel") ~ "Hotel",
#     property_type %in% c("Entire guesthouse", "Private room in guesthouse", "Shared room in guesthouse") ~ "Guesthouse",
#     property_type %in% c("Entire home/apt", "Private room in home", "Shared room in home") ~ "Home",
#     property_type %in% c("Entire townhouse", "Private room in townhouse") ~ "Townhouse",
#     property_type %in% c("Entire villa", "Private room in villa", "Shared room in villa") ~ "Villa",
#     TRUE ~ "Others"))

plot_ly(df_airbnb, x= ~property_type, color = ~property_type, type = "histogram") %>%
  layout(title = "<b>Distribución de property_type<b>")
Warning in RColorBrewer::brewer.pal(N, "Set2"): n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors

Warning in RColorBrewer::brewer.pal(N, "Set2"): n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Ver código
df_airbnb$property_type<- NULL 
review_scores_vars

Cemos todas las variables de review_scores tienden a concentrarse cerac de los 5 puntos por lo que armamos categorías Bajo, Medio y Alto (1 a 3, 4 y 5)

Ver código
vars_review <- c("review_scores_rating","review_scores_accuracy","review_scores_cleanliness","review_scores_checkin","review_scores_communication","review_scores_location","review_scores_value")

df_airbnb %>% 
  dplyr::select(vars_review) %>% 
  boxplot_function() %>% 
  gridExtra::grid.arrange(nrow=2, ncol=2) 
Warning: Using an external vector in selections was deprecated in tidyselect 1.1.0.
ℹ Please use `all_of()` or `any_of()` instead.
  # Was:
  data %>% select(vars_review)

  # Now:
  data %>% select(all_of(vars_review))

See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.

Ver código
df_airbnb <- df_airbnb %>% 
  mutate(across(vars_review, ~case_when(. <= 3 ~ "Low",
                                        . <= 4 ~ "Medium",
                                        . <= 5 ~ "Hight",
                                        TRUE   ~ "Medium"))) #  como los NA solo son un caso les pongo la categoria Medium 


rm(vars_review)
accommodates

La mayor parte de las habitaciones del hotel serán dobles pero habrá una de ellas que tendrá tres camas cuchetas de modo que armamos variables categóricas de acuerdo a este criterio contemplando una posible competencia con espacios donde pueda haber viajeros solitarios en parejas en grupos de hasta 3 o más pasajeros.

Ver código
df_airbnb <- df_airbnb %>% 
  mutate( accommodates_cont = accommodates,
          accommodates = case_when(accommodates == 1 ~ "1 pax",
                                  accommodates == 2 ~ "2 pax",
                                  accommodates == 3 ~ "3 pax",
                                  TRUE              ~ "4 and more pax")) 

# plot_ly(df_airbnb, x= ~accommodates, color = ~accommodates, type = "histogram") %>%
#   layout(title = "<b>Distribución de accommodates<b>")
bedrooms and beds
Ver código
# analizo si se pueden recuperar los NA 1999 por room_type y beds  
# # df_airbnb %>% 
# #   filter(is.na(bedrooms)) %>% 
# #   tabyl(room_type)
# 
# df_airbnb %>%
#   filter(is.na(bedrooms)) %>%
#   tabyl(room_type)
# 
# df_airbnb %>%
#   filter(is.na(bedrooms)) %>%
#   tabyl(beds)


df_airbnb <- df_airbnb %>% 
    mutate(bedrooms = case_when(bedrooms == 1 ~ "1 bedroom",
                                bedrooms == 2 ~ "2 bedrooms",
                                is.na(bedrooms) & beds == 1 ~ "1 bedroom",
                                is.na(bedrooms) & beds %in% c(2,3) ~ "2 bedrooms",
                                TRUE ~ "3+ bedrooms")) 


plot_ly(df_airbnb, x= ~bedrooms, color = ~bedrooms, type = "histogram") %>%
  layout(title = "<b>Distribución de bedrooms<b>")
Ver código
# df_airbnb %>% 
#   tabyl(bedrooms) %>% 
#   adorn_pct_formatting()
beds

Usamos un criterio similar al de accomodates

Ver código
df_airbnb <- df_airbnb %>% 
  mutate(beds = case_when(beds == 1 ~ "1 bed",
                          beds == 2 ~ "2 beds",
                          beds == 3 ~ "3 beds",
                          TRUE ~ "+4 beds"))

plot_ly(df_airbnb, x= ~beds, color = ~beds, type = "histogram") %>%
  layout(title = "<b>Distribución de beds<b>")
price_US

Nuestro cliente ofrece sus habitaciones en un hotel no tradicional o petit hotel por lo que observando las distribuciones de precio por tipo de hotel armamos una serie de categorías de precio posibles.

Ver código
df_airbnb %>% 
  plot_ly(x= ~room_type, y = ~price_US,type = "box", name = ~room_type, color = ~room_type) %>% 
  layout(title = "<b>Precios de las publicaciones en US$ según tipo<b>",
         yaxis = list(title = "Precio en US$ por noche"))
Ver código
df_airbnb <- df_airbnb %>% 
  mutate(price_US_cont = price_US, 
         price_US = case_when(
                           price_US <= 10 ~ "under 10",
                           price_US <= 15 ~ "11 - 15",
                           price_US <= 25 ~ "16 - 25",
                           price_US <= 35 ~ "26 - 35",
                           price_US <= 45 ~ "36 - 45",
                           price_US > 45 ~ "over 46"),
         )
Estimación de ocupación

La tasa de ocupación es el Número de publicaciones que el anfitrion tiene ocupadas / número total de publicaciones de ese host x 100. Este no es un dato que proporcione la base por lo que debemos estimarlo. Inside Airbnb sugiere el 55% de los viajeros deja un comentario, por lo que utilizamos la variable reviews_per_monthy la variable calculated_host_listings_count para estimarlo

Si vemos la categoría Private rooom (que es la más cercana a la oferta de nuestro cliente) tiene una media de 33.5%, valor que se encuentra bastante cercano al 36.1% y un poco más alejado del 26.4% publicado por el Sistema de Información Turística de la Argentina (SINTA) en su Encuesta de Ocupación Hotelera de diciembre 2022 (fecha correspondiente al scrapeo de la base) para los hoteles boutique y el sector para-hotelero correspondientemente.

Ver código
df_airbnb <- df_airbnb %>%
  mutate(occupancy = (reviews_per_month * 0.55) / calculated_host_listings_count * 100) 

df_airbnb %>% 
  group_by(room_type) %>% 
  summarise(occupancy_mean = round(mean(occupancy),1)) 
# A tibble: 4 × 2
  room_type       occupancy_mean
  <chr>                    <dbl>
1 Entire home/apt           42.3
2 Hotel room                 5.4
3 Private room              31.6
4 Shared room               10.7

Tomando como referencia la citada encuesta observamos la evolución y armamos hacemos cortes de deciles para las categorías “3 estrellas/boutiques/aparts”, “Para Hoteles”

Ver código
eoh <-read.csv("input/tasas-de-ocupacion-plazas-por-categoria.csv")

eoh %>%
  mutate(indice_tiempo = ymd(as.Date(indice_tiempo))) %>% 
  filter(categoria_del_hotel %in% c("3 estrellas/boutiques/aparts", "Para Hoteles") ) %>%
  mutate(tasa_de_ocupacion_plazas = tasa_de_ocupacion_plazas*100) %>%
  plot_ly(x = ~indice_tiempo, y = ~tasa_de_ocupacion_plazas, color = ~categoria_del_hotel, type = 'scatter', mode = 'lines') %>%
  layout(title = "<b>Tasa de ocupacion hotelera<b>", 
         xaxis = list(title = "", type = "date"),
         yaxis = list(title = ""),
         showlegend = T, legend = list(orientation = 'h')) %>% 
  add_annotations(x= 0., y= -0.1, 
                  xref = "paper",yref = "paper",
                  text = "Elaboración propia en base a datos abiertos SINTA",
                  showarrow = F)
Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels

Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels
Ver código
deciles_3estrellas <- quantile(eoh$tasa_de_ocupacion_plazas[eoh$categoria_del_hotel == "3 estrellas/boutiques/aparts"] * 100, probs = seq(0.1, 0.9, by = 0.1),na.rm = T)
deciles_3estrellas
 10%  20%  30%  40%  50%  60%  70%  80%  90% 
27.0 30.2 32.8 34.0 34.0 36.0 38.0 41.8 46.0 
Ver código
deciles_hotel <- quantile(eoh$tasa_de_ocupacion_plazas[eoh$categoria_del_hotel == "Para Hoteles"] * 100, probs = seq(0.1, 0.9, by = 0.1),na.rm = T)
deciles_hotel
 10%  20%  30%  40%  50%  60%  70%  80%  90% 
16.0 19.0 21.0 23.0 24.0 26.0 27.2 32.8 45.4 

Redondeamos los valores para una mejor comprension

Ver código
df_airbnb <- df_airbnb %>% 
  mutate(occupancy_cont = occupancy,
         occupancy    = case_when( occupancy <= 10 ~ "under 10%",
                                occupancy <= 20 ~ "11-20%",
                                occupancy <= 30 ~ "21-30%",
                                occupancy <= 40 ~ "31-40%",
                                occupancy <= 45 ~ "41-45%",
                                occupancy <= 50 ~ "46-50%",
                                occupancy <= 60 ~ "51-60%",
                                occupancy > 60 ~ "over 60%")) 


rm(eoh,deciles_hotel,deciles_3estrellas)

Analisis de correlación de factores

Para guiar una primera lectura analizamos las variables con el coeficiente de V de cramer que nos sirve para medir la asociación entre variables categóricas.

Ver código
# seleccion de variables para correlación
df_airbnb_for_cor_1 <- df_airbnb %>% 
  dplyr::select(-c("id","calculated_host_listings_count","reviews_per_month"))

# Initialize empty matrix to store coefficients

empty_m <- matrix(ncol = length(df_airbnb_for_cor_1),
                  nrow = length(df_airbnb_for_cor_1),
                  dimnames = list(names(df_airbnb_for_cor_1),
                                  names(df_airbnb_for_cor_1)))



cor_matrix_1 <- calculate_cramer(empty_m ,df_airbnb_for_cor_1)

corrplot::corrplot(cor_matrix_1, method = 'number',tl.col = "darkgray",number.cex=0.60, tl.cex = 0.75)

Ver código
# ggcorrplot(cor_matrix_1, 
#            type = "full", 
#            outline.color = "white", 
#            ggtheme = ggplot2::theme_gray, 
#            lab_size = 2,
#            tl.cex = 8,
#            tl.srt = 90,
#            colors = c("#6D9EC1", "white", "#E46726"))
# names(df_airbnb)

De esta primera lectura surge como primer punto a destacar que si bien las review scores se relacionan fuertemente entre sí, no parecen tener mucha relación con ninguna otra variable del set, por lo que las descartamos inicialmente de nuestro análisis .

A continuación listamo las relaciones más importantes y las agrupamos por temáticas:

Performance del anfitrión:

  • host_response_time y instant_bookable 0.27

  • host_response_time y host_identity_verified 0.19

  • host_response_time y host_is_superhost 0.18

Ubicación:

  • neighbourhood_cleansed y am_patio_balcony 0.22

Características de la habitación:

  • room_type y bath_type 0.80

  • bath_type y accommodates 0.53

  • bath_type y price_US 0.52

  • beds y bedrooms 0.51

  • bedrooms y accommodates 0.50

  • beds y accommodates 0.48

  • room_type y am_AC_heating 0.45

  • bedrooms y accommodates 0.40

  • bath_type y am_AC_heating 0.40

  • room_type y accommodates 0.35

  • room_type y am_tv 0.33

  • room_type y price_US 0.32

  • bedrooms y price_US 0.27

  • bath_type y am_tv 0.27

  • beds y price_US 0.19

  • room_type y am_elevator 0.18

Amenities:

  • am_bedroom_stuff y am_essentials 0.45

  • am_bedroom_stuff y am_bathtub 0.46

  • am_essentials y am_elevator 0.37

  • am_bedroom_stuff y am_parking 0.37

  • am_essentials y am_parking 0.31

  • am_bedroom_stuff y am_elevator 0.29

  • am_patio_balcony y balconyy am_parking 0.29

  • am_essentials y am_bath_stuff 0.29

  • am_patio_balcony y am_bedroom_stuff 0.28

  • am_bedroom_stuff y am_patio_balcony 0.28

  • am_essentials y am_bathtub 0.26

  • am_bedroom_stuff y am_bath_stuff 0.25

  • am_essentials y am_patio_balcony 0.24

  • am_elevator y am_bathtub 0.23

Ahora hagamos zoom en cada una de estos conjuntos de relaciones de variables y con una serie de Análisis de correspondencias múltiples (MCA)

Analisis de corresponecias múltiples (MCA)

Como señalan Joaquin Aldas y Ezequiel Uriel en “Análisis multivariado aplicado con R” (2017:159) el análisis de correspondencias es una técnica que muestra las distancias entre dos o más variables no métricas. Nuestro objetivo es ver si existen relaciones entre las variables que arrojen luz sobre los determinantes o drivers del mercado.En este caso vamos a alimentar el modelo directamente con las variables (raw data), pero es importante destacar que también se pueden utilizar un conjunto de tablas anidadas conocidas como matriz de burt (C) o una matriz de indicadores (Z) (los casos son filas y las columnas son todas respuestas dicotómicas)

Si analizamos todas juntas el MCA puede resultar poco explicativo por lo que cada una de las temáticas que indicamos arriba y su contribución e interacciones en una serie de MCA’s

Si analizamos las frecuencias generales sabemos que el 71% de los avisos se responden dentro de una hora y que el 89% tiene su identidad verificada pero el 68% no ofrece reservas inmediatas, alcanzando la categoría de superhost solo el 38% de los avisos.

Ver código
# df_airbnb %>% tabyl(host_response_time)
# df_airbnb %>% tabyl(instant_bookable)
# df_airbnb %>% tabyl(host_identity_verified)
# df_airbnb %>% tabyl(host_is_superhost)


# seleccion de variables para el MCA de Host performance
df_host_perfo <- df_airbnb %>% 
  dplyr::select(host_response_time,instant_bookable,host_identity_verified,host_is_superhost)

# aplicación de MCA para host perfo
fit_host_perfo <- MCA(df_host_perfo, ncp = 4, graph = F)

En las primeras dos dimensiones del MCA ya podemos explicar el 43% de la varianza de la performance del host, y recien en la tercera dimensión tenemos un corte (tal cual se observa en este gráfico de sedimentación)

Ver código
fviz_screeplot(fit_host_perfo,"variance", geom = c("bar", "line"), addlabels = T, title = "Scree Plot MCA Host performance")

Analizando un mapa perceptual de la distribución de los casos en torno alas categorías de estas variables vemos que en la dimensión 2 la verificación de la identidad y el superhost aparece rodeada (aunque contribuyendo minoritariamente) de una respuesta dentro de los 60 minutos y sobre todo de reservas inmediatas. Sin embargo ¿Es la variable que mejor nos aporta a esta dimensión?

Ver código
# Mapa perceptual decontribución a la varianza
fviz_mca_var(fit_host_perfo,col.var="contrib",gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"), repel = F)  + 
theme_gray()

Si vemos las contribuciones a cada dimensión observamos que el tiempo de respuesta parece marcar mejor las diferencias entre las diferentes dimensiones

Ver código
# contribución
fit_host_perfo$var$contrib
                             Dim 1      Dim 2        Dim 3      Dim 4
a few days or more       19.499639  8.5753363  8.007679383 23.6892776
within a day              8.566057  3.7253948 69.861584974  0.1123610
within a few hours        5.154641 22.4663351 20.924424599 14.6509988
within an hour           10.145324  4.4633331  0.331491242  0.2189696
instant_bookable_f        1.976601 14.5317713  0.008167697  3.1096440
instant_bookable_t        4.290268 31.5416140  0.017728213  6.7495688
host_identity_verified_f 23.390730  6.5431734  0.291169870  5.7350866
host_identity_verified_t  2.704903  0.7566524  0.033670875  0.6632052
host_is_superhost_f       9.291862  2.8315218  0.200632057 17.2542564
host_is_superhost_t      14.979974  4.5648678  0.323451090 27.8166318

Cuando el tiempo de respuesta es within an hour se ofrece reservas inmediatas instant_bookable

Ver código
df_airbnb %>%
  tabyl(host_response_time,instant_bookable) %>% 
  adorn_totals('col') %>% 
  adorn_percentages('col') %>% 
  adorn_totals() %>% 
  adorn_pct_formatting() %>% 
  adorn_title()
                    instant_bookable              
 host_response_time                f      t  Total
 a few days or more             5.5%   4.8%   5.3%
       within a day             8.3%   1.6%   6.1%
 within a few hours            22.9%   5.4%  17.4%
     within an hour            63.3%  88.2%  71.2%
              Total           100.0% 100.0% 100.0%

También vemos que a un mejor tiempo de respuesta tiene a ser super host en host_is_superhost

Ver código
df_airbnb %>%
  tabyl(host_response_time,host_is_superhost) %>% 
  adorn_totals('col') %>% 
  adorn_percentages('col') %>% 
  adorn_totals() %>% 
  adorn_pct_formatting() %>% 
  adorn_title()
                    host_is_superhost              
 host_response_time                 f      t  Total
 a few days or more              7.7%   1.4%   5.3%
       within a day              7.8%   3.5%   6.1%
 within a few hours             19.0%  14.9%  17.4%
     within an hour             65.6%  80.2%  71.2%
              Total            100.0% 100.0% 100.0%

Lo mismo sucede en relación a host_identity_verified

Ver código
df_host_perfo %>%
  tabyl(host_response_time,host_identity_verified) %>% 
  adorn_totals('col') %>% 
  adorn_percentages('col') %>% 
  adorn_totals() %>% 
  adorn_pct_formatting() %>% 
  adorn_title()
                    host_identity_verified              
 host_response_time                      f      t  Total
 a few days or more                  16.6%   4.0%   5.3%
       within a day                  10.3%   5.7%   6.1%
 within a few hours                  19.1%  17.2%  17.4%
     within an hour                  53.9%  73.2%  71.2%
              Total                 100.0% 100.0% 100.0%

Por lo tanto concluimos en que host_response_time es una buen indicador de la performance del host

En el análisis de correlaciones habíamos visto que la ubicación del hotel se relaciona con si tiene balcón y/o patio. Esto podemos verlo en la tabla de frecuencias y confirmarlo con chi cuadrado.

El valor p obtenido del chi cuadrado es importante para determinar si se debe rechazar o no la hipótesis nula. Si el valor p es menor que el nivel de significancia seleccionado (por ejemplo, 0.05), entonces se rechaza la hipótesis nula y se concluye que hay evidencia suficiente para afirmar que existe una asociación significativa entre las variables y que las diferencias que observa,os no se deben al azar.

Como podemos ver las viviendas ubicadas en Palermo ofrecen balcones y patios y que naturalmente caen cuando nos acercamos a los barrios del micro centro.

Avancemos y veamos cómo se comporta am_patio_balcony en la dimensión de comodidades y servicios para ver si vale la pena sumarla a nuestro modelo.

Ver código
gmodels::CrossTable(df_airbnb$neighbourhood_cleansed,df_airbnb$am_patio_balcony) 

 
   Cell Contents
|-------------------------|
|                       N |
| Chi-square contribution |
|           N / Row Total |
|           N / Col Total |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  13275 

 
                                 | df_airbnb$am_patio_balcony 
df_airbnb$neighbourhood_cleansed |         f |         t | Row Total | 
---------------------------------|-----------|-----------|-----------|
                  Other downtown |       964 |       483 |      1447 | 
                                 |   294.363 |   185.047 |           | 
                                 |     0.666 |     0.334 |     0.109 | 
                                 |     0.188 |     0.059 |           | 
                                 |     0.073 |     0.036 |           | 
---------------------------------|-----------|-----------|-----------|
                     Other north |      1309 |      2026 |      3335 | 
                                 |     0.367 |     0.231 |           | 
                                 |     0.393 |     0.607 |     0.251 | 
                                 |     0.255 |     0.249 |           | 
                                 |     0.099 |     0.153 |           | 
---------------------------------|-----------|-----------|-----------|
                 Other not north |      1137 |      2096 |      3233 | 
                                 |     9.856 |     6.196 |           | 
                                 |     0.352 |     0.648 |     0.244 | 
                                 |     0.222 |     0.257 |           | 
                                 |     0.086 |     0.158 |           | 
---------------------------------|-----------|-----------|-----------|
                         Palermo |      1480 |      3268 |      4748 | 
                                 |    67.868 |    42.664 |           | 
                                 |     0.312 |     0.688 |     0.358 | 
                                 |     0.289 |     0.401 |           | 
                                 |     0.111 |     0.246 |           | 
---------------------------------|-----------|-----------|-----------|
                   Puerto Madero |        39 |        97 |       136 | 
                                 |     3.469 |     2.181 |           | 
                                 |     0.287 |     0.713 |     0.010 | 
                                 |     0.008 |     0.012 |           | 
                                 |     0.003 |     0.007 |           | 
---------------------------------|-----------|-----------|-----------|
                       San Telmo |       195 |       181 |       376 | 
                                 |    17.135 |    10.772 |           | 
                                 |     0.519 |     0.481 |     0.028 | 
                                 |     0.038 |     0.022 |           | 
                                 |     0.015 |     0.014 |           | 
---------------------------------|-----------|-----------|-----------|
                    Column Total |      5124 |      8151 |     13275 | 
                                 |     0.386 |     0.614 |           | 
---------------------------------|-----------|-----------|-----------|

 
Ver código
df_airbnb %>% 
  tabyl(neighbourhood_cleansed,am_patio_balcony) %>% 
  chisq.test()

    Pearson's Chi-squared test

data:  .
X-squared = 640.15, df = 5, p-value < 2.2e-16

En bloque está compuesto por las variables room_type, bath_type, accommodates, price_US, beds, bedrooms, am_AC_heating, am_tv, am_elevator,

El 53% de los avisos tienen una cama, el 76% una habitación, 56% acepta hasta 2 pasajeros, pero mayoritariamente se alquila el espacio entero (91%) con un baño privado (94%), el aire acondicionado tampoco es un diferencial (93%) ni la tv (92%). El acceso a ascensor sí ya que hay un 36% que no lo tiene.

Ver código
# df_airbnb %>% tabyl(beds) %>% adorn_pct_formatting(digits = 0)
# df_airbnb %>% tabyl(bedrooms) %>% adorn_pct_formatting(digits = 0)
# df_airbnb %>% tabyl(accommodates) %>% adorn_pct_formatting(digits = 0)
# df_airbnb %>% tabyl(price_US) %>% adorn_pct_formatting(digits = 0)
# df_airbnb %>% tabyl(room_type) %>% adorn_pct_formatting(digits = 0)
# df_airbnb %>% tabyl(bath_type) %>% adorn_pct_formatting(digits = 0)
# df_airbnb %>% tabyl(am_AC_heating) %>% adorn_pct_formatting(digits = 0)
# df_airbnb %>% tabyl(am_tv) %>% adorn_pct_formatting(digits = 0)
# df_airbnb %>% tabyl(am_elevator) %>% adorn_pct_formatting(digits = 0)

Al observar la relación entre estas variables en un MCA las primeras dos dimensiones explican el 29% de la varianza y las primera 4 explican el 43%

Ver código
# seleccion de variables para el MCA de Host performance
df_room_drivers <- df_airbnb %>% 
  dplyr::select(room_type, bath_type, accommodates, price_US, beds, bedrooms, am_AC_heating, am_tv, am_elevator)

# aplicación de MCA para host perfo
fit_room_drivers <- MCA(df_room_drivers, ncp = 9 ,graph = F)
#summary(fit_room_drivers, nb.des = 3, ncp = 2) # Las primeras dos dimensiones explican el 29% de la varianza y las primera 4 explican el 43%

fviz_screeplot(fit_room_drivers, "variance", geom = c("bar", "line"), addlabels = T)

La primera dimensión parece representar la asociación entre habitaciones privadas con baño compartido para un pasajero y económicas (menos de 10 dolares), sin aire ni calefacción. Pareciera representar a los típicos avisos de hostels económicos que inspiraron plataformas como couchsurf y luego Airbnb.

La segunda dimensión muestra la asociación para aquellas publicaciones para muchos pasajeros (4 o más), que por lo tanto requieren de muchas camas (+4 beds) y al menos 2 habitaciones, en esto se parece un poco a la dimensión 3 (donde vemos una fuerte contribución de la cantidad de camas para su opción de 2 camas) y de 3 pasajeros (quizás una familia con un niño) y la dimensión 4 parece tener una fuerte relación con las categorías de 3 camas y 3 pasajeros (quizás tres adultos o más pero ya con habitaciones separadas)

Ver código
fit_room_drivers$var$contrib
                      Dim 1       Dim 2        Dim 3        Dim 4        Dim 5
Entire home/apt  1.93015192  0.15486792 2.736646e-02  0.015751344 3.880937e-03
Hotel room       0.97607936  0.60352284 1.383439e-01  4.784018988 1.805419e+01
Private room    16.21711178  0.86163953 2.113814e-01  1.152338076 2.411656e+00
Shared room      1.85452830  0.58534641 4.101034e-01  0.402694882 9.197678e+00
Private bath     1.10523859  0.11730768 2.503765e-02  0.004269464 4.088027e-02
Shared bath     17.53776302  1.86142103 3.972937e-01  0.067747219 6.486820e-01
1 pax           13.96500182  1.29771273 4.475025e-01  0.201700137 2.277044e+00
2 pax            0.20454343  9.87694085 5.763625e+00  0.965500132 1.316011e-02
3 pax            0.57048690  0.09644579 2.039094e+01 15.692128484 3.689118e-01
4 and more pax   2.55496510 17.24529837 5.604828e-01  3.159987787 5.725828e-04
11 - 15          1.38883554  0.81097805 8.600551e-02  0.010304120 1.978034e+01
16 - 25          0.18015921  3.51731760 5.051310e-02  0.979555976 3.381481e-02
26 - 35          0.71578478  0.02602670 7.845039e-01  2.786860702 4.707973e+00
36 - 45          0.50417945  1.14552697 2.671148e-01  0.150474976 2.010751e+00
over 46          1.14584458  6.39525083 1.825065e+00  0.014947386 2.058892e+00
under 10        12.49898868  1.49651153 4.855757e-01  0.041705517 5.535009e+00
+4 beds          0.15370579 10.99151798 1.404122e+01 13.416206049 2.402452e-01
1 bed            1.56912848  8.86241493 5.278933e+00  1.932648525 1.826315e-01
2 beds           0.44759824  0.11329453 2.240445e+01 13.068121086 8.710477e-01
3 beds           1.55999474  7.47346361 1.363980e+00 25.998932951 4.533725e-04
1 bedroom        0.72422155  5.22094645 1.693162e-01  0.041495141 2.789482e-01
2 bedrooms       2.50982604 10.51115418 7.093823e+00  3.583221108 2.722792e-01
3+ bedrooms      0.04010706  7.95571452 1.751648e+01 11.345160383 1.358636e+00
am_AC_heating_f 10.06257337  1.58349994 1.894751e-01  0.092405880 8.101783e-01
am_AC_heating_t  0.76946211  0.12108664 1.448873e-02  0.007066068 6.195250e-02
am_tv_f          6.36492449  0.52142124 5.200052e-02  0.070878315 5.115200e+00
am_tv_t          0.56766269  0.04650352 4.637723e-03  0.006321359 4.562047e-01
am_elevator_f    1.20520044  0.32473892 2.192781e-04  0.004842208 1.486936e+01
am_elevator_t    0.67593252  0.18212871 1.229814e-04  0.002715736 8.339429e+00
                       Dim 6        Dim 7        Dim 8       Dim 9
Entire home/apt 1.539136e-02 1.862413e-03 1.604820e-02  0.15702897
Hotel room      3.189523e+01 2.793488e-01 5.187480e-01 12.25996749
Private room    3.626419e-04 8.996825e-01 8.280354e-01  9.08265482
Shared room     3.934075e+01 1.177939e+01 5.835512e+00 11.70464183
Private bath    1.913609e-01 1.861479e-03 3.547834e-04  0.07527068
Shared bath     3.036487e+00 2.953768e-02 5.629651e-03  1.19438409
1 pax           2.172994e-01 2.773502e-01 2.169395e-01  2.08094850
2 pax           9.209081e-02 2.299087e-02 3.617738e-02  0.21640245
3 pax           9.637184e-02 6.597903e-02 1.302672e-01  0.45022904
4 and more pax  1.520945e-03 5.355747e-02 5.724370e-02  0.28829589
11 - 15         1.533606e-02 1.086502e+01 1.117616e+01  0.15844302
16 - 25         6.317434e-03 9.379275e+00 2.489208e+01  0.63423286
26 - 35         9.101814e-01 2.725287e-01 3.953083e+01  9.19588050
36 - 45         8.183689e+00 4.452058e+01 6.964309e+00 14.66830724
over 46         2.268558e+00 1.655562e+01 3.833239e-02  1.07006865
under 10        5.762852e-03 2.271631e+00 2.820262e-01  0.86539571
+4 beds         1.736818e-01 1.460991e-01 4.508713e-01  0.43556809
1 bed           1.882149e-03 3.987000e-03 6.032247e-03  0.07330476
2 beds          5.051139e-02 7.325802e-04 4.131888e-01  0.22858107
3 beds          5.264905e-01 2.187414e-02 1.580232e+00  0.41672919
1 bedroom       2.372739e-02 8.132391e-03 1.235309e-02  0.07738531
2 bedrooms      6.531692e-01 2.838000e-04 3.319820e-01  0.02109288
3+ bedrooms     1.300807e+00 1.919416e-01 6.516092e-01  0.82159655
am_AC_heating_f 3.516697e+00 4.123847e-02 1.122725e-02  2.36433583
am_AC_heating_t 2.689138e-01 3.153412e-03 8.585221e-04  0.18079538
am_tv_f         6.608612e+00 3.622295e-02 3.598257e-01  4.72814522
am_tv_t         5.893962e-01 3.230583e-03 3.209144e-02  0.42168476
am_elevator_f   6.026467e-03 1.452345e+00 3.601280e+00 16.74003702
am_elevator_t   3.379923e-03 8.145425e-01 2.019765e+00  9.38859219

Quizás podemos concluir que este set de variables no nos puede decir mucho acerca de lo que entendemos son la mayoría de los avisos (un departamento de dos ambientes con una cama matrimonial para 1 o 2 pasajeros) pero si permite distingue el aviso para viajeros solitarios y económicos sin muchos servicios (como aire acondicionado y calefacción) y el de avisos para más pasajeros donde hay más variabilidad de los precios lógicamente tienden a aumentar. Las amenities que seleccionamos no parecen estar contribuyendo mucho en este grupo de asociaciones vinculadas a las características de la habitación.

Ver código
fviz_mca_var(fit_room_drivers,col.var="contrib",gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"), repel = F)  +
theme_gray()

Seleccionamos entonces bath_type, room_typey accommodates

Ver código
rm(df_room_drivers,fit_room_drivers)

Veamos ahora como se relacionan las amenities de las cuales inicialmente sabemos que tienen las siguientes distribuciones:

am_bedroom_stuff 72% am_essentials 92% am_bathtub 46% am_parking 65% am_elevator 64% am_patio_balcony 61% am_bath_stuff 52%

Ver código
# df_airbnb %>% tabyl(am_bedroom_stuff) %>% adorn_pct_formatting(digits = 0)
# df_airbnb %>% tabyl(am_essentials) %>% adorn_pct_formatting(digits = 0)
# df_airbnb %>% tabyl(am_bathtub) %>% adorn_pct_formatting(digits = 0)
# df_airbnb %>% tabyl(am_parking) %>% adorn_pct_formatting(digits = 0)
# df_airbnb %>% tabyl(am_elevator) %>% adorn_pct_formatting(digits = 0)
# df_airbnb %>% tabyl(am_patio_balcony) %>% adorn_pct_formatting(digits = 0)
# df_airbnb %>% tabyl(am_bath_stuff) %>% adorn_pct_formatting(digits = 0)

En las dos primeras dimensiones ya podemos explicar el 52% de la varianza

Ver código
# seleccion de variables para el MCA de amenities
df_amenities <- df_airbnb %>% 
  dplyr::select(am_bedroom_stuff, am_essentials, am_bathtub, am_parking, am_elevator, am_patio_balcony, am_bath_stuff)

# aplicación de MCA de amenities
fit_amenities <- MCA(df_amenities, ncp = 7 ,graph = F)

# screeplot de amenities
fviz_screeplot(fit_amenities, "variance", geom = c("bar", "line"), addlabels = T)

Como podemos ver en el biplot las amenities esenciales (que como vimos en la ETL eran elementos esenciales,perchas,secador de pelo,agua caliente y plancha) y en menor medida elementos para la habitación (como almohadas extra y ropa de cama) explican aparecen juntas con la negativa de todo el resto de las amenities que podríamos llamar “de lujo” y que como vimos al comienzo tienen una menor variabilidad.

En la dimensión 2 vemos cómo las amenities de lujo se asocian entre sí sobre todo la bañera y elementos para el baño.

Ver código
fviz_mca_var(fit_amenities,col.var="contrib",gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"), repel = F)  +
theme_gray()

Ver código
# contribución a la varianza de cada categ
fit_amenities$var$contrib
                       Dim 1        Dim 2      Dim 3        Dim 4       Dim 5
am_bedroom_stuff_f 14.645168  3.033807492  8.5704260  0.362910666  1.52278105
am_bedroom_stuff_t  5.714514  1.183785393  3.3441625  0.141606989  0.59418601
am_essentials_f    16.320854  5.444612064  1.4027369  4.828062091  8.66342618
am_essentials_t     1.323851  0.441634792  0.1137817  0.391623898  0.70272599
am_bathtub_f        6.308515 11.729850301 10.7449285  0.005223010  0.01313577
am_bathtub_t        7.262306 13.503298742 12.3694656  0.006012682  0.01512178
am_parking_f        9.751767  0.012911464 11.1664416  0.222613409 40.39728569
am_parking_t        5.359089  0.007095503  6.1365247  0.122337333 22.20035264
am_elevator_f       8.763744  5.318273011  5.4205588 19.267404696  6.20657293
am_elevator_t       4.915116  2.982735128  3.0401018 10.806057660  3.48093508
am_patio_balcony_f  6.587398  9.331053303 19.7298567 15.341967128  9.90281004
am_patio_balcony_t  4.141066  5.865822245 12.4028690  9.644490193  6.22524827
am_bath_stuff_f     4.587157 21.190899382  2.8626023 20.013838208  0.03884269
am_bath_stuff_t     4.319455 19.954221182  2.6955439 18.845852038  0.03657588
                         Dim 6       Dim 7
am_bedroom_stuff_f  5.54774017 38.24936988
am_bedroom_stuff_t  2.16471671 14.92482482
am_essentials_f    36.95637890 18.88110526
am_essentials_t     2.99768331  1.53152381
am_bathtub_f        9.44837909  8.23584422
am_bathtub_t       10.87688949  9.48103019
am_parking_f        0.04570820  2.93811263
am_parking_t        0.02511897  1.61464156
am_elevator_f      16.70203130  2.38921215
am_elevator_t       9.36727681  1.33998142
am_patio_balcony_f  0.30155230  0.20649205
am_patio_balcony_t  0.18956618  0.12980803
am_bath_stuff_f     2.76928555  0.04020002
am_bath_stuff_t     2.60767301  0.03785399

Tomamos entonces am_essentials para sumarla a nuestro modelo

Conclusión de drivers de mercado

Como pudimos ver en esta sección podemos ordenar las variables en 5 temas o drivers: La performance del host (de la cual destacamos la variable host_response_time), las características de la habitación (room_drivers) donde claramente distinguimos aquellas más pequeñas y económicas con baño compartido de aquellas que apuntan a un perfil de más viajeros, donde distinguimos a su vez un tipo de habitación para tres pasajeros que necesitan una sola cama (quizás una familia con un niño pequeño) y otras destinadas 3 y más pasajeros donde se ofrecen más habitaciones y más camas. En este sentido (y para sorpresa de lo que nos imaginábamos) las variables que más contribuyen a describir la habitación no incluyen room_type sino que son beds,accommodatesy bath_type (expresando el 47% de la varianza tal cual vemos en el gráfico inferior)

Ver código
df_airbnb %>% 
  dplyr::select(beds,accommodates,bath_type) %>% 
  MCA(ncp = 3 ,graph = F) %>% 
  fviz_mca_var(col.var="contrib",gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"), repel = F, title = "MCA - Room drivers categories")  +
  theme_gray()

Finalmente analizamos la asociación entre amenities donde pudimos distinguir como amenities esenciales y en menor medida elementos básicos de la habitación (como almohadas extra y ropa de cama) se asocian entre sí contraponiéndose a otras que podríamos llamar amenities de lujo como la presencia de ascensor, estacionamiento, balcón o bañera (otra variable por la cual nunca hubiéramos apostado y nos sorprendió).

Cluster con K-modas

Teniendo en cuenta lo que vimos hasta ahora y ya haciéndonos una idea de los posibles perfiles de tipo de aviso que conforman la oferta en la que se va a insertar “Casa Angelito” vamos a usar las variables host_response_time, accommodates,beds,bath_type y am_essentials para hacer un análisis de conglomerados o cluster.

La idea general de esta técnica de es clasificar las observaciones en grupos que sean lo más homogéneos internamente y lo más heterogéneos entre sí teniendo en cuenta la cercanía de las observaciones entre ellas o lo que llamamos medidas de proximidad o similaridad. La elección de una medida u otra depende del tipo de de variable.

Vamos a utilizar la técnica de kmodas. k-modas es similar al k-medias pero en vez de calcular los centroides, usa las modas para representar cada cluster. El objetivo es encontrar los valores más frecuentes en las variables categóricas para cada cluster y asignar cada observación al cluster con la moda más cercana. El número de modas, k, se establece de antemano al igual que en k-media

En un archivo de colab aparte estimamos la cantidad de clusters con el método del codo con la distancia de Hamming. Como no encontramos la función para hacerla en R sino en Python lo corremos en el colab y pegamos los resultados a continuación.

Después de varias pruebas elegimos trabajar con 3 clusters que corremos usando la librería klaR.

Primero buscamos una iteración y una semilla que nos parezcan estables

Ver código
# selecciono las variables para el cluster
df_airbnb_cluster<-df_airbnb %>% dplyr::select(host_response_time,accommodates,beds,bath_type,am_essentials)

# Definir número de clusters
k <- 3

# Definir número de iteraciones
n_iter <- 100 # con 10 no era tan estable y lo subimos a 100

# Definir vector de semillas
seeds <- c(123, 456, 789, 321, 654, 987)

# Matriz de resultados
result_matrix <- matrix(0, nrow = length(seeds), ncol = k)

# Loop sobre las semillas
for (i in seq_along(seeds)) {
  # Fijar la semilla
  set.seed(seeds[i])

  # Aplicar kmodes
  kmodes_result <- klaR::kmodes(df_airbnb_cluster, k, iter.max = n_iter)

  # Guardar el tamaño de los clusters
  result_matrix[i,] <- kmodes_result$size
}

# Imprimir los resultados
print(result_matrix)
     [,1] [,2] [,3]
[1,]  918 9471 2886
[2,] 1612 4212 7451
[3,] 4596 4491 4188
[4,] 3163 2887 7225
[5,] 3111 7234 2930
[6,] 6009 2311 4955

Seleccionamos la semilla y corremos el proceso

Ver código
# Seleccion de la semilla que consideramos más estable
set.seed(321)

# aplicacion de algoritmo
df_airbnb_cluster_vector <- klaR::kmodes(df_airbnb_cluster,3, iter.max = 100)

#Chequear el tamaño de los clusters
df_airbnb_cluster_vector$size
cluster
   1    2    3 
3163 2887 7225 

Las variables y am_essentials, bath_type y host_response_time no parecen estar contribuyendo en la construcción de los cluster. El primer cluster (3163 publicaciones) nos recuerda a las publcaciones para grandes contingentes (4 pasajeros y 3 camas), el segundo cluster que habíamos asociados a un modelo familiar donde se acepta hasta 3 pasajeros con 2 camas y luego sí el cluster más populoso que conforma lo que podríamos llamar el airbnb base (7225) de 2 pasajeros y una cama.

Por supuesto todas estas son hipótesis que utilizamos en este examen a modo de ejemplo y para facilitar una posible lectura ya que no contamos con datos de los huèspedes y para ello se debería realizar otro tipo de estudio con otras fuentes. (por ej. encuestas, escrapeo y análisis de los comentarios de los huespedes, etc. etc.)

Ver código
df_airbnb_cluster_vector$modes
  host_response_time   accommodates   beds    bath_type am_essentials
1     within an hour 4 and more pax 3 beds Private bath             t
2     within an hour          3 pax 2 beds Private bath             t
3     within an hour          2 pax  1 bed Private bath             t

Ahora imputamos el vector con los clusters a la base para poder empezar a analizarlos

Ver código
# extraer el cluster
df_airbnb_cluster_index <- df_airbnb_cluster_vector$cluster %>% as.data.frame() %>% 
  rename("cluster" = ".")

#unirlo a la base
df_airbnb <- df_airbnb %>% cbind(df_airbnb_cluster_index)

# # chequear que los tamaños esten ok
#table(df_airbnb$cluster)

# eliminar df intermedios
rm(df_airbnb_cluster_index,df_airbnb_cluster_vector,df_airbnb_cluster)

# renombramos los clusters

df_airbnb <- df_airbnb %>% 
  mutate(cluster = case_when(cluster == 1 ~ "cluster_1",
                             cluster == 2 ~ "cluster_2",
                             cluster == 3 ~ "cluster_3"))

Caracterización de los cluster

Bajo la hipótesis de que los clusters que surjen están vinculados estos distintos tipos de contingentes (grandes contingentes, grupo familiar reducido, y parejas/ hasta 2 pasajeros) analizamos en esta sección el comportamiento de las variables en cada cluster para ver qué más nos dicen acerca de los mismos.

Tipos de habitación y cluster

Como se había sugerido en el MCA vemos que el cluster 3 (de dos pasajeros y donde es muy probable que están contemplados los pasajeros solitarios que son un grupo minoritario) muestran diferencias en la distribución (las cuales son estadísticamente significativas).

Ver código
df_airbnb %>% 
  tabyl(room_type,cluster) %>% 
  adorn_totals('col') %>% 
  adorn_percentages('col') %>% 
  adorn_totals() %>% 
  adorn_pct_formatting() %>% 
  gt() %>% 
  tab_header(title = "Tipos de habitacion por cluster")
Tipos de habitacion por cluster
room_type cluster_1 cluster_2 cluster_3 Total
Entire home/apt 96.3% 94.8% 86.7% 90.8%
Hotel room 0.6% 0.5% 0.3% 0.4%
Private room 2.3% 4.0% 12.5% 8.2%
Shared room 0.7% 0.8% 0.5% 0.6%
Total 100.0% 100.0% 100.0% 100.0%
Ver código
df_airbnb %>% 
  tabyl(room_type,cluster) %>% 
  chisq.test()

    Pearson's Chi-squared test

data:  .
X-squared = 399.62, df = 6, p-value < 2.2e-16

La oferta de habitaciones privadas (que aunque es siempre minoritaria nos interesa sobre todo en el cluster 3), no tiene una distribución exactamente igual en todos los barrios: San Telmo (nuestro barrio objetivo) tiene una distribución porcentual de habitaciones privadas que se asemeja a la del conjunto de los barrios “no turísticos” de la ciudad (Other not north) para el cluster 3.

Ver código
df_airbnb %>% 
  dplyr::group_by(cluster,neighbourhood_cleansed,room_type) %>% 
  summarise(recuento = n_distinct(id)) %>%
  mutate(prop = recuento/sum(recuento)*100) %>% 
  ggplot() +
  aes(x = cluster, y = prop, fill = room_type) +
  geom_col() +
  scale_fill_hue(direction = 1) +
  labs(
    title = "Tipo de habitación por barrio y cluster",
    fill = "Room type"
  ) +
  theme_minimal() +
  facet_wrap(vars(neighbourhood_cleansed), scales = "free_y")
`summarise()` has grouped output by 'cluster', 'neighbourhood_cleansed'. You
can override using the `.groups` argument.

Esto es importante porque también determinará el rango de precios en que se mueva la oferta que como es de esperar cae entre las viviendas que para el cluster 3.

Ver código
df_airbnb %>% 
   mutate(price_US = factor(price_US,
                            levels = c("under 10","11 - 15","16 - 25","26 - 35","36 - 45","over 46"),
                            labels = c("under 10","11 - 15","16 - 25","26 - 35","36 - 45","over 46"),
                            ordered = T)) %>% 
  group_by(cluster,room_type,price_US) %>% 
  summarise(recuento = n_distinct(id)) %>%
  mutate(prop = recuento/sum(recuento)*100) %>% 
  ggplot() +
  aes(x = cluster, y = prop, fill = price_US) +
  geom_col() +
  scale_fill_hue(direction = 1) +
  labs(
    title = "Rangos de precios en US$ de las habitaciones segun tipo y cluster",
    fill = "Room type"
  ) +
  theme_minimal() +
  facet_wrap(vars(room_type), scales = "free_y")
`summarise()` has grouped output by 'cluster', 'room_type'. You can override
using the `.groups` argument.

Ninguno de los clusters parece tener una oferta diferencial respecto de las reservas inmediatas

Respecto de la ocupación resulta lógico que a medida que nos vamos acercando al cluster 3 la media de ocupación de las habitaciones privadas vaya creciendo, hasta casi igualar la proporción de ocupación para habitaciones privadas.

Ver código
df_airbnb %>% 
  dplyr::group_by(cluster, room_type) %>% 
  summarise(occupancy_cont = (mean(occupancy_cont))) %>% 
  ggplot() +
  aes(x = room_type, y = occupancy_cont, fill = room_type) +
  geom_col() +
  scale_fill_hue(direction = 1) +
  labs(
    x = "Room type",
    y = "Mean occupancy",
    title = "Ocupación calculada promedio por tipo de habitación para cada cluster",
    caption = "Ocupación = reviews por cantidad de publicaciones que tiene un host",
    fill = "Room type"
  ) +
  theme_minimal() +
  facet_wrap(vars(cluster))
`summarise()` has grouped output by 'cluster'. You can override using the
`.groups` argument.

Amenities

No se observan cortes de preferencia por amenities entre los clusters, lo cual nos indica que es un punto a contrinuar exploraar y refinar

Ver código
df_airbnb %>%
  dplyr::select(starts_with("am_")) %>% 
  questionr::cross.multi.table(crossvar = df_airbnb$cluster, true.codes = "t", freq = T) %>% 
  as.data.frame() %>% 
  rownames_to_column("amenities") %>% 
  gt() %>% 
  tab_header(title = "Amenities segun cluster")
Amenities segun cluster
amenities cluster_1 cluster_2 cluster_3
am_tv 94.7 94.0 89.7
am_netflix 22.3 21.9 20.1
am_wifi 98.9 99.2 98.9
am_AC_heating 94.4 94.9 91.4
am_kitchen_stuff 99.4 99.1 98.7
am_washer 87.1 85.3 81.5
am_bedroom_stuff 74.7 72.0 70.7
am_essentials 93.1 93.2 91.9
am_long_term_stays 97.4 96.2 95.7
am_patio_balcony 62.8 60.6 61.1
am_parking 67.9 66.2 62.4
am_elevator 63.4 66.1 63.6
am_host_att 30.0 29.0 30.3
am_bath_stuff 52.5 52.2 50.8
am_bathtub 50.6 47.1 44.5
am_smoking_all 15.9 15.1 13.3
am_pet_friendly 20.7 16.8 12.6
am_fire_security 52.1 49.2 49.5

Precios y ocupación

Si bine los precios promedios para contingentes y familias son mayores el precio per capita es menor que para el cluster 3, de modo que el cliente deberá tener que tener en cuenta este efecto en caso de querer realizar ofertas o packs si desea competir con esos segmentos.

Ver código
df_airbnb %>% 
  group_by(cluster) %>% 
  summarise(precio_medio = round(mean(price_US_cont),2),
            precio_per_capita_medio = mean(price_US_cont/accommodates_cont)) %>% 
  gt() %>% 
  tab_header(title = "Precio promedio y per capita segun cluster")
Precio promedio y per capita segun cluster
cluster precio_medio precio_per_capita_medio
cluster_1 34.70 8.123692
cluster_2 25.66 8.874505
cluster_3 22.29 11.246528

Los precios promedio por noche en cada barrio muestran que si bien los contingentes representan un precio sustancialmente mayor en el cluster 1 y 2 la diferencia entre el cluster 2 y 3 parecen ser leves

Ver código
df_airbnb %>% 
  group_by(cluster,neighbourhood_cleansed) %>% 
  summarise(precio_medio = round(mean(price_US_cont),2)) %>% 
  plot_ly(x= ~cluster, y= ~precio_medio, color = ~neighbourhood_cleansed) %>% 
  layout(title = "<b>Precios promedio por barrio en cada cluster<b>") 
`summarise()` has grouped output by 'cluster'. You can override using the
`.groups` argument.
No trace type specified: Based on info supplied, a 'bar' trace seems
appropriate. Read more about this trace type ->
https://plotly.com/r/reference/#bar

En este gráfico podemos ver los saltos en el costo marginal de pasar de paquete twin los clusters de 2 y 3 donde es interesante destacar lo que sucede en San Telmo, donde el paso de uno a otro no está tan marcado y el precio es muy similar, por lo que en relación a otros barrios es poco competitivo recibir a un pasajero más.

Ver código
df_airbnb %>% 
  group_by(neighbourhood_cleansed, cluster) %>% 
  summarise(precio_medio = round(mean(price_US_cont),2)) %>% 
  mutate(costo_marginal = round((precio_medio/lead(precio_medio, default = last(precio_medio))) - 1, 2) ) %>% 
  filter(cluster != "cluster_3") %>% 
  plot_ly(x= ~cluster, y= ~costo_marginal, color = ~neighbourhood_cleansed) %>% 
  layout(title = "<b>Costo marginal sobre cluster 3 para pasar al cluster 2 y 1<b>") 
`summarise()` has grouped output by 'neighbourhood_cleansed'. You can override
using the `.groups` argument.
No trace type specified: Based on info supplied, a 'bar' trace seems
appropriate. Read more about this trace type ->
https://plotly.com/r/reference/#bar

En general se observan leves diferencias en el porcentaje de ocupación a favor de los clusters 1 y 2

Ver código
df_airbnb %>% 
  group_by(cluster) %>% 
  summarise(ocupacion_media = round(mean(occupancy_cont),2)) %>% 
  gt() %>% 
  tab_header(title = "Ocupación media según cluster")
Ocupación media según cluster
cluster ocupacion_media
cluster_1 43.22
cluster_2 41.84
cluster_3 39.90

Dentro del Cluster 3 la ocupación parece ser bastante homogéna entre los barrios entre el 40 y 45% lo cual es bastante elevado respecto de los valores oficiales que habíamos visto que reportaba SINTA para esta época del año (diciembre 2022). Resulta llamativa la ocupación en Puerto Madero, para los grandes contingentes, superando el 60%

Ver código
df_airbnb %>% 
  group_by(cluster,neighbourhood_cleansed) %>% 
  summarise(ocupacion_media = round(mean(occupancy_cont),2)) %>% 
  plot_ly(x= ~cluster, y= ~ocupacion_media, color = ~neighbourhood_cleansed) %>% 
  layout(title = "<b>Ocupación media por barrio segun cluster <b>")
`summarise()` has grouped output by 'cluster'. You can override using the
`.groups` argument.
No trace type specified: Based on info supplied, a 'bar' trace seems
appropriate. Read more about this trace type ->
https://plotly.com/r/reference/#bar

Respondiendo a la pregunta de nuestro cliente de en qué punto interceptan el precio y la ocupación en cada cluster para la cual aplicamos una regresión lineal para cada cluster y luego se igualamos las ecuaciones par ver en que punto interceptan ambas regresiones lineales

Ver código
# lm's para el cluster 1
df_cluster_1 <- df_airbnb %>% filter(cluster == "cluster_1")
eq_price_cluster_1 <- lm(price_US_cont ~ occupancy_cont, data = df_cluster_1)
eq_ocupp_cluster_1 <- lm(occupancy_cont ~ price_US_cont, data = df_cluster_1)

# lm's para el cluster 2
df_cluster_2 <- df_airbnb %>% filter(cluster == "cluster_2")
eq_price_cluster_2 <- lm(price_US_cont ~ occupancy_cont, data = df_cluster_2)
eq_ocupp_cluster_2 <- lm(occupancy_cont ~ price_US_cont, data = df_cluster_2)

# lm's para el cluster 3
df_cluster_3 <- df_airbnb %>% filter(cluster == "cluster_3")
eq_price_cluster_3 <- lm(price_US_cont ~ occupancy_cont, data = df_cluster_3)
eq_ocupp_cluster_3 <- lm(occupancy_cont ~ price_US_cont, data = df_cluster_3)
Ver código
intercept_cluster_1 <- lmIntx(eq_price_cluster_1,eq_ocupp_cluster_1) %>% 
  rownames_to_column("intercept") %>% 
  mutate(intercept = "cluster_1")

intercept_cluster_2 <- lmIntx(eq_price_cluster_2,eq_ocupp_cluster_2) %>% 
  rownames_to_column("intercept") %>% 
  mutate(intercept = "cluster_2")

intercept_cluster_3 <- lmIntx(eq_price_cluster_3,eq_ocupp_cluster_3) %>% 
  rownames_to_column("intercept") %>% 
  mutate(intercept = "cluster_3")

rbind(intercept_cluster_1,intercept_cluster_2,intercept_cluster_3) %>% 
  rename(occupancy_cont = x,
         price_US_cont = y) %>% 
  gt() %>% 
  tab_header("¿Cual es el precio cuando la ocupación alcanza su punto óptimo? según cluster")
¿Cual es el precio cuando la ocupación alcanza su punto óptimo? según cluster
intercept occupancy_cont price_US_cont
cluster_1 69.28 34.17
cluster_2 49.22 25.42
cluster_3 58.77 21.92

Como se puede observar el precio y la ocupación alcanzan su punto óptimo simultáneamente en el cluster 3 cuando la ocupación es del 58% y el precio es de US$ 22. Nos parece llamativo el valor de la ocupación para el cluster 1 pero entendemos que las reviews (en las cuales se basa el cálculo de la misma) quizás podrían estar sobre estimadas en los grandes contingentes.

Mapa de clusters

Por ultimo vamos graficar una muestra de los clusters sobre un mapa de la ciudad simplemente a modo ilustrativo donde podemos visualizar la importancia del corredor norte

Ver código
# traer la lat y long
df_airbnb_geo <- read.csv("input/listings.csv") %>%  dplyr::select("id","latitude","longitude")
df_airbnb <- df_airbnb %>% left_join(df_airbnb_geo)
Joining with `by = join_by(id)`
Ver código
rm(df_airbnb_geo)

library(leaflet)
Warning: package 'leaflet' was built under R version 4.2.3
Ver código
# Crear df para cada cluster
df_airbnb_c1 <- df_airbnb %>% filter(cluster == "cluster_1")
df_airbnb_c2 <- df_airbnb %>% filter(cluster == "cluster_2")
df_airbnb_c3 <- df_airbnb %>% filter(cluster == "cluster_3")


# Crear el mapa
mapa <- leaflet() %>%
  addTiles('https://wms.ign.gob.ar/geoserver/gwc/service/tms/1.0.0/capabaseargenmap@EPSG%3A3857@png/{z}/{x}/{-y}.png',attribution = "Argenmap v2 - Instituto Geográfico Nacional") %>%
  setView(-58.44169, -34.603954, zoom = 12)

# Agregar los puntos al mapa
mapa <- mapa %>% 
  addCircles(
  data = df_airbnb_c1,
  lat = ~latitude,
  lng = ~longitude,
  fillColor = "#ec407a",
  radius = 30,
  fillOpacity = 0.8,
  stroke = FALSE,
  group = "Cluster 1"
) %>% 
  addCircles(
  data = df_airbnb_c2,
  lat = ~latitude,
  lng = ~longitude,
  fillColor = "#8F83BC",
  radius = 30,
  fillOpacity = 0.8,
  stroke = FALSE,
  group = "Cluster 2"
)  %>% 
  addCircles(
  data = df_airbnb_c3,
  lat = ~latitude,
  lng = ~longitude,
  fillColor = "#50B7B0",
  radius = 30,
  fillOpacity = 0.8,
  stroke = FALSE,
  group = "Cluster 3"
) %>% 
  # Agregar control de las capas
  addLayersControl(
    overlayGroups = c("Cluster 1","Cluster 2","Cluster 3"),
    options = layersControlOptions(collapsed = FALSE)
  )

# Tirar mapa
mapa

Conclusiones

Como vimos a lo largo del TP se pueden distinguir tres tipos de avisos en Airbnb los cuales clasificamos principalmente por la capacidad de sus contingentes, y donde mayoritariamente el grupo 3 (hasta dos pasajeros con una cama matrimonial) es el más habitual.

De los grupos de asociaciones que analizamos inicialmente en relación a las amenities, no logramos hallar distinguir ninguna entre sí siendo este un tema a continuar explorando.

Respecto de las características de la habitación observamos que San Telmo la proporción de habitaciones privadas los saltos en el costo marginal de pasar de paquete twin los clusters de 2 y 3 en San Telmo resulta poco competitivo ya que la diferencia es de 31% y 29%.

Finalmente el precio y la ocupación alcanzan su punto óptimo simultáneamente en el cluster 3 (dentro del cual se ubicaría nuestro cliente) cuando la ocupación es del 58% y el precio es de US$ 22.

Para seguir profundizando

Todavía queda mucho por explorar, entre los posibles próximos pasos continuar evaluando otras estrategias de clasificación en las que podríamos por ej. :

  • ajustar kmodas con otros otros parámetros

  • evaluar la posibilidad de buscar subsegmentos dentro del cluster 3 (clusters dentro del gran cluster que es el cluster 3)

  • probar segmentaciones con árboles.