Der Erfolg von Datenanalysen beruht nicht nur auf den Ergebnissen selbst, sondern zunehmend auch darauf, wie sie präsentiert und anderen zugänglich gemacht werden. R bietet mit Shiny ein großartiges Werkzeug, um interaktive Webapplikationen zu erstellen. Dazu sind weder HTML- noch CSS- oder Javascript-Kenntnisse erforderlich.
Shiny: Umdenken von bisheriger R-Programmierung
Wer bereits Erfahrungen mit der R-Programmierung gesammelt hat, kann auch mit Shiny loslegen. Shiny ist sehr gut dokumentiert, sowohl mit eigenem Webseitenbereich bei RStudio / Posit, als auch mit Literatur, wie z. B. dem kostenlos online zugänglichen Buch von Hadley Wickham: Mastering Shiny.
Eine Herausforderung liegt darin, dass man gegenüber herkömmlicher R-Programmierung umdenken muss. Neben etlichen technischen Details von Shiny-Funktionen geht es dabei zunächst um ein anderes Paradigma der Programmierung: Reaktivität.
Reaktives Programmieren / Reaktivität
Die gute Nachricht lautet: Jeder, der schon mal ein Excel-Blatt mit Formeln verwendet hat, ist bereits mit der Grundidee vertraut!
Hier im Beispiel reagieren die grünen Felder auf Änderungen in den grauen Feldern. Es geht um Verkaufszahlen einer fiktiven Firma, die in den grauen Feldern links nach Monaten erfasst werden. In der Mitte werden Quartalszahlen aufgelistet, die sich mittels simpler Summenformeln aus den jeweiligen Monaten speisen. Rechts kommt noch eine Gesamtsumme für das ganze Jahr hinzu. Die grünen Zellen werden bei jeder Änderung in einer grauen Zelle automatisch aktualisiert.
Genau das ist auch das Grundprinzip von Shiny.
Für die Programmierung heißt das: Das R-Skript, aus dem Shiny die App erzeugt (mit HTML-, CSS- und Javascript-Elementen) wird nicht der Reihe nach Zeile für Zeile ausgeführt, wie man es von sonstigen R-Skripten gewohnt ist. Stattdessen werden in der App zwei wesentliche Bausteine definiert:
- eine Oberfläche für den Anwender, das User Interface (UI), sowie
- eine Server-Funktion, die beschreibt, wie R auf die Eingaben des Anwenders reagieren soll.
So entstehen Abhängigkeiten zwischen Inputs und Outputs, Eingabe und Ausgabe, die Shiny automatisch handhabt.
Anwendungsbeispiel: Eine simple reaktive App
Hier eine recht simple App mit einem Drop-Down-Feld, mit dem man in der Seitenspalte links eine Band / einen Künstler auswählen kann. Shiny erstellt im Hauptbereich rechts eine dazu passende Grafik.
Die Vorbereitung:
# Paket chartmusicdata von github installieren # Diesen Teil nur ein Mal ausführen, kann dann auskommentiert / gelöscht werden library(devtools) remotes::install_github("fjodor/chartmusicdata") # Pakete laden library(chartmusicdata) library(shiny) library(tidyverse) library(plotly) # Daten aus dem chartmusicdata-Paket bereitstellen data(songs2000) # Die 10 am häufigsten vertretenen Künstler / Bands bereitstellen artists <- songs2000 %>% count(artist, sort = TRUE) %>% slice_head(n = 10) %>% pull(artist) # Daten nach diesen 10 Künstlern / Bands filtern songsdata <- songs2000 %>% filter(artist %in% artists) # Hilfsfunktion für die x-Achsen-Beschriftung ntes_label <- function(n = 4) { function(x) {x[c(TRUE, rep(FALSE, n - 1))]} }
Das User Interface (UI) der Shiny-App
Nun können wir das User Interface (UI) definieren:
ui <- fluidPage( titlePanel("Simple Reaktivität: Dropdown"), sidebarLayout( sidebarPanel( selectInput(inputId = "bandname", label = "Künstler / Band auswählen", choices = artists, selected = "Drake") ), mainPanel( h2("Daten aus songs2000"), # h2 ist eine Überschrift Ebene 2 plotlyOutput(outputId = "bandplot") # Platzhalter für Grafik ) ) )
Mit ein paar simplen Shiny-Funktionen beschreiben wir das Layout der App: eine Seite mit Seitenspalte (sidebarPanel) und Hauptbereich (mainPanel). Der Nutzer interagiert mit der App über ein Drop-Down-Feld (selectInput). Die ID „bandname“ nutzen wir gleich in der Server-Funktion als Variable, um die Nutzereingabe zu verarbeiten. Es ist eine interaktive plotly-Grafik vorgesehen (plotlyOutput), deren Erstellung in der Server-Funktion erfolgt.
Die Server-Funktion der Shiny-App
Und so sieht die Server-Funktion aus:
server <- function(input, output, session) { output$bandplot <- renderPlotly({ p <- songsdata %>% filter(artist == input$bandname) %>% ggplot(aes(x = year_month, y = indicativerevenue, color = song, group = artist)) + geom_point(size = 1.5) + labs(title = paste("Songs von", input$bandname), x = "Monat und Jahr", y = "Indicative Revenue in USD") + scale_x_discrete(breaks = ntes_label()) + scale_y_continuous(labels = scales::label_dollar(scale = 1000)) + theme_bw(base_size = 14) + theme(axis.text.x = element_text(angle = 90), legend.position = "none") ggplotly(p) }) }
- Die Funktionsparameter input, output, session werden von Shiny verarbeitet
- An zwei Stellen verarbeiten wir die Nutzereingabe, die Auswahl der Band. Der Zugriff erfolgt über die Variable input$bandname, wobei die ID „bandname“ im User Interface definiert wurde.
Es wird nach der Band gefiltert und die Band im Diagrammtitel genannt. - Es handelt sich um ein ggplot2-Diagramm.
- Wir machen es interaktiv (sodass es auf den Mauszeiger reagiert und Details zum jeweiligen Song anzeigt) mit Hilfe des großartigen plotly-Pakets.
Den Abschluss bildet die Funktion shinyApp, die als Argumente das User Interface und die Server-Funktion enthält:
shinyApp(ui = ui, server = server)
Reaktivität (fast) ohne Programmieraufwand!
Bei jeder neuen Auswahl einer Band / eines Künstlers im Drop-Down-Menü aktualisiert Shiny automatisch die Grafik. Und wir haben so gut wie nichts dafür tun müssen!
Bedingung ist lediglich, dass die Grafik innerhalb einer render()-Funktion erstellt wird; hier handelt es sich um renderPlotly(), passend zum „Platzhalter“ im User Interface: plotlyOutput(). Das genügt Shiny bereits, um alles, was die Reaktivität erfordert, im Hintergrund zu regeln!
Reaktives Programmieren mit Shiny: Zusammenfassung
- Shiny-Apps bestehen aus User Interface und Server-Funktion
- Ausgaben werden im User Interface mit output()-Funktionen angelegt;
in unserem Beispiel: plotlyOutput() - Weitere Outputs:
plotOutput() für statische Diagramme;
tableOutput() für Tabellen;
dataTableOutput() für interaktive Tabellen mit dem DT-Paket;
textOutput() für reine Textausgaben - Die Reaktivität wird sichergestellt, indem die Ausgabe in der Server-Funktion über eine render()-Funktion erfolgt;
in unserem Beispiel: renderPlotly() - Weitere Render-Funktionen entsprechend der obigen Aufzählung für die Outputs:
renderPlot() für statische Diagramme;
renderTable() für Tabellen;
DT::renderDataTable() für interaktive Tabellen mit dem DT-Paket;
renderText() für Textausgaben - Die App reagiert auf Nutzereingaben, indem die Server-Funktion auf input-Variablen zugreift;
in unserem Beispiel: input$bandname, entsprechend der Id bandname in plotlyOutput() - Schnelle Funktionsübersicht: Siehe Shiny Cheatsheet
Viel Erfolg mit Euren Shiny-Apps!
Gern unterstütze ich Euch mit einem Workshop.