Ziele
Ziel dieses Tutorial ist das Kennenlernen von shiny-Apps und das Verständnis der Grundstruktur von shiny-Apps. Im Wesentlichen bestehen diese aus einem ‘UI’ (User-Interface) und einem ‘Server’-Teil. Das User-Interface beinhaltet dabei die Grundstruktur der App, Eingabefelder und so weiter. Das User-Interface beinhaltet also alle statischen Teile einer App (welche für alle Nutzer später gleich sind), der Server-Teil hingegegen beinhaltet ‘dynamische’ Elemente, welche sich von Nutzer zu Nutzer unterscheiden können.
Um dieses Tutorial erfolgreich zu absolvieren, sollten Sie…
- das
leaRn
-Tutorial absolviert haben. - das
RAPP
-Tutorial 1, eigene Apps inRAPP
anlegen, absolviert haben und…
- die Pakete
shiny
,learn
undrmarkdown
in IhrerR
-Version installiert haben.
Um eine App anzulegen, können Sie verschiedene Möglichkeiten wählen:
- Sie wählen in
RStudio
File
\(\rightarrow\)New File
\(\rightarrow\)Shiny Web App
. Auf diese Weise erstellen Sie eine App ohne die Unterstützung von RMarkdown. Dies kann in der Tat Vorteile haben, wie wollen aber im Wesentlichen ja selbst Tutorials anlegen können (für die Lehre oder die Nutzung des Wissens von abgeschlossenen Forschungsprojekten) und wenden uns daher vor allem der Möglichkeit 2 zu:
- Sie wählen
File
\(\rightarrow\)New File
\(\rightarrow\)R Markdown
und wählen dannFrom Template
und anschliessendInteractive Tutorial
.
Erstellen Sie nun auf Ihrem Computer einen Ordner RAPP_Tutorial1
, oder APPs
(der Name ist nicht wichtig, nur sollten Sie sich diesen merken).
Anschliessend legen Sie sich bitte gemäss Methode 2 ein Tutorial mit dem Titel APP1
im vorhin erzeugten Ordner an. Sie dürfen alles aus dem Tutorial herauslöschen, bis auf die ersten zwölf Zeilen (zusätzlich haben wir im Titel bei APP1 noch ein Leerzeichen ‘APP 1’ eingefügt):

Gehen Sie nun bitte zum nächsten Kapitel.
APP 1 - Eingabemasken
Da Sie bis hierhin Ja ein RMarkdown-Dokument APP1.Rmd angelegt haben, dürfen Sie im Dokument nach Belieben schreiben, fast so, wie in Word. Sie können das File zum Beispiel ergänzen mit folgenden Inhalten:
## Meine erste shiny-APP
Wir programmieren unsere erste shiny-App. Anscheinend kann in RMarkdown 'fast' gleich wie in WORD geschrieben werden. Man kann sich Worte **fett** oder *kursiv* ausgeben lassen. Bevor wir uns aber Programmierung einer App zuwenden, müssen wir wissen, wie innerhalb eines RMarkdown-Files `R` aufgerufen werden kann. Dies geschieht durch folgenden Code:
```{r, message=F, warning=F, echo=F}
zufallszahl<-sample(1:1000,size=1)
```
Unsere Zufallszahl lautet `r zufallszahl`.
Offensichtlich werden also mit drei rückwärtsgerichteten Apostrophen ein R-Code-Chunk
erzeugt. Im Text nachher kann dann durch den Befehl `r zufallszahl`
in das im Code-Chunk erzeugte Objekt zufallszahl
ausgegeben werden.
Als Erstes wollen wir untenstehende App selbst programmieren (ohne die gelbe Markierung, diese markiert nur die Elemente von APP1):
BEGINN App 1
ENDE App 1
Unser-RMarkdown-File schaut nun wie folgt aus:

