-
Notifications
You must be signed in to change notification settings - Fork 2
/
10_automatisierte-reports.Rmd
317 lines (256 loc) · 19.3 KB
/
10_automatisierte-reports.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
## **Reportautomatisierung**
[![License: CC BY 4.0](https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/deed.de)
*"Reportautomatisierung" von Lisa Reiber in "R Lernen - der Datenkurs von und für die Zivilgesellschaft" von CorrelAid e.V. Lizensiert unter [Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0/deed.de).*
![*Video: Reportautomatisierung (20min)*](https://youtu.be/78r2ugQ6bdQ)
### **Kernaussagen**
- Berichte in R Markdown anzulegen, bedeutet, **reproduzierbare Reports** zu erstellen
- So können...
a) Reports leicht **aktualisiert**,
b) **verschiedene Layoutformate** gleichzeitig und
c) **eine hohe Anzahl** von Berichten pro **Ausprägung einer Entität** (z.B. Länder und Zeiträume) erstellt werden
- In dieser **Flexibilität** von R Markdowns liegt auch ihr Mehrwert
- Technisch ist dafür zumeist das Verwenden von **Parametern** (zu dt. auch Übergabewerte) notwendig
- Parameter sind Variablen, die in Computerprogrammen **verschiedene Ausprägungen** annehmen können und für die Art und Weise der **Verarbeitung** von Informationen in Routinen relevant sind (z.B. wenn individuelle Reports für verschiedene Beobachtungen erstellt werden)
- Parameter werden im **YAML-Abschnitt** von R Markdown Dokumenten definiert
- Sie können über eine Anpassung des YAML-Abschnitt selbst oder innerhalb der **Renderfunktion** `rmarkdown::render("MyDocument.Rmd", params = ...)` adaptiert werden
- Für Nutzer:innen kann zudem die Auswahl über **interaktive Menüs** ermöglicht werden (**Menü-Option** `Knit -> Knit with parameters` oder `rmarkdown::render(input = ..., params = "ask")`)
- Innerhalb des R Markdowns kann auf die selbst benannten Parameter über `params$parameter_name` **zugegriffen** werden
- Möglich ist die Festlegung von Parametern von den folgenden **Typen**: `character` (Text), `integer` (ganze Zahl), `numeric` (Zahl), `logical` (logischer Ausdruck) und Ausdrücke in R
### **Quiz**
```{r 11quiz_reportauto}
quiz(caption = NULL,
question("R Markdowns sind reproduzierbar. Das hat den Vorteil, dass...",
answer("sie leicht aktualisiert werden können.", correct = TRUE),
answer("in verschiede Layoutformate übertragen werden können.", correct = TRUE),
answer("sie für verschiedene Szenarien erstellt werden können.", correct = TRUE),
answer("problemlos auf völlig andere Daten übertragen werden können."),
correct = "Richtig!",
incorrect = "Leider falsch: Die Operationen, die Ihr auf Basis von Daten durchführt, sind in den meisten Fällen auf den Datensatz angestimmt. Daher ist es shr wahrscheinlich, dass der gleiche Code nicht mit einem völlig anderen Datengrundlage funktioniert. Die Struktur der Daten sollte deshalb konstant sein, insbesondere wenn Ihr den Bericht parametrisieren wollt.",
allow_retry = TRUE,
try_again_button = "Nochmal versuchen"
),
question("Der Mehrwert von R Markdowns liegt in...",
answer("der leichten Anpassungsfähigkeit ihrer Designs."),
answer("ihrer Reproduzierbarkeit.", correct = TRUE),
answer("ihrem niedrigschwelligen User Interface für nicht so tech-affine Nutzer:innen."),
correct = "Richtig! Glücklicherweise gibt es tolle Layouttemplates und die Möglichkeit der interaktiven Parametrisierung durch GUIs, die es uns vereinfachen, Reports anschaulich und nutzerinnenfreundlich zu gestalten.",
incorrect = "Leider falsch: Das Layout von R Markdowns muss oft in HTML und CSS codiert werden. Deshalb nutzen wir in der Regel Templates. Teilen wir außerdem R Markdowns direkt (also nicht das Outputformat) ist auch die Nutzung in RStudio gar nicht so leicht - wie Ihr ja bereits wisst. Da müssen wir also Zeit in das Set-up und die gute Erklärung des Tools stecken.",
allow_retry = TRUE,
try_again_button = "Nochmal versuchen"
),
question("Parameter sind...",
answer("Platzhalter, die es ermöglichen einen Wert einmal zu definieren, und ihn an verschiedenen Stellen im Code zu übergeben.", correct = TRUE),
answer("eine Maßeinheit der Performance in der Computerwissenschaft."),
correct = "Richtig! Glücklicherweise müssen wir in der Computerwissenschaft keine 100 Parameter-Sprints laufen.",
incorrect = "Leider falsch: Glücklicherweise müssen wir in der Computerwissenschaft keine 100 Parameter-Sprints laufen.",
allow_retry = TRUE,
try_again_button = "Nochmal versuchen"
),
question("Wie können Parameter gesetzt und übergeben werden?",
answer("Über den YAML-Header, optional: Mit GUI", correct = TRUE),
answer("In einer beliebigen Stelle im Code"),
answer("Über die Render-Funktion, optional: Mit GUI", correct = TRUE),
correct = "Richtig! Dann kann es ja losgehen...",
incorrect = "Leider falsch: Parameter werden über den YAML-Header oder die Render-Funktion gesetzt.",
allow_retry = TRUE,
try_again_button = "Nochmal versuchen"
)
)
```
### Interaktive Übung
#### Schritt 0: Set-up
Zunächst ist es wichtig, die richtige Infrastruktur für Eure Reports zu schaffen. Eine Reportautomatisierung besteht aus:
- einem **Reporttemplate in R Markdown**, in dem der Inhalt des Reports, der Code, und die Parameter im YAML-Abschnitt definiert werden (reporttemplate.Rmd)
- einem **Ausführungsskript**, das die render-Funktion zur automatischen Erstellung der Reports enthält. Hier können verschiedene Optionen für den Prozess der Reportgenerierung eingestellt werden. Außerdem können Parameter Werte definiert werden, die dann über die Platzhalter Parameter sogar in den Code des R Markdown Dokuments einfließen können und somit den generierten Report beeinflussen. (diese Dateien werden oft render.R oder render.Rmd genannt. Wir verwenden report_factory.Rmd)
Eingebettet sollten die beiden Codeskripte in einem **R-Projekt** sein. Wie bereits im Kapitel "Einführung in R(Studio)" erklärt, helfen diese als metaphorischer Umzugskarton unsere Infrastruktur zusammenzuhalten. Ein R-Projekt, das alle enthaltenen Dateien einbettet, erleichtert die Übergabe der Werte zwischen den Dateien. Nützlich sind zudem die **Ordner "daten"**, in dem alle Daten hinterlegt sind, sowie der **Ordner "out"**, in dem alle erzeugten Reports gespeichert werden.
#### Schritt 1: Parameter definieren (und ggf. Standardwert setzen)
Um Parameter zu nutzen, müssen diese **immer** im YAML-Header definiert werden. Der folgende YAML-Header legt als Titel "Mein Report", als Autor:in "Maxi Mustermensch", als Datum den heutigen Tag (`r params$heute`) und als Output HTML fest. Darüber setzen wir noch eigene Parameter (params). Jede Zeile folgt der Logik eines **Key-Value-Paares** (zu dt. Schlüssel-Wert-Paar). Die Bezeichnung des Wertes steht also vor dem Doppelpunkt, der Wert danach (`parameter_name(key): parameter_wert(value)`). Dieser Wert ist Euer Standardwert bei der Ausführung des Codeskripts. Wenn der Wert frei bleibt, wird er automatisch auf `NULL` gesetzt.
---
title: "Mein Report"
author: "Maxi Mustermensch"
date: "`r format(Sys.Date(), '%d. %B %Y')`"
output: html_document
params:
heute: !r format(Sys.Date(), '%d. %B %Y')
daten: "daten/audit.csv"
filter_plastic: "hdpe"
filter_continent: "alle Kontinente"
---
Ausgewählt haben wir hier als Parameter das Ausstellungsdatum des Reports (den aktuellen Tag), Daten, Plastiktyp und einen Parameter mit diversen Ausprägungen (Kontinente), da dies Weichenstellungen sind, die wir bei der Erstellung und Aktualisierung von Reports häufig austauschen.
*Anmerkung: Achtung bei der Nutzung von R-Funktionen in den allgemeinen YAML-Headereinstellungen und den Parametern. Die Syntax unterscheidet sich (siehe oben).*
#### Schritt 2: Parameter einbinden
Parameter, die im YAML-Abschnitt definiert wurden, stehen als **Liste namens `params`** zur Verfügung. Um auf einen Parameter im Code zuzugreifen, benutzt Ihr `params$parameter_key`. Auf den Parameterwert könnt Ihr sowohl in Code Chunks als auch im Inhalt des R Markdowns zugreifen. Schauen wir uns das mal am Beispiel des Parameter "heute" an:
````
Das heutige Datum ist: `r knitr::inline_expr('params$heute')`
````
Beim Rendern des R Markdowns wird also der Platzhalter `params$heute` durch das Datum ersetzt, welches im YAML-Abschnitt definiert wurde. Aus dem Code wird also:\
````
Das heutige Datum ist: `r params$heute`.
````
Genauso funktioniert das Ganze auch für Code:
```{r, parameter_call, exercise = TRUE}
# Zugriff auf den Parameter "heute"
print(params$heute)
```
Druckt hier den als Parameter definierten Filter für Plastikart (`filter_plastic`).
``` {r 11quiz_parameter_call, exercise = TRUE}
# Hier Euer Code!
```
```{r 11quiz_parameter_call-solution}
# Zugriff auf den Parameter "filter_plastic"
print(params$filter_plastic)
```
```{r 11quiz_parameter_call-check}
grade_this_code()
```
<!-- Die Parameter können wir dann nutzen, um Daten einzulesen... -->
<!-- ```{r parameter_daten, exercise = TRUE} -->
<!-- # Daten mit Parameter laden -->
<!-- rio::import(here::here(params$daten), fill=TRUE) -->
<!-- ``` -->
Die Parameter können wir dann nutzen, um Daten zu filtern (wir nutzen hier den vorbereiteten Datensatz `audit`).
```{r parameter_filtern, exercise = TRUE}
# Laden des Audit-Datensatzes
audit <- rio::import(
here::here("daten/bffp2019_audit_by_country_and_company.csv")
)
```
```{r 11quiz_1-parameter_filtern, exercise = TRUE}
# Filtern des Audit-Datensatzes
audit %>%
dplyr::filter(plastic_type == params$filter_plastic)
```
Filtert nun zusätzlich den Kontinent (hinterlegt als `filter_continent`):
```{r 11quiz_parameter_filter, exercise = TRUE}
# Filtern des Audit-Datensatzes (hinterlegt als )
audit %>%
dplyr::filter(plastic_type == params$filter_plastic)
# Euer Code hier
```
```{r 11quiz_parameter_filter-solution}
# Filtern des Audit-Datensatzes
audit %>%
dplyr::filter(plastic_type == params$filter_plastic,
continent == params$filter_continent)
```
```{r 11quiz_reportauto_filter}
quiz(caption = NULL,
question("Wie viele Beobachtungen werden gefunden?",
answer("2886"),
answer("0", correct = TRUE),
answer("178"),
correct = "Richtig!",
incorrect = "Leider falsch.",
allow_retry = TRUE,
try_again_button = "Nochmal versuchen"
),
question("Warum ist das der Fall?",
answer("Es gibt für den Kontinent keine Beobachtungen.", correct = TRUE),
answer("Das muss ein Fehler sein..."),
correct = 'Richtig! Wir haben den Parameter "filter_continent" mit dem Standardwert "alle Kontinente" definiert. So einen Kontinent gibt es im Datensatz natürlich überhaupt nicht. Es ist aber ein guter Trick, denn so könnt Ihr eine Bedingung einbauen: Nur wenn der Parameter spezifiziert ist (ungleich "alle Kontinente"), wird ein spezieller Report angelegt. Sonst wird der Report für den ganzen Datensatz erzeugt. Cool, oder?',
incorrect = 'Leider falsch: Die Anzahl der Zeilen (= Beobachtungen beträgt 0, ablesbar in Description:df [0 × 16]. Wir haben den Parameter "filter_continent" mit dem Standardwert "alle Kontinente" definiert. So einen Kontinent gibt es im Datensatz natürlich überhaupt nicht. Es ist aber ein guter Trick, denn so könnt Ihr eine Bedingung einbauen: Nur wenn der Parameter spezifiziert ist (ungleich "alle Kontinente"), wird ein spezieller Report angelegt. Sonst wird der Report für den ganzen Datensatz erzeugt. Cool, oder?',
allow_retry = TRUE,
try_again_button = "Nochmal versuchen"
)
)
```
Umsetzen können wir die Option "alle Kontinente" übrigens, indem wir beispielsweise in der Datenbereinigung diese Option einbauen:
```{r filter_trick, exercise = TRUE}
### Filter für parametrisierte Reports einfügen
if (params$filter_continent != "alle Kontinente") {
audit <- audit %>% filter(continent == params$filter_continent)
}
# Head drucken
head(audit)
```
#### Schritt 3: Parameter anpassen
##### Option A1: YAML-Header (begrenzt praktikabel)
Falls Ihr den Report lediglich semi-automatisch ausführen wollt, könnt Ihr die Parameter **manuell im YAML-Header** jederzeit austauschen und anpassen. Der Report wird dann auf Basis der aktualisierten Parameter durchgeführt (Klickt einfach auf "Knit").
##### Option A2: YAML-Header mit GUI (begrenzt praktikabel)
Neben dem Knit-Button findet sich ein Menü, in dem "Knit with parameters..." ausgewählt werden kann. Daraufhin öffnet sich in R-Studio eine graphische Oberfläche, in der für die im R Markdown Dokument definierten Parameter eigene Werte eingegeben werden können.
![*Knitting mit Parametern - Standard (Screenshot)*](https://github.com/CorrelAid/lernplattform/blob/main/abbildungen/10_automatisierte-reports/knitwithparameters.png?raw=true){#id .class width=50% height=50%}
Das Layout der Parametereingabe könnt Ihr über den YAML-Header sogar
gestalten. Zur Verfügung steht Euch die freie Eingabe (Standard), Dateiuploards (nützlich für Daten), Slider (für numeriswche Werte oder Datumsangaben) und ein Dropdown-Menü zur Auswahl von Optionen. Außerdem könnt Ihr den Eingabefeldern individuelle Namen, ein Minimum und Maximum zuweisen.
---
title: "Mein Report"
author: "Maxi Mustermensch"
date: "`r format(Sys.Date(), '%d. %B %Y')`"
output: html_document
params:
heute: !r format(Sys.Date(), '%d. %B %Y')
daten: #Parametername
label: "Daten hochladen (CSV-Format):" #Beschreibung im Menü
value: "daten/audit.csv" #Standardwert
input: file #Layoutoption: File-Upload
filter_plastic:
value: "hdpe"
label: "Plastikart auswählen:"
choices: ["hdpe", "ldpe", "o", "pet", "pp", "ps", "pvc"] #Layoutoption: Einfachauswahl
filter_continent:
label: "Kontinent auswählen:"
value: "alle Kontinente"
input: select #Layoutoption: Einfachauswahl
choices: ["Amerikas", "Asien", "Afrika", "Europa", "Ozeanien", "alle Kontinente"]
---
![*Knitting mit Parametern - Menüoptionen (Screenshot)*](https://github.com/CorrelAid/lernplattform/blob/main/abbildungen/10_automatisierte-reports/knitwithparametersoptions.png?raw=true){#id .class width=50% height=50%}
##### Option B1: Die Render-Funktion mit GUI (begrenzt praktikabel)
Die andere Option, die wir Euch nicht vorenthalten möchten, ist das Starten des GUI über die `rmarkdown::render()`-Funktion. Ansonsten funktionier hier alles analog zu Option A2.
```r
rmarkdown::render("Report_Plastic.Rmd", params = "ask")
```
##### Option B2: Die Render-Funktion (Best Practice)
Am Besten - und wenn Ihr viele Reports auf einmal erstellen wollt - solltet Ihr nun die **Render-Datei** `render.R/render.Rmd` einrichten. Hauptbestandteil dieses Codescriptes ist die Render-Funktion:
```r
rmarkdown::render(
input = here::here("Report_Plastic.Rmd"), # Hier kommt Euer Reporttemplate hin
output_format = "html_document", # Hier legt Ihr das Outputformat fest
output_file = glue::glue("{lubridate::today()}_Report_{params$filter_continent}"), # Hier legt Ihr den Outputnamen fest
output_dir = "output" # Hier legt Ihr den Ordner fest, in dem Output gespeichert wird
params = list(filter_continent: "alle Kontinente") # Hier könnt Ihr die Parameterliste setzen
)
```
### **Exkurs: Die Reportfabrik**
Wollen wir viele Reports auf einmal erstellen, müssen wir die Render-Funktion in etwas einbetten, das definiert, dass der Report für die verschiedenen Werte in einer Liste gerendert werden soll. Wir nutzen dazu die `purrr::walk`-Funktion. Sie bedeutet: Für jedes Element der Liste, wende die Funktion an, die hier definiert wird:
```r
purrr::walk(liste, function(listenelement) {
beliebige_function(beliebiges_argument = bezug_listenelement)
})
```
Wir wollen für den [Report Ausblick](https://drive.google.com/file/d/1vjfbibPdMfXuw0HQHL_0p4MeGO_kcYP2/view?usp=share_link){target="_blank"}, der bereits den Trick mit "alle Kontinente" enthält, nun ein `render.R`-Skript erstellen, das auch zulässt, dass wir für jeden Kontinent einen Report erstellen. Dazu importierten wir im `render.R`-Skript den Audit-Datensatz und erstellen - analog zum Report - den Audit-Datensatz. Da wir für jeden Kontinent einen Report anlegen wollen, benötigen wir eine **Liste** der Kontinente.
```r
# Liste mit Kontinenten erstellen
kontinente_liste <- unique(audit$continent)
```
Im Anschluss kombinieren wir die `purrr::walk()`-Funktion mit der `rmarkdown::render()`-Funktion.
```r
purrr::walk(
.x = kontinente_liste,
.f = ~rmarkdown::render(
# Hier kommt Euer Reporttemplate hin
input = here::here("11_reports-uebung_NH_Ausblick.Rmd"),
# Hier legt Ihr das Outputformat fest
output_format = "html_document",
# Hier legt Ihr den Outputnamen fest, der nun den Kontinent beinhaltet
output_file = glue::glue("{lubridate::today()}_Report_{.x}"),
# Hier legt Ihr den Ordner fest, in dem Output gespeichert wird
output_dir = "output"
# Hier könnt Ihr die Parameterliste setzen: Wir ändern nur den Parameter "Kontinent"
params = list(filter_continent: "alle Kontinente")
)
```
*Tipp 1: Erstellt das zugehörige Directory automatisch, falls es nicht exisiert.*
```r
output_dir <- here::here("output")
if (!dir.exists(output_dir)) {
dir.create(output_dir)
}
```
*Tipp 2: Ihr wollt die Reports ganz automatisch ohne Euer Zutun erzeugen, z.B. auf einem Server? Dafür nutzen wir CronJobs, die zeitbasiert Prozesse für uns ausführen. Wie üblich gibt es zum vereinfachten Management ein Package in R: [cron R Dokumentation](https://cran.r-project.org/web/packages/cronR/cronR.pdf){target="_blank"}*
Tolle Tipps, Tricks und mehr Input findet Ihr auf der [Website von R-Studio](https://rmarkdown.rstudio.com/lesson-6.html){target="_blank"}, in den englischsprachigen Büchern [Markdown: The Definite Guide, Kapitel 15](https://bookdown.org/yihui/rmarkdown/parameterized-reports.html){target="_blank"} und [R Markdown Cookbook, Kapitel 17.4](https://bookdown.org/yihui/rmarkdown-cookbook/parameterized-reports.html){target="_blank"} und bei [Dataquest](https://app.dataquest.io/course/intermediate-r){target="_blank"}. In dem verlinkten Kapitel findet Ihr insbesondere weitere Hilfestellungen zu Wenn-Dann-Bedingungen, Funktionen und Iterationen.
### **Und jetzt Ihr**
Wenn Ihr einen **eigenen Report** habt - den Ihr letzte Woche bereits in R erstellt habt - dann könnt Ihr jetzt versuchen, diesen mit Hilfe der `rmarkdown::render()` Funktion zu generieren und die Funktion so anzupassen, dass der Dateiname nach euren Wünschen Angepasst wird. Wenn Ihr nicht genug vom Report fabrizieren bekommen könnt oder Ihr keinen eigenen Report habt, dann schaut in den Ordner `10_automatisierte-reports/10_automatisiertereports` im [Übungsordner](https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/CorrelAid/lernplattform/tree/main/uebungen){target="_blank"}, öffnet das R Projekt und versucht die Aufgaben in `Report_Factory.Rmd` zu bearbeiten.
### **Zusätzliche Ressourcen**
- [RStudio Webinar: **Rethink Reporting with Automation**](https://www.rstudio.com/resources/webinars/rethink-reporting-with-automation/){target="_blank"}
- Yihui Xie et al. [**RMarkdown: The Definitive Guide**](https://bookdown.org/yihui/rmarkdown/parameterized-reports.html){target="_blank"} (engl.)
- RStudios [Hilfeseite](https://rmarkdown.rstudio.com/lesson-6.html){target="_blank"}
- [CorrelTalk: **Automatisierte Reportgenerierung für die Weltläden**](https://soundcloud.com/correlaid_podcast/about-correlaid-eine-interaktive-weltkarte-fur-erlassjahrde?utm_source=clipboard&utm_medium=text&utm_campaign=social_sharing){target="_blank"}