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 zur Datenvisualisierung zwei interaktive Diagramme, sodass man aus einem Übersichtsdiagramm Gruppen auswählen kann, die dann in einem detaillierteren Diagramm automatisch hervorgehoben werden.
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.
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:
country | year | mortality_rate | region | capital | |
30 | Andorra | 1990 | 13.0 | Europe & Central Asia | Andorra la Vella |
31 | Andorra | 1991 | 12.3 | Europe & Central Asia | Andorra la Vella |
32 | Andorra | 1992 | 11.6 | Europe & Central Asia | Andorra la Vella |
33 | Andorra | 1993 | 11.1 | Europe & Central Asia | Andorra la Vella |
34 | Andorra | 1994 | 10.5 | Europe & Central Asia | Andorra la Vella |
35 | Andorra | 1995 | 9.9 | Europe & Central Asia | Andorra 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
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.
Datenvisualisierung – 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.
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:
Zur Webseite: Interactive web-based data visualization with R, plotly, and shiny
Zum Buch: Interactive Web-Based Data Visualization with R, plotly, and shiny: Carson Sievert
Ein Gedanke zu „Zwei interaktive Diagramme in R verknüpfen ohne Shiny: plotly, crosstalk“