Visualización de datos con plotly y
shiny
Introducción a las librerĆa plotly y
shiny
Introducción a plotly
- Cualquier grƔfico hecho con el paquete
Rdeplotlyes impulsado por la bibliotecaJavaScriptplotly.js. La funciónplot_ly()proporciona una interfaz ādirectaā aplotly.jscon algunas abstracciones adicionales para ayudar a reducir la escritura. Estas abstracciones, inspiradas en la gramĆ”tica de los grĆ”ficos yggplot2, hacen que sea mucho mĆ”s rĆ”pido iterar de un grĆ”fico a otro, facilitando el descubrimiento de caracterĆsticas interesantes en los datos(Wilkinson 2005; Wickham 2009). Para demostrarlo, usaremosplot_ly()para explorar el conjunto de datosdiamondsdeggplot2y aprenderemos un poco cómo funcionaplotly
library(plotly)
library(ggplot2)
data(diamonds, package = "ggplot2")
diamonds## # A tibble: 53,940 Ć 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
## 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
## 4 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63
## 5 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
## 6 0.24 Very Good J VVS2 62.8 57 336 3.94 3.96 2.48
## 7 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
## 8 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53
## 9 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49
## 10 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
## # ⦠with 53,930 more rows
- Si asignamos nombres de variables (p.Ā ej.,
cut, clarity, etc.) a propiedades visuales (p. ej.,x, y, color, etc.) dentro deplot_ly(), como se ve en la figura, se intenta encontrar una representación geométrica sensata de esa información por nosotros. En breve veremos cómo especificar estas representaciones geométricas (asà como otras codificaciones visuales) para crear diferentes tipos de grÔficos.
plot_ly(diamonds, x = ~cut)plot_ly(diamonds, x = ~cut, y = ~clarity)plot_ly(diamonds, x = ~cut, color = ~clarity, colors = "Accent")- La función
plot_ly()tiene numerosos argumentos que son exclusivos del paqueteR(por ejemplo,color, stroke, span, symbol, linetype, etc.) y facilitan la codificación de las variables de datos (por ejemplo, claridad del diamante) como propiedades visuales (por ejemplo, color). Por defecto, estos argumentos asignan valores de una variable de datos a un rango visual definido por la forma plural del argumento.
Introducción a ggplotly
La función
ggplotly()del paqueteplotlytiene la capacidad de traducirggplot2aplotly. Esta funcionalidad puede ser realmente útil para añadir rÔpidamente interactividad a su flujo de trabajoggplot2existente. AdemÔs, incluso si conoce bienplot_ly()yggplotly()puede seguir siendo deseable para crear visualizaciones que no son necesariamente fÔciles de lograr sin ella. Para demostrarlo, vamos a explorar la relación entrepricey otras variables del conocido conjunto de datosdiamonds.El binning hexagonal (es decir,
geom_hex()) es una forma útil de visualizar una densidad2D, como la relación entrepriceycarat, como se muestra en la siguiente figura, donde podemos ver que existe una fuerte relación lineal positiva entre el logaritmo de los quilates y el precio. También muestra que, para muchos, el quilate sólo se redondea a un número determinado, indicado por las bandas azul claro. Hacer este grÔfico interactivo facilita la decodificación de los colores hexagonales en los recuentos que representan.
p <- ggplot(diamonds, aes(x = log(carat), y = log(price))) +
geom_hex(bins = 100)
ggplotly(p)- Es bueno utilizar
ggplotly()sobreplot_ly()para aprovechar la interfaz consistente y expresiva deggplot2para explorar resĆŗmenes estadĆsticos entre grupos. Por ejemplo, al incluir una variable de color discreta (e.gcut) congeom_freqpoly(), se obtiene un polĆgono de frecuencia para cada nivel de esa variable. Esta capacidad de generar rĆ”pidamente codificaciones visuales de resĆŗmenes estadĆsticos a travĆ©s de un nĆŗmero arbitrario de grupos funciona bĆ”sicamente para cualquiergeom(e.g,geom_boxplot(), geom_histogram(), geom_density(), etc) y es una caracterĆstica clave deggplot2.
p <- ggplot(diamonds, aes(x = log(price), color = clarity)) +
geom_freqpoly()
ggplotly(p)Barras e histogramas
Las funciones
add_bars()yadd_histogram()envuelven los tipos de trazado de barras e histogramas deplotly.js. La principal diferencia entre ellas es que las trazas de barras requieren alturas de barras (tantoxcomoy), mientras que las trazas de histograma requieren sólo una variable, yplotly.jsmaneja el binning en el navegador. Y quizÔs de forma confusa, ambas funciones pueden utilizarse para visualizar la distribución de una variable numérica o discreta. Asà que, esencialmente, la única diferencia entre ellas es dónde se produce el binning.La siguiente figura compara el algoritmo de agrupación por defecto en
plotly.jscon algunos algoritmos diferentes disponibles enRa travĆ©s de la funciónhist(). Aunqueplotly.jstiene la capacidad de personalizar losbinsdel histograma a travĆ©s dexbins/ybins,Rtiene diversas facilidades para estimar el nĆŗmero óptimo debinsen un histograma que podemos aprovechar fĆ”cilmente. La funciónhist()por sĆ sola nos permite referenciar 3 algoritmos famosos por su nombre (Sturges 1926; Freedman-Diaconis 1981yScott 1979), pero tambiĆ©n hay paquetes (por ejemplo, el paquetehistogram) que amplĆan esta interfaz para incorporar mĆ”s metodologĆa (Mildenberger, Rozenholc y Zasada. 2009). La funciónprice_hist()que aparece a continuación envuelve la funciónhist()para obtener los resultados del binning, y mapear esos bins a una versión graficada del histograma usandoadd_bars()
p1 <- plot_ly(diamonds, x = ~price) %>%
add_histogram(name = "plotly.js")
price_hist <- function(method = "FD") {
h <- hist(diamonds$price, breaks = method, plot = FALSE)
plot_ly(x = h$mids, y = h$counts) %>% add_bars(name = method)
}
subplot(
p1, price_hist(), price_hist("Sturges"), price_hist("Scott"),
nrows = 4, shareX = TRUE
)- La siguiente figura muestra dos formas de crear un grƔfico de barras
bƔsico. Aunque los resultados visuales son los mismos, vale la pena
señalar la diferencia en la implementación. La función
add_histogram()envĆa todos los valores observados al navegador y deja queplotly.jsrealice el binning. Se necesita mĆ”s esfuerzo humano para realizar el binning enR, pero hacerlo tiene la ventaja de enviar menos datos, y requiere menos trabajo de cĆ”lculo del navegador web. En este caso, sólo tenemos unos 50.000 registros, por lo que no hay mucha diferencia en los tiempos de carga o el tamaƱo de la pĆ”gina. Sin embargo, con 1 millón de registros, el tiempo de carga de la pĆ”gina es mĆ”s del doble y el tamaƱo de la pĆ”gina casi se duplica.
library(dplyr)
p1 <- plot_ly(diamonds, x = ~cut) %>%
add_histogram()
p2 <- diamonds %>%
count(cut) %>%
plot_ly(x = ~cut, y = ~n) %>%
add_bars()
subplot(p1, p2) %>% hide_legend()- A menudo es útil ver cómo cambia la distribución numérica con
respecto a una variable discreta. Cuando se utilizan barras para
visualizar múltiples distribuciones numéricas, se
recomienda trazar cada distribución en su propio eje utilizando una
pantalla de múltiplos pequeños, en lugar de intentar superponerlas en un
solo eje. Obsérvese cómo la función
one_plot()define lo que debe mostrarse en cada panel, y luego se emplea una estrategia de dividir-aplicar-recombinar (es decir,split(), lapply(), subplot()) para generar la visualización del enrejado
one_plot <- function(d) {
plot_ly(d, x = ~price) %>%
add_annotations(
~unique(clarity), x = 0.5, y = 1,
xref = "paper", yref = "paper", showarrow = FALSE
)
}
diamonds %>%
split(.$clarity) %>%
lapply(one_plot) %>%
subplot(nrows = 2, shareX = TRUE, titleX = FALSE) %>%
hide_legend()- La visualización de las distribuciones discretas mĆŗltiples es difĆcil. Esta sutil complejidad se debe a que tanto los recuentos como las proporciones son importantes para comprender las distribuciones discretas multivariadas. La figura siguiente presenta los recuentos de diamantes, divididos por su talla y su claridad, mediante un grĆ”fico de barras agrupadas.
plot_ly(diamonds, x = ~cut, color = ~clarity) %>%
add_histogram()- La distribución de la claridad dentro de los diamantes āidealesā parece ser bastante similar a la de otros diamantes, pero es difĆcil hacer esta comparación utilizando los recuentos brutos. La siguiente figura facilita esta comparación mostrando la frecuencia relativa de los diamantes por claridad, dada una talla.
cc <- count(diamonds, cut, clarity)
cc2 <- left_join(cc, count(cc, cut, wt = n, name = 'nn'))
cc2 %>%
mutate(prop = n / nn) %>%
plot_ly(x = ~cut, y = ~prop, color = ~clarity) %>%
add_bars() %>%
layout(barmode = "stack")Boxplots
- Los boxplots codifican el resumen de cinco nĆŗmeros de una variable
numƩrica, y proporcionan una forma decente de comparar muchas
distribuciones numéricas. La tarea visual de comparar múltiples boxplots
es relativamente fÔcil (es decir, comparar la posición a lo largo de una
escala común) en comparación con algunas alternativas comunes (por
ejemplo, una visualización enrejada de histogramas), pero el
boxplotes a veces inadecuado para capturar distribuciones complejas (por ejemplo, multimodales) (en este caso, un polĆgono de frecuencia proporciona una buena alternativa). La funciónadd_boxplot()requiere una variable numĆ©rica y garantiza que losboxplotsse orienten correctamente, independientemente de si la variable numĆ©rica se coloca en la escalaxoy. Como muestra la siguiente figura, en el eje ortogonal al eje numĆ©rico, puede proporcionar una variable discreta (para el condicionamiento) o suministrar un Ćŗnico valor (para nombrar la categorĆa del eje).
p <- plot_ly(diamonds, y = ~price, color = I("black"),
alpha = 0.1, boxpoints = "suspectedoutliers")
p1 <- p %>% add_boxplot(x = "Overall")
p2 <- p %>% add_boxplot(x = ~cut)
subplot(
p1, p2, shareY = TRUE,
widths = c(0.2, 0.8), margin = 0
) %>% hide_legend()- Si desea realizar una partición por mĆ”s de una variable discreta, podrĆa utilizar la interacción de esas variables con el eje discreto, y colorear por la variable anidada, como hace la siguiente figura con la claridad y la talla del diamante. Otro enfoque serĆa utilizar una visualización enrejada
plot_ly(diamonds, x = ~price, y = ~interaction(clarity, cut)) %>%
add_boxplot(color = ~clarity) %>%
layout(yaxis = list(title = ""))- También es útil ordenar los grÔficos de caja según algo
significativo, como la mediana del precio. La siguiente figura presenta
la misma información anterior, pero ordena los grÔficos de caja por su
mediana, y deja claro de inmediato que los diamantes con una talla
"SI2"tienen el precio mƔs alto, por tƩrmino medio
d <- diamonds %>%
mutate(cc = interaction(clarity, cut))
lvls <- d %>%
group_by(cc) %>%
summarise(m = median(price)) %>%
arrange(m) %>%
pull(cc)
plot_ly(d, x = ~price, y = ~factor(cc, lvls)) %>%
add_boxplot(color = ~clarity) %>%
layout(yaxis = list(title = ""))- Al igual que
add_histogram(),add_boxplot()envĆa los datos brutos al navegador y permite aplotly.jscalcular las estadĆsticas de resumen. Desafortunadamente,plotly.jstodavĆa no permite calcular estadĆsticas paraboxplots.
Mapas
Hay numerosas maneras de hacer un mapa con
plotly, cada una con sus propios puntos fuertes y dĆ©biles. En general, los enfoques se dividen en dos categorĆas: integrados o personalizados. Los mapas integrados aprovechan el soporte integrado deplotly.jspara renderizar una capa de mapa base. Actualmente hay dos formas de hacer mapas integrados: a travĆ©s deMapboxo a travĆ©s de un mapa base integrado cond3.js. El enfoque integrado es conveniente si necesitas un mapa rĆ”pido y no necesariamente necesitas representaciones sofisticadas de objetos geoespaciales. Por otro lado, el enfoque de mapeo personalizado ofrece un control completo, ya que usted estĆ” proporcionando toda la información necesaria para representar el/los objetos geoespaciales. Mas adelante revisaremos la creación de mapas sofisticados (por ejemplo, cartogramas) utilizando el paquetesf R, pero tambiĆ©n es posible hacer mapas personalizados deplotlya travĆ©s de otras herramientas para la geoinformĆ”tica (por ejemplo,sp, ggmap, etc).Vale la pena seƱalar que
plotlypretende ser una biblioteca de visualización de propósito general, por lo tanto, no pretende ser el conjunto de herramientas de visualización geoespacial mÔs completo. Dicho esto, hay beneficios en el uso de mapas basados enplotly, ya que lasAPIsde mapeo son muy similares al resto deplotly, y se puede aprovechar el ecosistema mÔs grande deplotly(por ejemplo, la vinculación de las vistas del lado del cliente). Sin embargo, si te encuentras con limitaciones con la funcionalidad de mapeo deplotly, hay un conjunto muy rico de herramientas para la visualización geoespacial interactiva enR, incluyendo pero no limitado a:leaflet, mapview, mapedit, tmap, ymapdeck.
Mapas integrados
- Si tiene datos de latitud/longitud bastante simples y quiere hacer
un mapa rƔpido, puede probar una de las opciones de mapeo integradas de
plotly(es decir,plot_mapbox()yplot_geo()). En general, puede tratar estas funciones constructoras como un reemplazo directo deplot_ly()y obtener un mapa base dinĆ”mico representado detrĆ”s de sus datos. AdemĆ”s, todas las capas basadas en la dispersión funcionan como cabrĆa esperar conplot_ly(). Por ejemplo, la siguiente figura utilizaplot_mapbox()yadd_markers()para crear un grĆ”fico de burbujas. Para poder usarplot_mapbox, primero debes crear unMAPBOX_TOKENen el sitio web de Mapbox y luego agregar la siguiente lĆnea a tu código.
head(maps::canada.cities)## name country.etc pop lat long capital
## 1 Abbotsford BC BC 157795 49.06 -122.30 0
## 2 Acton ON ON 8308 43.63 -80.03 0
## 3 Acton Vale QC QC 5153 45.63 -72.57 0
## 4 Airdrie AB AB 25863 51.30 -114.02 0
## 5 Aklavik NT NT 643 68.22 -135.00 0
## 6 Albanel QC QC 1090 48.87 -72.42 0
library(plotly)
Sys.setenv('MAPBOX_TOKEN' = 'pk.eyJ1IjoibGloa2lyIiwiYSI6ImNrdXdtcWNyeTE4cngybm1wYzBpbjZrZzEifQ.gdBWHAZHRJQL265am4jHpg')
plot_mapbox(maps::canada.cities) %>%
add_markers(
x = ~long,
y = ~lat,
size = ~pop,
color = ~country.etc,
colors = "Accent",
text = ~paste(name, pop),
hoverinfo = "text"
)- El estilo del mapa base de
Mapboxse controla a travƩs del atributolayout.mapbox.style. El paquete deplotlyviene con soporte para 7 estilos diferentes, pero tambiƩn puedes suministrar unaURLpersonalizada a un estilomapboxpersonalizado. Para obtener todos los nombres de estilo de mapa base pre-empaquetados, puedes tomarlos del esquema oficialplotly.js(). Antes debes instalarlistviewerpara que funcione la siguiente orden
styles <- schema()$layout$layoutAttributes$mapbox$style$values
styles## [1] "basic" "streets" "outdoors"
## [4] "light" "dark" "satellite"
## [7] "satellite-streets" "carto-darkmatter" "carto-positron"
## [10] "open-street-map" "stamen-terrain" "stamen-toner"
## [13] "stamen-watercolor" "white-bg"
- Cualquiera de estos valores puede ser utilizado para un estilo
mapbox. La siguiente figura muestra el mapa base de imƔgenes terrestres por satƩlite.
layout(plot_mapbox(), mapbox = list(style = "satellite"))- Ahora veamos cómo crear un menú desplegable integrado en
plotly.jspara controlar el estilo del mapa base a través del atributolayout.updatemenus. La idea detrÔs de un menú desplegable integrado deplotly.jses proporcionar una lista de botones (es decir, elementos de menú) donde cada botón invoca un método deplotly.jscon algunos argumentos. En este caso, cada botón utiliza el métodorelayoutpara modificar el atributolayout.mapbox.style.
style_buttons <- lapply(styles, function(s) {
list(
label = s,
method = "relayout",
args = list("mapbox.style", s)
)
})
layout(
plot_mapbox(),
mapbox = list(style = "dark"),
updatemenus = list(
list(y = 0.8, buttons = style_buttons)
)
)- La otra solución de mapeo integrada en
plotlyesplot_geo(). En comparación conplot_mapbox(), este enfoque tiene soporte para diferentes proyecciones de mapeo, pero el estilo del mapa base es limitado y puede ser mÔs engorroso. La siguiente rutina muestra el uso deplot_geo()junto conadd_markers()yadd_segments()para visualizar las rutas de vuelo dentro de los Estados Unidos. Mientras queplot_mapbox()estÔ fijado a una proyección mercator, el constructorplot_geo()tiene un puñado de proyecciones diferentes disponibles, incluyendo la proyección ortogrÔfica que da la ilusión del globo 3D.
library(plotly)
library(dplyr)
air <- read.csv('https://plotly-r.com/data-raw/airport_locations.csv')
flights <- read.csv('https://plotly-r.com/data-raw/flight_paths.csv')
flights$id <- seq_len(nrow(flights))
geo <- list(
projection = list(
type = 'orthographic',
rotation = list(lon = -100, lat = 40, roll = 0)
),
showland = TRUE,
landcolor = toRGB("gray95"),
countrycolor = toRGB("gray80")
)
plot_geo(color = I("red")) %>%
add_markers(
data = air, x = ~long, y = ~lat, text = ~airport,
size = ~cnt, hoverinfo = "text", alpha = 0.5
) %>%
add_segments(
data = group_by(flights, id),
x = ~start_lon, xend = ~end_lon,
y = ~start_lat, yend = ~end_lat,
alpha = 0.3, size = I(1), hoverinfo = "none"
) %>%
layout(geo = geo, showlegend = FALSE)Mapas coroplƩticos
- AdemÔs de los trazos de dispersión, ambas soluciones de mapeo
integrado (es decir,
plot_mapbox()yplot_geo()) tienen un tipo de trazo de coroplĆ©tico optimizado (es decir, los tiposchoroplethmapboxychoroplethtrace). Comparativamente,choroplethmapboxes mĆ”s potente porque se puede especificar completamente la colección de caracterĆsticas utilizandoGeoJSON, pero elchoroplethtrace puede ser un poco mĆ”s fĆ”cil de utilizar si se ajusta a su caso de uso.
La siguiente rutina muestra la densidad de población de los Estados
Unidos a travƩs del trazado de coropletas utilizando los datos de los
estados de los Estados Unidos del paquete de conjuntos de datos
(R Core Team 2016). Con solo proporcionar un atributo
z, los objetos de plotly_geo() intentarƔn
crear un coropleto, pero tambiƩn tendrƔ que proporcionar
ubicaciones y un modo de ubicación. Vale la pena señalar que el modo de
ubicación se limita actualmente a los paĆses y los estados de EE.UU.,
por lo que si necesita una unidad geogrƔfica diferente (por ejemplo,
condados, municipios, etc.), debe utilizar el tipo de trazado
choroplethmapbox y/o utilizar un enfoque de mapeo
āpersonalizadoā que revisaremos mas adelante
density <- state.x77[, "Population"] / state.x77[, "Area"]
g <- list(
scope = 'usa',
projection = list(type = 'albers usa'),
lakecolor = toRGB('white')
)
plot_geo() %>%
add_trace(
z = ~density, text = state.name, span = I(0),
locations = state.abb, locationmode = 'USA-states'
) %>%
layout(geo = g)Choroplethmapboxes mĆ”s flexible quechoroplethporque usted suministra su propia definiciónGeoJSONdelchoropletha travĆ©s del atributogeojson. Actualmente este atributo debe ser unaURLque apunte a un archivogeojson. AdemĆ”s, la ubicación debe apuntar a un atributoidde nivel superior de cada caracterĆstica dentro del archivogeojson. La siguiente figura muestra cómo podrĆamos visualizar la información anterior, pero esta vez utilizandochoroplethmapbox
plot_ly() %>%
add_trace(
type = "choroplethmapbox",
geojson = paste(c(
"https://gist.githubusercontent.com/cpsievert/",
"7cdcb444fb2670bd2767d349379ae886/raw/",
"cf5631bfd2e385891bb0a9788a179d7f023bf6c8/",
"us-states.json"
), collapse = ""),
locations = row.names(state.x77),
z = state.x77[, "Population"] / state.x77[, "Area"],
span = I(0)
) %>%
layout(
mapbox = list(
style = "light",
zoom = 4,
center = list(lon = -98.58, lat = 39.82)
)
) %>%
config(
mapboxAccessToken = Sys.getenv("MAPBOX_TOKEN"),
toImageButtonOptions = list(
format = "svg",
width = NULL,
height = NULL
)
)- Las figuras anteriores no son una forma ideal de visualizar la
población estatal desde el punto de vista de la percepción grÔfica.
Normalmente utilizamos el color en las
coropletaspara codificar una variable numérica (por ejemplo, el PIB, las exportaciones netas, la nota media de la selectividad, etc.) y el ojo percibe de forma natural el Ôrea que cubre un color concreto como proporcional a su efecto global. Esto acaba siendo engañoso, ya que el Ôrea que cubre el color no suele tener ninguna relación razonable con los datos codificados por el color.
Shiny
Hay varios marcos diferentes para crear aplicaciones web a travƩs de
R, pero centraremos nuestra atención en la vinculación de grÔficosplotlyconshinyun paquete deRpara crear aplicaciones web reactivas completamente enR. El modelo de programación reactiva deShinypermite a los programadores deRaprovechar sus conocimientos existentes deRy crear aplicaciones web basadas en datos sin ninguna experiencia previa en programación web.Shinyen sà mismo es en gran medida agnóstico al motor utilizado para renderizar las vistas de datos (es decir, puede incorporar cualquier tipo de salida deR), pero el propioshinytambién agrega algún soporte especial para interactuar con grÔficos e imÔgenes estÔticas deR(Chang 2017).Al vincular los grÔficos en una aplicación web, hay compensaciones a considerar cuando se utilizan grÔficos estÔticos de
Ren lugar de grĆ”ficos basados en la web. Resulta que esas compensaciones se complementan muy bien con las fortalezas y debilidades relativas de la vinculación de vistas conplotly, lo que hace que su combinación sea un potente conjunto de herramientas para vincular vistas en la web desdeR.El propioShinyproporciona una forma de acceder a eventos con grĆ”ficos estĆ”ticos hechos con cualquiera de los siguientes paquetes deR:graphics, ggplot2ylattice. Estos paquetes son muy maduros, con todas las funciones, bien probados, y soportan una gama increĆblemente amplia de grĆ”ficos, pero como deben ser regenerados en el servidor, son fundamentalmente limitados desde una perspectiva de grĆ”ficos interactivos. Comparativamente,plotlyno tiene la misma gama e historia, pero proporciona mĆ”s opciones y control sobre la interactividad. MĆ”s concretamente, debido a queplotlyestĆ” intrĆnsecamente basado en la web, permite un mayor control sobre cómo se actualizan los grĆ”ficos en respuesta a la entrada del usuario (por ejemplo, cambiar el color de unos pocos puntos en lugar de redibujar toda la imagen). Esta sección aborda el cómo utilizar grĆ”ficosplotlydentro deshiny, cómo conseguir que esos grĆ”ficos se comuniquen con otros tipos de vistas de datos, y cómo hacerlo todo de forma eficiente.
Su primera aplicación Shiny
El patrón mÔs común de
plotly+shinyutiliza una entrada deshinypara controlar una salida deplotly. Mostraremos un ejemplo sencillo de uso de la funciónselectizeInput()deshinypara crear un desplegable que controla un grÔfico deplotly. Este ejemplo, asà como cualquier otra aplicaciónshiny, tiene dos partes principales:La interfaz de usuario,
ui, define cómo se muestran los widgets de entrada y salida en la pĆ”gina. La funciónfluidPage()ofrece una forma agradable y rĆ”pida de obtener un diseƱo responsivo basado en una cuadrĆcula, pero tambiĆ©n vale la pena seƱalar que laUIes completamente personalizable, y paquetes comoshinydashboardfacilitan el aprovechamiento de marcos de diseƱo mĆ”s sofisticados (Chang y Borges Ribeiro 2018).La función del servidor,
server, define un mapeo de los valores de entrada a los widgets de salida. MƔs concretamente, el servidorshinyes unaR function()entre los valores ingresados por el cliente y las salidas generadas en el servidor web.
Cada widget de entrada, incluido el
selectizeInput(), estÔ vinculado a un valor de entrada al que se puede acceder en el servidor dentro de una expresión reactiva. Las expresiones reactivas deShinyconstruyen un grÔfico de dependencia entre las salidas (también conocidas como puntos finales reactivos) y las entradas (también conocidas como fuentes reactivas). La verdadera potencia de las expresiones reactivas reside en su capacidad para encadenar y almacenar en caché los cÔlculos, pero centrémonos primero en la generación de salidas. Para generar una salida, tienes que elegir una función adecuada para renderizar el resultado de una expresión reactiva.La siguiente rutina utiliza la función
renderPlotly()para renderizar una expresión reactiva que genera un grÔficoplotly. Esta expresión depende del valor de entradainput$cities(es decir, el valor de entrada ligado al widget de entrada con uninputIdde"cities") y almacena la salida comooutput$p. Esto indica ashinyque inserte el grÔfico reactivo en el contenedorplotlyOutput(outputId = "p")definido en la interfaz de usuario.
library(shiny)
library(plotly)
ui <- fluidPage(
selectizeInput(
inputId = "cities",
label = "Select a city",
choices = unique(txhousing$city),
selected = "Abilene",
multiple = TRUE
),
plotlyOutput(outputId = "p")
)
server <- function(input, output, ...) {
output$p <- renderPlotly({
plot_ly(txhousing, x = ~date, y = ~median) %>%
filter(city %in% input$cities) %>%
group_by(city) %>%
add_lines()
})
}
shinyApp(ui, server)library("vembedr")
embed_url("https://youtu.be/htnDHPpnZbs")Si, en lugar de un grƔfico
plotly, una expresión reactiva genera un grÔficoRestÔtico, simplemente utilicerenderPlot()(en lugar derenderPlotly()) para renderizarlo yplotOutput()(en lugar deplotlyOutput()) para posicionarlo. Otros widgets de salidashinyutilizan esta convención de nombres:renderDataTable()/datatableOutput(), renderPrint()/verbatimTextOutput(), renderText()/textOutput(), renderImage()/imageOutput(), etc. Los paquetes que se basan en el estÔndarhtmlwidgets(por ejemplo,plotlyyleaflet) son, en cierto sentido, también widgets de salida deShinyque se animan a seguir esta misma convención de nombres (por ejemplo,renderPlotly()/plotlyOutput()yrenderLeaflet()/leafletOutput()).Shinytambién viene preempaquetado con un puñado de otros widgets de entrada útiles. Aunque muchas aplicaciones deShinylos utilizan directamente"out-of-the-box", los widgets de entrada pueden estilizarse fÔcilmente conCSSy/oSASS, e incluso se pueden integrar widgets de entrada personalizados (Mastny 2018; RStudio 2014a).selectInput()/selectizeInput()para los menús desplegables.numericInput()para un solo número.sliderInput()para un rango numérico.textInput()para una cadena de caracteres.dateInput()para una sola fecha.dateRangeInput()para un rango de fechas.fileInput()para subir archivos.checkboxInput()/checkboxGroupInput()/radioButtons()para elegir una lista de opciones.
A partir de ahora nuestro enfoque es enlazar múltiples grÔficos en
shinya través de la manipulación directa, por lo que nos centramos menos en el uso de estos widgets de entrada, y mÔs en el uso deplotlyy los grÔficos estÔticos deRcomo entradas a otros widgets de salida.
Ocultación y rediseño al cambiar de tamaño
- La función
renderPlotly()renderiza cualquier cosa que la funciónplotly_build()entienda, incluyendo los objetosplot_ly(), ggplotly()yggplot2. TambiĆ©n renderizaNULLcomo undivHTMLvacĆo, lo que es Ćŗtil para ciertos casos en los que no tiene sentido renderizar un grĆ”fico. La siguiente rutina aprovecha estas caracterĆsticas para representar undivvacĆo mientras se muestra el marcador de posición deselectizeInput(), pero luego representa un grĆ”ficoplotlymedianteggplotly()una vez que se han seleccionado las ciudades. TambiĆ©n muestra cómo hacer que la salida deplotlydependa del tamaƱo del contenedor que contiene el grĆ”fico deplotly. Por defecto, cuando un navegador cambia de tamaƱo, el tamaƱo del grĆ”fico se cambia puramente del lado del cliente, pero esta expresión reactiva se reejecutarĆ” cuando la ventana del navegador cambie de tamaƱo. Debido a razones tĆ©cnicas, esto puede mejorar el comportamiento del redimensionamiento deggplotly(), pero debe usarse con precaución cuando se manejan grandes datos y largos tiempos de representación.
library(shiny)
cities <- unique(txhousing$city)
ui <- fluidPage(
selectizeInput(
inputId = "cities",
label = NULL,
choices = c("Please choose a city" = "", cities),
multiple = TRUE
),
plotlyOutput(outputId = "p")
)
server <- function(input, output, session, ...) {
output$p <- renderPlotly({
req(input$cities)
if (identical(input$cities, "")) return(NULL)
p <- ggplot(data = filter(txhousing, city %in% input$cities)) +
geom_line(aes(date, median, group = city))
height <- session$clientData$output_p_height
width <- session$clientData$output_p_width
ggplotly(p, height = height, width = width)
})
}
shinyApp(ui, server)library("vembedr")
embed_url("https://youtu.be/Szj_CiDz1WI")Eventos de alcance
Esta sección aprovecha la interfaz para acceder a los eventos de entrada de
plotlypara informar a otras vistas de datos sobre esos eventos. Cuando se gestionan mĆŗltiples vistas que se comunican entre sĆ, es necesario saber quĆ© vistas son una fuente de interacción y cuĆ”les son un objetivo (Ā”una vista puede ser ambas cosas a la vez!). La funciónevent_data()proporciona un argumento de origen para ayudar a refinar quĆ© vista(s) sirve(n) como fuente de un evento. El argumento fuente toma unIDde cadena, y cuando eseIDcoincide con la fuente de un grĆ”fico deplot_ly()/ggplotly(), entonces elevent_data()se āasignaā a esa vista. Para tener una mejor idea de cómo funciona esto, considere el siguiente videoclipNótese que permite hacer clic en una celda de un mapa de calor de correlación para generar un grĆ”fico de dispersión de las dos variables correspondientes, lo que permite ver mĆ”s de cerca su relación. En el caso de un mapa de calor, los datos de eventos vinculados a un evento
plotly_clickcontienen las categorĆasxeyrelevantes (por ejemplo, los nombres de las variables de datos de interĆ©s) y el valorz(por ejemplo, la correlación de Pearson entre esas variables). Para obtener los datos de los clics del mapa tĆ©rmico, y sólo del mapa tĆ©rmico, es importante que el argumento fuente de la funciónevent_data()coincida con el argumento fuente deplot_ly(). De lo contrario, si el argumento fuente no se especificaraevent_data("plotly_click")tambiĆ©n se dispararĆa cuando el usuario hiciera clic en el grĆ”fico de dispersión, causando probablemente un error
library(shiny)
# cache computation of the correlation matrix
correlation <- round(cor(mtcars), 3)
ui <- fluidPage(
plotlyOutput("heat"),
plotlyOutput("scatterplot")
)
server <- function(input, output, session) {
output$heat <- renderPlotly({
plot_ly(source = "heat_plot") %>%
add_heatmap(
x = names(mtcars),
y = names(mtcars),
z = correlation
)
})
output$scatterplot <- renderPlotly({
# if there is no click data, render nothing!
clickData <- event_data("plotly_click", source = "heat_plot")
if (is.null(clickData)) return(NULL)
# Obtain the clicked x/y variables and fit linear model
vars <- c(clickData[["x"]], clickData[["y"]])
d <- setNames(mtcars[vars], c("x", "y"))
yhat <- fitted(lm(y ~ x, data = d))
# scatterplot with fitted line
plot_ly(d, x = ~x) %>%
add_markers(y = ~y) %>%
add_lines(y = ~yhat) %>%
layout(
xaxis = list(title = clickData[["x"]]),
yaxis = list(title = clickData[["y"]]),
showlegend = FALSE
)
})
}
shinyApp(ui, server)library("vembedr")
embed_url("https://youtu.be/QXUVPiSZHcw")Drill-down
- La siguiente app muestra las ventas desglosadas por categorĆa de
negocio (por ejemplo,
Furniture, Office Supplies, Technology) en un grĆ”fico circular. Permite al usuario hacer clic en una porción del grĆ”fico para āprofundizarā en las subcategorĆas de la categorĆa elegida. En tĆ©rminos de implementación, el aspecto clave aquĆ es mantener el estado de la categorĆa actualmente seleccionada a travĆ©s de unreactiveVal()y actualizar ese valor cuando se hace clic en una categorĆa o se pulsa el botón āBackā. La funciónreactive()envuelve una expresión normal para crear una expresión reactiva. Conceptualmente, una expresión reactiva es una expresión cuyo resultado cambiarĆ” con el tiempo.
library(shiny)
library(dplyr)
library(readr)
library(purrr) # just for `%||%`
sales <- read_csv("https://plotly-r.com/data-raw/sales.csv")
categories <- unique(sales$category)
ui <- fluidPage(plotlyOutput("pie"), uiOutput("back"))
server <- function(input, output, session) {
# for maintaining the current category (i.e. selection)
current_category <- reactiveVal()
# report sales by category, unless a category is chosen
sales_data <- reactive({
if (!length(current_category())) {
return(count(sales, category, wt = sales))
}
sales %>%
filter(category %in% current_category()) %>%
count(sub_category, wt = sales)
})
# Note that pie charts don't currently attach the label/value
# with the click data, but we can include as `customdata`
output$pie <- renderPlotly({
d <- setNames(sales_data(), c("labels", "values"))
plot_ly(d) %>%
add_pie(
labels = ~labels,
values = ~values,
customdata = ~labels
) %>%
layout(title = current_category() %||% "Total Sales")
})
# update the current category when appropriate
observe({
cd <- event_data("plotly_click")$customdata[[1]]
if (isTRUE(cd %in% categories)) current_category(cd)
})
# populate back button if category is chosen
output$back <- renderUI({
if (length(current_category()))
actionButton("clear", "Back", icon("chevron-left"))
})
# clear the chosen category on back button press
observeEvent(input$clear, current_category(NULL))
}
shinyApp(ui, server)library("vembedr")
embed_url("https://youtu.be/wAfmLwyHPWU")- Un desglose bĆ”sico como el de la app anterior es algo Ćŗtil por sĆ
mismo, pero se vuelve mucho mÔs útil cuando se vincula a múltiples
vistas de los datos. La siguiente rutina mejora la app anteriror para
mostrar las ventas a lo largo del tiempo por categorĆa o subcategorĆa
(si se elige una categorĆa). Observe que el aspecto clave de la
implementación sigue siendo el mismo (es decir, el mantenimiento del
estado a travƩs de
reactiveValue()), la principal diferencia es que la vista de la serie de tiempo ahora tambiĆ©n responde a los cambios en la categorĆa actualmente seleccionada. Es decir, ambas vistas muestran las ventas por categorĆa cuando no se selecciona ninguna categorĆa, y las ventas por subcategorĆa cuando se selecciona una categorĆa.
library(shiny)
library(dplyr)
library(readr)
library(plotly)
library(purrr)
sales <- read_csv("https://plotly-r.com/data-raw/sales.csv")
categories <- unique(sales$category)
ui <- fluidPage(
plotlyOutput("bar"),
uiOutput("back"),
plotlyOutput("time")
)
server <- function(input, output, session) {
current_category <- reactiveVal()
# report sales by category, unless a category is chosen
sales_data <- reactive({
if (!length(current_category())) {
return(count(sales, category, wt = sales))
}
sales %>%
filter(category %in% current_category()) %>%
count(sub_category, wt = sales)
})
# the pie chart
output$bar <- renderPlotly({
d <- setNames(sales_data(), c("x", "y"))
plot_ly(d) %>%
add_bars(x = ~x, y = ~y, color = ~x) %>%
layout(title = current_category() %||% "Total Sales")
})
# same as sales_data
sales_data_time <- reactive({
if (!length(current_category())) {
return(count(sales, category, order_date, wt = sales))
}
sales %>%
filter(category %in% current_category()) %>%
count(sub_category, order_date, wt = sales)
})
output$time <- renderPlotly({
d <- setNames(sales_data_time(), c("color", "x", "y"))
plot_ly(d) %>%
add_lines(x = ~x, y = ~y, color = ~color)
})
# update the current category when appropriate
observe({
cd <- event_data("plotly_click")$x
if (isTRUE(cd %in% categories)) current_category(cd)
})
# populate back button if category is chosen
output$back <- renderUI({
if (length(current_category()))
actionButton("clear", "Back", icon("chevron-left"))
})
# clear the chosen category on back button press
observeEvent(input$clear, current_category(NULL))
}
shinyApp(ui, server)library("vembedr")
embed_url("https://youtu.be/9sp1vUgmHLI")Templates Shiny
- Usualmente no deseamos partir de cero cuando se trata de crear una
aplicación web
Shiny. Con el fin de tener borradores reutilizables de aplicacionesShiny, existen sitios web donde podemos descargar algunos ejemplos incluso con código fuente, de tal forma que podamos reutilziarlos para nuestros objetivos personales (ver Shiny Templates). En este sitio web son publicados distintos proyectos los cuales pueden ser usados como guĆa, con el fin de preparar aplicaciones web para resolver problemas de interĆ©s en Data Science.
Despliegue de aplicación Shiny
- La plataforma
shinyapps.ioes bastante usada para desplegar aplicacionesShiny. Por lo tanto para seguir con los siguientes pasos debe crearse una cuenta en shinyapps.io.shinyapps.ioofrece un plan gratuito, pero estĆ” limitado a 5 aplicaciones activas y a un uso mensual de 25 horas activas mĆ”ximo. Si despliegas tu aplicación a disposición de un pĆŗblico amplio, esperarĆas superar el lĆmite mensual de horas activas con bastante rapidez. Para aumentar el lĆmite mensual (o para publicar mĆ”s de 5 aplicaciones), tendrĆ”s que actualizar tu plan a uno de pago.
- Abra
RStudioy cree una nueva aplicaciónShiny
- Asignele un nombre (sin espacio), elige dónde guardarlo y haz clic
en el botón
Create
- Luego de esto copie en la app por defecto el código por ejemplo de
la anterior app creada para el
drill-down. De la misma manera que cuando se abre un nuevo documentoR Markdown, se crea el código para una aplicaciónShinybÔsica. Ejecute la aplicación haciendo clic en el botónRun Apppara ver el resultado
4. Se abrirĆ” la app. Para
publicarla, debemos hacer click en el botón
Publish
- Luego de esto hacemos click en
ShinyApps.iopara conectarnos a nuestra cuenta, creada anteriormente
- Luego de esto necesitaremos un token para poder conectar nuestra app
al servidor de
ShinyApps.io. Con este fin, seguimos las instrucciones que aparecen en la ventana, esto es, vamos a nuestra cuenta y hacemos click en nuestro nombre de usuario, luego presionamos el botonTokens, despues de esto hacemos click enShowpara visualizar el token, luegoShow secret/Copy to clipboard. Luego de realizar esto, pegamos nuestro token en la ventana de publicación de nuestra app
- Luego le va a aparecer la siguiente ventana con la opción de
publicar su aplicación. Haga click en
Publish
- DespuĆ©s de varios segundos (dependiendo del peso de su aplicación), la aplicación Shiny deberĆa aparecer en su navegador de Internet. Para volver a desplegar la aplicación puede hacerlo desde su aplicación haciendo click en el botón resaltado en la imagen y deberĆ” visualizar su despliegue en la parte inferior. Ahora puede realizar cambio y usar sólo ese botón cuando desde hacer el despliegue de su aplicación. La app de este ejemplo ha sido publicada en el siguiente link https://lihkir.shinyapps.io/drill-down-app/.
- Puede revisar en su cuenta que se ha creado satisfactoriamente su aplicación. Puede visualizar las conexiones y demÔs información en los botones que se resaltan en rojo. Revise que información entrega cada uno.