Skip to Tutorial Content

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…

  1. das leaRn-Tutorial absolviert haben.
  2. das RAPP-Tutorial 1, eigene Apps in RAPP anlegen, absolviert haben und…
  3. die Pakete shiny, learn und rmarkdown in Ihrer R-Version installiert haben.

Um eine App anzulegen, können Sie verschiedene Möglichkeiten wählen:

  1. 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:
  2. Sie wählen File\(\rightarrow\)New File\(\rightarrow\)R Markdown und wählen dann From Template und anschliessend Interactive 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='...'), die inputID ist hierbei die Bezeichnung für die Eingabemaske und label 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 mit input$... aufgerufen werden: Der Name, welcher der User eingibt, ist dann in unserem Fall input$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('...') und uiOutput('...'). Wie man die Output-Elemente benennt ist gleichgültig. Wenn Sie nun obigen Code in Ihre App 1 einfügen und mit Run 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 mit output$... definiert. Da es sich hierbei um ein Text-Baustein handeln wird, wird die Funktion renderText({}) 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 Funktion renderUI({}).
  • 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 Befehl rnorm(n,0,1) simuliert werden, wobei n die Anzahl zu simulierenden Werte darstellt
  • Es werden zwei Vektoren, x<-rnorm(n,0,1) und e<-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 mit plotOutput() der App gesagt, dass ein reaktives Element vom Typ plot 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-Funktion render... erzeugt (wir kennen nun renderText, renderUI und renderPlot)
  • 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.frame df gespeichert.
  • Es wird mit ggplot() ein Plot unter p gespeichert. Für eine Einführung in ggplot() 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 mit return(p) mit nur einer Befehlszeile plot(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.

Steuerelemente in Apps