Instalacion y Carga de Pre-requerimientos
Carga de datos para el análisis.
Tratamiento datos
Resumen cambios en las variables.
Análisis Descriptivo
EDA: Análisis Exploratorio de Datos
KPIs
Conclusiones del Análisis
Tratamiento del Dataset procesado
Para ver la version online de este cuaderno, visite: https://andreatj.github.io/Tech-Girls-Glovo/
Este análisis explora un conjunto de datos de Airbnb, específicamente centrado en las listas de alojamientos disponibles en Madrid, España. Los datos han sido descargados desde el portal público OpenDataSoft. Se puede acceder directamente al dataset utilizado a través del siguiente enlace: Airbnb Listings en OpenDataSoft.
Instalacion y Carga de Pre-requerimientos
Previo al análisis, vamos a instalar y cargar los paquetes necesarios:
# Silas librerias no están ya instaladas , las instala.
if (!require("tidyverse")) install.packages("tidyverse")
## Loading required package: tidyverse
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.0 ✔ tibble 3.2.1
## ✔ lubridate 1.9.3 ✔ tidyr 1.3.1
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
if (!require("corrplot")) install.packages("corrplot")
## Loading required package: corrplot
## corrplot 0.92 loaded
if (!require("ggmap")) install.packages("ggmap")
## Loading required package: ggmap
## ℹ Google's Terms of Service: <https://mapsplatform.google.com>
## Stadia Maps' Terms of Service: <https://stadiamaps.com/terms-of-service/>
## OpenStreetMap's Tile Usage Policy: <https://operations.osmfoundation.org/policies/tiles/>
## ℹ Please cite ggmap if you use it! Use `citation("ggmap")` for details.
if (!require("leaflet")) install.packages("leaflet")
## Loading required package: leaflet
if (!require("leaflet.extras")) install.packages("leaflet.extras")
## Loading required package: leaflet.extras
if (!require("GGally")) install.packages("GGally")
## Loading required package: GGally
## Registered S3 method overwritten by 'GGally':
## method from
## +.gg ggplot2
if (!require("visdat")) install.packages("visdat")
## Loading required package: visdat
if (!require("naniar")) install.packages("naniar")
## Loading required package: naniar
# Cargar las librerías
library(tidyverse)
library(corrplot)
library(ggmap)
library(leaflet)
library(leaflet.extras)
library(GGally)
library(visdat)
library(naniar)
Descripción de Librerías de R usadas:
Tidyverse:
La librería tidyverse carga
en R un conjunto de paquetes que comparten una filosofía común de
manipulación y visualización de datos, incluyendo dplyr, ggplot2 y otros
paquetes que facilitan el flujo de trabajo de análisis de datos en
R.
Dplyr:
Proporciona un conjunto de
funciones para realizar operaciones de manipulación de datos como
filtrado, selección, agregación y unión de datos.
Ggplot2:
Permite crear una amplia
variedad de gráficos de alta calidad de forma sencilla y flexible.
Corrplot
: Se utiliza para visualizar
matrices de correlación de datos de una manera clara y concisa, lo que
facilita la identificación de patrones de correlación entre
variables.
Ggmap:
Esta librería se utiliza para
visualizar datos espaciales en mapas utilizando ggplot2. Permite
superponer datos sobre mapas de Google Maps, OpenStreetMap y otros
proveedores de mapas.
Leaflet:
Es una librería para la
creación de mapas interactivos en R. Permite crear mapas interactivos
con características como marcadores, polígonos, superposiciones y
herramientas de zoom.
Leaflet.extras:
Extiende las
funcionalidades de la librería leaflet proporcionando características
adicionales para la creación de mapas interactivos más avanzados.
GGally:
Proporciona extensiones para
ggplot2, permitiendo crear gráficos de matriz y otras visualizaciones
complejas de forma más sencilla.
Visdat
es una librería que proporciona
herramientas para visualizar la estructura de los datos. Su objetivo
principal es ayudar a comprender la distribución y la integridad de los
datos, incluidos los datos faltantes, ayudando a guiar las decisiones
sobre la limpieza y el preprocesamiento de los datos.
Naniar
es una librería que se centra
específicamente en el manejo y la visualización de datos faltantes. es
especialmente útil cuando se trabaja con conjuntos de datos que
contienen muchos valores faltantes o cuando se necesita realizar
análisis sensibles a la presencia de datos faltantes, como modelos
predictivos o análisis de datos longitudinales.
Carga de datos para el análisis.
Leemos el dataset desde su ubicación y especificamos que el separador de campos es un punto y coma.
library(tidyverse)
# Leer el archivo CSV
airbnb_data_raw <- read.csv("../data/raw/air-bnb-listings.csv", sep = ';')
Mostramos las primeras filas del dataset, para comprobar que los datos se han cargado correctamente.
# Ver las primeras filas de los datos
head(airbnb_data_raw)
## Room.ID Name Host.ID
## 1 36707573 Soggiorno Madrid-Hospitality , comfort & passion. 275911703
## 2 21859113 Be Mate Plaza España Wild 203 157114944
## 3 21862103 Designer flat in Madrid's exclusive Salamanca area 8851341
## 4 21875158 Estudio de diseño en pleno centro de Madrid 159570292
## 5 21932504 Cosy apartment in the cosmopolitan Gran Vía 160055902
## 6 22042300 Habitación 1. Para estudiar o trabajar 160981040
## Neighbourhood Room.type Room.Price Minimum.nights Number.of.reviews
## 1 Centro Storico Private room 155 1 1
## 2 Argüelles Entire home/apt 147 1 1
## 3 Recoletos Entire home/apt 625 5 2
## 4 Sol Private room 500 1 110
## 5 Palacio Entire home/apt 62 2 194
## 6 Portazgo Private room 12 15 11
## Date.last.review Number.of.reviews.per.month Rooms.rent.by.the.host
## 1 2019-12-19 0.16 3
## 2 2018-12-01 0.05 36
## 3 2018-08-09 0.06 1
## 4 2020-02-26 3.41 2
## 5 2020-03-11 6.16 1
## 6 2018-10-30 0.40 3
## Availibility Updated.Date City Country
## 1 173 2020-06-19 Florence Italy
## 2 0 2020-07-17 Madrid Spain
## 3 177 2020-07-17 Madrid Spain
## 4 347 2020-07-17 Madrid Spain
## 5 286 2020-07-17 Madrid Spain
## 6 106 2020-07-17 Madrid Spain
## Coordinates Location
## 1 43.78225294444298, 11.241335882500344 Italy, Florence, Centro Storico
## 2 40.421911823829916, -3.716298875039167 Spain, Madrid, Argüelles
## 3 40.42205487717857, -3.6889613202974916 Spain, Madrid, Recoletos
## 4 40.41573794398538, -3.7044166450981693 Spain, Madrid, Sol
## 5 40.421747567976034, -3.710994512030235 Spain, Madrid, Palacio
## 6 40.391493766002014, -3.6450831761057922 Spain, Madrid, Portazgo
Calculamos y mostramos las dimensiones originales del dataset, obteniendo el número de filas y columnas.
dimension_original <- dim(airbnb_data_raw)
paste('El numero de filas que tiene el dataframe airbnb_data es:', dimension_original[1])
## [1] "El numero de filas que tiene el dataframe airbnb_data es: 21278"
paste('El numero de columnas que tiene el dataframe airbnb_data es:', dimension_original[2])
## [1] "El numero de columnas que tiene el dataframe airbnb_data es: 17"
El dataset original, tiene 17 variables(columnas)
y
21278 observaciones(filas).
Vamos a analizar qué variables tenemos:
colnames(airbnb_data_raw)
## [1] "Room.ID" "Name"
## [3] "Host.ID" "Neighbourhood"
## [5] "Room.type" "Room.Price"
## [7] "Minimum.nights" "Number.of.reviews"
## [9] "Date.last.review" "Number.of.reviews.per.month"
## [11] "Rooms.rent.by.the.host" "Availibility"
## [13] "Updated.Date" "City"
## [15] "Country" "Coordinates"
## [17] "Location"
Room.ID
: Es el identificador único
de cada listado en Airbnb. Es esencial para identificar los registros de
manera única.
Name
: Es el nombre descriptivo del
listado. En algunos casos, puede contener más detalles descriptivos del
alojamiento.
Host.ID
: Es el identificador único
del anfitrión del listado en Airbnb. Muy útil para asociar múltiples
listados al mismo anfitrión.
Neighbourhood
: Nos indica el Barrio
dónde se encuentra el listado. Es un dato muy importante a la hora de
hacer un análisis de distribución geográfica.
Room.Type
: Es el tipo de
alojamiento ofrecido. Nos permite diferenciar entre los tipos de
alojamiento, que pueden ser: apartamento/casa entero, una habitación en
un hotel, una habitación privada o una habitación compartida.
Room.Price
: Es el número mínimo de
noches requerido para la reserva. De suma importancia, al influir en la
decisión de reservas de los usuarios.
Minimum.Nights
: Número mínimo de
noches requeridas para hacer una reserva, crucial para entender las
políticas de reservas.
Number.of.Reviews
: Nos indica el
número total de reseñas recibidas por los huéspedes del listado. Es un
indicador de popularidad y experiencia del cliente.
Date.Last.Review
: Es la fecha de la
última reseña recibida por un huésped para un alojamiento. Nos permite
evaluar si la última reseña es reciente.
Number.of.Reviews.Per.Month
: Es el
promedio de reseñas recibidas por mes. Refleja la actividad reciente del
listado.
Rooms.Rent.By.Host
: Nos indica el
número total de habitaciones o propiedades que el anfitrión ofrece en
alquiler. Permite identificar a los anfitriones con múltiples
propiedades.
Availability
: Es el número de días
al año en que el listado está disponible. Nos indica la oferta
disponible..
Updated.Date
: Es la fecha en que se
actualizó la información del listado por última vez.
City
: Especifica la ciudad dónde se
ubica el listado.
Country
: Especifica el país donde
se ubica el listado
Coordinates
: Son las coordenadas
geográficas del alojamiento.
Location
: Incluye País, Ciudad y
Barrio en una sola variable.
Ahora, examinamos que tipo de variables tenemos.
str(airbnb_data_raw)
## 'data.frame': 21278 obs. of 17 variables:
## $ Room.ID : int 36707573 21859113 21862103 21875158 21932504 22042300 22043848 22048206 22066174 22079865 ...
## $ Name : chr "Soggiorno Madrid-Hospitality , comfort & passion." "Be Mate Plaza España Wild 203" "Designer flat in Madrid's exclusive Salamanca area" "Estudio de diseño en pleno centro de Madrid" ...
## $ Host.ID : int 275911703 157114944 8851341 159570292 160055902 160981040 160967778 105797031 54343289 34563914 ...
## $ Neighbourhood : chr "Centro Storico" "Argüelles" "Recoletos" "Sol" ...
## $ Room.type : chr "Private room" "Entire home/apt" "Entire home/apt" "Private room" ...
## $ Room.Price : int 155 147 625 500 62 12 70 69 81 50 ...
## $ Minimum.nights : int 1 1 5 1 2 15 4 10 1 7 ...
## $ Number.of.reviews : int 1 1 2 110 194 11 109 51 87 12 ...
## $ Date.last.review : chr "2019-12-19" "2018-12-01" "2018-08-09" "2020-02-26" ...
## $ Number.of.reviews.per.month: num 0.16 0.05 0.06 3.41 6.16 0.4 3.43 1.64 2.74 0.44 ...
## $ Rooms.rent.by.the.host : int 3 36 1 2 1 3 6 2 6 1 ...
## $ Availibility : int 173 0 177 347 286 106 125 47 0 301 ...
## $ Updated.Date : chr "2020-06-19" "2020-07-17" "2020-07-17" "2020-07-17" ...
## $ City : chr "Florence" "Madrid" "Madrid" "Madrid" ...
## $ Country : chr "Italy" "Spain" "Spain" "Spain" ...
## $ Coordinates : chr "43.78225294444298, 11.241335882500344" "40.421911823829916, -3.716298875039167" "40.42205487717857, -3.6889613202974916" "40.41573794398538, -3.7044166450981693" ...
## $ Location : chr "Italy, Florence, Centro Storico" "Spain, Madrid, Argüelles" "Spain, Madrid, Recoletos" "Spain, Madrid, Sol" ...
Vemos que tenemos variables categóricas como
Room.type, Neighbourhood, City y Country
que no se están
tomando como tal, sino como character
. Lo mismo sucede con
las fechas.
Con esta información, vamos a proceder a hacer las conversiones necesarias:
Tratamiento datos
Conversión de variables
Para asegurar la integridad del dataframe original mientras
realizamos cambios, primero crearemos una copia del mismo. Todas las
modificaciones y análisis se realizarán en esta copia, denominada
airbnb_data
.
airbnb_data <- data.frame(airbnb_data_raw)
Como hemos visto arriba, hay columnas que deberían ser de diferente clase.
str(airbnb_data[, c("Date.last.review", "Updated.Date", "Room.type", "Neighbourhood", "City", "Country")])
## 'data.frame': 21278 obs. of 6 variables:
## $ Date.last.review: chr "2019-12-19" "2018-12-01" "2018-08-09" "2020-02-26" ...
## $ Updated.Date : chr "2020-06-19" "2020-07-17" "2020-07-17" "2020-07-17" ...
## $ Room.type : chr "Private room" "Entire home/apt" "Entire home/apt" "Private room" ...
## $ Neighbourhood : chr "Centro Storico" "Argüelles" "Recoletos" "Sol" ...
## $ City : chr "Florence" "Madrid" "Madrid" "Madrid" ...
## $ Country : chr "Italy" "Spain" "Spain" "Spain" ...
Data.last.review y Update.Date
.
Deberían ser fecha: Ya que ambas variables permiten cálculos como la diferencia de días entre eventos o agrupación por períodos de tiempo (por ejemplo, mes, año), lo cual sería difícil si estas variables fueran tratadas como character.
Room.type, Neighbourhood, City y Country
Deberían ser categóricas: Ya que agrupan los datos usando etiquetas textuales y facilitan análisis por segmentos. Además, no representan cantidades ni establecen un orden, características típicas de las variables categóricas.
Data.last.review y Update.Date
Convertimos ambas columnas a fechas:
# Convertir la columna Date.last.review a tipo fecha
airbnb_data$Date.last.review <- as.Date(airbnb_data$Date.last.review, format = "%Y-%m-%d")
# Convertir la columna Updated.Date a tipo fecha
airbnb_data$Updated.Date <- as.Date(airbnb_data$Updated.Date, format = "%Y-%m-%d")
Comprobamos que se ha realizado con éxito el cambio:
str(airbnb_data[, c("Date.last.review", "Updated.Date")])
## 'data.frame': 21278 obs. of 2 variables:
## $ Date.last.review: Date, format: "2019-12-19" "2018-12-01" ...
## $ Updated.Date : Date, format: "2020-06-19" "2020-07-17" ...
Como podemos comprobar arriba, el cambio se hizo correctamente.
Data.last.review y Update.Date
son ahora fechas.
Room.type, Neighbourhood, City y Country
Convertimos estas variables en categóricas.
# Convertir columnas a factores (categóricas)
airbnb_data$Room.type <- as.factor(airbnb_data$Room.type)
airbnb_data$Neighbourhood <- as.factor(airbnb_data$Neighbourhood)
airbnb_data$City <- as.factor(airbnb_data$City)
airbnb_data$Country <- as.factor(airbnb_data$Country)
Nos aseguramos que los cambios se han hecho bien:
summary(airbnb_data[, c("Room.type", "Neighbourhood", "City", "Country")])
## Room.type Neighbourhood City
## Entire home/apt:12719 Embajadores: 2559 Madrid :21255
## Hotel room : 221 Universidad: 2059 Girona : 6
## Private room : 8006 Palacio : 1490 Barcelona : 5
## Shared room : 332 Sol : 1366 Mallorca : 2
## Justicia : 1116 San-francisco: 2
## Cortes : 976 Brussels : 1
## (Other) :11712 (Other) : 7
## Country
## Spain :21269
## Italy : 2
## United states: 2
## Australia : 1
## Belgium : 1
## Portugal : 1
## (Other) : 2
Con la información de arriba, comprobamos que efectivamente que
Room.type, Neighbourhood, City y Country
son ahora
variables catégoricas.
City y Country
Podemos notar que en City y Country
, hay algo que llama
la atención. Se incluyen otras ciudades y países que no deberían
estar.
summary(airbnb_data[, c("City", "Country")])
## City Country
## Madrid :21255 Spain :21269
## Girona : 6 Italy : 2
## Barcelona : 5 United states: 2
## Mallorca : 2 Australia : 1
## San-francisco: 2 Belgium : 1
## Brussels : 1 Portugal : 1
## (Other) : 7 (Other) : 2
Vemos que no todos los registros que pertenecen a España, son de Madrid.
Hacemos un filtrado en el que solo mantenemos las filas donde el
valor de la columna City es Madrid.
# Filtrar el dataset para mantener solo las filas donde la ciudad es Madrid
airbnb_data <- airbnb_data |> filter(City == "Madrid")
Una vez hecho el filtro, verificamos que en City
, no
aparezca nada que no sea Madrid
.
# Verificar que sólo quedan entradas de Madrid en el dataset original
summary(airbnb_data[, c("City", "Country")])
## City Country
## Madrid :21255 Spain :21255
## Barcelona: 0 Australia: 0
## Brussels : 0 Belgium : 0
## Florence : 0 Italy : 0
## Girona : 0 Portugal : 0
## Istanbul : 0 Turkey : 0
## (Other) : 0 (Other) : 0
La información de arriba, es lo que esperabamos, solo quedan los
registros de Madrid.
Sin embargo siguen apareciendo otros niveles tanto en
City y Country
. Ya no los necesitamos, así los vamos a
eliminar.
airbnb_data$City <- droplevels(airbnb_data$City)
airbnb_data$Country <- droplevels(airbnb_data$Country)
#Verificamos que solo queden los niveles deseados en City y Country
summary(airbnb_data[, c("City", "Country")])
## City Country
## Madrid:21255 Spain:21255
Neighbourhood
Hemos notado que en los barrios, parecen haber barrios que no pertenecen a Madrid. Vamos a verlo:
# Revisamos las frecuencias de cada barrio
table_neighbourhoods <- table(airbnb_data$Neighbourhood)
# Mostrar los barrios con frecuencia cero
zero_freq_neighbourhoods <- table_neighbourhoods[table_neighbourhoods == 0]
print(zero_freq_neighbourhoods)
##
## Areeiro Arenal Blanes
## 0 0 0
## Bruxelles Camden Castell-Platja d'Aro
## 0 0 0
## Catania Centro Storico Excelsior
## 0 0 0
## Fatih les Tres Torres Palma de Mallorca
## 0 0 0
## Roses Sants Sydney
## 0 0 0
Vemos que hay 0 alojamientos en Madrid, para esos barrios, porque no son barrios madrileños. Eliminamos esos niveles.
# Eliminar esos niveles del factor
airbnb_data$Neighbourhood <- droplevels(airbnb_data$Neighbourhood)
Ahora podemos comprobar, que esos niveles se ha removido:
# Verificar nuevamente las frecuencias para confirmar que los niveles cero han sido eliminados
table(airbnb_data$Neighbourhood)
##
## Abrantes Acacias
## 48 210
## Adelfas Aeropuerto
## 78 14
## Aguilas Alameda de Osuna
## 55 62
## Almagro Almenara
## 221 93
## Almendrales Aluche
## 68 102
## Ambroz Amposta
## 20 13
## Apostol Santiago Arapiles
## 25 203
## Aravaca Arcos
## 38 51
## Argüelles Atocha
## 307 20
## Bellas Vistas Berruguete
## 169 154
## Buenavista Butarque
## 75 13
## Campamento Canillas
## 29 111
## Canillejas Cármenes
## 87 59
## Casa de Campo Casco Histórico de Barajas
## 104 36
## Casco Histórico de Vallecas Casco Histórico de Vicálvaro
## 86 49
## Castellana Castilla
## 200 61
## Castillejos Chopera
## 147 156
## Ciudad Jardín Ciudad Universitaria
## 93 75
## Colina Comillas
## 22 97
## Concepción Corralejos
## 107 17
## Cortes Costillares
## 976 43
## Cuatro Caminos Cuatro Vientos
## 224 9
## Delicias El Goloso
## 195 14
## El Pardo El Plantío
## 3 6
## El Viso Embajadores
## 96 2559
## Entrevías Estrella
## 47 32
## Fontarrón Fuente del Berro
## 51 147
## Fuentelareina Gaztambide
## 3 250
## Goya Guindalera
## 347 264
## Hellín Hispanoamérica
## 29 112
## Horcajo Ibiza
## 4 196
## Imperial Jerónimos
## 136 104
## Justicia La Paz
## 1116 29
## Legazpi Lista
## 57 209
## Los Angeles Los Rosales
## 31 62
## Lucero Marroquina
## 97 28
## Media Legua Mirasierra
## 21 36
## Moscardó Niño Jesús
## 106 48
## Nueva España Numancia
## 64 204
## Opañel Orcasitas
## 96 13
## Orcasur Pacífico
## 27 212
## Palacio Palomas
## 1490 17
## Palomeras Bajas Palomeras Sureste
## 82 40
## Palos de Moguer Pavones
## 373 5
## Peñagrande Pilar
## 76 70
## Pinar del Rey Piovera
## 92 32
## Portazgo Pradolongo
## 55 40
## Prosperidad Pueblo Nuevo
## 156 140
## Puerta Bonita Puerta del Angel
## 75 339
## Quintana Recoletos
## 115 332
## Rejas Rios Rosas
## 92 217
## Rosas Salvador
## 105 28
## San Andrés San Cristobal
## 49 21
## San Diego San Fermín
## 204 35
## San Isidro San Juan Bautista
## 168 32
## San Pascual Santa Eugenia
## 40 10
## Simancas Sol
## 131 1366
## Timón Trafalgar
## 59 394
## Universidad Valdeacederas
## 2059 117
## Valdefuentes Valdemarín
## 105 6
## Valdezarza Vallehermoso
## 57 79
## Valverde Ventas
## 100 181
## Vinateros Vista Alegre
## 20 120
## Zofío
## 53
No aparece ningún nivel con frecuencia 0.
Availibility
Transformamos la columna Availibility
en una variable categórica, definiendo rangos específicos que
representan distintos niveles de disponibilidad.
Esta categorización nos permite analizar y visualizar la información de
forma más eficiente.
# Rangos de disponibilidad y sus etiquetas
breaks <- c(-1, 0, 30, 60, 90, 180, 365)
labels <- c("No disponible", "Hasta 30 días", "31 a 60 días", "61 a 90 días", "91 a 180 días", "181 a 365 días")
# Categorizar 'Availability' usando cut()
airbnb_data$Availability_Cat <- cut(airbnb_data$Availibility, breaks = breaks, labels = labels, include.lowest = TRUE)
airbnb_data$Availability_Cat<- as.factor(airbnb_data$Availability_Cat)
Realizamos una revisión de la nueva columna para asegurarnos de que la asignación de categorías se hizo correctamente
summary(airbnb_data$Availability_Cat)
## No disponible Hasta 30 días 31 a 60 días 61 a 90 días 91 a 180 días
## 5465 1519 815 1982 3288
## 181 a 365 días
## 8186
Coordinates
La columna Coordinates
contiene pares
de valores separados por comas que representan la latitud y longitud
geográfica
head(airbnb_data$Coordinates)
## [1] "40.421911823829916, -3.716298875039167"
## [2] "40.42205487717857, -3.6889613202974916"
## [3] "40.41573794398538, -3.7044166450981693"
## [4] "40.421747567976034, -3.710994512030235"
## [5] "40.391493766002014, -3.6450831761057922"
## [6] "40.418435611771486, -3.7013169106966464"
Dividimos Coordinates
en dos columnas,
Latitude y Longitude que representan las coordenadas geográficas.
# Asegurándonos de que la columna Coordinates está en formato de string
airbnb_data$Coordinates <- as.character(airbnb_data$Coordinates)
# Separar la columna Coordinates en dos nuevas columnas: Latitude y Longitude
airbnb_data<- airbnb_data |>
separate(Coordinates, into = c("Latitude", "Longitude"), sep = ",", convert = TRUE)
Nos aseguramos de que el cambio se ha hecho efectivo:
str(airbnb_data[, c("Latitude", "Longitude")])
## 'data.frame': 21255 obs. of 2 variables:
## $ Latitude : num 40.4 40.4 40.4 40.4 40.4 ...
## $ Longitude: num -3.72 -3.69 -3.7 -3.71 -3.65 ...
Como podemos comprobar, ahora la columna
Coordinates
se ha convertido en 2:
Latitude y Longitude
Location
Parece que la columna Location
es la combinación de
Country, City, y Neighbourhood
. Vamos a probarlo:
#Crea una nueva columna temporal llamada Combined que combina Country,City y Neighbourhood, separados por ",".
airbnb_data <- airbnb_data |> mutate(Combined = paste(Country, City, Neighbourhood, sep = ", "))
#Compara la columna Location con la columna Combined para verificar si son iguales.
airbnb_data <- airbnb_data |> mutate(Location_Match = Location == Combined)
Se ha creado una nueva columna
Combined
, combinando los valores de
Country
,
City
y
Neighbourhood
con comas como separadores.
Luego se añade la columna Location_Match
para probar si esta combinación coincide exactamente con los valores de
Location
. Si coinciden
Combined y Location
devuelve
True
, sino False
.
Ahora contamos cuantas filas tienen True, en
Location_Match
#Revisa cuánto filas de Location_Match tienen true
table(airbnb_data$Location_Match)
##
## TRUE
## 21255
El número de filas, coincide con el número registros totales del dataset.
Location
es, por tanto, redundante; por lo que se puede
eliminar.
También elimaremos las columnas temporales que creamos para llegar a
esta conclusiónLocation_Match y Combined:
#Eliminar Location, Location_Match y Combined
airbnb_data <- select(airbnb_data, -Location)
airbnb_data <- select(airbnb_data, -Location_Match)
airbnb_data <- select(airbnb_data, -Combined)
Estudio de los valores nulos y ceros.
Creamos un dataframe solo con las columnas que tienen nulos o ceros
# Dataframe con la contabilización de Nulos y Ceros
resultados <- data.frame(
NA_Count = colSums(is.na(airbnb_data)),
Zero_Count = colSums(airbnb_data == 0, na.rm = TRUE)
)
# Filtrar y mostrar solo las filas con NA o ceros
resultados_con_NA_o_ceros <- resultados[apply(resultados, 1, function(x) any(x > 0)), ]
print(resultados_con_NA_o_ceros)
## NA_Count Zero_Count
## Room.Price 0 1
## Number.of.reviews 0 5400
## Date.last.review 5400 0
## Number.of.reviews.per.month 5400 0
## Availibility 0 5465
La columna NA_Count
muestra la cantidad
de valores nulos en cada variable, mientras que
Zero_Count
indica cuántas veces aparece el
valor cero.
Room.Price
carece de nulos pero tiene
un cero, lo que puede indicar una oferta sin coste o un error.
Number.of.reviews
tiene 5400 ceros,
mostrando que muchas propiedades no han sido evaluadas. Las columnas
Date.last.review
y
Number.of.reviews.per.month
tienen 5400
NAs cada una, sugiriendo que muchas propiedades no han sido revisadas
recientemente. Availability
no tiene NAs
pero presenta 5465 ceros, indicando una alta cantidad de propiedades no
disponibles.
Abajo, podemos ver un gráfico que ilustra todas las atributos que tienen valores nulos. Enseñandonos su porcentaje con respecto al total de datos.
Los valores nulos de Date.last.review
y
Number.of.reviews.per.month
representan alrededor del 25%
de sus datos.
library(visdat)
library(naniar)
vis_miss(airbnb_data, sort_miss = TRUE)
Tratamiento de los valores nulos y ceros.
Room.Price
Tiene un solo registro con valor cero.
paste("Total suma de filas de Room.Price con ceros:", sum(airbnb_data$Room.Price == 0))
## [1] "Total suma de filas de Room.Price con ceros: 1"
Eliminamos ese registro porque probablemente es un error de entrada, ya que es poco probable que una habitación de hotel tenga un coste cero. Este error podría distorsionar nuestro análisis estadístico y posterior modelo predictivo. Eliminando este dato, aseguramos la calidad y consistencia del dataset.
Number.of.reviews
, Date.last.review
y
Number.of.reviews.per.month.
Vemos que existe la misma cantidad de ceros en
Number.of.reviews
, que nulos en
Date.last.review
y
Number.of.reviews.per.month.
¿Están relacionados estos tres valores?
Vamos a verlo:
# Añadir una columna temporal para verificar la condición
airbnb_data <- airbnb_data |>
mutate(
check = Number.of.reviews == 0 & is.na(Date.last.review) & is.na(Number.of.reviews.per.month)
)
# Sumar para comprobar la condición basada en Number.of.reviews
result <- airbnb_data |>
filter(Number.of.reviews == 0) |>
summarise(
Total_Zero_Reviews = n(),
Correct_Cases = sum(check)
)
print(result)
## Total_Zero_Reviews Correct_Cases
## 1 5400 5400
Creamos una columna llamada check
. Esta columna nos
ayuda a ver si el número de reseñas = 0
y, al mismo
tiempo, si las
fechas de la última reseña y el promedio de reseñas por mes están vacíos
.
Si se cumplen estas condiciones, la columna indica
True; si no, muestra False.
Comparamos, la columna check
con la cuenta de los
registros donde el número de reseñas es cero.
Este paso nos permite comprobar si nuestra teoría sobre la relación entre estas variables se sostiene.
Y si, tiene sentido, ya que si un alojamiento no ha recibido reseñas, no podría tener una fecha de última reseña ni un promedio de reseñas mensuales.
Ahora elimaremos la columna temporal check.
#Eliminar check
airbnb_data <- select(airbnb_data, -check)
Categorización de Number.of.reviews
y
Number.of.reviews.per.month
Vamos a categorizar Number.of.reviews
y
Number.of.reviews.per.month
para un mejor análisis
Empezamos colocando ceros en los valores nulos de
Number.of.reviews.per.month, para
asegurarmos podamos operar
sobre esta columna, más adelante.
sum(is.na(airbnb_data$Number.of.reviews.per.month))
## [1] 5400
Tenemos 5400 registros vacíos
de
Number.of.reviews.per.month.
Sustituimos NA por el valor cero.
# Sustituir directamente los NA por 0 en la columna específica
airbnb_data$Number.of.reviews.per.month[is.na(airbnb_data$Number.of.reviews.per.month)] <- 0
#Calculo el total de filas con NA
paste("Total suma de filas NA:", sum(is.na(airbnb_data$Number.of.reviews.per.month)))
## [1] "Total suma de filas NA: 0"
#Calculo el total de filas con ceros
paste("Total suma de filas con ceros:", sum(airbnb_data$Number.of.reviews.per.month == 0))
## [1] "Total suma de filas con ceros: 5400"
Como podemos comprobar, el cambio se ha hecho correctamente. Los
5400 registros vacíos
de
Number.of.reviews.per.month
ahora contienen ceros.
Ahora categorizamos la variable:
airbnb_data <- airbnb_data |>
mutate(
Review_category = factor(
cut(
Number.of.reviews.per.month,
breaks = c(-Inf, 0, 1, 5, Inf), # Incluyendo la categoría de "sin reseñas" en los breaks
labels = c('sin reseñas', '0-1/mes', '1-5/mes', '+5/mes'),
include.lowest = TRUE,
right = TRUE
)
)
)
summary(airbnb_data$Review_category)
## sin reseñas 0-1/mes 1-5/mes +5/mes
## 5400 8771 6452 632
#NOTA:
#El intervalo sin reseñas, incluye todos los ceros
#El intervalo 0-1/mes, incluye los valores desde 0.01 reviews.per.month a 1 (incluido)
#El intervalo 1-5/mes, incluye los valores desde 1,01 reviews.per.month a 5 (incluido) y asi sucesivamente
Si el valor es cero, lo categorizamos como “sin reseñas”. Luego, los
valores se dividen en los siguientes rangos: 0-1/mes, 1-5/mes, +5/mes.
Finalmente, creamos una nueva variable llamada
Review_category
que contiene estas
categorías.
Ahora categorizamos la variable Number.of.reviews
:
airbnb_data <- airbnb_data |>
mutate(
Review_Count_Category = factor(
cut(
Number.of.reviews,
breaks = c(-Inf, 0, 1, 10, 50, 100, Inf), # Incluyendo la condición de 0 reseñas directamente en los breaks
labels = c('sin reseñas', '1 reseña', '2-10 reseñas', '11-50 reseñas', '51-100 reseñas', '+ 101 reseñas'),
include.lowest = TRUE,
right = TRUE
),
levels = c('sin reseñas', '1 reseña', '2-10 reseñas', '11-50 reseñas', '51-100 reseñas', '+ 101 reseñas')
)
)
# Puedes verificar el resultado con:
summary(airbnb_data$Review_Count_Category)
## sin reseñas 1 reseña 2-10 reseñas 11-50 reseñas 51-100 reseñas
## 5400 1791 4871 4749 2078
## + 101 reseñas
## 2366
#NOTA:
#El intervalo sin reseñas, incluye todos los ceros
#El intervalo1 reseña, incluye solos los valor 1
#El intervalo '2-10 reseñas', incluye los valores desde 2 a 10 (incluido)
#El intervalo '11-50 reseñas', incluye los valores desde 11 a 50 (incluido) y asi sucesivamente
Si hay cero reseñas, se etiqueta como “sin reseñas”. De lo contrario,
se divide en varios rangos, como 1 reseña, 2-10 reseñas, 11-50 reseñas,
etc. Luego, se crea una nueva variable llamada
Review_Count_Category
con estas
categorías.
Date.last.review
Es fundamental considerar la actualidad de la información y su
influencia en los análisis y decisiones basadas en estos datos. Vamos a
calcular la cantidad de días transcurridos desde la última review hasta
la fecha de extracción de los datos*.
*La fecha de extracción, ha sido obtenida directamente del apartado de "Information --> Last processing -> data" del dataset: Information Airbnb Listings en OpenDataSoft.
# Calcular el número de días desde la última revisión hasta la fecha de extracción
#La fecha del ultimo scrapping fue
date_scraped <- as.Date("2020-08-07")
#Calculamos la diferencia entre ambas fechas
airbnb_data$time_since_last_review <- as.numeric(difftime(date_scraped, airbnb_data$Date.last.review, units = "days"))
Se calcula el tiempo transcurrido en días desde la fecha de
extracción de datos, representada por “2020-08-07”, hasta la fecha de la
última reseña. La diferencia se almacena en una nueva columna llamada
time_since_last_review
Ahora veamos la distribución de los datos en
time_since_last_review
# Histograma del tiempo transcurrido desde la última reseña
ggplot(airbnb_data, aes(x = time_since_last_review)) +
geom_histogram(bins = 30, fill = "#69b3a2", color = "#e9ecef") + # Colores personalizados para las barras
theme_minimal() +
theme(
text = element_text(family = "Helvetica", size = 12), # Cambia la fuente y el tamaño del texto
plot.title = element_text(face = "bold", hjust = 0.5), # Negrita y centrado para el título
plot.subtitle = element_text(face = "italic", hjust = 0.5), # Subtítulo en cursiva y centrado
axis.title = element_text(face = "bold", size = 13) # Títulos de los ejes en negrita y tamaño aumentado
) +
labs(
title = "Distribución de días desde la última reseña",
subtitle = "Visualización de la frecuencia de días transcurridos",
x = "Días desde la última reseña", y = "Frecuencia"
)
## Warning: Removed 5400 rows containing non-finite outside the scale range
## (`stat_bin()`).
La mayoría de las reseñas se agrupan en los primeros 250 días, donde vemos que los datos se concentran entre 0 y 50 días, lo que indica que muchas propiedades fueron reseñadas poco antes de la fecha en que se extrajeron los datos.
Después de este pico inicial, se ve una disminución gradual en la cantidad de reseñas a medida que aumentan los días desde la última reseña, lo que sugiere una menor actividad en propiedades con reseñas más antiguas.
En el gráfico, se eliminaron 5400 filas que contenían valores nulos .
Dado que estas filas no tienen la fecha de la última reseña
(Date.last.review es nulo
), el cálculo de
los días transcurridos desde la última reseña hasta la fecha de
extracción también resulta en valores nulos.
Para poder realizar un mejor análisis de los datos, vamos a
categorizar las nueva columna:
time_since_last_review
airbnb_data <- airbnb_data |>
mutate(
Time_category = factor(
if_else(
is.na(time_since_last_review),
'sin reseñas', # Asignar directamente 'sin reseñas' si el valor es NA
cut( # Usar cut solo en valores no-NA
time_since_last_review,
breaks = c(0, 60, 182, 365, Inf), # Definiendo los rangos
labels = c('hasta 8 semanas', '8 semanas - 6 meses', '6-12 meses', '1+ año'),
include.lowest = TRUE,
right = TRUE
)
),
levels = c('sin reseñas', 'hasta 8 semanas', '8 semanas - 6 meses', '6-12 meses', '1+ año')
)
)
summary(airbnb_data$Time_category)
## sin reseñas hasta 8 semanas 8 semanas - 6 meses 6-12 meses
## 5400 1672 7150 3802
## 1+ año
## 3231
#NOTA:
#El intervalo sin reseñas, incluye todos los nulos
#El intervalo hasta 8 semanas, incluye los valores hasta e 60 (incluida)
#El intervalo 8 semanas - 6 meses, incluye los valores desde 61 a 182 (incluido) y asi sucesivamente
Una vez categorizada
time_since_last_review
, ya no es
necesaria para los siguientes pasos, porque usaremos
Time_category
, en su lugar; así que la
eliminamos:
#Eliminar time_since_last_review
airbnb_data <- select(airbnb_data, -time_since_last_review)
Availibility
#Calculo el total de filas con ceros
paste("Total suma de filas con ceros en la columna Availibility es:", sum(airbnb_data$Availibility == 0))
## [1] "Total suma de filas con ceros en la columna Availibility es: 5465"
Los ceros de Availibility
, se han
tratado en la columna Availibility_Cat
como alojamientos “No disponibles”
paste("Total suma de filas con 'No disponible' en la columna Availability_Cat es:", sum(airbnb_data$Availability_Cat == "No disponible"))
## [1] "Total suma de filas con 'No disponible' en la columna Availability_Cat es: 5465"
Vemos que el recuento de “No disponible” en
Availibility_Cat
, coincide con el recuento
de ceros en Availibility.
Una vez categorizada Availibility
, y
comprobando que el tratamiento de ceros, se ha realizado correctamente,
ya no es necesaria para los siguientes pasos. Usaremos
Availibility_Cat
, en su lugar, así que la
eliminamos:
#Eliminar Availibility
airbnb_data <- select(airbnb_data, -Availibility)
Comprobación de Duplicados
Veamos sin en nuestro dataset, hay filas duplicadas.
filas_duplicadas <- airbnb_data[duplicated(airbnb_data), ]
if (nrow(filas_duplicadas) == 0) {
print("No hay filas duplicadas en el dataset.")
} else {
print("Se han encontrado filas duplicadas en el dataset.")
}
## [1] "No hay filas duplicadas en el dataset."
Utilizamos duplicated()
para crear un
vector que identifica filas repetidas en
airbnb_data
, almacenando dichas filas en
filas_duplicadas
. Luego, contamos cuantas
filas hay en filas_duplicadas
, con nrow()
.
El resultado es que no hay filas duplicadas.
Resumen cambios en las variables.
Resumen de las transformaciones aplicadas:
1. Date.last.review y Updated.Date
%Y-%m-%d
).2020-08-07
) hasta Date.last.review
. El
resultado se almacena en Time_category
y se categoriza en:
2. Room.type, Neighbourhood, City, Country
3. Availability
Categorizada en la columna Availibility_Cat
desde ‘No
disponible’ hasta ‘181 a 365 días’ en rangos que representan distintos
niveles de disponibilidad:
Tras categorizar Availibility
y comprobar que el
tratamiento de ceros, en la nueva columna Availibility_Cat era correcto.
Availibility se eliminó.
4. City
5. Coordinates
Latitude
y
Longitude
.6. Location
Country
,
City
, y Neighbourhood
.7. Number.of.reviews & Number.of.reviews.per.month
Review_Count_Category (basado en
Number.of.reviews
):
Review_category (basado en
Number.of.reviews.per.month
):
8. Neighbourhood
También se ha comprobado que no existan duplicados en el dataset.
Análisis Descriptivo
Análisis Outliers
Outliers variables numéricas
# Boxplot para Room.Price
ggplot(airbnb_data, aes(y = Room.Price)) +
geom_boxplot(fill = "lightblue", color = "darkblue") +
labs(title = "Distribución del Precio de la Habitación",
y = "Precio (€)",
x = "") +
theme_minimal()
# Boxplot para Minimum.nights
ggplot(airbnb_data, aes(y = Minimum.nights)) +
geom_boxplot(fill = "lightgreen", color = "darkgreen") +
labs(title = "Distribución del Número Mínimo de Noches Requeridas",
y = "Número Mínimo de Noches",
x = "") +
theme_minimal()
# Boxplot para Rooms.rent.by.the.host
ggplot(airbnb_data, aes(y = Rooms.rent.by.the.host)) +
geom_boxplot(fill = "lightyellow", color = "goldenrod") +
labs(title = "Distribución del Número de Habitaciones alquiladas por el host",
y = "Habitaciones alquiladas por el host",
x = "") +
theme_minimal()
library(dplyr)
library(tidyr) # Para pivotar el dataframe
# Filtrar sólo columnas numéricas
numeric_data <- airbnb_data %>%
select(where(is.numeric))
# Calcular y mostrar el porcentaje de outliers para cada columna numérica
outliers_summary <- numeric_data %>%
summarise(across(everything(), ~ {
q1 <- quantile(., 0.25, na.rm = TRUE)
q3 <- quantile(., 0.75, na.rm = TRUE)
irq <- q3 - q1
lower_bound <- q1 - 1.5 * irq
upper_bound <- q3 + 1.5 * irq
perc_outliers <- mean(. < lower_bound | . > upper_bound, na.rm = TRUE) * 100
sprintf("%.2f%%", perc_outliers)
}, .names = "outliers_in_{.col}")) %>%
pivot_longer(cols = everything(), names_to = "Variable", values_to = "Percentage_Outliers")
# Imprimir el resumen de outliers como una tabla
print(outliers_summary)
## # A tibble: 9 × 2
## Variable Percentage_Outliers
## <chr> <chr>
## 1 outliers_in_Room.ID 0.00%
## 2 outliers_in_Host.ID 0.00%
## 3 outliers_in_Room.Price 11.40%
## 4 outliers_in_Minimum.nights 11.39%
## 5 outliers_in_Number.of.reviews 11.77%
## 6 outliers_in_Number.of.reviews.per.month 6.72%
## 7 outliers_in_Rooms.rent.by.the.host 15.43%
## 8 outliers_in_Latitude 8.48%
## 9 outliers_in_Longitude 11.83%
Al analizar los outliers de los atributos numéricos, percibimos que aquellos que tienen datos extraños son el precio por habitación, el mínimo de noches, el número de reviews, el número de reviews por mes, las habitaciones alquiladas por cada host, latitud y longitud. Estos datos se normalizarán en el paso siguiente de pre-procesamiento. Por otra parte, el Room ID y el Host ID no presentan outliers.
Outliers variables catégoricas
library(dplyr)
library(tidyr)
library(purrr)
library(ggplot2)
# Filtrar solo columnas categóricas de tipo factor
categorical_data <- airbnb_data %>%
select(where(is.factor))
# Calcula las tablas de frecuencia para cada variable categórica, las ordena y crea gráficos
graphs <- imap(categorical_data, ~ {
freq <- table(.x)
df <- tibble(
Category = names(freq),
Frequency = as.integer(freq),
Percentage = round(100 * prop.table(freq), 2)
) %>%
arrange(desc(Percentage))
# Verificar si estamos trabajando con la variable 'Neighbourhood'
if (.y == "Neighbourhood") {
# Filtrar para obtener los top 3 y bottom 10
df <- df %>%
mutate(Position = ifelse(row_number() <= 3, "Top 3",
ifelse(row_number() > n() - 10, "Bottom 10", "Otros"))) %>%
filter(Position != "Otros") %>%
arrange(desc(Frequency))
# Crear un gráfico para la variable 'Neighbourhood'
ggplot(df, aes(x = reorder(Category, -Frequency), y = Frequency, fill = Position)) +
geom_bar(stat = "identity") +
labs(title = "Top 3 y Bottom 10 Barrios por Frecuencia",
x = "Barrio",
y = "Frecuencia") +
theme_minimal() +
coord_flip()
} else {
# Crear un gráfico para cualquier otra variable categórica
ggplot(df, aes(x = reorder(Category, -Percentage), y = Percentage, fill = Category)) +
geom_bar(stat = "identity") +
labs(title = paste("Tabla de Frecuencia para la Variable:", .y),
x = "Categoría",
y = "Porcentaje (%)") +
theme_minimal() +
coord_flip()
}
})
# Iterar sobre la lista de gráficos y imprimir cada uno
walk(graphs, ~ suppressWarnings(print(.x)))
## Don't know how to automatically pick scale for object of type <table>.
## Defaulting to continuous.
## Don't know how to automatically pick scale for object of type <table>.
## Defaulting to continuous.
## Don't know how to automatically pick scale for object of type <table>.
## Defaulting to continuous.
## Don't know how to automatically pick scale for object of type <table>.
## Defaulting to continuous.
## Don't know how to automatically pick scale for object of type <table>.
## Defaulting to continuous.
## Don't know how to automatically pick scale for object of type <table>.
## Defaulting to continuous.
## Don't know how to automatically pick scale for object of type <table>.
## Defaulting to continuous.
Para analizar los valores extremos de los atributos categóricos, hemos utilizado las frecuencias de cada uno. Gracias a esto, podemos percibir que hay muchos barrios de Madrid en los que apenas hay listings, mientras que aquellos en los que más hay son aquellos que se encuentran cerca de la universidad o cerca del centro. Del mismo modo, podemos ver que las habitaciones de hotel y las habitaciones compartidas son poco comunes entre los tipos de habitación. En el resto de variables categóricas no se perciben valores inusualmente extremos.
Análisis Variables Categóricas
1. Neighbourhood
#Histogramas
# Grafico para barrio
# Calcular la frecuencia de cada barrio
neighbourhood_freq <- airbnb_data |>
count(Neighbourhood) |>
arrange(desc(n)) |>
top_n(10, n) # Selecciona solo los top 10
# Graficar la distribución por barrio solo para los top 10
ggplot(neighbourhood_freq, aes(x = n, y = reorder(Neighbourhood, n))) +
geom_bar(stat = "identity", fill = "steelblue") + # Cambia el color de las barras
theme_minimal() +
theme(axis.text.y = element_text(size = 15), # Aumenta el tamaño del texto del eje y
plot.title = element_text(size = 22, face = "bold")) + # Aumenta y pone en negrita el título
labs(title = "Distribución por Barrio", x = "Frecuencia", y = "Barrio")
Observaciones clave del gráfico:
El gráfico representa la
frecuencia de listados de Airbnb en los top 10 barrios más populares
.
Cada barra representa un barrio diferente, ordenados de mayor a menor
frecuencia de arriba hacia abajo.
Barrios (Eje Y): Desde la parte superior, los barrios listados son Embajadores, Universidad, Palacio, Sol, Justicia, Cortes, Trafalgar, Palos de Moguer, Goya, y Puerta del Angel.
Frecuencia (Eje X): La escala del eje X va desde 0 hasta más de 2000, indicando el número de listados en cada barrio.
El barrio de Embajadores
tiene la mayor cantidad de
listados, seguido por Universidad y Palacio
. Los barrios de
Goya y Puerta del Angel
, al final de la lista, tienen
considerablemente menos listados comparados con los más altos en la
gráfica.
Suposiciones
2. Room.type
# Gráfico para Room.type
ggplot(airbnb_data, aes(x = Room.type)) +
geom_bar(fill = "coral") +
labs(title = "Distribución por Tipo de Habitación", x = "Tipo de Habitación", y = "Frecuencia")
Las barras representan la frecuencia de cada tipo de habitación en el
dataset.
Tipos de Habitación (Eje X): Las categorías mostradas son “Entire home/apt” (casa o apartamento completo), “Hotel room” (habitación de hotel), “Private room” (habitación privada) y “Shared room” (habitación compartida).
Frecuencia (Eje Y): El eje vertical muestra la cantidad de listados, con una escala que va de 0 a más de 10,000.
Observaciones clave del gráfico:
Casa o apartamento completo tiene la frecuencia más alta, indicando que es el tipo de alojamiento más comúnmente listado.
Habitación privada es el segundo tipo más común, aunque con una frecuencia significativamente menor en comparación con las casas o apartamentos completos.
Habitación de hotel y habitación compartida tienen las menores frecuencias, con la habitación compartida mostrando la menor popularidad entre los listados.
Suposiciones
3. Availability_Cat
# Gráfico para Availability_Cat
ggplot(airbnb_data, aes(x = Availability_Cat)) +
geom_bar(fill = "purple") +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
labs(title = "Distribución por Categoría de Disponibilidad", x = "Categoría de Disponibilidad", y = "Frecuencia")
Las barras representan la cantidad de propiedades disponibles según diferentes rangos de días al año en que están disponibles para ser alquiladas.
Categorías de Disponibilidad (Eje X): Se dividen en seis categorías distintas: “No disponible”, “Hasta 30 días”, “31 a 60 días”, “61 a 90 días”, “91 a 180 días”, y “181 a 365 días”.
Frecuencia (Eje Y): El eje vertical indica la cantidad de listados, con una escala que va desde 0 hasta más de 8000.
Observaciones clave del gráfico:
No disponible y 181 a 365 días son las categorías con la mayor frecuencia, indicando que una gran cantidad de propiedades no están disponibles para alquiler o están disponibles casi todo el año.
Las demás categorías presentan una menor frecuencia, con la categoría “31 a 60 días” mostrando la menor cantidad de propiedades. Esto sugiere que menos propiedades están disponibles por períodos intermedios de tiempo.
Las propiedades que se alquilan por “91 a 180 días” tienen una frecuencia considerablemente más alta que las de corto plazo pero menor que las de muy largo plazo, reflejando posiblemente una preferencia por alquileres de medio término.
Suposiciones:
4. Time_category
# Gráfico para Time_category
ggplot(airbnb_data, aes(x = Time_category)) +
geom_bar(fill = "yellow") +
labs(title = "Distribución de Time_category (Resta entre Scrapping date y Date.last.review)", x = "Time_category", y = "Frecuencia")
Observaciones clave del gráfico:
El gráfico clasifica las propiedades de Airbnb según el tiempo transcurrido desde la última reseña, distribuyendo esta información a lo largo de cinco categorías distintas. El eje X representa las categorías de tiempo, y el eje Y muestra la frecuencia de listados en cada categoría.
Sin reseñas: Mayor frecuencia, indicando muchas propiedades sin reseñas recientes.
Hasta 8 semanas: Frecuencia significativamente más baja, menos propiedades revisadas recientemente.
8 semanas - 6 meses: Mayor frecuencia, refleja un volumen considerable de propiedades con reseñas moderadamente recientes.
6-12 meses: Frecuencia más baja que la categoría “8 semanas - 6 meses”, muestra menos actividad reciente.
1+ año: Frecuencia similar a “sin reseñas”, muchas propiedades sin reseñas en el último año.
Suposiciones:
Review_category
# Gráfico para Review_category
ggplot(airbnb_data, aes(x = Review_category)) +
geom_bar(fill = "green") +
labs(title = "Distribución de Review_category", subtitle ="(Categorizacion de Number.of.reviews.per.month)", x = "Review_category", y = "Frecuencia")
Observaciones clave del gráfico:
El gráfico “Distribución de Review_category” muestra la frecuencia de propiedades de Airbnb según el número de reseñas que reciben por mes. Las categorías van desde “sin reseñas” hasta “más de 5 reseñas por mes”. El eje X categoriza el número de reseñas mensuales y el eje Y muestra la frecuencia de propiedades en cada categoría.
Sin Reseñas: Representa una frecuencia moderada, lo que indica que un número considerable de propiedades no ha recibido reseñas.
0-1/mes: Esta categoría tiene la frecuencia más alta, sugiriendo que la mayoría de las propiedades reciben entre cero y una reseña por mes.
1-5/mes: La segunda categoría más frecuente, mostrando una actividad moderada en términos de reseñas.
+5/mes: Tiene menos frecuencia en comparación con las categorías de 0-1 y 1-5 reseñas por mes, lo que indica que pocas propiedades reciben más de cinco reseñas mensuales.
Suposiciones:
6. Review_Count_Category
# Gráfico para Review_Count_Category
ggplot(airbnb_data, aes(x = Review_Count_Category)) +
geom_bar(fill = "blue") +
labs(title = "Distribución de Review_Count_Category", subtitle ="(Categorizacion de Number.of.reviews)", x = "Review_Count_Category", y = "Frecuencia")
Observaciones clave del gráfico:
El gráfico muestra la distribución de
propiedades de Airbnb según el número total de reseñas recibidas
,
categorizando desde “sin reseñas” hasta “más de 101 reseñas”. En el
eje X se categoriza el número total de reseñas y el
eje Y muestra la frecuencia de propiedades por
categoría.
Sin reseñas: Mayor frecuencia, muchas propiedades sin reseñas.
1 Reseña: Frecuencia menor que “sin reseñas”, notable descenso.
2-10 Reseñas: Mayor que “1 Reseña”, indica varias propiedades moderadamente revisadas.
11-50 Reseñas: Frecuencia similar a “2-10 Reseñas”, refleja estabilidad.
51-100 Reseñas: Menor que las categorías intermedias, aún significativa.
+101 Reseñas: La menor frecuencia, pocas propiedades muy revisadas.
Suposiciones:
Análisis Variables Numéricas
1. Room.Price
ggplot(airbnb_data, aes(x = Room.Price)) +
geom_density(fill = "lightblue", alpha = 0.7) +
labs(title = "Densidad del Precio de la Habitación",
x = "Precio (€)",
y = "Densidad") +
theme_minimal()
Observaciones clave del gráfico:
Alta concentración de precios bajos: La mayoría de los precios se agrupan en el extremo inferior, con un pico pronunciado cerca del origen.
Cola larga hacia precios altos: Existe una extensión hacia precios más elevados, indicando que algunas propiedades son considerablemente más caras.
Escasez de opciones caras: Los puntos dispersos hacia el final de la gráfica muestran que las propiedades muy caras son raras.
Predominio de opciones económicas: Una gran proporción de las habitaciones son asequibles, lo que sugiere que Airbnb es accesible para una amplia gama de usuarios.
Suposiciones:
2. Minimum.nights
ggplot(airbnb_data, aes(x = Minimum.nights)) +
geom_density(fill = "lightgreen", alpha = 0.7) +
labs(title = "Densidad del Número Mínimo de Noches Requeridas",
x = "Número Mínimo de Noches",
y = "Densidad") +
theme_minimal()
Observaciones clave del gráfico:
Dominio de estancias cortas: Una gran mayoría de los listados requieren un número mínimo de noches bajo, indicado por el pico prominente cerca de cero.
Raras exigencias de estancias largas: Hay algunas propiedades que requieren un número significativamente mayor de noches mínimas, como se observa en los picos menores y la cola hacia el derecho del gráfico.
Poca frecuencia en requisitos extremos: El gráfico tiene una cola muy larga con densidad cercana a cero, lo que sugiere que es muy poco común que se requieran estancias muy largas.
Suposiciones:
3.
Rooms.rent.by.the.host
ggplot(airbnb_data, aes(x = Rooms.rent.by.the.host)) +
geom_density(fill = "lightyellow", alpha = 0.7) +
labs(title = "Densidad del Número de Habitaciones alquiladas por el Host",
x = "Habitaciones alquiladas por el host",
y = "Densidad") +
theme_minimal()
Observaciones clave del gráfico:
Concentración en pocos listados: La mayoría de los hosts en Airbnb alquilan pocas habitaciones, como muestra el pico pronunciado cerca del origen.
Cola larga hacia más habitaciones: Existen algunos hosts que gestionan un número mayor de habitaciones, aunque son bastante menos frecuentes, evidenciado por la cola larga y baja hacia la derecha.
Hosts con múltiples propiedades son raros: La densidad se reduce drásticamente a medida que aumenta el número de habitaciones alquiladas, sugiriendo que los hosts con muchas propiedades son excepcionales.
Suposiciones:
EDA: Análisis Exploratorio de Datos
# Calcular la frecuencia de cada barrio
barrio_frecuencia <- airbnb_data %>%
count(Neighbourhood) %>%
arrange(desc(n)) # Ordenar en orden descendente por frecuencia
# Obtener los top 10 barrios más frecuentes
top10_barrios <- barrio_frecuencia %>%
top_n(10, n)
# Reorganizar el factor Neighbourhood en función de su frecuencia
airbnb_data$Neighbourhood <- factor(airbnb_data$Neighbourhood, levels = barrio_frecuencia$Neighbourhood)
# Filtrar solo las filas que corresponden a los 10 principales barrios
airbnb_data_top10 <- airbnb_data %>%
filter(Neighbourhood %in% top10_barrios$Neighbourhood)
# Trazar el histograma
ggplot(airbnb_data_top10, aes(x = Neighbourhood)) +
geom_bar(fill = "skyblue", color = "black") +
labs(title = "Top10 Barrios Más Populares Airbnb", subtitle="(Top 10, Orden Descendente)",
x = "Barrio",
y = "Frecuencia") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
¿Cuáles son los barrios más populares en Airbnb?
Embajadores es el barrio con la mayor frecuencia de listados, seguido de Universidad y Palacio, indicando que estas áreas son probablemente las más demandadas o con más oferta de alojamientos en Airbnb.
A partir de Sol en adelante, la frecuencia de los listados comienza a disminuir progresivamente.
Los barrios como Goya y Puerta del Angel muestran las menores frecuencias dentro de este top 10, lo que podría sugerir una menor popularidad o una oferta más limitada de alojamientos en estas áreas comparado con barrios como Embajadores o Universidad.
ggplot(airbnb_data, aes(x = Room.Price, y = Number.of.reviews)) +
geom_point(color = "blue") +
labs(title = "Distribución de Precio vs Número de Reseñas", x = "Precio de la Habitación (€)", y = "Número de Reseñas")
¿Existe una correlación entre los precios de las habitaciones y el número de reseñas que reciben, indicando la popularidad o la percepción de valor por parte de los usuarios?
Predominio de precios bajos y reseñas moderadas: La mayoría de los listados tienen precios inferiores a 1,000 euros y menos de 200 reseñas, indicando que las propiedades más asequibles son comunes pero no acumulan muchas reseñas.
Propiedades caras con reseñas dispersas: Algunas propiedades con precios superiores a 2,500 euros tienen desde cero hasta muchas reseñas, lo que sugiere que un precio más alto no garantiza una mayor popularidad.
Picos de popularidad a precios accesibles: Un número significativo de reseñas se observa en propiedades con precios razonables, lo que indica que ciertas propiedades a precios moderados son muy populares.
En resumen, no existe una correlación clara entre precios altos y un mayor número de reseñas, implicando que otros factores como ubicación y calidad podrían influir más en la popularidad.
ggplot(airbnb_data, aes(x = Room.type, y = Room.Price)) +
geom_bar(stat = "summary", fun = "mean", fill = "lightgreen", color = "darkgreen") +
geom_hline(yintercept = mean(airbnb_data$Room.Price), color = "red", linetype = "dashed", linewidth = 1) +
labs(title = "Precio Promedio vs Tipo de Habitación", x = "Tipo de Habitación", y = "Precio Promedio (€)")
¿Cómo varían los precios promedios de las diferentes tipos de habitaciones en Airbnb, y qué tipo de habitación tiene el precio promedio más alto en Madrid?
Casa o Apartamento Completo: Este tipo de alojamiento tiene el precio promedio más alto, considerablemente más alto que los otros tipos, lo que indica que alquilar una propiedad completa es generalmente más costoso.
Habitación de Hotel: Tiene un precio promedio significativamente más bajo que un apartamento o casa completa, pero más alto que una habitación privada o compartida.
Habitación Privada: Presenta un precio promedio más bajo que la habitación de hotel.
Habitación Compartida: Es la opción más económica, con el precio promedio más bajo entre los tipos de habitaciones mostrados.
library(dplyr)
library(ggplot2)
# Calcula la cantidad total de listados por barrio
barrios_counts <- airbnb_data %>%
group_by(Neighbourhood) %>%
summarise(Total = n()) %>%
arrange(desc(Total))
# Selecciona los top 10 barrios
top_barrios <- barrios_counts %>%
top_n(10, Total) %>%
pull(Neighbourhood)
# Filtra el dataset original para incluir solo esos barrios
filtered_data <- airbnb_data %>%
filter(Neighbourhood %in% top_barrios)
# Crear el gráfico de barras apiladas para los top 10 barrios
ggplot(data = filtered_data, aes(x = Neighbourhood, fill = `Room.type`)) +
geom_bar() +
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
labs(x = "barrio", y = "Cantidad") +
ggtitle("Distribución de tipos de propiedad por barrios en Madrid")
#
¿Cómo se distribuyen los diferentes tipos de habitaciones en el top 10 de barrios de Madrid y qué barrios presentan una mayor diversidad de opciones de alojamiento?
Diversidad de Habitaciones:
Embajadores y Universidad: Son los barrios con más alojamientos y una amplia variedad de tipos de habitaciones, predominando los apartamentos enteros y habitaciones privadas, con escasas opciones de habitaciones compartidas y de hotel.
Palacio, Sol y Justicia: Presentan un balance entre apartamentos enteros y habitaciones privadas, con Palacio ofreciendo algo más de habitaciones de hotel.
Mayor y Menor Diversidad:
Mayor Diversidad: Embajadores y Universidad destacan por su alta cantidad de listados y diversidad, con una mezcla de casi todos los tipos de alojamiento.
Menor Diversidad: Barrios como Tetuán, Triángulo del Arte, Chamberí, y Puerta del Ángel tienen menos opciones y se enfocan más en apartamentos enteros y habitaciones privadas, con limitadas ofertas de habitaciones de hotel o compartidas.
library(dplyr)
library(ggplot2)
# Calcula la cantidad total de listados por barrio
barrios_counts <- airbnb_data %>%
group_by(Neighbourhood) %>%
summarise(Total = n()) %>%
arrange(desc(Total))
# Selecciona los top 10 barrios
top_barrios <- barrios_counts %>%
slice_max(Total, n = 10) %>%
pull(Neighbourhood)
# Filtra el dataset original para incluir solo esos barrios
filtered_data <- airbnb_data %>%
filter(Neighbourhood %in% top_barrios)
# Crea el gráfico de boxplot para los top 10 barrios
ggplot(data = filtered_data, aes(x = Neighbourhood, y = Room.Price)) +
geom_boxplot(outlier.colour = "red", outlier.shape = 1) +
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
labs(x = "barrio", y = "Precio (€)", title = "Variabilidad de precios por barrio en Madrid - Top 10 barrios") +
theme_minimal()
¿Cómo varían los precios de los alojamientos de Airbnb entre diferentes barrios del top10 de Madrid y cuáles presentan la mayor variabilidad en sus precios?
barrios con Mayor Variabilidad: barrios como Embajadores, Universidad, y Palacio muestran una amplia variabilidad en precios, con numerosos valores atípicos que indican precios extremadamente altos comparados con la mediana.
barrios con Menor Variabilidad: barrios como Justicia, Cortes, y Puerta del Ángel parecen tener una variabilidad más contenida, con menos valores atípicos y cajas más estrechas, lo que sugiere precios más consistentes y predecibles.
Las columnas Latitude
y Longitude
están
disponibles, podemos crear un mapa de puntos en Madrid:
# Crear un mapa con Leaflet
madrid_map <- leaflet(data = airbnb_data) |>
addTiles() # Añadir el mapa base
# Añadir la capa de mapa de calor
madrid_map <- madrid_map |>
addHeatmap(
lng = ~Longitude, lat = ~Latitude, intensity = ~1,
blur = 20, max = 0.05, radius = 15
)
# Establecer la vista inicial del mapa y mostrarlo
madrid_map <- madrid_map |>
setView(lng = -3.703, lat = 40.416, zoom = 11)
# Imprimir el mapa
madrid_map
¿Cómo se distribuyen geográficamente los listados de Airbnb en Madrid y qué áreas muestran mayor concentración de alojamientos?
Alta Concentración en el Centro: La mayor densidad de listados de Airbnb se observa en el centro de Madrid. Las áreas alrededor del centro de la ciudad, particularmente barrios como Sol, Gran Vía, y La Latina, muestran los puntos más calientes en rojo intenso, indicando una concentración muy alta de alojamientos.
Dispersión hacia las Afueras: A medida que te alejas del centro, la concentración de listados disminuye, como se puede ver en las áreas que rodean el núcleo central de la ciudad. Sin embargo, hay bolsas de moderada actividad (áreas en amarillo y verde) en barrios periféricos y municipios cercanos como Pozuelo de Alarcón y Alcobendas.
# Definir una función para determinar el color basado en el precio
get_color <- function(price) {
if (price < 50) {
"#00FF00" # Verde para precios bajos
} else if (price < 100) {
"#FFFF00" # Amarillo para precios medios
} else {
"#FF0000" # Rojo para precios altos
}
}
# Crear el mapa usando leaflet, aplicando directamente la función de colores
madrid_map <- leaflet(data = airbnb_data) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(
~Longitude, ~Latitude,
radius = 5,
color = ~sapply(Room.Price, get_color),
fillOpacity = 0.5,
stroke = FALSE, # No dibujar bordes alrededor de los círculos
group = "Airbnb Locations"
) %>%
setView(lng = -3.703, lat = 40.416, zoom = 11)
# Imprimir el mapa
madrid_map
Observaciones del Gráfico:
Zonas Verdes (Precios Bajos): Las áreas con alojamientos de precios bajos (menos de 50 €) están dispersas pero menos concentradas que las de precios más altos. Parecen estar más presentes en las periferias de la ciudad y en municipios alejados del centro de Madrid.
Zonas Amarillas (Precios Medios): Las zonas que representan precios medios (entre 50 y 100 €) forman una transición entre las áreas más baratas y las más caras, y están distribuidas de forma bastante uniforme en toda la ciudad, indicando una opción de alojamiento común en muchas áreas.
Zonas Rojas (Precios Altos): Las áreas con alojamientos de precios altos (más de 100 €) están altamente concentradas en el centro de Madrid, especialmente en barrios céntricos y turísticos. Estas zonas rojas destacan en el corazón de la ciudad, donde se localizan muchas de las principales atracciones y servicios.
# Seleccionando columnas relevantes para la correlación
cor_data <- airbnb_data |>
select(Room.Price, Minimum.nights, Number.of.reviews, Number.of.reviews.per.month, Rooms.rent.by.the.host) |>
na.omit() # Eliminando filas con valores NA para evitar errores
# Calculando la matriz de correlación
cor_matrix <- cor(cor_data)
# Generando el mapa de calor
corrplot(cor_matrix, method = "color", type = "upper", order = "hclust",
tl.col = "black", tl.srt = 45, addrect = 2,
title = "Mapa de Calor de Correlación para AirBnB en Madrid",
mar = c(0, 0, 2, 0)) # Ajusta el margen superior para dar más espacio al título
¿Qué variables están más fuertemente correlacionadas con el precio de los alojamientos en Airbnb?
“Rooms.rent.by.the.host”: Esta variable muestra una correlación negativa con “Room.Price”, indicada por el tono azul oscuro en el gráfico. Esto sugiere que cuanto más habitaciones alquila un host, podría haber una tendencia a precios más bajos por habitación.
“Number.of.reviews” y “Number.of.reviews.per.month”: Ambas variables tienen una correlación muy baja con “Room.Price”, como se ve por los tonos más claros en el gráfico. Esto indica que la cantidad de reseñas no influye significativamente en el precio.
“Minimum.nights”: La correlación entre “Minimum.nights” y “Room.Price” también es baja, lo que indica que la cantidad mínima de noches requeridas no es un factor significativo en el precio de los alojamientos.
# Las columnas relevantes para la correlación, están guardadas en la variable cor_data
ggpairs(cor_data,
lower = list(continuous = wrap("points", alpha = 0.3,size=0.3 ,color='blue')),
upper = list(continuous = wrap("cor", size=4))
)
Correlaciones Notables:
Rooms.rent.by.the.host vs. Room.Price: Presenta una correlación positiva moderada (0.170***), sugiriendo que los anfitriones que alquilan más habitaciones tienden a cobrar precios más altos, aunque la relación no es muy fuerte.
Number.of.reviews.per.month vs. Number.of.reviews: Muestra una correlación muy alta (0.828***), lo cual es esperado ya que estas variables están intrínsecamente relacionadas.
Correlaciones Débiles o Negativas:
KPIs
Basado en nuestro análisis exploratorio, estos son nuestros KPIs elegidos:
Distribución geográfica de listados
Barrios más populares en Airbnb y su distribución según los de tipos de habitación
Correlación entre precio y número de reseñas
Variación de precios por tipo de habitación
Variabilidad de precios por barrio
Conclusiones del Análisis
Tratamiento del Dataset procesado
Una vez tratado el dataset, este es el set de datos con el trabajaremos en el pre-procesamiento y modelado.
colnames(airbnb_data)
## [1] "Room.ID" "Name"
## [3] "Host.ID" "Neighbourhood"
## [5] "Room.type" "Room.Price"
## [7] "Minimum.nights" "Number.of.reviews"
## [9] "Date.last.review" "Number.of.reviews.per.month"
## [11] "Rooms.rent.by.the.host" "Updated.Date"
## [13] "City" "Country"
## [15] "Latitude" "Longitude"
## [17] "Availability_Cat" "Review_category"
## [19] "Review_Count_Category" "Time_category"
Con las siguientes variables:
Room.ID
: Identificador único de cada
listado en Airbnb.Name
: Nombre descriptivo del
listado.Host.ID
: Identificador único del
anfitrión del listado.Neighbourhood
: Barrio donde se
encuentra el listado.Room.type
: Tipo de alojamiento
ofrecido.Room.Price
: Precio por noche del
alojamiento.Minimum.nights
: Número mínimo de
noches requerido para la reserva.Number.of.reviews
: Número total de
reseñas recibidas.Date.last.review
: Fecha de la última
reseña recibida.Number.of.reviews.per.month
: Promedio
de reseñas recibidas por mes.Rooms.rent.by.the.host
: Número total
de habitaciones o propiedades que el anfitrión ofrece en alquiler.Updated.Date
: Fecha en que se
actualizó la información del listado por última vez.City
: Ciudad donde se ubica el
listado, en nuestro caso, siempre Madrid.Country
: País donde se ubica el
listado, en nuestro caso, siempre España.Latitude
: Latitud geográfica del
listado. Es el uno de los resultados de dividir la variable
Coordinates.Longitude
: Longitud geográfica del
listado. Es el uno de los resultados de dividir la variable
Coordinates.Availability_Cat
: Categorización de la
variable “Availability” en grupos de disponibilidad.Review_category
: Categoriza listados
basándose en la frecuencia de reseñas por mes (se ha catergorizado la
variable Number.of.reviews.per.month).ReviewCountCategory
: Categoriza
listados según el número total de reseñas recibidas(se ha catergorizado
la variable Number.of.reviews).Time_category
: Categoriza listados
basado en el tiempo transcurrido desde la última reseña hasta el dia del
scrapping.Se eliminó Location
ya que era
redundante con País, Ciudad y Barrio.
Ahora vamos a exportar el dataset procesado:
# Exportar el dataframe procesado a CSV
write.csv2(airbnb_data, "../data/processed/processed-air-bnb-listings.csv", row.names = FALSE, fileEncoding = "UTF-8")