Wenn Sie dieses durch Klicken von Run Document
kompilieren, sollten Sie eine Zufallszahl im Text ausgegeben haben. Nur ist dies noch keine App, da die Zufallszahl nicht pro Benutzer (wenn die App später mal online ist) generiert wird, sondern alle Nutzer werden die gleiche Zufallszahl sehen. Um dies zu verstehen, wenden wir uns nun aber dem Programmieren der ersten eigentlichen App zu.
Offensichtlich hat die APP1 ein Eingabefeld ‘Ihr Name:’. Wenn Sie nun Ihren Namen eingeben, so erscheint offensichtlich ein Textfeld, welches Ihren Namen zurück gibt. Zudem gibt es gleich drei unterschiedliche Textfelder, wobei die ersten beiden anders formatiert sind und das dritte eine Zufallszahl ausgibt (die nun aber per Benutzer individuell ist), wir haben also genau genommen drei dynamische Elemente.
Dabei ist die Eingabemaske der ‘UI’-Teil, und die beiden Ausgabesätze, welche danach erscheinen, der ‘Server’-Teil der App. Genau genommen ist das Ausgabefeld selbst ebenfalls Teil des User-Interfaces, aber der INHALT hängt ja von Ihrer Eingabe ab und gehört somit in den ‘Server’-Teil.
Wir fügen zu unserer App nun das User-Interface (UI) hinzu:
```{r, message=F, warning=F, echo=F}
fluidRow(
column(width=3,textInput("name","Ihr Name:",value = ""))
)
textOutput("mytext1")
uiOutput("mytext2")
textOutput("mytext3")
```
Dies geschieht offensichtlich über einen gewöhnlichen RMarkdown-Code-Chunk. Die beiden wichtigsten Teile sind:
textInput(inputID=...,label='...',value='...')
, dieinputID
ist hierbei die Bezeichnung für die Eingabemaske undlabel
die anzuzeigende Beschriftung. Eine vollständige Liste aller verfügbaren Steuerelemente unter shiny findet sich hier. Alle Eingabemasken können später auf der Server Seite mitinput$...
aufgerufen werden: Der Name, welcher der User eingibt, ist dann in unserem Fallinput$name
.
- Aus den Inputs wollen wir natürlich Ausgaben generieren, die Outputs. Die sind ebenfalls Bestandteil des User-Interface. Im Code sind zwei Typen von Ausgaben angegeben:
textOutput('...')
unduiOutput('...')
. Wie man die Output-Elemente benennt ist gleichgültig. Wenn Sie nun obigen Code in Ihre App 1 einfügen und mitRun Document
kompilieren, so sollten Sie die Eingabeaufforderung mit ‘Ihr Name:’ sehen.
Unser-RMarkdown-File schaut nun wie folgt aus:

