Francisco Guijarro
Universidad Politécnica de Valencia
Creative Commons Attribution 4.0 International License (CC BY 4.0)
En este capítulo vamos a introducir los elementos básicos de R que nos permiten realizar gráficos. Si bien R trae por defecto un gran número de gráficos básicos, la utilización de la librería ggplot2 nos permitirá realizar gráficos de mayor complejidad y calidad.
Para el desarrollo de este capítulo trataremos con una fuente de datos de mayor complejidad que las analizadas en capítulos anteriores. En concreto, vamos a trabajar con una base de datos de reservas de hotel obtenida del paper Antonio et al. (2019).
Deberás descargar el archivo “bookings.csv” y leerlo con el siguiente código. Tendrás que sustituir la ruta por aquella en la que has descargado el arhivo:
library(readr)
bookings <- read_csv("./../Datos/bookings.csv")
summary(bookings)
## IsCanceled LeadTime ArrivalDateYear ArrivalDateMonth
## Min. :0.0000 Min. : 0.0 Min. :2015 Length:79330
## 1st Qu.:0.0000 1st Qu.: 23.0 1st Qu.:2016 Class :character
## Median :0.0000 Median : 74.0 Median :2016 Mode :character
## Mean :0.4173 Mean :109.7 Mean :2016
## 3rd Qu.:1.0000 3rd Qu.:163.0 3rd Qu.:2017
## Max. :1.0000 Max. :629.0 Max. :2017
##
## ArrivalDateWeekNumber ArrivalDateDayOfMonth StaysInWeekendNights
## Min. : 1.00 Min. : 1.00 Min. : 0.0000
## 1st Qu.:17.00 1st Qu.: 8.00 1st Qu.: 0.0000
## Median :27.00 Median :16.00 Median : 1.0000
## Mean :27.18 Mean :15.79 Mean : 0.7952
## 3rd Qu.:38.00 3rd Qu.:23.00 3rd Qu.: 2.0000
## Max. :53.00 Max. :31.00 Max. :16.0000
##
## StaysInWeekNights Adults Children Babies
## Min. : 0.000 Min. :0.000 Min. :0.00000 Min. : 0.000000
## 1st Qu.: 1.000 1st Qu.:2.000 1st Qu.:0.00000 1st Qu.: 0.000000
## Median : 2.000 Median :2.000 Median :0.00000 Median : 0.000000
## Mean : 2.183 Mean :1.851 Mean :0.09137 Mean : 0.004941
## 3rd Qu.: 3.000 3rd Qu.:2.000 3rd Qu.:0.00000 3rd Qu.: 0.000000
## Max. :41.000 Max. :4.000 Max. :3.00000 Max. :10.000000
## NA's :4
## Meal Country MarketSegment DistributionChannel
## Length:79330 Length:79330 Length:79330 Length:79330
## Class :character Class :character Class :character Class :character
## Mode :character Mode :character Mode :character Mode :character
##
##
##
##
## IsRepeatedGuest PreviousCancellations PreviousBookingsNotCanceled
## Min. :0.00000 Min. : 0.00000 Min. : 0.0000
## 1st Qu.:0.00000 1st Qu.: 0.00000 1st Qu.: 0.0000
## Median :0.00000 Median : 0.00000 Median : 0.0000
## Mean :0.02561 Mean : 0.07974 Mean : 0.1324
## 3rd Qu.:0.00000 3rd Qu.: 0.00000 3rd Qu.: 0.0000
## Max. :1.00000 Max. :21.00000 Max. :72.0000
##
## ReservedRoomType AssignedRoomType BookingChanges DepositType
## Length:79330 Length:79330 Min. : 0.0000 Length:79330
## Class :character Class :character 1st Qu.: 0.0000 Class :character
## Mode :character Mode :character Median : 0.0000 Mode :character
## Mean : 0.1874
## 3rd Qu.: 0.0000
## Max. :21.0000
##
## Agent Company DaysInWaitingList CustomerType
## Length:79330 Length:79330 Min. : 0.000 Length:79330
## Class :character Class :character 1st Qu.: 0.000 Class :character
## Mode :character Mode :character Median : 0.000 Mode :character
## Mean : 3.227
## 3rd Qu.: 0.000
## Max. :391.000
##
## ADR RequiredCarParkingSpaces TotalOfSpecialRequests
## Min. : 0.0 Min. :0.00000 Min. :0.0000
## 1st Qu.: 79.2 1st Qu.:0.00000 1st Qu.:0.0000
## Median : 99.9 Median :0.00000 Median :0.0000
## Mean : 105.3 Mean :0.02437 Mean :0.5469
## 3rd Qu.: 126.0 3rd Qu.:0.00000 3rd Qu.:1.0000
## Max. :5400.0 Max. :3.00000 Max. :5.0000
##
## ReservationStatus ReservationStatusDate
## Length:79330 Min. :2014-10-17
## Class :character 1st Qu.:2016-02-05
## Mode :character Median :2016-08-10
## Mean :2016-07-30
## 3rd Qu.:2017-02-06
## Max. :2017-09-07
##
Se trata de un dataframe con 79330 observaciones y 31 variables. Puesto que se trata de un archivo con un número de datos considerable, vamos a centrarnos en un conjunto de variables determinado que sea de nuestro interés de cara a su posterior análisis, obviando el resto. Para ello desecharemos algunas de las variables:
library(dplyr)
bookings <- bookings %>%
select(-ArrivalDateDayOfMonth, -Agent, -Company, -DaysInWaitingList, -ADR, -ReservationStatus,
-ReservationStatusDate)
A continuación reproducimos una tabla con las variables que utilizaremos, junto con el tipo y descripción de las mismas:
| Variable | Tipo | Descripción |
|---|---|---|
| Adults | Integer | Number of adults |
| ArrivalDateMonth | Categorical | Month of arrival date with 12 categories: “January” to “December” |
| ArrivalDateWeekNumber | Integer | Week number of the arrival date |
| ArrivalDateYear | Integer | Year of arrival date |
| AssignedRoomType | Categorical | Code for the type of room assigned to the booking. Sometimes the assigned room type differs from the reserved room type due to hotel operation reasons (e.g. overbooking) or by customer request. Code is presented instead of designation for anonymity reasons |
| Babies | Integer | Number of babies |
| BookingChanges | Integer | Number of changes/amendments made to the booking from the moment the booking was entered on the PMS until the moment of check-in or cancellation |
| Children | Integer | Number of children |
| Country | Categorical | Country of origin. Categories are repre- sented in the ISO 3155–3:2013 format |
| CustomerType | Categorical | Type of booking, assuming one of four categories: Contract - when the booking has an allotment or other type of contract associated to it; Group – when the booking is associated to a group; Transient – when the booking is not part of a group or contract, and is not associated to other transient booking; Transient-party – when the booking is transient, but is associated to at least other transient booking |
| DepositType | Categorical | Indication on if the customer made a deposit to guarantee the booking. This variable can assume three categories: No Deposit – no deposit was made; Non Refund – a deposit was made in the value of the total stay cost; Refundable – a deposit was made with a value under the total cost of stay. |
| DistributionChannel | Categorical | Booking distribution channel. The term “TA” means “Travel Agents” and “TO” means “Tour Operators” |
| IsCanceled | Categorical | Value indicating if the booking was canceled (1) or not (0) |
| IsRepeatedGuest | Categorical | Value indicating if the booking name was from a repeated guest (1) or not (0) |
| LeadTime | Integer | Number of days that elapsed between the entering date of the booking into the PMS and the arrival date |
| MarketSegment | Categorical | Market segment designation. In categories, the term “TA” means “Travel Agents” and “TO” means “Tour Operators” |
| Meal | Categorical | Type of meal booked. Categories are presented in standard hospitality meal packages: Undefined/SC – no meal package; BB – Bed & Breakfast; HB – Half board (breakfast and one other meal – usually dinner); FB – Full board (breakfast, lunch and dinner) |
| PreviousBookingsNotCanceled | Integer | Number of previous bookings not cancelled by the customer prior to the current booking |
| PreviousCancellations | Integer | Number of previous bookings that were cancelled by the customer prior to the current booking |
| RequiredCardParkingSpaces | Integer | Number of car parking spaces required by the customer |
| ReservedRoomType | Categorical | Code of room type reserved. Code is presented instead of designation for anonymity reasons |
| StaysInWeekendNights | Integer | Number of weekend nights (Saturday or Sunday) the guest stayed or booked to stay at the hotel |
| StaysInWeekNights | Integer | Number of week nights (Monday to Fri- day) the guest stayed or booked to stay at the hotel |
| TotalOfSpecialRequests | Integer | Number of special requests made by the customer (e.g. twin bed or high floor) |
Antes de empezar a trabajar con esta base de datos, conviene hacer una primera depuración de los datos. En este caso no nos referimos a rellenar casos vacíos, o eliminar casos incoherentes. En esta primera aproximación a la base de datos de reservas hoteleras nos limitaremos a examinar la tipología de datos que se ha asignado a cada variable, realizando tantas modificaciones como sean necesarias. En primer lugar, examinamos el tipo de datos que hay detrás de cada una de las columnas de bookings:
str(bookings)
## tibble [79,330 × 24] (S3: tbl_df/tbl/data.frame)
## $ IsCanceled : num [1:79330] 0 1 1 1 1 1 0 1 1 1 ...
## $ LeadTime : num [1:79330] 6 88 65 92 100 79 3 63 62 62 ...
## $ ArrivalDateYear : num [1:79330] 2015 2015 2015 2015 2015 ...
## $ ArrivalDateMonth : chr [1:79330] "July" "July" "July" "July" ...
## $ ArrivalDateWeekNumber : num [1:79330] 27 27 27 27 27 27 27 27 27 27 ...
## $ StaysInWeekendNights : num [1:79330] 0 0 0 2 0 0 0 1 2 2 ...
## $ StaysInWeekNights : num [1:79330] 2 4 4 4 2 3 3 3 3 3 ...
## $ Adults : num [1:79330] 1 2 1 2 2 2 1 1 2 2 ...
## $ Children : num [1:79330] 0 0 0 0 0 0 0 0 0 0 ...
## $ Babies : num [1:79330] 0 0 0 0 0 0 0 0 0 0 ...
## $ Meal : chr [1:79330] "HB" "BB" "BB" "BB" ...
## $ Country : chr [1:79330] "PRT" "PRT" "PRT" "PRT" ...
## $ MarketSegment : chr [1:79330] "Offline TA/TO" "Online TA" "Online TA" "Online TA" ...
## $ DistributionChannel : chr [1:79330] "TA/TO" "TA/TO" "TA/TO" "TA/TO" ...
## $ IsRepeatedGuest : num [1:79330] 0 0 0 0 0 0 0 0 0 0 ...
## $ PreviousCancellations : num [1:79330] 0 0 0 0 0 0 0 0 0 0 ...
## $ PreviousBookingsNotCanceled: num [1:79330] 0 0 0 0 0 0 0 0 0 0 ...
## $ ReservedRoomType : chr [1:79330] "A" "A" "A" "A" ...
## $ AssignedRoomType : chr [1:79330] "A" "A" "A" "A" ...
## $ BookingChanges : num [1:79330] 0 0 0 0 0 0 1 0 0 0 ...
## $ DepositType : chr [1:79330] "No Deposit" "No Deposit" "No Deposit" "No Deposit" ...
## $ CustomerType : chr [1:79330] "Transient" "Transient" "Transient" "Transient" ...
## $ RequiredCarParkingSpaces : num [1:79330] 0 0 0 0 0 0 0 0 0 0 ...
## $ TotalOfSpecialRequests : num [1:79330] 0 1 1 2 1 1 0 0 1 1 ...
Por ejemplo, vemos que la variable IsCanceled toma los valores 0 y 1, que indicarían si el cliente canceló (1) o no canceló (0) su reserva. En este caso parece más adecuado asignar el valor TRUE a la cancelación de la reserva, y 0 al caso en que no se cancelara:
bookings <- bookings %>%
mutate(IsCanceled = as.logical(IsCanceled))
head(bookings$IsCanceled)
## [1] FALSE TRUE TRUE TRUE TRUE TRUE
A continuación presentamos otra modificación que es conveniente realizar antes de proseguir con el resto del capítulo:
bookings <- bookings %>%
mutate(ArrivalDateMonth = factor(ArrivalDateMonth,
levels = c("January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December")))
De igual forma podríamos transformar en factor otras variables que actualmente son del tipo carácter. Sin embargo, así como en el mes de llegada existe un orden entre los niveles de la variable (Enero, Febrero, etc.), no ocurre lo mismo con el resto de variables incluidas en bookings.
En esta sección veremos algunas funciones para gráficos básicos.
Vamos a utilizar un gráfico de barras para visualizar los tipos de cliente que reservan en el hotel (CustomerType). La función barplot toma como entrada la distribución de frecuencias de una variable. En nuestro caso, vamos a calcular la frecuencia de la variable CustomerType para luego representarla mediante la función barplot.
table(bookings$CustomerType)
##
## Contract Group Transient Transient-Party
## 2300 293 59404 17333
barplot(table(bookings$CustomerType), main="Gráfico de barra", xlab="Tipo de cliente", ylab="Frecuencia")
barplot(table(bookings$CustomerType), main="Gráfico de barra", xlab="Tipo de cliente", ylab="Frecuencia", horiz = TRUE)
Vamos a representar esta misma información relacionada con la pensión en que se registran los huéspedes del hotel (Meal):
BB: Cama y desayuno (bed and breakfast).
HB: Media pensión (Half board).
FB: Pensión completa (Full board).
SC: Sólo cama.
En lugar de proporcionar la frecuencia de la variable CustomerType, ahora proporcionaremos como entrada la tabla de contingencia entre CustomerTypey Meal:
frecuencias <- table(bookings$CustomerType, bookings$Meal)
barplot(table(bookings$CustomerType, bookings$Meal), main="Gráfico de barra", xlab="Tipo de pensión", ylab="Frecuencia", legend = rownames(frecuencias), col = c("yellow", "red", "green", "blue"))
barplot(table(bookings$CustomerType, bookings$Meal), main="Gráfico de barra", xlab="Tipo de pensión", ylab="Frecuencia", legend = rownames(frecuencias), col = c("yellow", "red", "green", "blue"), beside = TRUE)
Un problema al visualizar estos datos son los niveles Group y Contract, que tienen muy pocas observaciones en la base de datos, y esto hace que no podamos distinguir con claridad qué tipo de alojamiento y pensión han escogido los huéspedes. Para solucionarlo, podemos utilizar un espinograma de la librería vcd:
library(vcd)
frecuencias <- table(bookings$Meal, bookings$CustomerType)
spine(frecuencias, main = "Espinograma")
Utilizaremos un gráfico tarta de manzana (pie) para representar la distribución de la nacionalidad de los huéspedes:
paises <- table(bookings$Country)
str(paises)
## 'table' int [1:167(1d)] 2 338 1 9 2 40 157 6 1 2 ...
## - attr(*, "dimnames")=List of 1
## ..$ : chr [1:167] "ABW" "AGO" "AIA" "ALB" ...
pie(paises, labels = rownames(paises))
Como tenemos un número de países muy amplio, podemos limitar la representación a aquellos que representan un mayor porcentaje:
library(plotrix)
paises_importantes <- c("PRT", "FRA", "GBR", "ESP", "DEU", "ITA")
fan.plot(paises[paises_importantes], labels = rownames(paises[paises_importantes]))
Se observa que la mayor parte de los clientes son portugueses, seguidos a una distancia considerable por otros países europeos. Esto es, la clientela del hotel se concentra en turistas nacionales, pero con un importante aportación de los países cercanos de Europa.
Más adelante podemos plantearnos en qué meses (temporada alta, temporada baja) reservan los clientes según su nacionalidad
El histograma puede emplearse, por ejemplo, para representar el tiempo de antelación con que se realizan las reservas (LeadTime). El parámetro breaks puede emplearse para poder distinguir el rango de valores de cada barra con mejor precisión:
hist(bookings$LeadTime)
hist(bookings$LeadTime, breaks = 50)
Vemos que una parte importante de las reservas en el hotel se realizan con muy poco tiempo de antelación. Más adelante se podrá analizar qué características tienen este tipo de reservas, frente a las que se reaalizan con más tiempo.
Los diagramas de caja (box plot) permiten observar de forma rápida la posición central de los datos y dónde se ubica el 50% de los datos (dentro de la caja). También podemos ver lo que se podría considerar como datos atípicos, por situarse muy lejos de lo que marcan el resto de observaciones. En el caso del primer gráfico, vemos que se consideran atípicas aquellas reservas realizadas con más de 400 días de antelación. Al representar esta variable frente al mes para el que se produce la reserva, podemos observar que la antelación de la reserva no se distribuye por igual entre dichos meses. Sin embargo, al no estar la variable ArrivalDateMonth ordenada cronológicamente, se hace difícil distinguir para qué meses se realizan las reservas con mayor antelación frente a las reservas “de última hora”.
boxplot(bookings$LeadTime)
boxplot(bookings$LeadTime ~ bookings$ArrivalDateMonth)
Ejercicio
1¿En qué meses reservan los clientes según la nacionalidad de los mismos? Analizar diferencias entre portugueses, británicos, alemanes y españoles. Para ello, puedes utilizar el parámetro
datade la funciónboxplot. De esa forma, puedes pasarle a la función un subconjunto de datos (por ejemplo, sólo los huéspedes de nacionalidad portuguesa). ¿Son todas las nacionalidades igual de previsoras a la hora de realizar las reservas¿ ¿Quiénes son más clientes “de última hora”?
ggplot2En esta sección vamos a introducir la librería ggplot2, que contiene una gran variedad de gráficos en un formato más atractivo que las funciones vistas en la sección anterior. Para ello, deberás en primer lugar instalar la librería (install.packages) y cargarla en memoria.
library(ggplot2)
Aquí tienes un ejemplo de sintaxis de un gráfico ggplot2, donde se pueden ver importantes diferencias respecto del resultado obtenido con la función boxplot de la anterior sección:
ggplot(bookings %>% filter(Country %in% paises_importantes), aes(x = ArrivalDateMonth, y = LeadTime, fill = factor(Country))) +
geom_boxplot() +
facet_wrap(~ Country) +
labs(x = "Mes de llegada", y = "Antelación de la reserva (días)", fill = "Países") +
theme(axis.text.x = element_blank())
ggplot2Es la parte fundamental del gráfico ggplot2, por lo general un dataframe en el que pasamos los datos que queremos visualizar, sin especificar de momento el tipo de gráfico con el que queremos representarlos:
p <- ggplot(bookings)
class(p)
## [1] "gg" "ggplot"
De momento sólo hemos indicado que vamos a trabajar con el dataframe bookings, pero no le hemos dicho ni qué variables de las muchas que tiene queremos representar, ni con qué tipo de gráficos de los muchos disponibles las queremos representar.
En el apartado de estéticas, o aes en la terminología usada por ggplot2, definiremos las variables que queremos representar gráficamente, así como las características gráficas que queremos incluir en la imagen (color, tamaño de los puntos o las líneas, etc.).
Siguiendo con el patrón de gráficos visto en la sección anterior, vamos a reproducir algunas figuras utilizando la sintaxis de ggplot2.
frecuencias <- table(bookings$CustomerType, bookings$Meal)
p <- ggplot(bookings) +
aes(y = Meal) +
geom_bar() +
labs(x = "Frecuencia", y = "Tipo de pensión")
p
p <- ggplot(bookings) +
aes(y = Meal, fill = Meal) +
geom_bar() +
labs(x = "Frecuencia", y = "Tipo de pensión")
p
También podemos representar una tabla de contingencia a través del gráfico geom_tile. Supongamos que obtenemos la tabla de contingencia que relaciona el número de adultos frente al número de niños en las reservas:
tabla <- table(bookings$Adults, bookings$Children)
str(tabla)
## 'table' int [1:5, 1:4] 167 15570 53810 4642 31 4 199 2715 105 0 ...
## - attr(*, "dimnames")=List of 2
## ..$ : chr [1:5] "0" "1" "2" "3" ...
## ..$ : chr [1:4] "0" "1" "2" "3"
tabla <- tabla %>%
as.data.frame()
colnames(tabla) <- c("Adultos", "Niños", "Frecuencia")
tabla
## Adultos Niños Frecuencia
## 1 0 0 167
## 2 1 0 15570
## 3 2 0 53810
## 4 3 0 4642
## 5 4 0 31
## 6 0 1 4
## 7 1 1 199
## 8 2 1 2715
## 9 3 1 105
## 10 4 1 0
## 11 0 2 208
## 12 1 2 108
## 13 2 2 1681
## 14 3 2 27
## 15 4 2 0
## 16 0 3 11
## 17 1 3 2
## 18 2 3 46
## 19 3 3 0
## 20 4 3 0
ggplot(tabla) +
aes(x = Adultos, y = Niños, fill = Frecuencia) +
geom_tile() +
scale_fill_gradient("Frecuencia", low = "grey50", high = "grey10")
Un gráfico horrible. Justo es reconocerlo.
Ejercicio
2Obtén los siguientes gráficos a través de las funciones que aparecen en el encabezado de cada uno de ellos:
Las facetas permiten representar gráficos donde una o más variables dependen de otra variable. En el siguiente ejemplo de gráfico de barras representamos el tipo de pensión del huésped (Meal) en función del tipo de cliente (CustomerType):
p <- ggplot(bookings) +
aes(y = Meal, fill = Meal) +
geom_bar() +
facet_wrap(~ CustomerType) +
labs(x = "Frecuencia", y = "Tipo de pensión")
p
Ejercicio
3Representa el número de peticiones especiales realizadas por los clientes (
TotalOfSpecialRequests) en función del país de procedencia. Para que el gráfico no sea muy extenso, limita la representación a los anteriormente denominadospaises_importantes. Utiliza un gráfico de barras (geom_bar) junto con una faceta (facet_wrap) ¿Observas diferencias según la nacionalidad de los huéspedes?
Ejercicio
4Otra variable importante desde el punto de vista de gestión es la que se refiere al segmento de mercado (
MarketSegment). Representa el segmento del mercado según los denominadospaises_importantes. Comenta qué país tiene una segmentación diferente del resto. ¿Qué medio es el más utilizado por los españoles? ¿Y los portugueses?
En los siguientes ejercicios deberás responder a una serie de preguntas, haciendo uso de cualquiera de las funciones que se han visto hasta ahora en los diferentes capítulos.
Ejercicio
5¿Qué porcentaje de reservas son canceladas?
Ejercicio
6Obtén un modelo de regresión que explique la variable
IsCanceleda partir deLeadTime,ArrivalDateMonthyMarketSegment. Comenta los resultados. ¿Qué mes es más propicio para que se produzcan cancelaciones? ¿Dependen los resultados del segmento de mercado? ¿Influye la antelación con que se hizo la reserva en la probabilidad de cancelación?
Ejercicio
7Calcula la matriz de confusión de este modelo y coméntala.
Ejercicio
8Repite las cuestiones anteriores pero tomando las siguientes variables explicativas:
LeadTime,ArrivalDateMonth,MarketSegment,StaysInWeekendNights,StaysInWeekNights,Adults,Children,Meal,PreviousCancellations. ¿Mejoran los resultados en la matriz de confusión?
Ejercicio
9Obtén una tabla con la media de cancelaciones por países, sólo para aquellos países con más de 500 reservas. Los resulados deben aparecer ordenados de menor a mayor media de cancelaciones
## Country media_cancelaciones n_reservas
## 1 DEU 0.1761999 6084
## 2 AUT 0.1861349 1053
## 3 FRA 0.1957065 8804
## 4 NLD 0.2081761 1590
## 5 SWE 0.2138889 720
## 6 BEL 0.2191130 1894
## 7 CHE 0.2440154 1295
## 8 ISR 0.2449298 641
## 9 USA 0.2651422 1618
## 10 CN 0.2776801 569
## 11 ESP 0.2875732 4611
## 12 GBR 0.2938852 5315
## 13 POL 0.3003413 586
## 14 IRL 0.3308519 1209
## 15 ITA 0.3788933 3307
## 16 BRA 0.4063545 1794
## 17 CHN 0.5236994 865
## 18 PRT 0.6486111 30960
Ejercicio
10Vamos a crear una serie de variables que nos indiquen la nacionalidad de los huéspedes, solo para aquellos países en los que hemos detectado un elevado porcentaje de cancelaciones. Incluyendo estas nuevas variables como explicativas en el modelo de regresión, repite el análisis:
bookings <- bookings %>%
mutate(PRT = (Country == "PRT")) %>%
mutate(BRA = (Country == "BRA")) %>%
mutate(ITA = (Country == "ITA")) %>%
mutate(GBR = (Country == "GBR")) %>%
mutate(ESP = (Country == "ESP"))
%in%aesas.data.framebarplotboxplotcoord_flipfacet_wrapfactorfan.plotgeom_bargeom_boxplotgeom_tileggplothistlabslevelsmutatepieread_csvrownamesscale_fill_gradientselectspinetheme