Shiny Web Applikationen
Jede Person, die irgendwie mit Daten arbeitet, kommt nicht herum, aus Analysen oder Modellen gezogene Erkenntnisse mit anderen zu teilen. Meist haben diese Personen keinen statistischen oder mathematischen Hintergrund. Für diese sollten die Ergebnisse nicht nur verständlich, sondern im besten Fall auch visuell ansprechend aufbereitet sein. Neben recht teuren Softwarelösungen wie Tableau oder QlikView gibt es von R-Studio auch eine (zumindest im kleinen Rahmen) kostenfreie Lösung – R-Shiny.
Shiny ist ein R Paket, mit dessen Hilfe man interaktive Webapplikationen oder Dashboards erstellen kann, bei dem man auf den vollen Funktionsumfang aller R-Pakete zugreifen kann.
Bei der Erstellung für einfache Shiny-Apps sind keine HTML, CSS oder Javascript Kenntnisse nötig. Shiny teilt sich im Prinzip in zwei Programme: Das Front-End wird in der Datei ui.r festgelegt. Alles was im Back-End passiert, wird in der Datei server.r beschrieben. R-Studio übernimmt danach das Rendern des Front- Ends und man erhält eine übliche HTML-Datei, in dessen Backend R läuft.
Die Vorteile der Einfachheit, nur mit R eine funktionale Web-App erstellen können, hat natürlich auch seine Nachteile. Shiny ist, was das Design betrifft, eher limitiert und auch die Platzierung von Inputs wie Slidern, Drop-Downs oder auch Outputs wie Grafiken oder Tabellen ist stark beschränkt.
Eine kaum bekannte und dokumentierte Funktion von R-Shiny ist die Funktion „htmlTemplate“. Mit dieser lassen sich komplett in HTML, CSS und gegebenenfalls Javascript geschriebene Websites mit der vollen Funktionalität von R im Back-End integrieren – und sehen um Längen besser aus als rein in R geschriebene Web-Apps.
Wie man auf diese Art Shiny Apps programmiert zeige ich nun anhand des Folgenden Beispiels. Die folgenden Erklärungen sind mit Absicht kurz gehalten und stellen kein Tutorial dar, sondern sollen vielmehr die Möglichkeiten der Funktion „htmlTemplate“ zeigen.
Zunächst zur ui.R:
library(shiny) library(corrplot) library(ggplot2) ## ui.R ## htmlTemplate("template.html", slider = sliderInput("weight", "Gewicht", 1.5, 5.4, c(1.7, 3.2)), plot1 = plotOutput("plot1"), plot2 = plotOutput("plot2"), plot3 = plotOutput("plot3") )
Der Code in der ui.R Datei ist recht einfach gehalten. Es werden nur die Bibliotheken geladen, auf die R zugreifen muss. Danach wird das html Template mit dem entsprechenden Namen geladen. Ansonsten werden in dieser Datei nur Input und Output als Variablen festgelegt.
library(shiny) library(corrplot) library(dplyr) library(ggplot2) shinyServer <- function(input, output) { ### Berechnung der Daten ueber das Reactive Statement filterddata <- reactive ({ mtcars$cyl <- as.numeric(mtcars$cyl) data <- mtcars %>% filter(wt > input$weight[1]) %>% filter(wt < input$weight[2]) data }) ### Erstellung der Plots output$plot1 <- renderPlot({ ggplot(data = filterddata(), aes(x = mpg, y = wt)) + geom_point(color = "blue") + geom_smooth(method = "lm", color = "red") + xlab("Meilen pro Galone Benzin") + ylab("Gewicht in Tonnen") + ggtitle("Zusammenhang Gewicht und Benzinverbrauch") + theme_bw() }) output$plot2 <- renderPlot({ ggplot(data = filterddata(), aes(x = cyl)) + geom_bar(fill = "#6b86b2") + xlab("Zylinder") + ylab("Anzahl") + ggtitle("Anzahl Fahrzeuge pro Gruppe") + geom_vline(aes(xintercept = mean(filterddata()$cyl), color = "mean"), size = 1) + geom_vline(aes(xintercept = median(filterddata()$cyl), color = "median"), size = 1) + scale_color_manual(name = "Statistiken", values = c(mean = "red", median = "blue")) + theme_bw() }) output$plot3 <- renderPlot({ M <- cor(filterddata()) corrplot(M, method="square", order="AOE", col = colorRampPalette(c("red", "yellow", "green"))(100)) }) }
In der Server.R Datei wird in diesem Beispiel der bekannte und oft verwendete Datensatz Mtcars verwendet. Zunächst wird mit dem Paket dplyr und der Funktion filter ein neuer Datensatz berechnet, der auf Nutzereingaben reagiert (sliderInput, siehe ui.R). Wenn in R-Shiny in DataFrames Berechnungen durchgeführt werden, müssen diese immer in einem sog. reactive Statement stehen. Danach werden mittels ggplot2 insgesamt drei Plots zu dem Datensatz erstellt.
Plot 1 stellt einen Zusammenhang zwischen Gewicht und Benzinverbrauch mittels linearer Regression dar. Plot 2 zeigt an, wie viele Zylinder die Fahrzeuge aus dem gefilterten Datensatz haben und Plot 3 zeigt die Korrelationen zwischen den Variablen an. Diese drei Plots sollen dem Endnutzer interaktiv zur Verfügung stehen.
<!DOCTYPE html> <html> <head> <link rel='stylesheet' href='style.css'/> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css"> <link rel="stylesheet" href="http://getbootstrap.com/examples/jumbotron-narrow/jumbotron-narrow.css"> {{ headContent() }} </head> <script type="text/javascript"> function Collapse1() { var x = document.getElementById("plot1"); var y = document.getElementById("btn1"); if (x.style.display === "none") { x.style.display = "block"; y.innerHTML = "Plot 1 verstecken"; } else { x.style.display = "none"; y.innerHTML = "Plot 1 anzeigen"; } } function Collapse2() { var x = document.getElementById("plot2"); var y = document.getElementById("btn2"); if (x.style.display === "none") { x.style.display = "block"; y.innerHTML = "Plot 2 verstecken"; } else { x.style.display = "none"; y.innerHTML = "Plot 2 anzeigen"; } } function Collapse3() { var x = document.getElementById("plot3"); var y = document.getElementById("btn3"); if (x.style.display === "none") { x.style.display = "block"; y.innerHTML = "Plot 3 verstecken"; } else { x.style.display = "none"; y.innerHTML = "Plot 3 anzeigen"; } } </script> <body> <header> <div class="container"> <div id="branding"> <h1 id="header">MT Cars Datenanalyse</h1> </div> <nav class="right"> <h2>Dies ist ein renderHTML Beispiel</h2> </nav> </div> </header> <section id="input"> <div class="container"> <div class="row"> <div class="col-sm-6 col-md-6"> <h2>Input Gewicht</h2> {{ slider }} <p>Mit diesem Slider kannst du das Gewicht verändern</p> </div> <div class="col-sm-6 col-md-6"> <h2>Optionen</h2> <a href="#" class="btn" onclick="Collapse1()" id="btn1">Plot 1 verstecken</a> <a href="#" class="btn" onclick="Collapse2()" id="btn2">Plot 2 verstecken</a> <a href="#" class="btn" onclick="Collapse3()" id="btn3">Plot 3 verstecken</a> </div> </div> </div> </section> <section id="showcase"> <div class="container"> <h1>Hier sind die verschiedenen Grafiken</h1> <div class="row"> <div class="col-sm-4 col-md-4" id="plot1"> {{plot1}} </p> </div> <div class="col-sm-4 col-md-4" id="plot2"> {{plot2}} </p> </div> <div class="col-sm-4 col-md-4" id="plot3"> {{plot3}} </p> </div> </div> <p>Dies ist eine Shiny App, die über renderHTML erstellt wurde (incl. "fancy" CSS Animationen ;-) )</p> </div> </section> <footer> <p>Markus Lang, Copyright © 2017</p> </footer> </body> </html>
In dieser HTML Datei wird die Struktur der Web App festgelegt. Diese enthält neben reichlich HTML auch ein paar Zeilen Internal Javascript, mit dem sich die die Diagramme ein- und ausblenden lassen. Das wichtigste in dieser Datei ist jedoch die Funktionsweise, mit der die in der ui.R Datei die Variablen an das Template übergeben werden. Jede template.html muss im Kopf (<head> … /<head>) die Funktion {{ headContent() }} enthalten. Damit werden die für Shiny benötigte Depedencies beim Rendern geladen. Diese übrigen, in der ui.R Datei deklarierten Variablen, werden ebenfalls mittels zwei geschweiften Klammern an das Template übergeben.
body{ font-family: Arial, Helvetica, sans-serif; font-size: 15px; line-height: 1.5; padding:0; margin:0; background-color:#f4f4f4; } /*Global*/ .container{ width: 80%; margin:auto; overflow:hidden; } ul{ margin:0; padding:0; } header{ background:#35424a; color:#ffffff; padding-top:30px; min-height:70px; border-bottom:#e8491d 3px solid; } header #branding{ float:left; } header #branding h1{ animation-name: myanimation; position: relative; animation-duration: 4s; } @keyframes myanimation { 0% {top: -200px;} 100% {top: 0px;} } @keyframes myanimation2 { 0% {left: -1000px;} 100% {left: 0px;} } header .right h2{ padding-bottom: 20px; animation-name: myanimation; position: relative; animation-duration: 4s; } header nav{ float:right; margin-top:10px; } h2{ align-content:center; } #showcase{ background-color: #c5dee2; background-size:cover; background-position:center; height:55vh; display:flex; flex-direction:column; justify-content: center; align-items:center; text-align:center; margin-bottom: 10px; } #showcase h1{ margin-top:30px; font-size:30px; margin-bottom:10px; } .btn{ display: inline-block; background-color: black; color:white; text-decoration:None; padding: 0.7rem 2rem; padding-bottom:20px; margin-right: 20px; opacity: 0; animation-name: button; animation-duration: 2s; animation-delay: 3s; animation-fill-mode: forwards; transition-property: transform; transition-duration: 1s; } .btn:hover{ background-color:#e8491d; color:white!important; } @keyframes button{ 0% {opacity:0} 100% {opacity: 1} } footer{ background-color:#e8491d; text-align: center; margin-top:20px; padding:20px; color:#ffffff; bottom:0; } p{ font-weight: bold; }
Nun muss für das Styling der App nur doch eine CSS-Datei geladen werden. Wichtig ist zu beachten, dass externe CSS Dateien bei Shiny immer in einem gesonderten Ordner mit dem Namen „www“ abgespeichert werden müssen. Auf diesen Ordner wird in der HTML Datei nicht gesondert verwiesen. Es reicht der Verweis <link rel=’stylesheet’ href=’style.css’/>.
Für den Upload der Datei müssen server.R, ui.R und template.html auf einer Ebene liegen, während wie bereits erwähnt die CSS Datei in einem gesonderten Ordner namens „www“ abliegen muss.
Die Web App liegt unter folgendem Link ab: https://markuslang1987.shinyapps.io/CustomShiny/
Einiges an der App ist sicherlich Spielerei, der Artikel soll in erster Linie aber die Möglichkeiten zeigen, die man mit einem selbst erstellten HTML Template im Gegensatz zu den recht eingeschränkten Möglichkeiten der normalen Shiny Programmierung zur Verfügung hat. Außerdem möchte ich mit diesem Artikel zeigen, dass Webentwicklung und Data Science/Analytics nicht zwangsläufig komplett voneinander unabhängige Welten sind.