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 R de plotly es impulsado por la biblioteca JavaScript plotly.js. La función plot_ly() proporciona una interfaz ā€˜directa’ a plotly.js con algunas abstracciones adicionales para ayudar a reducir la escritura. Estas abstracciones, inspiradas en la gramĆ”tica de los grĆ”ficos y ggplot2, 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, usaremos plot_ly() para explorar el conjunto de datos diamonds de ggplot2 y aprenderemos un poco cómo funciona plotly
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 de plot_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 paquete R (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 paquete plotly tiene la capacidad de traducir ggplot2 a plotly. Esta funcionalidad puede ser realmente Ćŗtil para aƱadir rĆ”pidamente interactividad a su flujo de trabajo ggplot2 existente. AdemĆ”s, incluso si conoce bien plot_ly() y ggplotly() 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 entre price y otras variables del conocido conjunto de datos diamonds.

  • El binning hexagonal (es decir, geom_hex()) es una forma Ćŗtil de visualizar una densidad 2D, como la relación entre price y carat, 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() sobre plot_ly() para aprovechar la interfaz consistente y expresiva de ggplot2 para explorar resĆŗmenes estadĆ­sticos entre grupos. Por ejemplo, al incluir una variable de color discreta (e.g cut) con geom_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 cualquier geom (e.g, geom_boxplot(), geom_histogram(), geom_density(), etc) y es una caracterĆ­stica clave de ggplot2.
p <- ggplot(diamonds, aes(x = log(price), color = clarity)) + 
  geom_freqpoly()
ggplotly(p)

Barras e histogramas

  • Las funciones add_bars() y add_histogram() envuelven los tipos de trazado de barras e histogramas de plotly.js. La principal diferencia entre ellas es que las trazas de barras requieren alturas de barras (tanto x como y), mientras que las trazas de histograma requieren sólo una variable, y plotly.js maneja 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.js con algunos algoritmos diferentes disponibles en R a travĆ©s de la función hist(). Aunque plotly.js tiene la capacidad de personalizar los bins del histograma a travĆ©s de xbins/ybins, R tiene diversas facilidades para estimar el nĆŗmero óptimo de bins en un histograma que podemos aprovechar fĆ”cilmente. La función hist() por sĆ­ sola nos permite referenciar 3 algoritmos famosos por su nombre (Sturges 1926; Freedman-Diaconis 1981 y Scott 1979), pero tambiĆ©n hay paquetes (por ejemplo, el paquete histogram) que amplĆ­an esta interfaz para incorporar mĆ”s metodologĆ­a (Mildenberger, Rozenholc y Zasada. 2009). La función price_hist() que aparece a continuación envuelve la función hist() para obtener los resultados del binning, y mapear esos bins a una versión graficada del histograma usando add_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 que plotly.js realice el binning. Se necesita mĆ”s esfuerzo humano para realizar el binning en R, 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 boxplot es a veces inadecuado para capturar distribuciones complejas (por ejemplo, multimodales) (en este caso, un polĆ­gono de frecuencia proporciona una buena alternativa). La función add_boxplot() requiere una variable numĆ©rica y garantiza que los boxplots se orienten correctamente, independientemente de si la variable numĆ©rica se coloca en la escala x o y. 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 a plotly.js calcular las estadĆ­sticas de resumen. Desafortunadamente, plotly.js todavĆ­a no permite calcular estadĆ­sticas para boxplots.

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 de plotly.js para renderizar una capa de mapa base. Actualmente hay dos formas de hacer mapas integrados: a travĆ©s de Mapbox o a travĆ©s de un mapa base integrado con d3.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 paquete sf R, pero tambiĆ©n es posible hacer mapas personalizados de plotly a travĆ©s de otras herramientas para la geoinformĆ”tica (por ejemplo, sp, ggmap, etc).

  • Vale la pena seƱalar que plotly pretende 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 en plotly, ya que las APIs de mapeo son muy similares al resto de plotly, y se puede aprovechar el ecosistema mĆ”s grande de plotly (por ejemplo, la vinculación de las vistas del lado del cliente). Sin embargo, si te encuentras con limitaciones con la funcionalidad de mapeo de plotly, hay un conjunto muy rico de herramientas para la visualización geoespacial interactiva en R, incluyendo pero no limitado a: leaflet, mapview, mapedit, tmap, y mapdeck.

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() y plot_geo()). En general, puede tratar estas funciones constructoras como un reemplazo directo de plot_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 con plot_ly(). Por ejemplo, la siguiente figura utiliza plot_mapbox() y add_markers() para crear un grĆ”fico de burbujas. Para poder usar plot_mapbox, primero debes crear un MAPBOX_TOKEN en 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 Mapbox se controla a travĆ©s del atributo layout.mapbox.style. El paquete de plotly viene con soporte para 7 estilos diferentes, pero tambiĆ©n puedes suministrar una URL personalizada a un estilo mapbox personalizado. Para obtener todos los nombres de estilo de mapa base pre-empaquetados, puedes tomarlos del esquema oficial plotly.js(). Antes debes instalar listviewer para 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.js para controlar el estilo del mapa base a travĆ©s del atributo layout.updatemenus. La idea detrĆ”s de un menĆŗ desplegable integrado de plotly.js es proporcionar una lista de botones (es decir, elementos de menĆŗ) donde cada botón invoca un mĆ©todo de plotly.js con algunos argumentos. En este caso, cada botón utiliza el mĆ©todo relayout para modificar el atributo layout.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 plotly es plot_geo(). En comparación con plot_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 de plot_geo() junto con add_markers() y add_segments() para visualizar las rutas de vuelo dentro de los Estados Unidos. Mientras que plot_mapbox() estĆ” fijado a una proyección mercator, el constructor plot_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() y plot_geo()) tienen un tipo de trazo de coroplĆ©tico optimizado (es decir, los tipos choroplethmapbox y choropleth trace). Comparativamente, choroplethmapbox es mĆ”s potente porque se puede especificar completamente la colección de caracterĆ­sticas utilizando GeoJSON, pero el choropleth trace 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)
  • Choroplethmapbox es mĆ”s flexible que choropleth porque usted suministra su propia definición GeoJSON del choropleth a travĆ©s del atributo geojson. Actualmente este atributo debe ser una URL que apunte a un archivo geojson. AdemĆ”s, la ubicación debe apuntar a un atributo id de nivel superior de cada caracterĆ­stica dentro del archivo geojson. La siguiente figura muestra cómo podrĆ­amos visualizar la información anterior, pero esta vez utilizando choroplethmapbox
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 coropletas para 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Ć”ficos plotly con shiny un paquete de R para crear aplicaciones web reactivas completamente en R. El modelo de programación reactiva de Shiny permite a los programadores de R aprovechar sus conocimientos existentes de R y crear aplicaciones web basadas en datos sin ninguna experiencia previa en programación web. Shiny en 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 de R), pero el propio shiny tambiĆ©n agrega algĆŗn soporte especial para interactuar con grĆ”ficos e imĆ”genes estĆ”ticas de R (Chang 2017).

  • Al vincular los grĆ”ficos en una aplicación web, hay compensaciones a considerar cuando se utilizan grĆ”ficos estĆ”ticos de R en 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 con plotly, lo que hace que su combinación sea un potente conjunto de herramientas para vincular vistas en la web desde R. El propio Shiny proporciona una forma de acceder a eventos con grĆ”ficos estĆ”ticos hechos con cualquiera de los siguientes paquetes de R: graphics, ggplot2 y lattice. 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, plotly no tiene la misma gama e historia, pero proporciona mĆ”s opciones y control sobre la interactividad. MĆ”s concretamente, debido a que plotly estĆ” 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Ć”ficos plotly dentro de shiny, 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+shiny utiliza una entrada de shiny para controlar una salida de plotly. Mostraremos un ejemplo sencillo de uso de la función selectizeInput() de shiny para crear un desplegable que controla un grĆ”fico de plotly. Este ejemplo, asĆ­ como cualquier otra aplicación shiny, 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ón fluidPage() 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 la UI es completamente personalizable, y paquetes como shinydashboard facilitan 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 servidor shiny es una R 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 de Shiny construyen 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Ć”fico plotly. Esta expresión depende del valor de entrada input$cities (es decir, el valor de entrada ligado al widget de entrada con un inputId de "cities") y almacena la salida como output$p. Esto indica a shiny que inserte el grĆ”fico reactivo en el contenedor plotlyOutput(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Ć”fico R estĆ”tico, simplemente utilice renderPlot() (en lugar de renderPlotly()) para renderizarlo y plotOutput() (en lugar de plotlyOutput()) para posicionarlo. Otros widgets de salida shiny utilizan esta convención de nombres: renderDataTable()/datatableOutput(), renderPrint()/verbatimTextOutput(), renderText()/textOutput(), renderImage()/imageOutput(), etc. Los paquetes que se basan en el estĆ”ndar htmlwidgets (por ejemplo, plotly y leaflet) son, en cierto sentido, tambiĆ©n widgets de salida de Shiny que se animan a seguir esta misma convención de nombres (por ejemplo, renderPlotly()/plotlyOutput() y renderLeaflet()/leafletOutput()).

  • Shiny tambiĆ©n viene preempaquetado con un puƱado de otros widgets de entrada Ćŗtiles. Aunque muchas aplicaciones de Shiny los utilizan directamente"out-of-the-box", los widgets de entrada pueden estilizarse fĆ”cilmente con CSS y/o SASS, 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 shiny a 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 de plotly y los grĆ”ficos estĆ”ticos de R como 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ón plotly_build() entienda, incluyendo los objetos plot_ly(), ggplotly() y ggplot2. TambiĆ©n renderiza NULL como un div HTML vacĆ­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 un div vacĆ­o mientras se muestra el marcador de posición de selectizeInput(), pero luego representa un grĆ”fico plotly mediante ggplotly() una vez que se han seleccionado las ciudades. TambiĆ©n muestra cómo hacer que la salida de plotly dependa del tamaƱo del contenedor que contiene el grĆ”fico de plotly. 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 de ggplotly(), 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 plotly para 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ón event_data() proporciona un argumento de origen para ayudar a refinar quĆ© vista(s) sirve(n) como fuente de un evento. El argumento fuente toma un ID de cadena, y cuando ese ID coincide con la fuente de un grĆ”fico de plot_ly()/ggplotly(), entonces el event_data() se ā€œasignaā€ a esa vista. Para tener una mejor idea de cómo funciona esto, considere el siguiente videoclip

  • Nó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_click contienen las categorĆ­as x e y relevantes (por ejemplo, los nombres de las variables de datos de interĆ©s) y el valor z (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ón event_data() coincida con el argumento fuente de plot_ly(). De lo contrario, si el argumento fuente no se especificara event_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 un reactiveVal() y actualizar ese valor cuando se hace clic en una categorĆ­a o se pulsa el botón ā€œBackā€. La función reactive() 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 aplicaciones Shiny, 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.io es bastante usada para desplegar aplicaciones Shiny. Por lo tanto para seguir con los siguientes pasos debe crearse una cuenta en shinyapps.io. shinyapps.io ofrece 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.
  1. Abra RStudio y cree una nueva aplicación Shiny

  1. Asignele un nombre (sin espacio), elige dónde guardarlo y haz clic en el botón Create

  1. 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 documento R Markdown, se crea el código para una aplicación Shiny bÔsica. Ejecute la aplicación haciendo clic en el botón Run App para 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.io para conectarnos a nuestra cuenta, creada anteriormente

  1. 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 boton Tokens, despues de esto hacemos click en Show para visualizar el token, luego Show secret/Copy to clipboard. Luego de realizar esto, pegamos nuestro token en la ventana de publicación de nuestra app

  1. Luego le va a aparecer la siguiente ventana con la opción de publicar su aplicación. Haga click en Publish

  1. 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/.

  1. 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.