Nun fügen wir noch den Server-Teil hinzu:
```{r, context="server"}
output$mytext1<-renderText({
if(input$name==""){
return("")
}else{
return(paste("Dein Name ist also ",input$name,"!",sep=""))
}
})
output$mytext2<-renderUI({
if(input$name==""){
return("")
}else{
return(h2(paste("Dein Name ist also ",input$name,"!",sep="")))
}
})
output$mytext3<-renderText({
if(input$name==""){
return("")
}else{
return(paste("Die Zufallszahl lautet ",sample(1:1000,size=1),"!",sep=""))
}
})
```
Offensichtlich wird der Server-Teil mit einem RMarkdown-Code-Chunk eröffnet, wobei der Code context="server"
hinzugefügt wird.
- Der erste Block definiert das Output Element
mytext1
auf der Server-Seite. Alle Output-Elemente werden mitoutput$...
definiert. Da es sich hierbei um ein Text-Baustein handeln wird, wird die FunktionrenderText({})
aufgerufen. - Im zweiten Block wird das nächste Output Element
mytext2
definiert. Diesen Text wollen wir nun aber als HTML-Objekt ausgeben, dies geschieht in shiny über die FunktionrenderUI({})
. - Im dritten Block wird der Satz mit der Zufallszahl ausgegeben. Der Unterschied zum vorherigen Beispiel, wo wir ja ebenfalls eine Zufallszahl erzeugt hatten liegt nun daran, dass diese Zufallszahl hier im Server-Teil definiert wird und daher für alle Benutzer der App anders sein wird, dies war für die vorherige nicht der Fall, da sie im User-Interface definiert wurde.
Die vollständige App 1 kann (ev. auch zum Vergleich) nun auf dem RAPP-Server hochgeladen werden - allenfalls müssen Sie eine vorherig erzeugte App in ein Archiv verschieben oder vorübergehend löschen.
In der nächsten App, App 2, werden Sie nun weitere Steuerelemente kennen lernen. Gehen Sie nun bitte zum nächsten Kapitel.
APP 2 - Plots
Für die Entwicklung der weiteren Beispiel-Apps gehen Sie gleich vor wie für die Erstellung der App 1. Das heisst, im vorhin angelegten Ordner können Sie das File App1.Rmd kopieren, unbenennen in App2.Rmd, den Titel entsprechend ändern und die Dokumentinhalte bis auf den Dokument-Kopf löschen.
In diesem Tutorial wollen wir folgende App nachvollziehen. Ein User soll \(n\) Datenpunkte \(x\) und \(y\) simulieren (aus einer \(N(0,1)\)-Verteilung). Dabei kann der User die Anzahl Werte \(50\leq n \leq 500\) wählen (in 50er Schritten) und zusätzlich eine Korrelation \(-1 \leq r \leq +1\) eingeben (in 0.1-Schritte).
BEGINN App 2
ENDE App 2
Zur obenstehenden App noch einige kurze Erklärungen:
- In
R
können standard-normal verteilte Werte mit dem Befehlrnorm(n,0,1)
simuliert werden, wobein
die Anzahl zu simulierenden Werte darstellt - Es werden zwei Vektoren,
x<-rnorm(n,0,1)
unde<-rnorm(n,0,1)
simuliert. - Aus diesen zwei Vektoren wird der dritte Vektor \(y=r\cdot x+\sqrt{1-r^2}\cdot e\) gebildet. Danach ‘sollten’ die Vektoren \(x\) und \(y\) die Korrelation \(r\) aufweisen.
Sie können den Code für diese App 2 untenstehend herunterladen, falls Sie diese nicht ‘Schritt für Schritt’ selber bilden wollen:
Wir fügen zu unserer App nun den Kopf, das Aufrufen der Bibliotheken und das User-Interface (UI) hinzu: Die Bibliothek ggplot
, welche mit dem Befehl library(ggplot2)
aufgerufen wird, ist eine sehr mächtige Umgebung zum Erzeugen von Abbildungen. Sie braucht allerdings etwas Ausdauer zum Erlernen. Die Bibliothek ist natürlich auf dem RAPP-Server installiert. Falls Sie aber nicht sicher sind, ob sie dies tatsächlich ist, so loggen Sie sich ein in RStudio unter https://v000551.fhnw.ch/rstudio/ und versuchen dort, den Code library(ggplot2)
auszuführen. Erschiene eine Fehlermeldung, so ist die Bibliothek nicht installiert und Sie müssen diese bei RAPP beantragen.
---
title: "APP 2 - Plots"
output: learnr::tutorial
runtime: shiny_prerendered
---
```{r setup, include=FALSE}
library(learnr)
library(ggplot2)
knitr::opts_chunk$set(echo = FALSE)
```
## Meine zweite shiny-APP
Wir programmieren unsere zweite shiny-App.
```{r, message=F, warning=F, echo=F}
fluidRow(
column(width=4,sliderInput("nn","Anzahl Werte n:",min=50,max=500,step = 50,value=50)),
column(width=4,sliderInput("rr","Korrelation r:",min=-1,max=+1,step=0.1,value=0))
)
fluidRow(
column(width=12,plotOutput("myplot"))
)
```
- Mit
fluidRow()
wird sichergestellt, dass die ersten beiden Slider auf der gleichen Zeile (row) erscheinen. In shiny ist die Spaltenweite der Seite immer 12, eine Weite von 4 implementiert hier somit eine Breite pro Slider von einem Drittel der Seite. - Die zweite
fluidRow()
wäre hier nicht nötig, es ist aber generell gut, diese Elemente für das Platzieren der Inhalte der App zu nutzen. Hier wird mitplotOutput()
der App gesagt, dass ein reaktives Element vom Typplot
erscheinen soll.
Wir fügen nun den Server-Teil hinzu:
```{r,context="server"}
output$myplot<-renderPlot({
x<-rnorm(input$nn,0,1)
e<-rnorm(input$nn,0,1)
rr<-input$rr
y<-rr*x+sqrt(1-(rr)^2)*e
df<-data.frame(x=x,y=y)
p<-ggplot(data=df)+geom_point(aes(x=x,y=y),shape=21,fill="steelblue",color="black")+coord_equal()+scale_x_continuous(limits = c(-4,4))+scale_y_continuous(limits=c(-4,4))+
labs(caption=paste("Die simulierte Korrelation beträgt: r = ",round(cor(x,y),4),sep=""))
return(p)
})
```
- Wie schon bekannt werden Die Output-Elemente mit
output$...
ausgegeben und mit einer Wrap-Funktionrender...
erzeugt (wir kennen nunrenderText
,renderUI
undrenderPlot
) - Es werden zwei normal-verteilte Variablen \(x\) und \(e\) erzeugt, die Korrelation wird aus dem Input-Slider gelesen (
rr<-input$rr
). Danach wird die Variable \(y\) entsprechend der Rechenvorschrift von oben bestimmt, so dass die beiden Variablen \(x\) und \(y\) ‘ungefähr’ die eingegebene Korrelation ausweisen sollten. - Da ein Plot-Objekt aus
ggplot()
am besten mit data.frames funktioniert, werden die Variablen \(x\) und \(y\) in einem data.framedf
gespeichert. - Es wird mit
ggplot()
ein Plot unterp
gespeichert. Für eine Einführung inggplot()
wenden Sie sich bitte folgendem Tutorial zu (es lohnt sich): ggplot tutorial.
- Ist Ihnen dies zu kompliziert, und zu Beginn ist das Ziel ja das Funktionieren einer App, so ersetzen Sie in ‘ihrer’ App2 die Zeilen
df<-
bis und mitreturn(p)
mit nur einer Befehlszeileplot(x,y)
.
WICHTIG: Innerhalb der Funktion renderPlot()
werden die beiden Input-Parameter input$nn
und input$rr
addressiert. Das bedeutet, dass bei jeder Änderung dieser Inputs die gesamte Anweisung renderPlot()
erneut ausgeführt wird! Angenommen, die App wird gleichzeitig von 100 Usern benutzt und es müssen nicht nur zwei sondern vielleicht fünf Inputs voreingestellt werden, würde die Funktion renderPlot()
bei jedem User bei jeder Neueinstellung neu berechnet. Zudem wollen wir bei Änderung der Korrelation nicht, dass die Daten x
und e
neu simuliert werden, sondern nur die ‘Drehung’ der Punktwolke beobachten.
Erfahren Sie im nächsten Kapitel, wie die App verbessert wird.
APP 3 - bedingte Ausführung
In untenstehender App 3 ist nun neu ein Button ‘Neu berechnen’ hinzu gekommen. Stellen Sie nun die Korrelation auf \(-1\) und die Anzahl Werte \(n\) auf \(200\). Klicken Sie nun auf ‘neu berechnen’. Anschliessend können Sie auf die Play-Taste bei der Korrelation drücken. Sie sehen nun, wie sich die Punktwolke dreht, wenn die Korrelation sich von \(-1\) hin zu \(+1\) ändert, jedoch ohne die Daten neu zu simulieren.
Anschliessend setzen Sie die Korrelation auf \(-0.5\). Sie sehen, dass sich die Punktwolke sofort dreht, ohne dass Sie den Knopf ‘neu berechnen’ aktivieren müssen. Ändern Sie jedoch die Stichprobengrösse \(n\), so ändert sich die Punktwolke nicht, erst wenn Sie den Button ‘neu berechnen’ gedrückt haben.
BEGINN App 3
ENDE App 3
Wiederum legen Sie nun eine neues File ‘App3.Rmd’ an. Alternativ können Sie den Code auch hier holen:
Wir wollen nun die App 3 mit obig spezifiziertem Verhalten reproduzieren.
Als erstes wenden wir uns wieder dem UI zu. Nun ist ein sogenanner Action-Button hinzugekommen, welcher mit “Neu berechnen” angeschrieben und mit input$calculate
angesteuert werden kann.
---
title: "APP 3 - bedingte Ausführung"
output: learnr::tutorial
runtime: shiny_prerendered
---
```{r setup, include=FALSE}
library(learnr)
library(ggplot2)
knitr::opts_chunk$set(echo = FALSE)
```
## Meine dritte shiny-APP
Wir programmieren unsere dritte shiny-App.
```{r, message=F, warning=F, echo=F}
fluidRow(
column(width=4,sliderInput("nn","Anzahl Werte n:",min=50,max=500,step = 50,value=50)),
column(width=4,sliderInput("rr","Korrelation r:",min=-1,max=+1,step=0.1,value=0,animate = TRUE)),
column(width=4,actionButton("calculate","Neu berechnen"))
)
fluidRow(
column(width=12,plotOutput("myplot"))
)
```
Interessant wird es aber nun auf der Server-Seite. * Falls nur die Korrelation \(r\) im Input geändert wird, sollen die Vektoren \(e\) und \(x\) nicht erneut simuliert werden, sondern nur die Variable \(y\) neu berechnet werden. * Eine Änderung bei der Stichprobengrösse \(n\) soll keinen Effekt auf den Grafen haben, bevor der Action-Button gedrückt wurde.
```{r,context="server"}
observeEvent(input$calculate,{
x<-rnorm(input$nn,0,1)
e<-rnorm(input$nn,0,1)
output$myplot2<-renderPlot({
rr<-input$rr
y<-rr*x+sqrt(1-(rr)^2)*e
df<-data.frame(x=x,y=y)
p<-ggplot(data=df)+geom_point(aes(x=x,y=y),shape=21,fill="steelblue",color="black")+coord_equal()+scale_x_continuous(limits = c(-4,4))+scale_y_continuous(limits=c(-4,4))+
labs(caption=paste("Die simulierte Korrelation beträgt: r = ",round(cor(x,y),4),sep=""))
return(p)
})
})
```
Wir haben nun die von vorhin bekannte Struktur aus der App 2 in eine Funktion observeEvent({})
geschachtelt. Dies ist ein reaktiver Ausdruck, welcher auf ein Ereignis ‘wartet’. Das Ereignis, auf welches gewartet wird, ist der Action-Button, und zwar observeEvent(input$calculate,{...})
.
* Wichtig ist nun, dass die Vektoren \(x\) und \(y\) ausserhalb der Funktion renderPlot({})
angelegt werden. Warum? Diese beiden Vektoren hören auf das Steuerelement input$nn
. Dieses Steuerelement findet sich aber nirgends mehr innerhalb von renderPlot({})
. Das heisst, eine Betätigung von input$nn
hat ohne Auslösen des Action-Buttons keine Wirkung auf den Plot.
* Das Steuerelement der Korrelation findet sich aber nach wie vor innerhalb der Funktion renderPlot({})
, das heisst, bei Auslösen ds Steuerelementes wird die Funktion automatisch aktiviert und der Graph neu berechnet - der Action-Button muss hierfür nicht ausgelöst werden.
Aus Erfahrung bereitet das korrekte Benutzen von Steuerelementen, resp. deren bedingte Auslösung, sehr häufig Kopfzerbrechnen.