NOTA: este tutorial usa R + RStudio + ciertas librerías (packages) de R para mostrar el uso de visualizaciones de datos para inspeccionar y analizar un conjunto de datos. Os recomendamos explorar los siguientes enlaces:
## Loading required package: ggplot2
## Loading required package: MASS
## Loading required package: survival
## You can cite this package as:
## Patil, I. (2021). Visualizations with statistical details: The 'ggstatsplot' approach.
## Journal of Open Source Software, 6(61), 3167, doi:10.21105/joss.03167
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ lubridate 1.9.3 ✔ tibble 3.2.1
## ✔ purrr 1.0.2 ✔ tidyr 1.3.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ✖ dplyr::select() masks MASS::select()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
Leemos el fichero de datos en formato CSV, tiene 119,390 filas y 32 columnas:
## [1] 119390 32
Primero inspeccionaremos los datos usando la función summary() incluída en R. La explicación de cada variable se puede encontrar en el artículo en el cual se describe este conjunto de datos de forma detallada, aunque los nombres de las variables son casi auto-explicativos:
## hotel is_canceled lead_time arrival_date_year
## City Hotel :79330 Min. :0.0000 Min. : 0 Min. :2015
## Resort Hotel:40060 1st Qu.:0.0000 1st Qu.: 18 1st Qu.:2016
## Median :0.0000 Median : 69 Median :2016
## Mean :0.3704 Mean :104 Mean :2016
## 3rd Qu.:1.0000 3rd Qu.:160 3rd Qu.:2017
## Max. :1.0000 Max. :737 Max. :2017
##
## arrival_date_month arrival_date_week_number arrival_date_day_of_month
## August :13877 Min. : 1.00 Min. : 1.0
## July :12661 1st Qu.:16.00 1st Qu.: 8.0
## May :11791 Median :28.00 Median :16.0
## October:11160 Mean :27.17 Mean :15.8
## April :11089 3rd Qu.:38.00 3rd Qu.:23.0
## June :10939 Max. :53.00 Max. :31.0
## (Other):47873
## stays_in_weekend_nights stays_in_week_nights adults
## Min. : 0.0000 Min. : 0.0 Min. : 0.000
## 1st Qu.: 0.0000 1st Qu.: 1.0 1st Qu.: 2.000
## Median : 1.0000 Median : 2.0 Median : 2.000
## Mean : 0.9276 Mean : 2.5 Mean : 1.856
## 3rd Qu.: 2.0000 3rd Qu.: 3.0 3rd Qu.: 2.000
## Max. :19.0000 Max. :50.0 Max. :55.000
##
## children babies meal country
## Min. : 0.0000 Min. : 0.000000 BB :92310 PRT :48590
## 1st Qu.: 0.0000 1st Qu.: 0.000000 FB : 798 GBR :12129
## Median : 0.0000 Median : 0.000000 HB :14463 FRA :10415
## Mean : 0.1039 Mean : 0.007949 SC :10650 ESP : 8568
## 3rd Qu.: 0.0000 3rd Qu.: 0.000000 Undefined: 1169 DEU : 7287
## Max. :10.0000 Max. :10.000000 ITA : 3766
## NA's :4 (Other):28635
## market_segment distribution_channel is_repeated_guest
## Online TA :56477 Corporate: 6677 Min. :0.00000
## Offline TA/TO:24219 Direct :14645 1st Qu.:0.00000
## Groups :19811 GDS : 193 Median :0.00000
## Direct :12606 TA/TO :97870 Mean :0.03191
## Corporate : 5295 Undefined: 5 3rd Qu.:0.00000
## Complementary: 743 Max. :1.00000
## (Other) : 239
## previous_cancellations previous_bookings_not_canceled reserved_room_type
## Min. : 0.00000 Min. : 0.0000 A :85994
## 1st Qu.: 0.00000 1st Qu.: 0.0000 D :19201
## Median : 0.00000 Median : 0.0000 E : 6535
## Mean : 0.08712 Mean : 0.1371 F : 2897
## 3rd Qu.: 0.00000 3rd Qu.: 0.0000 G : 2094
## Max. :26.00000 Max. :72.0000 B : 1118
## (Other): 1551
## assigned_room_type booking_changes deposit_type agent
## A :74053 Min. : 0.0000 No Deposit:104641 9 :31961
## D :25322 1st Qu.: 0.0000 Non Refund: 14587 NULL :16340
## E : 7806 Median : 0.0000 Refundable: 162 240 :13922
## F : 3751 Mean : 0.2211 1 : 7191
## G : 2553 3rd Qu.: 0.0000 14 : 3640
## C : 2375 Max. :21.0000 7 : 3539
## (Other): 3530 (Other):42797
## company days_in_waiting_list customer_type
## NULL :112593 Min. : 0.000 Contract : 4076
## 40 : 927 1st Qu.: 0.000 Group : 577
## 223 : 784 Median : 0.000 Transient :89613
## 67 : 267 Mean : 2.321 Transient-Party:25124
## 45 : 250 3rd Qu.: 0.000
## 153 : 215 Max. :391.000
## (Other): 4354
## adr required_car_parking_spaces total_of_special_requests
## Min. : -6.38 Min. :0.00000 Min. :0.0000
## 1st Qu.: 69.29 1st Qu.:0.00000 1st Qu.:0.0000
## Median : 94.58 Median :0.00000 Median :0.0000
## Mean : 101.83 Mean :0.06252 Mean :0.5714
## 3rd Qu.: 126.00 3rd Qu.:0.00000 3rd Qu.:1.0000
## Max. :5400.00 Max. :8.00000 Max. :5.0000
##
## reservation_status reservation_status_date
## Canceled :43017 2015-10-21: 1461
## Check-Out:75166 2015-07-06: 805
## No-Show : 1207 2016-11-25: 790
## 2015-01-01: 763
## 2016-01-18: 625
## 2015-07-02: 469
## (Other) :114477
Podemos observar algunos valores extraños para algunas variables, por ejemplo:
Vamos a visualizar el histograma de la variable adults, indicando al menos 55 intervalos en el histograma, usando la función hist() de R:
Se puede ver que el histograma no muestra ninguna barra alrededor del 55, dado que se trata de un conjunto muy grande y seguramente se tratará solamente de un caso o pocos. En estos casos, para analizar valores extremos de una variable se pueden pintar los valores de la variable en cuestión de la siguiente manera, ordenando los datos (si son numéricos como en este caso):
La variable Index es la posición del elemento una vez ordenado, pero nos
interesa más el eje Y, ya que podemos ver que hay unos pocos elementos
con valores de 10 o superior. Como se trata de una variable entera pero
con un conjunto limitado de valores posibles podemos usar table() para
verlos:
##
## 0 1 2 3 4 5 6 10 20 26 27 40 50
## 403 23027 89680 6202 62 2 1 1 2 5 2 1 1
## 55
## 1
Como se puede ver, hay un caso de una reserva con 10 adultos, dos con 20 adultos, etc., hasta una de 55 adultos! Sin entrar en más consideraciones, eliminaremos todas las filas con reservas de 10 adultos o más:
EJERCICIO: hacer lo mismo con las variables children y babies
El histograma de la variable adr (gasto medio por dí) presenta el mismo problema que el caso de la variable adults, así que directamente haremos un gráfico con los valores ordenados:
En este caso se ve que hay solamente un valor muy por encima del resto,
lo consideramos un outlier y lo eliminamos, así como los valores
negativos que no tienen una explicación clara, aunque dejamos los
valores 0:
El histograma ahora sí que nos aporta información relevante. Lo dibujamos usando el package ggplot2 que ofrece muchas más opciones que hist():
EJERCICIO: retocar el gráfico para que el nombre de los ejes, título,
etc. sea el adecuado para una presentación
Podemos ver que hay un conjunto de unos 2000 valores 0, los cuales podrían ser analizados de forma separada, por ejemplo. Existen packages de R que nos pueden ayudar a estimar dicha distribución y los parámetros que la determinan de forma visual, como por ejemplo el package fitdistrplus mediante la función descdist():
## summary statistics
## ------
## min: 0 max: 510
## median: 94.6
## mean: 101.7987
## estimated sd: 48.14364
## estimated skewness: 1.018857
## estimated kurtosis: 5.13317
Como se puede observar, los datos reales (observación, en azul) y los simulados (en amarillo) están cerca de lo que podría ser una distribución lognormal.
De todas formas, con el objetivo de experimentar con un conjunto de datos lo más limpio posible vamos a proceder a:
Por lo que respecta a las variables categóricas, la función summary() ya nos da una primera idea de los valores posibles que puede coger cada una. Por ejemplo, en el conjunto original (antes de eliminar outliers) hay 79,330 reservas en un hotel de ciudad (Lisboa) y 40,060 en un resort (el Algarve). Podemos preguntarnos si la distribución del coste es la misma para ambos grupos, ya sea mediante el test estadístico adecuado o simplemente comparando histogramas, en este caso usando el package ggplot2 mucho más potente para crear gráficos de todo tipo:
Se puede observar que los precios en Lisboa (City Hotel) más típicos
están ligeramente a la derecha de los más típicos en el Algarve (Resort
Hotel), aunque en cambio los precios más altos en Lisboa decrecen más
rápido que en el Algarve. Con un plot de tipo violin podremos ver más
detalle, especialmente si también mostramos los cuartiles típicos de un
box-plot:
Existe un package de R llamado ggstatsplot que dispone de funciones
específicas para cada tipo de gráfico, incluyendo también los tests
estadísticos adecuados para establecer si existen diferencias entre
grupos:
Una variable interesante es la procedencia de los clientes del hotel (country). El problema es que es una variable con muchos valores diferentes (178), por lo que debemos quedarnos con los paises que aportan más turistas, mostrando también si escogen hotel de ciudad o resort:
Obviamente, Portugal (PRT) ocupa la primera posición destacada, seguida de paises “cercanos”, como Gran Bretaña, Francia y España. Los visitantes de Gran Bretaña e Irlanda optan más por un resort, mientras que los de Francia, Alemania e Italia principalmente visitan la ciudad de Lisboa.
EJERCICIO: existen diferencias entre los habitantes de Portugal (del país) y el resto (“extranjeros”)?
Otra de las variables interesantes es is_canceled que nos indica si una reserva fue cancelada o no (un 37.0% de las veces). Podemos ver la relación entre dos variables categóricas usando un gráfico de mosaico:
## Warning: The `scale_name` argument of `continuous_scale()` is deprecated as of ggplot2
## 3.5.0.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: The `trans` argument of `continuous_scale()` is deprecated as of ggplot2 3.5.0.
## ℹ Please use the `transform` argument instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: `unite_()` was deprecated in tidyr 1.2.0.
## ℹ Please use `unite()` instead.
## ℹ The deprecated feature was likely used in the ggmosaic package.
## Please report the issue at <https://github.com/haleyjeppson/ggmosaic>.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
Se puede observar que el porcentaje de cancelaciones (1 en el eje Y) en
un resort es inferior al de un hotel en la ciudad de Lisboa. En el eje
X, los tamaños relativos de cada columna se corresponden también con la
proporción de cada tipo de hotel. Es importante no pensar en las
etiquetas del eje Y (0 / 1) como la propoción numérica real de
cancelación, ya que puede llevar a engaño.
En el caso de cancelación por país para los países con más turistas:
Se puede comprobar que la tasa de cancelaciones es mucho mayor para los
turistas locales (de Portugal, PRT), mientras que es mucho más baja para
el resto de paises. No obstante, este gráfico no es de lectura fácil, en
este caso no hay ningún orden ni de los paises ni del porcentaje de
cancelaciones.
EJERCICIO: mejorar el gráfico anterior para hacerlo más inteligible, y plantearse si es posible visualizar las relaciones entre tres o más variables de tipo categórico.
Finalmente, vamos a analizar el comportamiento de las reservas con respecto a la fecha de entrada. Primero, usando el package lubridate de R (una maravilla para manipular datos de tipo fecha y hora) crearemos una variable dia para saber qué día de la semana fue la llegada al hotel, y analizaremos cuantas reservas hubo cada día:
Tal y como describe el artículo, los datos comprenden desde el 1 de
Julio de 2015 hasta el 31 de agosto de 2017. Se pueden observar algunos
picos que podrían ser interesantes.
EJERCICIO: mejorar y desdoblar el gráfico anterior por tipo de hotel o por país de orígen.
Con el día calculado, junto con las variables stays_in_week/weekend_nights podemos tratar de categorizar manualmente el tipo de viaje, de acuerdo a los siguientes criterios (arbitrarios, claramente mejorables):
Una manera de refinar esta clasificación sería mirar la cantidad de adultos, niños y bebés para decidir si se trata de una persona viajando por trabajo o bien una familia.
Las posibilidades son infinitas: se puede enriquecer el dataset con datos de tipo geográfico (la distancia entre paises), demográficos, económicos (renta per capita), etc. Debéis explorar este dataset y en este proceso de exploración decidir qué historia queréis explicar sobre el mismo.