Zwei interaktive Diagramme in R verknüpfen ohne Shiny: plotly, crosstalk

plotly: Zwei interaktive Diagramme verbinden

Bis vor kurzem habe ich das plotly-Paket von Carson Sievert fast nur mit der ggploty()-Funktion genutzt. Doch plotly kann so viel mehr! Hier verknüpfen wir zwei interaktive Diagramme, sodass man aus einem Übersichtsdiagramm Gruppen auswählen kann, die dann in einem detaillierteren Diagramm automatisch hervorgehoben werden.

plotly: Zwei interaktive Diagramme verbinden ohne Shiny

Präsentation per Dashboard: flexdashboard

Die Analyse ist in einem Dashboard dargestellt, das mit dem flexdashboard-Paket erstellt wurde – einer Erweiterung des Markdown-Formats, die flexiblere Layouts ermöglicht.

Anklicken zum Vergrößern
Dashboard mit flexdashboard; Navigation oben auf der blauen Leiste; hier werden ein statisches ggplot-Diagramm (links) und ein interaktives plotly-Diagramm mit Mouse-Over-Effekten (rechts) gegenübergestellt. Die interaktive Version liegt hier – inklusive R-Code (Reiter „Source Code“).

Datenbasis: Weltbank

Die Daten stammen von der Weltbank: World Development Indicators, WDI. Heute geht es vor allem um die Kindersterblichkeit bis 5 Jahre (pro 1000 Geburten): Mortality rate, under-5 (per 1,000 live births). Der Datenzugriff erfolgt über das WDI-Paket.

library(tidyverse)
library(knitr)
library(kableExtra)
library(flexdashboard)
library(WDI)
library(plotly)
library(crosstalk)
library(ggthemes)

cache <- WDIcache()
  
data_org <- WDI(country = "all",
 indicator = c(mortality_under_5 = "SH.DYN.MORT",
 renewable_energy_consumption = "EG.FEC.RNEW.ZS"),
 extra = TRUE,
 cache = cache)
  
data <- data_org %>% 
   filter(region != "Aggregates") %>% 
   na.omit() %>%
   select(country, year, mortality_under_5, region, capital) %>% 
   head()

Die ersten sechs Zeilen sehen so aus:

countryyearmortality_rateregioncapital
30Andorra199013.0Europe & Central AsiaAndorra la Vella
31Andorra199112.3Europe & Central AsiaAndorra la Vella
32Andorra199211.6Europe & Central AsiaAndorra la Vella
33Andorra199311.1Europe & Central AsiaAndorra la Vella
34Andorra199410.5Europe & Central AsiaAndorra la Vella
35Andorra19959.9Europe & Central AsiaAndorra la Vella

Kindersterblichkeit im Zeitverlauf: Zu viele Linien …

Als erste Annäherung erstellen wir ein ggplot-Diagramm, um die Kindersterblichkeit im Zeitverlauf für alle Länder darzustellen. Es ist nur begrenzt hilfreich:

