NOTA: aquest tutorial utilitza R + RStudio + alguns paquets de R per mostrar el potencial de l’ús de la visualització de dades per inspeccionar i analitzar un conjunt de dades. Recomanem explorar els següents enllaços:
Baixeu i instal·leu RTools de “https://cran.rstudio.com/bin/windows/Rtools/rtools45/rtools.html”
Baixeu ggmosaic executant:
install.packages("ggmosaic", repos = c("https://haleyjeppson.r-universe.dev", "https://cloud.r-project.org"))
NOTA: si voleu utilitzar un entorn virtual creat amb conda per a aquest projecte, podeu seguir les instruccions del fitxer README.md.
NOTA: Si no voleu utilitzar l’entorn virtual i voleu editar i
executar aquest fitxer Rmd directament a VSCode, canvieu el camí a
l’executable R (RPath) per a la vostra plataforma. En la configuració
proporcionada de VSCode (settings.json) el camí al fitxer R utilitza
l’entorn virtual creat a la carpeta .conda a la base del
directori del projecte en macos.
"r.rpath.mac": "${workspaceFolder}/.conda/bin/R"
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.6
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.1 ✔ tibble 3.3.0
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.2
## ✔ purrr 1.2.0
## ── 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
## Loading required package: MASS
##
## Attaching package: 'MASS'
##
## The following object is masked from 'package:dplyr':
##
## select
##
## 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 package: 'kableExtra'
##
## The following object is masked from 'package:dplyr':
##
## group_rows
##
## Attaching package: 'reshape2'
##
## The following object is masked from 'package:tidyr':
##
## smiths
##
## Attaching package: 'scales'
##
## The following object is masked from 'package:purrr':
##
## discard
##
## The following object is masked from 'package:readr':
##
## col_factor
| Variable | Tipus | Descripcio |
|---|---|---|
| hotel | factor | Tipus d’hotel (ciutat o resort) |
| is_canceled | factor | 1 si la reserva es va cancel·lar, 0 en cas contrari |
| lead_time | integer | Nombre de dies entre la reserva i l’arribada |
| arrival_date_year | integer | Any d’arribada |
| arrival_date_month | integer | Mes d’arribada, amb 12 categories: «January» a «December» |
| arrival_date_week_number | integer | Setmana d’arribada de l’any |
| arrival_date_day_of_month | integer | Dia del mes de l’arribada |
| stays_in_weekend_nights | integer | Nombre de nits d’estada en cap de setmana |
| stays_in_week_nights | integer | Nombre de nits d’estada entre setmana |
| adults | integer | Nombre d’adults |
| children | integer | Nombre de nens |
| babies | integer | Nombre de nadons |
| meal | factor | Tipus de règim alimentari reservat. Tipus d’àpat reservat. Les categories són: Undefined/SC (sense regim alimentari); BB (Allotjament i esmorzar); HB (Mitja pensió amb l’esmorzar i un altre àpat, normalment sopar); FB (Pensió completa amb esmorzar, dinar i sopar) |
| country | factor | País de residència de l’hoste |
| market_segment | factor | Segment de mercat de l’hotel. «TA» significa «agents de viatges (Travel Agents)» i «TO» significa «operadors turístics (Tour Operators)» |
| distribution_channel | factor | Canal de distribució utilitzat. El terme «TA» significa «agents de viatges (Travel Agents)» i «TO» significa «operadors turístics (Tour Operators)». |
| is_repeated_guest | factor | 1 si l’hoste és un client repetit, 0 en cas contrari |
| previous_cancellations | integer | Nombre de cancel·lacions anteriors per a aquest hoste |
| previous_bookings_not_canceled | integer | Nombre de reserves anteriors no cancel·lades per a aquest hoste |
| reserved_room_type | factor | Tipus d’habitació reservada |
| assigned_room_type | factor | Tipus d’habitació assignada |
| booking_changes | integer | Nombre de canvis en la reserva |
| deposit_type | factor | Tipus de dipòsit realitzat. Hi ha 3 categories: «No Deposit»: sense dipòsit, «Non Refund»: no reemborsable, «Refundable»: reemborsable |
| agent | factor | Identificador de l’agent de viatges |
| company | factor | Identificador de l’empresa |
| days_in_waiting_list | integer | Nombre de dies a la llista d’espera |
| customer_type | factor | Tipus de client. Tipus de reserva, assumint una de les quatre categories següents: Contract - quan la reserva té una assignació o un altre tipus de contracte associat; Group – quan la reserva està associada a un grup; Transient – quan la reserva no forma part d’un grup o contracte, i no està associada a altres reserves transitòries; Transient-party – quan la reserva és transitòria, però està associada almenys a una altra reserva transitòria |
| adr | numeric | Tarifa diària mitjana |
| required_car_parking_spaces | integer | Nombre d’espais d’aparcament de cotxes requerits |
| total_of_special_requests | integer | Nombre total de sol·licituds especials (per exemple, llit individual o pis alt) |
| reservation_status | factor | Estat final de la reserva. «Canceled»: el client ha cancel·lat la reserva; «Check-Out»: el client ha fet el check-in però ja ha marxat; «No-Show»: el client no ha fet el check-in i ha informat l’hotel del motiu. |
| reservation_status_date | date | Data de l’estat final de la reserva |
Llegim el conjunt de dades en format CSV, amb 119.390 files i 32 columnes:
## [1] 119390 32
El PMS va assegurar que no falten dades a les taules de la seva base
de dades. Tanmateix, en algunes variables categòriques com ara
agent o company, el valor “NULL” es presenta
com una de les categories. Això no s’ha de considerar com que falta el
valor, sinó com a “no aplicable”. Per exemple, si un agent de reserva
agent es defineix com a “NULL”, significa que la reserva no
prové d’una agència de viatges.
# converteix el tipus de dades de les columnes segons la seva naturalesa
x <- x |>
mutate(
# dates
reservation_status_date = as.Date(reservation_status_date),
# factors
hotel = as.factor(hotel),
agent = as.factor(agent),
arrival_date_month = factor(arrival_date_month, levels = month.name),
assigned_room_type = as.factor(assigned_room_type),
company = as.factor(company),
country = as.factor(country),
customer_type = as.factor(customer_type),
deposit_type = factor(
deposit_type,
levels = c("No Deposit", "Refundable", "Non Refund")
),
distribution_channel = as.factor(distribution_channel),
is_canceled = as.factor(is_canceled),
is_repeated_guest = as.factor(is_repeated_guest),
market_segment = as.factor(market_segment),
meal = factor(
meal,
levels = c("Undefined", "SC", "BB", "HB", "FB"), ordered = TRUE
),
reservation_status = factor(
reservation_status,
levels = c("No-Show", "Check-Out", "Canceled"), ordered = TRUE
),
reserved_room_type = as.factor(reserved_room_type),
# numerics
adr = as.numeric(adr),
# integers
adults = as.integer(adults),
babies = as.integer(babies),
children = as.integer(children)
)Primer, inspeccionarem les dades amb la funció summary()
inclosa a R.
Podeu trobar una explicació de cada variable a l’article que descriu
aquest conjunt de dades en detall, tot i que els noms de les variables
són força explicatives:
## hotel is_canceled lead_time arrival_date_year
## City Hotel :79330 0:75166 Min. : 0 Min. :2015
## Resort Hotel:40060 1:44224 1st Qu.: 18 1st Qu.:2016
## Median : 69 Median :2016
## Mean :104 Mean :2016
## 3rd Qu.:160 3rd Qu.:2017
## 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 Undefined: 1169 PRT :48590
## 1st Qu.: 0.0000 1st Qu.: 0.000000 SC :10650 GBR :12129
## Median : 0.0000 Median : 0.000000 BB :92310 FRA :10415
## Mean : 0.1039 Mean : 0.007949 HB :14463 ESP : 8568
## 3rd Qu.: 0.0000 3rd Qu.: 0.000000 FB : 798 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 0:115580
## Offline TA/TO:24219 Direct :14645 1: 3810
## Groups :19811 GDS : 193
## Direct :12606 TA/TO :97870
## Corporate : 5295 Undefined: 5
## Complementary: 743
## (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 Refundable: 162 NULL :16340
## E : 7806 Median : 0.0000 Non Refund: 14587 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
## No-Show : 1207 Min. :2014-10-17
## Check-Out:75166 1st Qu.:2016-02-01
## Canceled :43017 Median :2016-08-07
## Mean :2016-07-30
## 3rd Qu.:2017-02-08
## Max. :2017-09-14
##
Nombre de files repetides:
## [1] 31994
No s’eliminen les files repetides, ja que no es disposa de suficient informació per saber si es tracta d’un error o si realment són reserves diferents amb les mateixes files, p. ex. amb el número d’habitació assignada.
S’observen alguns valors inesperats (outliers?) en diverses variables. Per exemple:
adultschildren (incloent valors
perduts)babiesadr) o
molt elevatsVisualitzem l’histograma de la variable adults, amb
almenys 55 intervals:
Es pot observar que l’histograma no mostra barres al voltant del valor 55, ja que es tracta d’un conjunt molt gran i probablement només és un o pocs casos. En aquests casos, per analitzar els valors extrems d’una variable, els valors de la variable en qüestió es poden representar gràficament de la següent manera, ordenant i representant les dades (si són numèriques, com en aquest cas):
# Desa el gràfic com a fitxer SVG
# svg(filename = "img/plot_adults.svg", width = 8, height = 6)
# plot(sort(x$adults))
# grid()
# dev.off()L’índex representa la posició de l’element un cop ordenat, però ens interessa més l’eix Y, ja que podem veure que alguns elements tenen valors de 10 o més. Com que aquesta és una variable entera amb un conjunt limitat de valors possibles, podem utilitzar table() per visualitzar-los:
##
## 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
Com es pot veure, hi ha una reserva per a 10 adults, dues per a 20 adults, i així successivament, fins a una per a 55 adults! Sense entrar en més detalls, eliminarem totes les files amb reserves per a 10 o més adults:
Exercici
Repetiu aquest procés amb les variables children i
babies. Proveu també de canviar el llindar a menys de 5 en
lloc de 10.
Fi de l’exercici
L’histograma de la variable adr (tarifa diària mitjana)
presenta el mateix problema que la variable adults, així
que simplement crearem un gràfic amb els valors ordenats de nou:
En aquest cas, observem que només un valor és significativament més alt que la resta. El considerem un valor atípic i l’eliminem, així com els valors negatius que no tenen una explicació clara, tot i que mantenim els valors 0:
# Elimina els valors extrems que estiguin fora de l'interval [0, 1000)
x <- x[x$adr >= 0 & x$adr < 1000, ]
# Elimina els valors NA
x <- x[!is.na(x$adr), ]
# Mostra un resum de la variable adr
summary(x$adr)## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.00 69.29 94.62 101.80 126.00 510.00
L’histograma ara ens proporciona informació rellevant. El dibuixem
utilitzant el paquet ggplot2, que ofereix moltes més opcions que
hist():
Exercici:
Milloreu el gràfic per fer que els eixos, el títol, etc. siguin més adequats.
Fi de l’exercici
Podem veure que hi ha un conjunt d’aproximadament 2.000 valors zero,
que es podrien analitzar per separat, per exemple. Hi ha paquets R que
ens ajuden a estimar aquesta distribució i els paràmetres que la
determinen visualment, com el paquet fitdistrplus, que proporciona la
funció descdist() (atenció, lent!):
## summary statistics
## ------
## min: 0 max: 510
## median: 94.62
## mean: 101.8011
## estimated sd: 48.14287
## estimated skewness: 1.018971
## estimated kurtosis: 5.13332
Com es pot veure, les dades reals (observacions, un punt de color) i les dades simulades (en un altre color) s’aproximen al que podria semblar una distribució lognormal. No obstant això, per experimentar amb el conjunt de dades més net possible, farem:
x[is.na(x$children), "children"] <- 0
x <- x[x$adr > 0 &
(x$stays_in_week_nights + x$stays_in_weekend_nights) > 0 &
(x$adults + x$children + x$babies) > 0 &
!is.na(x$children), ]Per a les variables categòriques, la funció summary()
ens dóna una primera idea dels valors possibles que pot prendre cada
una. Per exemple, en el conjunt original (abans d’eliminar valors
extrems), hi ha 79.330 reserves en un hotel de ciutat (Lisboa) i 40.060
en un resort (Algarve). Podem preguntar-nos si la distribució dels
costos és la mateixa per a ambdós grups, ja sigui utilitzant la prova
estadística adequada o simplement comparant histogrames, en aquest cas
utilitzant el paquet ggplot2, que és molt més potent per crear tot tipus
de gràfics:
Es pot veure que els preus més comuns a Lisboa (hotels de ciutat) estan lleugerament a la dreta dels preus més comuns a l’Algarve (hotels de resort), tot i que els preus més alts a Lisboa disminueixen més ràpidament que a l’Algarve. Utilitzant un gràfic de violí, podem veure més detalls, especialment si també mostrem els típics quartils d’un diagrama de caixa:
Hi ha un paquet R anomenat ggstatsplot que té funcions específiques per a cada tipus de gràfic, incloent proves estadístiques adequades per determinar si hi ha diferències entre grups:
Una altra variable interessant és l’origen dels hostes de l’hotel
(country). El problema és que aquesta variable té molts
valors diferents (178), així que hauríem de centrar-nos en els països
amb més turistes, mostrant també si trien un hotel de ciutat o un
resort:
Obviament, Portugal (PRT) ocupa el primer lloc, seguida dels països veïns com Gran Bretanya, França i Espanya. Els visitants de Gran Bretanya i Irlanda són més propensos a triar un resort, mentre que els de França, Alemanya i Itàlia visiten principalment Lisboa.
Exercici:
Hi ha diferències entre els residents de Portugal i la resta?
Fi de l’exercici
Una altra variable interessant és is_canceled, que
indica si una reserva va ser cancel·lada o no (el 37,0% de les vegades).
Podem observar la relació entre dues variables categòriques utilitzant
un gràfic mosaic:
Es pot veure que la taxa de cancel·lació (denotada per 1 a l’eix Y) en un resort és inferior a la d’un hotel a Lisboa. A l’eix X, la mida relativa de cada columna també correspon a la proporció de cada tipus d’hotel. És important no considerar les etiquetes de l’eix Y (0/1) com la taxa numèrica real de cancel·lació, ja que això pot ser enganyós.
Exercici:
Quin altre tipus de gràfic es podria utilitzar per representar aquestes dades?
Canviar el gràfic mosaic per un gràfic de barres apilades o un gràfic de sectors.
## `summarise()` has grouped output by 'hotel'. You can override using the
## `.groups` argument.
Fi de l’exercici
En el cas de la cancel·lació per país per als països amb més turistes:
Es pot veure que la taxa de cancel·lació és molt més alta per als turistes locals (de Portugal, PRT), mentre que és molt més baixa per a la resta de països. No obstant això, aquest gràfic no és fàcil de llegir; en aquest cas, no hi ha ordre ni dels països ni del percentatge de cancel·lacions.
Exercici
Millora el gràfic anterior per fer-lo més comprensible i considera si és possible visualitzar les relacions entre tres o més variables categòriques.
# Almenys 1000 reserves
xx <- x |>
group_by(country) |>
mutate(pais = n()) |>
filter(pais >= 1000)
ggplot(data = xx, aes(
x = reorder(country, -as.numeric(is_canceled)),
fill = is_canceled
)) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent) +
scale_fill_manual(
name = "Cancellation Status",
labels = c("Not Canceled", "Canceled"),
values = c("0" = "#00BFC4", "1" = "#F8766D")
) +
labs(
title = "Proportion of Cancellations by Country",
x = "Country",
y = "Proportion of Cancellations",
fill = "Cancellation Status",
) +
# mostra els percentatges en cada barra amb 2 decimals
geom_text(
aes(label = scales::percent(after_stat(count) / sum(after_stat(count)),
accuracy = 0.01, decimal.mark = ","
)),
stat = "count",
# Centra els percentatges verticalment
position = position_fill(vjust = 0.5),
# Gira 90 graus els percentatges per adaptar-los a les barres estretes
angle = 90,
# Mida de lletra petita
size = 3,
# Color del text blanc
color = "white"
) +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
plot.title = element_text(hjust = 0.5)
)# Desa el gràfic com a fitxer SVG
# ggsave(
# "img/exercici_bar_country_is_canceled.svg",
# plot = last_plot(), width = 8, height = 6
# )## Warning: The dot-dot notation (`..count..`) was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(count)` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
Fi de l’exercici
Finalment, analitzem el comportament de les reserves en relació amb
la data d’arribada. Primer, utilitzant el paquet R
lubridate (una meravella per manipular dades de data i
hora), crearem una variable dia per determinar el dia de la
setmana en què es va fer el check-in a l’hotel i analitzarem quantes
reserves hi va haver cada dia:
x$dia <- as_date(paste0(
x$arrival_date_year, "-",
x$arrival_date_month, "-", x$arrival_date_day_of_month
))Exercici:
Millora i divideix el gràfic anterior per tipus d’hotel o país d’origen.
Fi de l’exercici
Com s’ha descrit a l’article, les dades cobreixen el període de l’1 de juliol de 2015 al 31 d’agost de 2017. Es poden observar alguns pics que podrien ser interessants d’explicar (què va passar aquells dies, per exemple 2015-12-05?). Pots consultar Google Trends per obtenir algunes idees:
https://trends.google.es/trends/explore?date=2015-01-01%202017-12-31&q=lisboa,algarve&hl=es
## [1] 439
## 2015-12-05
## 158
Amb el dia calculat, juntament amb les variables
stays_in_week i weekend_nights, podem intentar
categoritzar manualment el tipus de viatge segons els següents criteris
(això és arbitrari, clarament millorables):
stays_in_weekend_nights és zero => viatge de
feinastays_in_week_nights és zero o un i en aquest cas
l’entrada és en divendres => cap de setmanastays_in_week_nights és cinc i
stays_in_weekend_nights és tres (és a dir, de dissabte o
diumenge a dissabte o diumenge) => paquet de vacances setmanalstays_in_weekend_nights és un o dos i
stays_in_week_nights és cinc o menys => feina +
descansx$tipo <- ifelse(x$stays_in_weekend_nights == 0, "work",
ifelse(x$stays_in_week_nights == 0, "weekend",
ifelse(x$stays_in_week_nights == 1 & wday(x$dia) == 6, "weekend",
ifelse(x$stays_in_week_nights == 5 &
(x$stays_in_weekend_nights == 3 |
x$stays_in_weekend_nights == 4), "package",
ifelse(x$stays_in_week_nights <= 5 &
x$stays_in_weekend_nights < 3, "work+rest",
"rest"
)
)
)
)
)Una manera de refinar aquesta classificació seria mirar el nombre d’adults, nens i nadons per decidir si es tracta d’un viatger de negocis o d’una família. Les possibilitats són infinites: es pot enriquir el conjunt de dades amb dades geogràfiques (distància entre països), dades demogràfiques, dades econòmiques (renda per càpita), dades meteorològiques (tant a Portugal com al país d’origen), etc.
Exercici
Has d’explorar aquest conjunt de dades enriquit i, en aquest procés d’exploració, decidir quina història vols explicar sobre ell. Algunes idees:
tipo i el cost
adrNOTA: Aquest és un bon exemple d’ús de ChatGPT o altres IA generatives per fer preguntes interessants sobre el conjunt de dades proposat. El següent article descriu els usos potencials de la IA generativa en les diferents fases de la creació d’una visualització de dades per a la narració:
https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=10891192
# Filtrar països amb més de 1000 reserves
xx <- x |>
group_by(country) |>
mutate(n_bookings = n()) |>
filter(n_bookings > 1000) |>
ungroup()Patrons de cancel·lació
# nombre de files amb reservation_status = 'Check-Out' i is_canceled = 1
nrow(x[x$reservation_status == "Check-Out" & x$is_canceled == 1, ])## [1] 0
# nombre de files amb reservation_status = 'Canceled' i is_canceled = 0
nrow(x[x$reservation_status == "Canceled" & x$is_canceled == 0, ])## [1] 0
# nombre de files amb reservation_status = 'Canceled' i is_canceled = 0
nrow(x[x$reservation_status == "No-Show" & x$is_canceled == 0, ])## [1] 0
# diagrama de sectors de reservation_status
reservation_status_summary <- xx |>
group_by(hotel, reservation_status) |>
summarise(n = n(), .groups = "drop") |>
group_by(hotel) |>
tidyr::complete(reservation_status = c("Check-Out", "Canceled", "No-Show"), fill = list(n = 0)) |>
mutate(prop = n / sum(n))
ggplot(reservation_status_summary, aes(x = factor(1), y = prop, fill = reservation_status)) +
coord_polar(theta = "y", start = 0) +
facet_wrap(~hotel, strip.position = "bottom") +
scale_y_continuous(limits = c(0, 1), labels = scales::percent) +
scale_fill_manual(
name = "Estat",
labels = c("Cancel·lada", "Checkout", "No-Show"),
values = c("Canceled" = "#e74c3c", "Check-Out" = "#2ecc71", "No-Show" = "#f1c40f")
) +
geom_text(aes(label = percent(prop, accuracy = 0.1)), position = position_stack(vjust = 0.5), size = 5, fontface = "bold", color = "white") +
labs(title = "Percentatge d'estat de la reserva per tipus d'hotel", x = NULL, y = NULL) +
geom_col(width = 1) +
theme_void() +
theme(plot.title = element_text(hjust = 0.5))# diagrama de sectors de is_canceled
cancellation_summary <- xx |>
group_by(hotel, is_canceled) |>
summarise(n = n(), .groups = "drop") |>
group_by(hotel) |>
tidyr::complete(is_canceled = c("0", "1"), fill = list(n = 0)) |>
mutate(pct = n / sum(n))
ggplot(cancellation_summary, aes(x = factor(1), y = pct, fill = is_canceled)) +
coord_polar(theta = "y", start = 0) +
facet_wrap(~hotel, strip.position = "bottom") +
scale_y_continuous(limits = c(0, 1), labels = scales::percent) +
scale_fill_manual(
name = "Estat de la cancel·lació",
labels = c("No cancel·lada", "Cancel·lada"),
values = c("0" = "#2ecc71", "1" = "#e74c3c")
) +
geom_text(aes(label = percent(pct, accuracy = 0.1)), position = position_stack(vjust = 0.5), size = 5, fontface = "bold", color = "white") +
labs(title = "Percentatge de cancel·lacions per tipus d'hotel", x = NULL, y = NULL) +
geom_col(width = 1) +
theme_void() +
theme(plot.title = element_text(hjust = 0.5))# Percentatge de cancel·lacions entre països amb més de 1000 reserves i Portugal
xx_is_portugal <- xx |>
mutate(is_portugal = ifelse(country == "PRT", "Portugal", "Altres")) |>
group_by(is_portugal, is_canceled) |>
summarise(n = n(), .groups = "drop") |>
group_by(is_portugal) |>
mutate(pct = n / sum(n))
ggplot(xx_is_portugal, aes(x = factor(1), y = pct, fill = is_canceled)) +
geom_col(width = 1) +
coord_polar(theta = "y") +
facet_wrap(~is_portugal, strip.position = "bottom") +
scale_y_continuous(labels = scales::percent) +
scale_fill_manual(name = "Estat", labels = c("No Cancel·lada", "Cancel·lada"), values = c("0" = "#2ecc71", "1" = "#e74c3c")) +
geom_text(aes(label = percent(pct, accuracy = 0.1)), position = position_stack(vjust = 0.5), size = 5, fontface = "bold", color = "white") +
labs(title = "Percentatge de cancel·lacions per país d'origen", x = NULL, y = NULL) +
theme_void() +
theme(plot.title = element_text(hjust = 0.5))rm(xx_is_portugal)
xx_is_portugal_by_hotel <- xx |>
mutate(is_portugal = ifelse(country == "PRT", "Portugal", "Altres")) |>
group_by(is_portugal, hotel, is_canceled) |>
summarise(n = n(), .groups = "drop") |>
group_by(is_portugal) |>
mutate(pct = n / sum(n))
ggplot(
data = xx_is_portugal_by_hotel,
aes(
x = is_portugal,
y = n,
fill = interaction(is_canceled, hotel, lex.order = TRUE)
)
) +
geom_col(position = "fill") +
scale_y_continuous(labels = scales::percent) +
scale_fill_discrete(
name = "Estat de la cancel·lació i tipus d'hotel",
labels = c(
"No Cancel·lada - Hotel de Ciutat", "No Cancel·lada - Hotel de Resort",
"Cancel·lada - Hotel de Ciutat", "Cancel·lada - Hotel de Resort"
)
) +
geom_text(
aes(label = scales::percent(pct, accuracy = 0.1)),
position = position_fill(vjust = 0.5),
size = 5, fontface = "bold", color = "white"
) +
labs(
title = "Percentatge de cancel·lacions per país d'origen i tipus d'hotel",
x = "País d'origen",
y = "Percentatge de cancel·lacions"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
axis.text.x = element_text(size = 12),
axis.text.y = element_text(size = 11)
)# Diagrama de sectors amb el percentatge de cancel·lacios per tipus de dipòsit
deposit_type_summary <- xx |>
group_by(deposit_type, is_canceled) |>
summarise(n = n(), .groups = "drop") |>
group_by(deposit_type) |>
tidyr::complete(is_canceled = c("0", "1"), fill = list(n = 0)) |>
mutate(prop = n / sum(n))
ggplot(deposit_type_summary, aes(x = factor(1), y = prop, fill = is_canceled)) +
coord_polar(theta = "y", start = 0) +
facet_wrap(~deposit_type, strip.position = "bottom") +
scale_y_continuous(limits = c(0, 1), labels = scales::percent) +
scale_fill_manual(
name = "Estat de la cancel·lació",
labels = c("No cancel·lada", "Cancel·lada"),
values = c("0" = "#2ecc71", "1" = "#e74c3c")
) +
geom_text(aes(label = percent(prop, accuracy = 0.1)), position = position_stack(vjust = 0.5), size = 5, fontface = "bold", color = "white") +
labs(title = "Percentatge de cancel·lacions segons el tipus de dipòsit", x = NULL, y = NULL) +
geom_col(width = 1) +
theme_void() +
theme(plot.title = element_text(hjust = 0.5))# Diagrama de sectors amb el percentatge de cancel·lacios per tipus de dipòsit
deposit_type_summary <- xx |>
group_by(hotel, deposit_type, is_canceled) |>
summarise(n = n(), .groups = "drop") |>
group_by(hotel, deposit_type) |>
tidyr::complete(is_canceled = c("0", "1"), fill = list(n = 0)) |>
mutate(prop = n / sum(n))
ggplot(deposit_type_summary, aes(x = factor(1), y = prop, fill = is_canceled)) +
coord_polar(theta = "y", start = 0) +
facet_grid(deposit_type ~ hotel, switch = "y") +
scale_y_continuous(limits = c(0, 1), labels = scales::percent) +
scale_fill_manual(
name = "Estat de la cancel·lació",
labels = c("No cancel·lada", "Cancel·lada"),
values = c("0" = "#2ecc71", "1" = "#e74c3c")
) +
geom_text(aes(label = percent(prop, accuracy = 0.1)), position = position_stack(vjust = 0.5), size = 5, fontface = "bold", color = "white") +
labs(title = "Percentatge de cancel·lacions segons el tipus de dipòsit i tipus d'hotel", x = NULL, y = NULL) +
geom_col(width = 1) +
theme_void() +
theme(plot.title = element_text(hjust = 0.5))# Tenen reemborsament les reserves cancel·lades?
ggplot(xx, aes(
x = reorder(deposit_type, deposit_type, function(x) length(x),
decreasing = TRUE
),
fill = is_canceled
)) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent) +
scale_fill_manual(
name = "Estat de cancel·lació",
labels = c("No cancel·lat", "Cancel·lat"),
values = c("0" = "#2ecc71", "1" = "#e74c3c")
) +
facet_wrap(~hotel) +
labs(
title = "Percentatge de cancel·lacions segons el tipus de dipòsit",
x = "Tipus de dipòsit",
y = "Percentatge de cancel·lacions"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)
)# Nombre de reserves i cancel·lacions per tipus d'hotel
ggplot(xx, aes(x = hotel, fill = is_canceled)) +
geom_bar(position = "dodge") +
scale_fill_manual(
name = "Llegenda",
labels = c("Reserves", "Cancel·lacions"),
values = c("0" = "#2ecc71", "1" = "#e74c3c")
) +
labs(
title = "Nombre de reserves i cancel·lacions per tipus d'hotel",
x = "Tipus d'hotel",
y = "Nombre",
) +
theme_minimal() +
theme(
legend.title = element_blank(),
plot.title = element_text(hjust = 0.5)
)# El percentatge de cancel·lacions és diferent segons el tipus d'hotel?
ggplot(xx, aes(x = hotel, fill = is_canceled)) +
geom_bar(position = "fill") +
scale_fill_manual(
name = "Estat",
labels = c("No Cancel·lada", "Cancel·lada"),
values = c("0" = "#2ecc71", "1" = "#e74c3c")
) +
scale_y_continuous(labels = scales::percent) +
geom_text(
aes(label = scales::percent(..count.. / sum(..count..),
accuracy = 0.1
)),
stat = "count",
position = position_fill(vjust = 0.5),
size = 5, fontface = "bold", color = "white"
) +
labs(
title = "Percentatge de cancel·lació per tipus d'hotel",
x = "Tipus d'hotel",
y = "Proporció"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5, size = 14),
legend.position = "bottom",
axis.text.x = element_text(size = 12),
axis.text.y = element_text(size = 11)
)# percentatge de cancel·lacions per mes d'arribada
ggplot(xx, aes(
x = reorder(arrival_date_month, as.numeric(arrival_date_month)),
fill = is_canceled
)) +
geom_bar(position = "fill") +
scale_fill_manual(
name = "Estat",
labels = c("No Cancel·lada", "Cancel·lada"),
values = c("0" = "#2ecc71", "1" = "#e74c3c")
) +
scale_y_continuous(labels = scales::percent) +
facet_wrap(~hotel, strip.position = "bottom") +
labs(
title = "Percentatge de cancel·lacions per mes d'arribada",
x = "Mes d'arribada",
y = "Percentatge de cancel·lacions"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)
)# diagrama de sectors del tipus de dipòsit enfrontant el dos tipus d'hotel
deposit_type_summary <- xx |>
group_by(hotel, deposit_type) |>
summarise(n = n(), .groups = "drop") |>
group_by(hotel) |>
tidyr::complete(deposit_type = unique(xx$deposit_type), fill = list(n = 0)) |>
mutate(prop = n / sum(n))
ggplot(deposit_type_summary, aes(x = factor(1), y = prop, fill = deposit_type)) +
coord_polar(theta = "y", start = 0) +
facet_wrap(~hotel, strip.position = "bottom") +
scale_y_continuous(limits = c(0, 1), labels = scales::percent) +
scale_fill_manual(
name = "Tipus de dipòsit",
values = c("No Deposit" = "#3498db", "Non Refund" = "#9b59b6", "Refundable" = "#f1c40f")
) +
labs(title = "Percentatge de tipus de dipòsit per tipus d'hotel", x = NULL, y = NULL) +
geom_col(width = 1) +
theme_void() +
theme(plot.title = element_text(hjust = 0.5))Es filtren els clients del 2016 amb status de reserva ‘Check-Out’. El valor de reservation_status = ‘Check-Out’ indica que el client es va presentar i va completar la seva estada a l’hotel.
# Nombre de clients amb Check-Out que repeteixen segons el mes d'arribada per
# a l'any 2016
ggplot(
xx[xx$arrival_date_year == 2016 & xx$reservation_status == "Check-Out", ],
aes(
x = reorder(
arrival_date_month,
as.numeric(arrival_date_month)
),
fill = arrival_date_month
)
) +
geom_bar(position = "dodge") +
facet_wrap(~hotel, strip.position = "bottom") +
labs(
title = "Nombre de clients amb Check-Out que repeteixen (2016)",
x = "Mes d'arribada",
y = "Nombre de clients"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)
)# Nombre de clients amb Check-Out que repeteixen i no repeteixen segons el mes
# d'arribada per a l'any 2016
ggplot(
xx[xx$arrival_date_year == 2016 & xx$reservation_status == "Check-Out", ],
aes(
x = reorder(
arrival_date_month,
as.numeric(arrival_date_month)
),
fill = as.factor(is_repeated_guest)
)
) +
geom_bar(position = "dodge") +
scale_fill_manual(
name = "Client que repeteix",
labels = c("No", "Sí"),
values = c("0" = "#3498db", "1" = "#9b59b6")
) +
facet_wrap(~hotel, strip.position = "bottom") +
labs(
title = "Nombre de clients amb Check-Out que repeteixen i no (2016)",
x = "Mes d'arribada",
y = "Nombre de clients"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)
)# Percentatge de clients amb Check-Out que repeteixen segons el mes d'arribada
# per a l'any 2016
ggplot(
xx[xx$arrival_date_year == 2016 & xx$reservation_status == "Check-Out", ],
aes(
x = reorder(
arrival_date_month,
as.numeric(arrival_date_month)
),
fill = as.factor(is_repeated_guest)
)
) +
geom_bar(position = "fill") +
scale_fill_manual(
name = "Client que repeteix",
labels = c("No", "Sí"),
values = c("0" = "#3498db", "1" = "#9b59b6")
) +
scale_y_continuous(labels = scales::percent) +
facet_wrap(~hotel, strip.position = "bottom") +
labs(
title = "Percentatge de clients amb Check-Out que repeteixen (2016)",
x = "Mes d'arribada",
y = "Percentatge de clients"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)
)# Diagrames de violins del nombre de dies d'estada (x$stays_in_week_nights +
# x$stays_in_weekend_nights) per a cada mes d'arribada per a l'any 2016 per a
# reserves amb Check-Out
ggplot(
xx[xx$arrival_date_year == 2016 & xx$reservation_status == "Check-Out", ],
aes(
x = reorder(
arrival_date_month,
as.numeric(arrival_date_month)
),
y = stays_in_week_nights + stays_in_weekend_nights,
fill = arrival_date_month
)
) +
geom_violin() +
geom_boxplot(width = .1, outliers = FALSE) +
facet_wrap(~hotel, strip.position = "bottom") +
coord_flip() +
labs(
title = "Dies d'estada reserves amb Check-Out segons el mes d'arribada (2016)",
x = "Mes d'arribada",
y = "Dies d'estada"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
legend.position = "none"
)# boxplot de les nits d'estada (x$stays_in_week_nights + x$stays_in_weekend_nights)
# per a cada tipus d'hotel per a reserves amb Check-Out l'any 2016
ggplot(
xx[xx$reservation_status == "Check-Out" & xx$arrival_date_year == 2016, ],
aes(x = hotel, y = stays_in_week_nights + stays_in_weekend_nights, fill = hotel)
) +
geom_violin() +
geom_boxplot(width = .1, outliers = FALSE) +
coord_flip() +
labs(
title = "Dies d'estada reserves amb Check-Out segons el tipus d'hotel (2016)",
x = "Tipus d'hotel",
y = "Dies d'estada"
) +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5), legend.position = "none")# Valors ordenats de stay_days amb reservation_status = 'Check-Out' per a l'any
# 2016 per a cada tipus d'hotel amb plot
ggplot(
xx[xx$arrival_date_year == 2016 & xx$reservation_status == "Check-Out", ],
aes(
x = reorder(
as.factor(1:nrow(xx[xx$arrival_date_year == 2016 &
xx$reservation_status == "Check-Out", ])),
stays_in_week_nights + stays_in_weekend_nights
),
y = stays_in_week_nights + stays_in_weekend_nights,
color = hotel
)
) +
geom_point() +
labs(
title = "Dies d'estada reserves amb Check-Out ordenats segons el tipus d'hotel (2016)",
x = "Reserves ordenades",
y = "Dies d'estada"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5), legend.position = "bottom",
axis.text.x = element_blank()
)Preferències de tipus d’hotel segons diferents variables.
# Percentatge de reserves per tipus d'hotel i país
ggplot(
xx,
aes(
x = reorder(country, as.numeric(hotel), decreasing = FALSE),
fill = hotel
)
) +
geom_bar(position = "fill") +
scale_fill_manual(
name = "Tipus d'hotel",
labels = c("City Hotel", "Resort Hotel"),
values = c("City Hotel" = "#00BFC4", "Resort Hotel" = "#F8766D")
) +
scale_y_continuous(labels = scales::percent) +
coord_flip() +
labs(
title = "Preferència de tipus d'hotel per país",
x = "País",
y = "Percentatge de reserves"
) +
theme_minimal() +
theme(
axis.text.x = element_text(vjust = 0.5, hjust = 1),
plot.title = element_text(hjust = 0.5)
)# El nombre de dies entre la reserva i l'arribada mig per tipus d'hotel és el
# mateix?
ggplot(
xx[xx$arrival_date_year == 2016 & xx$reservation_status == "Check-Out", ],
aes(x = hotel, y = lead_time, fill = hotel)
) +
geom_violin() +
geom_boxplot(width = .1, outliers = FALSE) +
coord_flip() +
labs(
title = "Dies entre la reserva i l'arribada amb Check-Out (2016)",
x = "Tipus d'hotel",
y = "Dies entre la reserva i l'arribada"
) +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5), legend.position = "none")# Canvia el nombre de dies mig entre la reserva i l'arribada segons el país
# d'origen?
ggplot(
xx[xx$arrival_date_year == 2016 & xx$reservation_status == "Check-Out", ],
aes(
x = reorder(country, lead_time, FUN = median),
y = lead_time, fill = hotel
)
) +
geom_violin() +
geom_boxplot(width = .1, outliers = FALSE) +
facet_wrap(~hotel, strip.position = "bottom") +
coord_flip() +
labs(
title = "Dies entre la reserva i l'arribada amb Check-Out segons el país d'origen (2016)",
x = "País d'origen",
y = "Dies entre la reserva i l'arribada"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
legend.position = "none"
)# Canvia el nombre de dies mig entre la reserva i l'arribada segons
# l'estacionalitat?
ggplot(
xx[xx$arrival_date_year == 2016 & xx$reservation_status == "Check-Out", ],
aes(
x = arrival_date_month,
y = lead_time,
group = arrival_date_month,
fill = hotel
)
) +
geom_violin() +
geom_boxplot(width = .1, outliers = FALSE) +
facet_wrap(~hotel, strip.position = "bottom") +
coord_flip() +
labs(
title = "Dies entre la reserva i l'arribada amb Check-Out segons el mes d'arribada (2016)",
x = "Mes d'arribada",
y = "Dies entre la reserva i l'arribada"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
legend.position = "none"
)Preferències segons el tipus de client.
# Quins tipus de dipòsit trien els diferents països?
ggplot(xx, aes(
x = reorder(country, deposit_type, FUN = function(x) {
table(x)[which.max(table(x))]
}),
fill = deposit_type
)) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent) +
labs(
title = "Tipus de dipòsit segons el país d'origen",
x = "País",
y = "Percentatge",
fill = "Tipus de dipòsit"
) +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
plot.title = element_text(hjust = 0.5)
)# Quin tipus de client acostuma a fer més reserves?
ggplot(xx, aes(
x = reorder(customer_type, customer_type, function(x) length(x),
decreasing = TRUE
),
fill = customer_type
)) +
geom_bar() +
facet_wrap(~hotel) +
labs(
title = "Nombre de reserves segons el tipus de client",
x = "Tipus de client",
y = "Nombre de reserves"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
legend.position = "none"
)# Diagrama de sectors del percentatge de reserves per tipus de client
customer_type_summary <- xx |>
group_by(hotel, customer_type) |>
summarise(count = n()) |>
mutate(percentage = count / sum(count) * 100)## `summarise()` has grouped output by 'hotel'. You can override using the
## `.groups` argument.
ggplot(
customer_type_summary, aes(x = "", y = percentage, fill = customer_type)
) +
geom_bar(width = 1, stat = "identity") +
coord_polar("y", start = 0) +
facet_wrap(~hotel, strip.position = "bottom") +
labs(
title = "Percentatge de reserves per tipus de client",
fill = "Tipus de client"
) +
# Mostra el percentatge a cada sector
geom_text(aes(label = paste0(round(percentage, 1), "%")),
position = position_stack(vjust = 0.5)
) +
theme_void() +
theme(
# Centra el títol
plot.title = element_text(hjust = 0.5)
)rm(customer_type_summary)
# Quin tipus de de client té una taxa de cancel·lació més alta?
ggplot(xx, aes(
x = reorder(customer_type, customer_type, function(x) length(x),
decreasing = TRUE
),
fill = is_canceled
)) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent) +
scale_fill_manual(
name = "Estat de cancel·lació",
labels = c("No cancel·lat", "Cancel·lat"),
values = c("0" = "#2ecc71", "1" = "#e74c3c")
) +
facet_wrap(~hotel) +
labs(
title = "Percentatge de cancel·lació segons el tipus de client",
x = "Tipus de client",
y = "Percentatge de cancel·lació"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)
)# Quina és la despesa mitjana per tipus de client?
ggplot(
xx |> filter(arrival_date_year == 2016),
aes(
x = reorder(customer_type, customer_type, function(x) length(x),
decreasing = TRUE
),
y = adr, fill = customer_type
)
) +
geom_violin() +
geom_boxplot(width = .1, outliers = FALSE) +
coord_flip() +
facet_wrap(~hotel) +
labs(
title = "Tarifa diària mitjana (ADR) segons el tipus de client (2016)",
x = "Tipus de client",
y = "Tarifa diària mitjana (ADR)"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
legend.position = "none",
axis.title.x = element_blank(),
axis.title.y = element_blank()
)# El consum mig (ADR) varia en funció del mes d'arribada per a l'any 2016?
ggplot(
xx |> filter(arrival_date_year == 2016),
aes(
x = arrival_date_month,
y = adr,
group = arrival_date_month,
fill = hotel
)
) +
geom_violin() +
geom_boxplot(width = .1, outliers = FALSE) +
facet_wrap(~hotel, strip.position = "bottom") +
coord_flip() +
labs(
title = "Tarifa diària mitjana (ADR) segons el mes d'arribada (2016)",
x = "Mes d'arribada",
y = "Tarifa diària mitjana (ADR)"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
legend.position = "none"
)# quin és el percentatge de reserves cancel·lades per tipus de visita amb
# dipòsit no reemborsable en funció del tipus d'hotel?
ggplot(xx, aes(
x = tipo,
fill = is_canceled
)) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent) +
scale_fill_manual(
name = "Estat de cancel·lació",
labels = c("No cancel·lat", "Cancel·lat"),
values = c("0" = "#2ecc71", "1" = "#e74c3c")
) +
facet_wrap(~deposit_type) +
labs(
title = "Percentatge de cancel·lació segons el tipus de visita i tipus de dipòsit",
x = "Tipus de visita",
y = "Percentatge de cancel·lació"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)
)# diagrama amb el nombre de cancel·lacions segons el canal de distribució
ggplot(
xx,
aes(
x = reorder(
distribution_channel,
distribution_channel,
function(x) length(x),
decreasing = TRUE
),
fill = as.factor(is_canceled)
)
) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent) +
scale_fill_manual(
name = "Estat",
labels = c("No Cancel·lada", "Cancel·lada"),
values = c("0" = "#2ecc71", "1" = "#e74c3c")
) +
labs(
title = "Percentatge de cancel·lacions segons el canal de distribució",
x = "Canal de distribució",
y = "Percentatge de cancel·lacions"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)
)# diagrama sectors segons el canal de distribució
distribution_channel_summary <- xx |>
group_by(distribution_channel, is_canceled) |>
summarise(n = n(), .groups = "drop") |>
mutate(prop = n / sum(n))
ggplot(
distribution_channel_summary,
aes(
x = factor(1), y = prop,
fill = interaction(distribution_channel, is_canceled, lex.order = TRUE)
)
) +
scale_fill_discrete(
name = "Canal de distribució i estat",
labels = c(
"TA/TO.0" = "TA - No Cancel·lada",
"TA/TO.1" = "TA - Cancel·lada",
"GDS.0" = "GDS - No Cancel·lada",
"GDS.1" = "GDS - Cancel·lada",
"Corporate.0" = "Corporate - No Cancel·lada",
"Corporate.1" = "Corporate - Cancel·lada",
"Direct.0" = "Direct - No Cancel·lada",
"Direct.1" = "Direct - Cancel·lada"
)
) +
coord_polar(theta = "y", start = 0) +
geom_col(width = 1) +
labs(
title = "Percentatge de reserves segons el canal de distribució",
x = NULL, y = NULL, fill = "Canal de distribució"
) +
theme_void() +
theme(plot.title = element_text(hjust = 0.5))# percentatge de reserves amb check-out segons el canal de distribució
ggplot(
xx[xx$reservation_status == "Check-Out", ],
aes(
x = reorder(
distribution_channel,
distribution_channel,
function(x) length(x),
decreasing = TRUE
),
fill = as.factor(is_repeated_guest)
)
) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent) +
scale_fill_manual(
name = "Client que repeteix",
labels = c("No", "Sí"),
values = c("0" = "#3498db", "1" = "#9b59b6")
) +
labs(
title = "Percentatge de fidelització efectiva segons el canal de distribució",
x = "Canal de distribució",
y = "Percentatge de clients que repeteixen"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)
)rm(distribution_channel_summary)
# nombre de dies segon el canal de distribució
ggplot(
xx,
aes(
x = reorder(
distribution_channel,
distribution_channel,
function(x) length(x),
decreasing = TRUE
),
y = stays_in_week_nights + stays_in_weekend_nights,
fill = distribution_channel
)
) +
geom_violin() +
geom_boxplot(width = .1, outliers = FALSE) +
coord_flip() +
labs(
title = "Nombre de dies d'estada segons el canal de distribució",
x = "Canal de distribució",
y = "Nombre de dies d'estada"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
legend.position = "none"
)## Warning: Groups with fewer than two datapoints have been dropped.
## ℹ Set `drop = FALSE` to consider such groups for position adjustment purposes.
# Calcula el nombre de dies d'estada per a cada reserva
x$stay_days <- x$stays_in_week_nights + x$stays_in_weekend_nights
# Diagrama de violí amb el nombre de dies d'estada segons el tipus d'hotel
ggplot(data = x, aes(x = hotel, y = stay_days, fill = hotel)) +
geom_violin() +
geom_boxplot(width = .1, outliers = FALSE) +
coord_flip() +
labs(
title = "Nombre de dies d'estada segons el tipus d'hotel",
x = "Tipus d'hotel",
y = "Nombre de dies d'estada"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
legend.position = "none"
)# Histograma del nombre de dies d'estada per tipus d'hotel
ggplot(data = x, aes(x = stay_days, fill = hotel)) +
geom_histogram(position = "dodge", binwidth = 1, color = "black") +
labs(
title = "Nombre de dies d'estada segons el tipus d'hotel",
x = "Nombre de dies de l'estada",
y = "Freqüència"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
legend.position = "bottom"
)# Habitacions ocupades dels hotels per dia
occupation_data <- x |>
filter(is_canceled == 0) |>
rowwise() |>
mutate(stay_dates = list(seq.Date(dia, by = "day", length.out = stay_days))) |>
unnest(cols = c(stay_dates)) |>
group_by(hotel, stay_dates) |>
summarise(occupied_rooms = n(), .groups = "drop")
# Ocupació diària de cada tipus d'hotel
ggplot(occupation_data, aes(x = stay_dates, y = occupied_rooms, color = hotel)) +
geom_line() +
labs(
title = "Habitacions ocupades diàriament segons el tipus d'hotel",
x = "Data",
y = "Nombre d'habitacions ocupades",
color = "Tipus d'hotel"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
legend.position = "bottom"
)occupation_data$year <- as.integer(format.Date(occupation_data$stay_dates, "%Y"))
ggplot(occupation_data, aes(
x = stay_dates,
y = occupied_rooms, color = as.factor(year)
)) +
geom_line() +
facet_wrap(~hotel) +
labs(
title = "Habitacions ocupades diàriament segons el tipus d'hotel i any",
x = "Data",
y = "Nombre d'habitacions ocupades",
color = "Any"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
legend.position = "bottom"
)# Ocupació mitjana setmanal de cada tipus d'hotel
weekly_occupation <- occupation_data |>
mutate(week = floor_date(stay_dates, "week")) |>
group_by(hotel, week) |>
summarise(avg_occupied_rooms = mean(occupied_rooms), .groups = "drop")
ggplot(weekly_occupation, aes(x = week, y = avg_occupied_rooms, color = hotel)) +
geom_line() +
labs(
title = "Habitacions ocupades setmanalment segons el tipus d'hotel",
x = "Setmana",
y = "Nombre mitjà d'habitacions ocupades",
color = "Tipus d'hotel"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
legend.position = "bottom"
)# Ocupació mitjana mensual de cada tipus d'hotel
monthly_occupation <- occupation_data |>
mutate(month = floor_date(stay_dates, "month")) |>
group_by(hotel, month) |>
summarise(avg_occupied_rooms = mean(occupied_rooms), .groups = "drop")
ggplot(monthly_occupation, aes(x = month, y = avg_occupied_rooms, color = hotel)) +
geom_line() +
labs(
title = "Habitacions ocupades mensualment segons el tipus d'hotel",
x = "Mes",
y = "Nombre mitjà d'habitacions ocupades",
color = "Tipus d'hotel"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
legend.position = "bottom"
)# Cost mitja per dia mitjà dels dos hotels
daily_occupation_revenue <- occupation_data |>
left_join(
x |>
filter(is_canceled == 0) |>
rowwise() |>
mutate(stay_dates = list(seq.Date(dia, by = "day", length.out = stay_days))) |>
unnest(cols = c(stay_dates)) |>
group_by(hotel, stay_dates) |>
summarise(daily_adr = mean(adr), .groups = "drop"),
by = c("hotel", "stay_dates")
) |>
mutate(daily_revenue = occupied_rooms * daily_adr)
ggplot(
daily_occupation_revenue,
aes(x = stay_dates, y = daily_revenue, color = hotel)
) +
geom_line() +
labs(
title = "Ingressos diaris segons el tipus d'hotel",
x = "Data",
y = "Ingressos diaris",
color = "Tipus d'hotel"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
legend.position = "bottom"
)# Mostra en una línea l'ocupació per dia, i en un altra línea el preu per dia
ggplot(daily_occupation_revenue, aes(x = stay_dates)) +
geom_line(aes(y = occupied_rooms, color = "Ocupació")) +
geom_line(aes(y = daily_adr, color = "Preu mitjà (ADR)")) +
facet_wrap(~hotel) +
labs(
title = "Ocupació i preu mitjà diari segons el tipus d'hotel",
x = "Data",
y = "Valor",
color = "Mètrica"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
legend.position = "bottom"
)# Eliminació dels objectes temporals
# rm(occupation_data, monthly_occupation, weekly_occupation, daily_occupation_revenue)# calcula els beneficis diaris a partir de l'ADR i les habitacions ocupades
daily_revenue <- x |>
filter(is_canceled == 0) |>
rowwise() |>
mutate(stay_dates = list(seq.Date(dia, by = "day", length.out = stay_days))) |>
unnest(cols = c(stay_dates)) |>
group_by(hotel, stay_dates) |>
summarise(daily_revenue = sum(adr), .groups = "drop")
# Ingressos diaris de cada tipus d'hotel
ggplot(daily_revenue, aes(x = stay_dates, y = daily_revenue, color = hotel)) +
geom_line() +
labs(
title = "Ingressos diaris segons el tipus d'hotel",
x = "Data",
y = "Ingressos diaris",
color = "Tipus d'hotel"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
legend.position = "bottom"
)# Ingressos setmanals mitjans de cada tipus d'hotel
weekly_revenue <- daily_revenue |>
mutate(week = floor_date(stay_dates, "week")) |>
group_by(hotel, week) |>
summarise(avg_weekly_revenue = mean(daily_revenue), .groups = "drop")
ggplot(weekly_revenue, aes(x = week, y = avg_weekly_revenue, color = hotel)) +
geom_line() +
labs(
title = "Ingressos setmanals mitjans segons el tipus d'hotel",
x = "Setmana",
y = "Ingressos setmanals mitjans",
color = "Tipus d'hotel"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
legend.position = "bottom"
)# Ingressos mensuals mitjans de cada tipus d'hotel
monthly_revenue <- daily_revenue |>
mutate(month = floor_date(stay_dates, "month")) |>
group_by(hotel, month) |>
summarise(avg_monthly_revenue = mean(daily_revenue), .groups = "drop")
ggplot(monthly_revenue, aes(x = month, y = avg_monthly_revenue, color = hotel)) +
geom_line() +
labs(
title = "Ingressos mensuals mitjans segons el tipus d'hotel",
x = "Mes",
y = "Ingressos mensuals mitjans",
color = "Tipus d'hotel"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
legend.position = "bottom"
)# Comparar el benefici dels dos hotels pel tipus de règim alimentari 'meal'
revenue_by_meal_plan <- x |>
filter(is_canceled == 0) |>
rowwise() |>
mutate(stay_dates = list(seq.Date(dia, by = "day", length.out = stay_days))) |>
unnest(cols = c(stay_dates)) |>
group_by(hotel, meal, stay_dates) |>
summarise(daily_revenue = sum(adr), .groups = "drop")
ggplot(revenue_by_meal_plan, aes(
x = stay_dates,
y = daily_revenue, fill = fct_reorder(meal, daily_revenue, .desc = TRUE)
)) +
geom_bar(stat = "identity", position = "stack") +
facet_wrap(~hotel) +
labs(
title = "Ingressos diaris segons el tipus d'hotel i règim alimentari",
x = "Data",
y = "Ingressos diaris",
color = "Tipus d'hotel i règim alimentari"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
legend.position = "bottom"
)## Ignoring unknown labels:
## • colour : "Tipus d'hotel i règim alimentari"
# Matriu de correlació per a les variables numèriques
# selecciona el nom de les variables numèriques de daily_revenue
xx <- x |>
group_by(country) |>
mutate(pais = n()) |>
filter(pais >= 1000)
numeric_vars <- xx[, sapply(
xx,
function(col) is.numeric(col)
)]
correlation_matrix <- cor(numeric_vars, use = "complete.obs")
melted_correlation <- melt(correlation_matrix)
ggplot(data = melted_correlation, aes(
x = Var1, y = Var2, fill = value
)) +
geom_tile() +
scale_fill_gradient2(
low = "blue", high = "red", mid = "white",
limit = c(-1, 1), name = "Correlation"
) +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
plot.title = element_text(hjust = 0.5)
) +
labs(title = "Matriu de correlació de variables numèriques")Els patrons importen
# Ingressos setmanals de cada tipus d'hotel
p <- ggplot(
daily_revenue |>
mutate(week = floor_date(stay_dates, "week")) |>
group_by(hotel, week) |>
summarise(weekly_revenue = sum(daily_revenue), .groups = "drop"),
aes(x = week, y = weekly_revenue, color = hotel),
) +
scale_y_continuous(labels = scales::label_number(scale = 1, big.mark = ".")) +
geom_line(linewidth = 1.5) +
labs(
title = "Ingressos setmanals segons el tipus d'hotel",
x = "Setmana",
y = "Ingressos setmanals",
color = "Tipus d'hotel"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0, size = 36),
# text de les marques dels eixos
axis.text.x = element_text(size = 18),
axis.text.y = element_text(size = 18),
# fons transparents
panel.background = element_rect(fill = "transparent", color = NA),
plot.background = element_rect(fill = "transparent", color = NA),
legend.background = element_rect(fill = "transparent", color = NA),
legend.box.background = element_rect(fill = "transparent", color = NA),
legend.key = element_rect(fill = "transparent", color = NA),
# text dels títols dels eixos
axis.title.y = element_blank(),
axis.title.x = element_blank(),
# augmenta la mida de la graella
panel.grid.major = element_line(size = 1),
panel.grid.minor = element_line(size = 0.5),
# text del facet
strip.text = element_text(size = 24),
# llegenda
legend.position = "bottom",
legend.text = element_text(size = 20),
legend.title = element_blank()
)## Warning: The `size` argument of `element_line()` is deprecated as of ggplot2 3.4.0.
## ℹ Please use the `linewidth` argument instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
# desa la imatge en un fitxer SVG
# ggsave("img/weekly_revenue.svg",
# plot = p,
# width = 11.2, height = 9, # aspect ratio 16:9, width x 0.7 (storytell area)
# bg = "transparent"
# )
rm(p)Els hotels s’enfronten a una realitat preocupant
# Mostra en una línia l'ocupació mitjana setmanal, i en un altra línia el preu
# mitjà setmanal
p <- ggplot(weekly_occupation, aes(x = week)) +
geom_line(
aes(
y = avg_occupied_rooms,
color = "Ocupació mitjana setmanal"
),
linewidth = 1.5
) +
geom_line(
data = daily_occupation_revenue |>
mutate(week = floor_date(stay_dates, "week")) |>
group_by(hotel, week) |>
summarise(avg_weekly_adr = mean(daily_adr), .groups = "drop"),
aes(y = avg_weekly_adr, color = "ADR mitjà setmanal"),
linewidth = 1.5
) +
facet_wrap(~hotel, strip.position = "bottom") +
labs(
title = "Ocupació i preu mitjà setmanal",
x = "Setmana",
y = "Valor",
color = "Mètrica"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0, size = 36),
# text de les marques dels eixos
axis.text.x = element_text(size = 18),
axis.text.y = element_text(size = 18),
# fons transparents
panel.background = element_rect(fill = "transparent", color = NA),
plot.background = element_rect(fill = "transparent", color = NA),
legend.background = element_rect(fill = "transparent", color = NA),
legend.box.background = element_rect(fill = "transparent", color = NA),
legend.key = element_rect(fill = "transparent", color = NA),
# text dels títols dels eixos
axis.title.y = element_blank(),
axis.title.x = element_blank(),
# augmenta la mida de la graella
panel.grid.major = element_line(size = 1),
panel.grid.minor = element_line(size = 0.5),
# text del facet
strip.text = element_text(size = 24),
# llegenda
legend.position = "bottom",
legend.text = element_text(size = 20),
legend.title = element_blank()
)
# desa la imatge en un fitxer SVG
# ggsave("img/weekly_occupation_adr.svg",
# plot = p,
# width = 11.2, height = 9, # aspect ratio 16:9, width x 0.7 (storytell area)
# bg = "transparent"
# )
rm(p)Regim alimentari i ingressos: una relació clau:
# Comparar el benefici dels dos hotels pel tipus de règim alimentari 'meal'
revenue_by_meal_plan <- x |>
filter(is_canceled == 0) |>
rowwise() |>
mutate(stay_dates = list(seq.Date(dia, by = "day", length.out = stay_days))) |>
unnest(cols = c(stay_dates)) |>
group_by(hotel, meal, stay_dates) |>
summarise(daily_revenue = sum(adr), .groups = "drop")
revenue_by_meal_plan## # A tibble: 4,976 × 4
## hotel meal stay_dates daily_revenue
## <fct> <ord> <date> <dbl>
## 1 City Hotel SC 2015-07-03 60.3
## 2 City Hotel SC 2015-07-04 60.3
## 3 City Hotel SC 2015-07-05 60.3
## 4 City Hotel SC 2015-07-06 60.3
## 5 City Hotel SC 2015-07-07 60.3
## 6 City Hotel SC 2015-07-08 60.3
## 7 City Hotel SC 2015-07-09 60.3
## 8 City Hotel SC 2015-07-11 104.
## 9 City Hotel SC 2015-07-20 46.7
## 10 City Hotel SC 2015-07-21 46.7
## # ℹ 4,966 more rows
p <- ggplot(revenue_by_meal_plan, aes(
x = stay_dates,
# y = daily_revenue, fill = fct_reorder(meal, daily_revenue, .desc = FALSE)
y = daily_revenue, fill = meal
)) +
geom_bar(stat = "identity", position = "stack") +
facet_wrap(~hotel) +
scale_y_continuous(labels = scales::label_number(scale = 1, big.mark = ".")) +
scale_fill_manual(
name = "Règim alimentari",
labels = c(
"Undefined" = "No definit",
"SC" = "Sense menjar",
"BB" = "Esmorzar",
"HB" = "Mitja pensió",
"FB" = "Pensió completa"
),
values = c(
"Undefined" = "#cbcbcb",
"SC" = "#dad9d9",
"BB" = "#8ae3f9",
"HB" = "#0033ff",
"FB" = "#FF00CC"
)
) +
labs(
title = "Ingressos diaris segons el règim alimentari",
x = "Data",
y = "Ingressos diaris",
fill = "Règim alimentari"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0, size = 36),
axis.text.x = element_text(size = 18),
axis.text.y = element_text(size = 18),
panel.background = element_rect(fill = "transparent", color = NA),
plot.background = element_rect(fill = "transparent", color = NA),
legend.background = element_rect(fill = "transparent", color = NA),
legend.box.background = element_rect(fill = "transparent", color = NA),
legend.key = element_rect(fill = "transparent", color = NA),
axis.title.y = element_blank(),
axis.title.x = element_blank(),
panel.grid.major = element_line(size = 1),
panel.grid.minor = element_line(size = 0.5),
strip.text = element_text(size = 24),
legend.position = "bottom",
legend.text = element_text(size = 20),
legend.title = element_blank()
)
# ggsave("img/revenue_by_meal_plan.svg",
# plot = p,
# width = 11.2, height = 9, # aspect ratio 16:9, width x 0.7 (storytell area)
# bg = "transparent"
# )
rm(p)Hotels City vs. Resort: Disparitats de Cancel·lació
# Gràfica: Taxa de cancel·lació per tipus d'hotel diagrama de sectors de is_canceled
cancellation_summary <- xx |>
group_by(hotel, is_canceled) |>
summarise(n = n(), .groups = "drop") |>
group_by(hotel) |>
tidyr::complete(is_canceled = c("0", "1"), fill = list(n = 0)) |>
mutate(pct = n / sum(n))
p <- ggplot(cancellation_summary, aes(x = factor(1), y = pct, fill = is_canceled)) +
coord_polar(theta = "y", start = 0) +
facet_wrap(~hotel, strip.position = "bottom") +
scale_y_continuous(limits = c(0, 1), labels = scales::percent) +
scale_fill_manual(
name = "Estat",
values = c("0" = "#2ecc71", "1" = "#e74c3c"),
labels = c("No cancel·lada", "Cancel·lada")
) +
geom_col(width = 1) +
geom_text(aes(label = percent(pct, accuracy = 0.1)),
position = position_stack(vjust = 0.5),
size = 5, fontface = "bold", color = "white"
) +
labs(
title = "Percentatge de cancel·lacions per tipus d'hotel",
x = NULL, y = NULL
) +
theme_void() +
theme(
plot.title = element_text(hjust = 0, size = 36),
# text de les marques dels eixos
axis.text.x = element_blank(),
axis.text.y = element_blank(),
# fons transparents
panel.background = element_rect(fill = "transparent", color = NA),
plot.background = element_rect(fill = "transparent", color = NA),
legend.background = element_rect(fill = "transparent", color = NA),
legend.box.background = element_rect(fill = "transparent", color = NA),
legend.key = element_rect(fill = "transparent", color = NA),
# text dels títols dels eixos
axis.title.y = element_blank(),
axis.title.x = element_blank(),
# text del facet
strip.text = element_text(size = 24),
# llegenda
legend.position = "bottom",
legend.text = element_text(size = 20),
legend.title = element_blank()
)
# ggsave("img/cancellation_by_hotel.svg",
# plot = p,
# width = 11.2, height = 9, # aspect ratio 16:9, width x 0.7 (storytell area)
# bg = "transparent"
# )Origen Geogràfic: Clients Locals vs. Internacionals
# Gràfica: Taxa de cancel·lació per origen (País)
# Categoritzar entre Portugal i International
xx_is_portugal <- xx |>
mutate(is_portugal = ifelse(country == "PRT", "Portugal", "Altres")) |>
group_by(is_portugal, is_canceled) |>
summarise(n = n(), .groups = "drop") |>
group_by(is_portugal) |>
mutate(pct = n / sum(n))
p <- ggplot(xx_is_portugal, aes(x = factor(1), y = pct, fill = is_canceled)) +
geom_col(width = 1) +
coord_polar(theta = "y") +
facet_wrap(~is_portugal, strip.position = "bottom") +
scale_y_continuous(labels = scales::percent) +
scale_fill_manual(name = "Estat", labels = c("No Cancel·lada", "Cancel·lada"), values = c("0" = "#2ecc71", "1" = "#e74c3c")) +
geom_text(aes(label = percent(pct, accuracy = 0.1)), position = position_stack(vjust = 0.5), size = 5, fontface = "bold", color = "white") +
labs(title = "Percentatge de cancel·lacions per país d'origen", x = NULL, y = NULL) +
theme_void() +
theme(
plot.title = element_text(hjust = 0, size = 36),
# text de les marques dels eixos
axis.text.x = element_blank(),
axis.text.y = element_blank(),
# fons transparents
panel.background = element_rect(fill = "transparent", color = NA),
plot.background = element_rect(fill = "transparent", color = NA),
legend.background = element_rect(fill = "transparent", color = NA),
legend.box.background = element_rect(fill = "transparent", color = NA),
legend.key = element_rect(fill = "transparent", color = NA),
# text dels títols dels eixos
axis.title.y = element_blank(),
axis.title.x = element_blank(),
# text del facet
strip.text = element_text(size = 24),
# llegenda
legend.position = "bottom",
legend.text = element_text(size = 20),
legend.title = element_blank()
)
rm(xx_is_portugal)
# ggsave("img/cancellation_by_origin.svg",
# plot = p,
# width = 11.2, height = 9, # aspect ratio 16:9, width x 0.7 (storytell area)
# bg = "transparent"
# )Dipòsits No Reemborsables: Una Anomalia Preocupant
# Gràfica: Taxa de cancel·lació per tipus de dipòsit diagrama de sectors de is_canceled
cancellation_summary <- xx |>
group_by(deposit_type, is_canceled) |>
summarise(n = n(), .groups = "drop") |>
group_by(deposit_type) |>
tidyr::complete(is_canceled = c("0", "1"), fill = list(n = 0)) |>
mutate(pct = n / sum(n))
p <- ggplot(cancellation_summary, aes(x = factor(1), y = pct, fill = is_canceled)) +
coord_polar(theta = "y", start = 0) +
facet_wrap(~deposit_type, strip.position = "bottom") +
scale_y_continuous(limits = c(0, 1), labels = scales::percent) +
scale_fill_manual(
name = "Estat",
values = c("0" = "#2ecc71", "1" = "#e74c3c"),
labels = c("No cancel·lada", "Cancel·lada")
) +
geom_col(width = 1) +
geom_text(aes(label = percent(pct, accuracy = 0.1)),
position = position_stack(vjust = 0.5),
size = 5, fontface = "bold", color = "white"
) +
labs(
title = "Percentatge de cancel·lacions per tipus de dipòsit",
x = NULL, y = NULL
) +
theme_void() +
theme(
plot.title = element_text(hjust = 0, size = 36),
# text de les marques dels eixos
axis.text.x = element_blank(),
axis.text.y = element_blank(),
# fons transparents
panel.background = element_rect(fill = "transparent", color = NA),
plot.background = element_rect(fill = "transparent", color = NA),
legend.background = element_rect(fill = "transparent", color = NA),
legend.box.background = element_rect(fill = "transparent", color = NA),
legend.key = element_rect(fill = "transparent", color = NA),
# text dels títols dels eixos
axis.title.y = element_blank(),
axis.title.x = element_blank(),
# text del facet
strip.text = element_text(size = 24),
# llegenda
legend.position = "bottom",
legend.text = element_text(size = 20),
legend.title = element_blank()
)
# ggsave("img/cancellation_by_deposit.svg",
# plot = p,
# width = 11.2, height = 9, # aspect ratio 16:9, width x 0.7 (storytell area)
# bg = "transparent"
# )Pic d’Ocupació: Maig-Octubre i Nadal:
# Gràfica: Ocupació mensual
monthly_occupancy <- xx |>
filter(is_canceled == 0) |>
rowwise() |>
mutate(stay_dates = list(seq.Date(dia, by = "day", length.out = stay_days))) |>
unnest(cols = c(stay_dates)) |>
filter(year(stay_dates) == 2016) |>
group_by(month = month(stay_dates)) |>
summarise(
avg_occupancy = round((100 * n()) / (360 * days_in_month(
as.Date(paste0("2016-", month(stay_dates), "-01"))
)
), 1),
total_reservations = n()
)## Warning: Returning more (or less) than 1 row per `summarise()` group was deprecated in
## dplyr 1.1.0.
## ℹ Please use `reframe()` instead.
## ℹ When switching from `summarise()` to `reframe()`, remember that `reframe()`
## always returns an ungrouped data frame and adjust accordingly.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## `summarise()` has grouped output by 'month'. You can override using the
## `.groups` argument.
## # A tibble: 12 × 3
## # Groups: month [12]
## month avg_occupancy total_reservations
## <dbl> <dbl> <int>
## 1 1 35 3903
## 2 2 58 6055
## 3 3 79.8 8901
## 4 4 86.9 9387
## 5 5 95.5 10658
## 6 6 92.2 9957
## 7 7 95.7 10678
## 8 8 99.5 11104
## 9 9 96.7 10443
## 10 10 97.4 10873
## 11 11 72.2 7793
## 12 12 58.5 6530
ggplot(
monthly_occupancy[!duplicated(monthly_occupancy), ],
aes(x = factor(month, labels = month.abb), y = avg_occupancy)
) +
geom_line(color = "steelblue", linewidth = 1.5) +
geom_point(size = 4, color = "steelblue") +
geom_text(aes(label = paste0(avg_occupancy, "%")), vjust = -1, size = 5) +
labs(
title = "Estacionalitat: Ocupació mitjana per mes",
x = "Mes",
y = "Ocupació (%)"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0, size = 36),
axis.text = element_text(size = 18),
panel.background = element_rect(fill = "transparent", color = NA),
plot.background = element_rect(fill = "transparent", color = NA),
legend.position = "none",
axis.title = element_blank()
)## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
# ggsave("img/monthly_occupancy.svg",
# plot = last_plot(),
# width = 11.2, height = 9, # aspect ratio 16:9, width x 0.7 (storytell area)
# bg = "transparent"
# )Valls de Demanda
# Gràfica: Períodes de baix i alt ocupació
demand_pattern <- xx |>
filter(is_canceled == 0) |>
rowwise() |>
mutate(stay_dates = list(seq.Date(dia, by = "day", length.out = stay_days))) |>
unnest(cols = c(stay_dates)) |>
mutate(
month = month(stay_dates),
year = year(stay_dates)
) |>
filter(year == 2016) |>
mutate(season = case_when(
month %in% c(5, 6, 7, 8, 9, 10) ~ "Alta (Maig-Octubre)",
month %in% c(12, 1) ~ "Nadal",
TRUE ~ "Baixa (Nov-Febrer)"
)) |>
group_by(season) |>
summarise(
avg_occupancy = round((mean((100 * n()) / (360 * case_when(
season == "Baixa (Nov-Febrer)" ~ 119,
season == "Nadal" ~ 62,
TRUE ~ 184
)))), 1),
year = year(stay_dates),
total_bookings = n()
)## Warning: Returning more (or less) than 1 row per `summarise()` group was deprecated in
## dplyr 1.1.0.
## ℹ Please use `reframe()` instead.
## ℹ When switching from `summarise()` to `reframe()`, remember that `reframe()`
## always returns an ungrouped data frame and adjust accordingly.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## `summarise()` has grouped output by 'season'. You can override using the
## `.groups` argument.
## # A tibble: 3 × 4
## # Groups: season [3]
## season avg_occupancy year total_bookings
## <chr> <dbl> <dbl> <int>
## 1 Alta (Maig-Octubre) 96.2 2016 63713
## 2 Baixa (Nov-Febrer) 75 2016 32136
## 3 Nadal 46.7 2016 10433
ggplot(demand_pattern[!duplicated(demand_pattern), ], aes(
x = factor(season, levels = c("Alta (Maig-Octubre)", "Nadal", "Baixa (Nov-Febrer)")),
y = avg_occupancy, fill = season
)) +
geom_col() +
scale_fill_manual(
values = c(
"Alta (Maig-Octubre)" = "#08519c",
"Nadal" = "#3182bd",
"Baixa (Nov-Febrer)" = "#9ecae1"
)
) +
# línia amb la mitjana d'ocupació
# geom_hline(yintercept = mean(monthly_occupancy$avg_occupancy), linetype = "dashed", color = "red", size = 1) +
# annotate("text",
# x = 2.5, y = mean(monthly_occupancy$avg_occupancy) + 2,
# label = paste0("Mitjana d'ocupació: ", round(mean(monthly_occupancy$avg_occupancy), 1), "%"),
# color = "red", size = 6
# ) +
geom_text(aes(label = paste0(avg_occupancy, "%")), vjust = -0.5, size = 6) +
labs(
title = "Patró estacional: Ocupació per temporada",
x = "Temporada",
y = "Ocupació (%)"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0, size = 36),
axis.text = element_text(size = 18),
panel.background = element_rect(fill = "transparent", color = NA),
plot.background = element_rect(fill = "transparent", color = NA),
legend.position = "none",
axis.title = element_blank()
)# ggsave("img/seasonal_pattern.svg",
# plot = last_plot(),
# width = 11.2, height = 9, # aspect ratio 16:9, width x 0.7 (storytell area)
# bg = "transparent"
# )Patró Consistent
# Gràfica: Tendència estacional per any
yearly_monthly_occupancy <- xx |>
filter(is_canceled == 0) |>
rowwise() |>
mutate(stay_dates = list(seq.Date(dia, by = "day", length.out = stay_days))) |>
unnest(cols = c(stay_dates)) |>
mutate(
year = year(stay_dates),
month = month(stay_dates)
) |>
group_by(year, month) |>
summarise(
total_stays = n(),
avg_occupancy = round((100 * n()) /
(360 * days_in_month(as.Date(paste(year, month, "01", sep = "-")))), 1),
.groups = "drop"
)## Warning: Returning more (or less) than 1 row per `summarise()` group was deprecated in
## dplyr 1.1.0.
## ℹ Please use `reframe()` instead.
## ℹ When switching from `summarise()` to `reframe()`, remember that `reframe()`
## always returns an ungrouped data frame and adjust accordingly.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## # A tibble: 27 × 4
## year month total_stays avg_occupancy
## <dbl> <dbl> <int> <dbl>
## 1 2015 7 5535 49.6
## 2 2015 8 8046 72.1
## 3 2015 9 9714 89.9
## 4 2015 10 9140 81.9
## 5 2015 11 5140 47.6
## 6 2015 12 4851 43.5
## 7 2016 1 3903 35
## 8 2016 2 6055 58
## 9 2016 3 8901 79.8
## 10 2016 4 9387 86.9
## # ℹ 17 more rows
ggplot(
yearly_monthly_occupancy[!duplicated(yearly_monthly_occupancy), ],
aes(x = month, y = avg_occupancy, color = factor(year), group = year)
) +
scale_x_continuous(breaks = 1:12, labels = month.abb) +
geom_line(linewidth = 1.5) +
geom_point(size = 3) +
labs(
title = "Consistència estacional",
x = "Mes",
y = "Ocupació (%)",
color = "Any"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0, size = 36),
axis.text = element_text(size = 18),
panel.background = element_rect(fill = "transparent", color = NA),
plot.background = element_rect(fill = "transparent", color = NA),
legend.text = element_text(size = 16),
legend.title = element_text(size = 16),
axis.title = element_blank()
)# ggsave("img/yearly_seasonality.svg",
# plot = last_plot(),
# width = 11.2, height = 9, # aspect ratio 16:9, width x 0.7 (storytell area)
# bg = "transparent"
# )Nits d’Estada: Resort Supera City en Durada
# Gràfica: Durada mitjana d'estada per hotel
stay_duration <- xx |>
filter(is_canceled == 0) |>
mutate(total_stay = stays_in_weekend_nights + stays_in_week_nights) |>
group_by(hotel) |>
summarise(
avg_stay = round(mean(total_stay), 1),
median_stay = round(median(total_stay), 1),
sd_stay = round(sd(total_stay), 1)
)
ggplot(
xx |> mutate(total_stay = stays_in_weekend_nights + stays_in_week_nights),
aes(x = hotel, y = total_stay, fill = hotel)
) +
geom_violin(alpha = 0.7) +
geom_boxplot(alpha = 0.7, outliers = FALSE) +
coord_flip() +
labs(
title = "Durada de l'estada: City vs. Resort",
x = "Tipus d'hotel",
y = "Nits d'estada"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0, size = 36),
axis.text = element_text(size = 18),
panel.background = element_rect(fill = "transparent", color = NA),
plot.background = element_rect(fill = "transparent", color = NA),
legend.position = "none",
axis.title = element_blank()
)Turoperadors (TA/TO): Volum Alt, Cancel·lacions Altes
# Gràfica: Reserves i cancel·lacions per canal de distribució
distribution_channels <- xx |>
group_by(market_segment) |>
summarise(
total_bookings = n(),
cancellation_number = sum(as.numeric(is_canceled) - 1),
cancellation_rate = round(mean(as.numeric(is_canceled) - 1) * 100, 1),
avg_stay = round(mean(stays_in_weekend_nights + stays_in_week_nights), 1)
) |>
filter(total_bookings >= 1000) |>
arrange(desc(total_bookings))
distribution_channels## # A tibble: 5 × 5
## market_segment total_bookings cancellation_number cancellation_rate avg_stay
## <fct> <int> <dbl> <dbl> <dbl>
## 1 Online TA 48478 17578 36.3 3.6
## 2 Offline TA/TO 22765 8236 36.2 3.9
## 3 Groups 18749 12036 64.2 3
## 4 Direct 11197 1718 15.3 3.2
## 5 Corporate 4826 970 20.1 2.1
ggplot(distribution_channels, aes(
x = reorder(market_segment, -total_bookings),
y = cancellation_rate, fill = market_segment
)) +
geom_col() +
scale_fill_manual(
values = c(
"Online TA" = "#08519c",
"Offline TA/TO" = "#3182bd",
"Groups" = "#6baed6",
"Direct" = "#9ecae1",
"Corporate" = "#c6dbef"
)
) +
geom_text(aes(label = paste0(cancellation_rate, "%")), vjust = -0.5, size = 5) +
# línia amb el percentatge sobre el total de reserves
geom_line(aes(market_segment, total_bookings / max(total_bookings) * max(cancellation_rate)),
group = 1, color = "red", size = 1
) +
geom_point(aes(market_segment, total_bookings / max(total_bookings) * max(cancellation_rate)),
color = "red", size = 3
) +
scale_y_continuous(
sec.axis = sec_axis(~ . * max(distribution_channels$total_bookings) / max(distribution_channels$cancellation_rate),
name = "Total de reserves"
)
) +
labs(
title = "Taxa de cancel·lació per segment de mercat",
x = "Segment de mercat",
y = "Taxa de cancel·lació (%)"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0, size = 36),
axis.title = element_text(size = 20),
axis.text.x = element_text(size = 16, angle = 45, hjust = 1),
axis.text.y = element_text(size = 18),
panel.background = element_rect(fill = "transparent", color = NA),
plot.background = element_rect(fill = "transparent", color = NA),
legend.position = "none"
)## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
# ggsave("img/market_segment_cancellation.svg",
# plot = last_plot(),
# width = 11.2, height = 9, # aspect ratio 16:9, width x 0.7 (storytell area)
# bg = "transparent"
# )Clients Transitoris: Alta Flexibilitat, Risc Elevat
# Gràfica: Lead time vs. Cancel·lació per segment
lead_time_cancellation <- xx |>
filter(lead_time >= 0 & !is.na(lead_time)) |>
mutate(lead_time_category = cut(lead_time,
breaks = c(0, 7, 30, 90, 2000),
labels = c("0-7 dies", "8-30 dies", "31-90 dies", ">90 dies")
)) |>
group_by(lead_time_category) |>
summarise(
cancellation_rate = round(mean(as.numeric(is_canceled) - 1) * 100, 1),
total_bookings = n()
)
ggplot(lead_time_cancellation, aes(x = lead_time_category, y = cancellation_rate, fill = lead_time_category)) +
geom_col() +
geom_text(aes(label = paste0(cancellation_rate, "%")), vjust = -0.5, size = 5) +
scale_fill_manual(
values = c(
"0-7 dies" = "#6baed6",
"8-30 dies" = "#3182bd",
"31-90 dies" = "#08519c",
">90 dies" = "#ff0000"
)
) +
labs(
title = "Cancel·lacions segons temps d'anticipació",
x = "Anticipació (Lead time)",
y = "Taxa de cancel·lació (%)"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0, size = 36),
axis.text = element_text(size = 18),
panel.background = element_rect(fill = "transparent", color = NA),
plot.background = element_rect(fill = "transparent", color = NA),
legend.position = "none",
axis.title = element_blank()
)# ggsave("img/lead_time_cancellation.svg",
# plot = last_plot(),
# width = 11.2, height = 9, # aspect ratio 16:9, width x 0.7 (storytell area)
# bg = "transparent"
# )Clients Locals: Patrons de Reserva Curts
# Gràfica: Durada d'estada per origen
stay_by_origin <- xx |>
mutate(
origin = if_else(country == "PRT", "Portugal", "Internacional"),
total_stay = stays_in_weekend_nights + stays_in_week_nights
) |>
group_by(origin) |>
summarise(
avg_stay = round(mean(total_stay), 1),
median_stay = round(median(total_stay), 1)
)
ggplot(
xx |>
mutate(
origin = if_else(country == "PRT", "Portugal", "Internacional"),
total_stay = stays_in_weekend_nights + stays_in_week_nights
),
aes(x = origin, y = total_stay, fill = origin)
) +
geom_violin(alpha = 0.7) +
geom_boxplot(alpha = 0.7, outliers = FALSE) +
coord_flip() +
labs(
title = "Durada d'estada: local vs. extern",
x = "Origen",
y = "Nits d'estada"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0, size = 36),
axis.text = element_text(size = 18),
panel.background = element_rect(fill = "transparent", color = NA),
plot.background = element_rect(fill = "transparent", color = NA),
legend.position = "none",
axis.title = element_blank()
)# ggsave("img/stay_by_origin.svg",
# plot = last_plot(),
# width = 11.2, height = 9, # aspect ratio 16:9, width x 0.7 (storytell area)
# bg = "transparent"
# )Més Enllà de TA/TO: Construir Canals Alternatius
# Gràfica: Distribució de reserves per canal i cancel·lacions
channel_distribution <- xx |>
group_by(booking_changes) |>
summarise(
name = first(booking_changes),
total_bookings = n(),
cancellation_rate = round(mean(as.numeric(is_canceled) - 1) * 100, 1),
percentage = round(n() / nrow(xx) * 100, 1)
) |>
arrange(desc(total_bookings))
channel_distribution## # A tibble: 18 × 5
## booking_changes name total_bookings cancellation_rate percentage
## <int> <int> <int> <dbl> <dbl>
## 1 0 0 90693 42 85.3
## 2 1 1 11081 14.8 10.4
## 3 2 2 3233 20 3
## 4 3 3 772 15.9 0.7
## 5 4 4 300 19 0.3
## 6 5 5 90 15.6 0.1
## 7 6 6 48 33.3 0
## 8 7 7 24 12.5 0
## 9 8 8 12 33.3 0
## 10 9 9 7 14.3 0
## 11 10 10 6 16.7 0
## 12 13 13 4 0 0
## 13 14 14 2 50 0
## 14 15 15 2 0 0
## 15 11 11 1 0 0
## 16 12 12 1 0 0
## 17 16 16 1 100 0
## 18 17 17 1 0 0
# grafica de bombolles de channel_distribution
ggplot(channel_distribution, aes(
x = booking_changes,
y = cancellation_rate,
size = total_bookings,
color = booking_changes
)) +
geom_point(alpha = 0.7) +
labs(
title = "Distribució de reserves: Flexibilitat de clients",
x = "Canvis en la reserva",
y = "Taxa de cancel·lació (%)",
size = "Total de reserves",
color = "Canvis en la reserva"
) +
geom_text(aes(label = ifelse(percentage > 10, paste0(percentage, "%"), "")),
vjust = -1, size = 6
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0, size = 36),
axis.text = element_text(size = 18),
panel.background = element_rect(fill = "transparent", color = NA),
plot.background = element_rect(fill = "transparent", color = NA),
legend.position = "right"
)# ggsave("img/client_flexibility_bubble.svg",
# plot = last_plot(),
# width = 11.2, height = 9, # aspect ratio 16:9, width x 0.7 (storytell area)
# bg = "transparent"
# )
# grafica de sectors de channel_distribution
ggplot(channel_distribution, aes(x = "", y = percentage, fill = total_bookings)) +
geom_col(width = 1, color = "white") +
coord_polar(theta = "y") +
labs(
title = "Distribució de reserves: Flexibilitat de clients",
fill = "Tipus de client"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0, size = 36),
axis.text = element_text(size = 18),
panel.background = element_rect(fill = "transparent", color = NA),
plot.background = element_rect(fill = "transparent", color = NA),
legend.position = "right",
axis.title = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_blank()
)# ggsave("img/client_flexibility_pie.svg",
# plot = last_plot(),
# width = 11.2, height = 9, # aspect ratio 16:9, width x 0.7 (storytell area)
# bg = "transparent"
# )
# Simplificar a tres categories: 0, 1-2, 3+
channel_summary <- xx |>
mutate(channel_type = case_when(
booking_changes == 0 ~ "Sense canvis",
booking_changes >= 1 & booking_changes <= 2 ~ "1-2 canvis",
booking_changes >= 3 ~ "3+ canvis"
)) |>
group_by(channel_type) |>
summarise(
total_bookings = n(),
cancellation_rate = round(mean(as.numeric(is_canceled) - 1) * 100, 1),
percentage = round(n() / nrow(xx) * 100, 1)
)
channel_summary## # A tibble: 3 × 4
## channel_type total_bookings cancellation_rate percentage
## <chr> <int> <dbl> <dbl>
## 1 1-2 canvis 14314 15.9 13.5
## 2 3+ canvis 1271 17.4 1.2
## 3 Sense canvis 90693 42 85.3
ggplot(channel_summary, aes(x = reorder(channel_type, -total_bookings), y = percentage, fill = channel_type)) +
geom_col() +
geom_text(aes(label = paste0(percentage, "%")), vjust = -0.5, size = 5) +
labs(
title = "Distribució de reserves: Flexibilitat de clients",
x = "Tipus de client",
y = "Percentatge de reserves (%)"
) +
theme_void() +
theme(
plot.title = element_text(hjust = 0, size = 36),
axis.text = element_text(size = 18),
panel.background = element_rect(fill = "transparent", color = NA),
plot.background = element_rect(fill = "transparent", color = NA),
legend.position = "none",
axis.title = element_blank()
)