p <- ggplot(data, aes(x = year, y = mortality_under_5, group = country)) +
   geom_line(color = "blue") +
   theme_economist_white() +
   labs(title = "Mortality by Year",
        x = "Year",
        y = "Mortality Rate under-5 per 1,000 live births)",
        caption = "Data Source: World Bank, World Development Indicators (WDI),
                   obtained via the WDI R package, version 2.7.1")
 p
Kindersterblichkeit pro Jahr im Zeitverlauf. Zu viele Linien ...
Kindersterblichkeit pro Jahr im Zeitverlauf. Zu viele Linien – so kaum verwendbar.

Im Dashboard ist rechts die interaktive Version zu sehen, die mit dem simplen Einzeiler

ggplotly(p)

erreichbar ist. Vorteil: Wenn man mit der Maus auf eine Linie zeigt, werden das Land sowie die Koordinaten, also Kindersterblichkeit und Jahr, eingeblendet (siehe auch Screenshot oben). Diese Interaktivität wird durch Javascript umgesetzt, das heißt: Sie funktioniert in HTML-Formaten, die von Internet-Browsern oder von RStudio dargestellt werden. In alternativen Ausgabeformaten wie Word, Powerpoint oder PDF funktioniert das dagegen mangels Javascript-Unterstützung nicht.

Es sind immer noch zu viele Linien, das Diagramm ist ausgesprochen schlecht lesbar. Eine übliche Abhilfe in ggplot2 besteht darin, mit sog. facets zu arbeiten, also separaten Diagrammen für Untergruppen. Hier bieten sich die Regionen dafür an.

Heute möchte ich jedoch einen anderen Weg zeigen: Die Verknüpfung zweier Diagramme, sodass man auf dem ersten eine Gruppe auswählen kann und diese dann auf einem zweiten, detaillierteren hervorgehoben wird.

Zwei interaktive Diagramme verknüpfen ohne Shiny: plotly und crosstalk

Ein hohes Maß an Interaktivität und Flexibilität bietet Shiny: Ein R-Paket, mit dem man Web-Applikationen bauen kann.

Nachteil 1: Die App benötigt R zur Laufzeit. D. h. entweder zeigt man sie auf einem lokalen Rechner, auf dem R installiert ist, oder bettet sie in eine Webseite ein – wobei dann R auf dem Server laufen muss, was nicht für alle Anwendungsfälle so einfach realisiert werden kann.

Nachteil 2 ist die Lernkurve. Wer mit Shiny programmiert, muss etwas umdenken gegenüber sonstiger R-Programmierung (Stichworte: User Interface und Server-Funktionen; Reaktivität).

Daher ist es zweckmäßig, eine Alternative zu kennen, die shiny-ähnliche Interaktivität bietet (wenn auch nicht mit der gleichen Gestaltungsfreiheit), aber zur Laufzeit nicht mehr auf R angewiesen und zudem mit weniger Code umsetzbar ist.

Anklicken zum Vergrößern
plotly und crosstalk: Ein Übersichtsdiagramm links zeigt Mittelwerte pro Region, ein detailliertes Diagramm rechts Zeitverläufe pro Land. Links wurde Latin America & Carribean ausgewählt, rechts sind die Länder dieser Region automatisch hervorgehoben. Die Maus zeigt auf den Ausreißer Haiti, das 2010 von einem verheerenden Erdbeben heimgesucht wurde – die Kindersterblichkeit stieg sprunghaft an.

Wie zuvor greifen wir auf das plotly-Paket zurück, allerdings diesmal nicht mit der einfachen ggplotly()-Funktion. Zusätzlich nutzen wir crosstalk, um die beiden Diagramme zu verknüpfen. Dazu dient ein sog. Shared Data-Objekt: Eine spezielle Struktur, die beiden Diagrammen die Daten so zur Verfügung stellt, dass sie gemeinsam auf Nutzereingaben (Mausklicks) reagieren können.

shared_data <- SharedData$new(data, key = ~region)

p1 <- shared_data %>% 
   plot_ly() %>% 
   group_by(region) %>% 
   summarise(avg_mort = round(mean(mortality_under_5, na.rm = TRUE), 1)) %>% 
   add_markers(x = ~avg_mort, y = ~region, size = 2,
               hoverinfo = "text",
               text = ~paste("Region:", region,
                             "Average mortality rate, under-5 (per 1,000 live births):", avg_mort)) %>% 
   layout(xaxis = list(title = "Average Mortality Rate,\nunder-5 (per 1,000 live births)"))

SharedData$new() erzeugt auf Basis des R6-Systems für Objektorientiertes Programmieren in R dieses gemeinsame Datenobjekt, das den Ausgangspunkt für beide Diagramme bildet.

Für das Übersichtsdiagramm p1 mit den Mittelwerten können wir dplyr-Funktionen wie group_by() und summarise() nutzen – sogar nachdem ein plot_ly()-Diagramm initialisiert wurde. [Stand 2.2.2021: group_by() funktioniert hier mit dplyr-Version 1.0.2, nicht aber mit 1.0.4.] Plotlys add_markers() übernimmt die Aufgabe von geom_point() in ggplot2-Schreibweise.

Das zweite Diagramm enthält eine graue Linie je Land:

p2 <- shared_data %>% 
   plot_ly(x = ~year, y = ~mortality_under_5, ids = ~country,
           hoverinfo = "text",
           text = ~paste("Country:", country,
                         "Region:", region,
                         "Year:", year,
                         "Mortality rate, under-5 (per 1,000 live births):", mortality_under_5)) %>% 
   group_by(country) %>%
   add_lines(color = I("darkgrey")) %>% 
   layout(xaxis = list(title = "Year"), yaxis = list(title = "Mortality Rate, under-5 (per 1,000 live births"),
          title = list(text = "Mortality under-5 by Year"))

Nun fehlt nur noch eine Farbskala und die Anordnung der beiden Diagramme. Dies geschieht durch die subplot()-Funktion. highlight() definiert, wie die Hervorhebung ausgelöst wird – hier per Klick. Alternative wäre durch Auswahl (Kästchen mit der Maus ziehen).

cols <- toRGB(RColorBrewer::brewer.pal(3, "Dark2"))

p <- subplot(p1, p2, shareX = FALSE, shareY = FALSE, titleX = TRUE) %>% 
   hide_legend() %>%
   highlight(on = "plotly_click", off = "plotly_doubleclick",
             dynamic = TRUE, color = cols, selectize = TRUE)

p

Nun ist die Darstellung schon deutlich informativer als beim ersten Versuch nur mit ggplot2!

Bonus: dynamic = TRUE gibt uns eine Schaltfläche, um die Hervorhebungsfarbe zu ändern, und selectize = TRUE erlaubt uns, die Auswahl alternativ per Drop-Down-Feld vorzunehmen.

Interaktive Diagramme verknüpfen ohne shiny: Fazit

Ich finde diese Möglichkeit sehr spannend – was meint Ihr? Nutzt Ihr das bereits oder könnt Ihr Euch vorstellen, diese Technik künftig zu nutzen?

Gern zeige ich mehr Tricks bei einem Workshop!

Zur Visualisierung einer großen Anzahl an Kategorien / Untergruppen siehe auch: Große Datenmengen visualisieren mit R, ggplot2 und trelliscopejs.

Literatur:

Interactive web-based data visualization with R, plotly, and shiny

Ein Gedanke zu „Zwei interaktive Diagramme in R verknüpfen ohne Shiny: plotly, crosstalk“

Freue mich über Kommentare!