Präsentation für MHP
An die empfohlene Struktur eines R-Pakets anlehnen (vgl. Buch R Packages).
Basisdateien und Ordner eines R-Pakets:
DESCRIPTION: Basis-Informationen zur AppNAMESPACE: Definition des NamensraumsR: Quellcode, wird automatisch geladenman: Hilfe/Benutzerhandbuch (automatisch mit dem Paket Roxygen2 erstellen)test: Unit TestsEin Shiny-Projekt sollte zusätzlich mindestens folgende Dateien enthalten:
server.Rui.Rglobal.Rdevtools::check oder devtools::test.Beim Start der App werden die Dateien und Ordner in folgender Reihenfolge geladen:
global.RRui.Rserver.RDas automatische Laden des R-Ordners kann mit dem Befehl options(shiny.autoload.r = FALSE) deaktiviert werden.
Server und UI immer in zwei getrennten Dateien ablegen!
Im Buch Advanced R werden Richtlinien für gutes Code Styling gegeben. Diese Richtlinien sind als tidyverse style guide auch online verfügbar.
Grundsätzlich müssen alle Dateien logisch benannt werden:
Dateinamen im R-Ordner
Dateinamen (genau wie Code) immer auf Englisch benennen! Notfalls kurz Google-Translator bemühen. Das erleichtert die Arbeit für andere Entwickler ungemein.
Bei Shiny Modulen wird die UI immer in einer vom Server getrennten Datei abgelegt. Die UI-Code-Datei endet immer mit _ui.R; die Server-Code-Datei endet immer mit _server.R.
Für allgemeine Regelen wie Schreibweise der Dateien siehe The tidyverse style guide - Files.
Grundsätzlich gilt: Immer selbsterklärende Namen benutzen.
Hadley Wickham empfielt in seinem Buch Advanced R, Variablen und Funktionen klein zu schreiben und alle Worte mit einem Unterstrich zu trennen (siehe The tidyverse style guide - Syntax).
Beispiele:
In anderen Programmiersprachen wie Java hat sich allerdings Camel Case durchgesetz:
Camel Case
Beispiele:
dayOne <- 1
incrementValue <- function(value) {value + 1}
getIncrementedValue <- function(value) {value + 1}Meine Empfehlung ist ganz klar Camel Case!
Konstanten werden übrigens in beiden Fällen groß geschrieben und Worte werden mit Unterstrich getrennt, z.B. C_START_VALUE <- 1.
Sofern selbsterklärende Namen benutzt werden, sind zusätzliche Kommentare bei ausschließlich intern verwendeten Funktionen und Variablen in der Regel nicht nötig.
Wenn etwas im Code dokumentiert werden muss, dann im Roxygen2-Stil, siehe The tidyverse style guide - Documentation.
RStudio: Addins ansehen und starten
RStudio: Sytler mit einem Tastenkürzel verknüpfen
Wichtigste Regel: Don’t repeat yourself!
Lösung mit Hilfe von
R Code sollte bis auf ganz wenige Ausnahmen nicht mit einem eval(parse(text = "myRCodeAsCharacterString") Aufruf ausgeführt werden.
Das erschwert den Navigation im Code für alle Entwickler unnötig, macht den Code undurchsichig und fehleranfällig.
Außerdem ist die Performance schlecht. Das lässt sich z.B. anhand des folgenden einfachen Beispiels zeigen:
library(rbenchmark)
evalParseDemo <- function(dynamicList) {
eval(parse(text = "param <- paste0(sample(5), collapse = '')"))
eval(parse(text = "value <- paste0(sample(5), collapse = '')"))
eval(parse(text = paste0("dynamicList$p", param, " <- ", value)))
return(dynamicList)
}
rDemo <- function(dynamicList) {
param <- paste0(sample(5), collapse = '')
value <- paste0(sample(5), collapse = '')
dynamicList[[paste0("p", param)]] <- value
return(dynamicList)
}
dynamicList1 <- list()
dynamicList2 <- list()print(kable(benchmark(
dynamicList1 <- rDemo(dynamicList1),
dynamicList2 <- evalParseDemo(dynamicList2),
replications = 100000
), format = "pipe"))| test | replications | elapsed | relative | user.self | sys.self | user.child | sys.child |
|---|---|---|---|---|---|---|---|
| dynamicList1 <- rDemo(dynamicList1) | 1e+05 | 1.95 | 1.00 | 1.95 | 0 | NA | NA |
| dynamicList2 <- evalParseDemo(dynamicList2) | 1e+05 | 11.33 | 5.81 | 11.22 | 0 | NA | NA |
eval-parse-Code ist in diesem Beispiel etwa um den Faktor 5 langsamer als die gleichen Operationen in reinem R.
R ist bei Schleifen extrem langsam.
library(Rcpp)
cppFunction(
"
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
NumericVector longRunningFunctionCpp(int size = 1000) {
NumericVector result = NumericVector(size, NA_REAL);
for (int i = 0; i < size; i++) {
result[i] = sqrt(i + pow(i, 2));
}
return result;
}
"
)
longRunningFunctionRLoop <- function(size) {
result <- c()
for (i in 1:size) {
result <- c(result, sqrt(i + i^2))
}
return(result)
}
longRunningFunctionSApply <- function(size) {
return(sapply(1:size, function(i) {sqrt(i + i^2)}))
}Schleife mit 100 Iterationen:
print(kable(benchmark(
longRunningFunctionCpp(100),
longRunningFunctionRLoop(100),
longRunningFunctionSApply(100),
replications = 200
), format = "pipe"))| test | replications | elapsed | relative | user.self | sys.self | user.child | sys.child |
|---|---|---|---|---|---|---|---|
| longRunningFunctionCpp(100) | 200 | 0.00 | NA | 0.00 | 0 | NA | NA |
| longRunningFunctionRLoop(100) | 200 | 0.01 | NA | 0.02 | 0 | NA | NA |
| longRunningFunctionSApply(100) | 200 | 0.03 | NA | 0.04 | 0 | NA | NA |
Schleife mit 1.000 Iterationen:
print(kable(benchmark(
longRunningFunctionCpp(1000),
longRunningFunctionRLoop(1000),
longRunningFunctionSApply(1000),
replications = 100
), format = "pipe"))| test | replications | elapsed | relative | user.self | sys.self | user.child | sys.child |
|---|---|---|---|---|---|---|---|
| longRunningFunctionCpp(1000) | 100 | 0.00 | NA | 0.00 | 0 | NA | NA |
| longRunningFunctionRLoop(1000) | 100 | 0.22 | NA | 0.21 | 0 | NA | NA |
| longRunningFunctionSApply(1000) | 100 | 0.10 | NA | 0.10 | 0 | NA | NA |
Schleife mit 10.000 Iterationen:
print(kable(benchmark(
longRunningFunctionCpp(10000),
longRunningFunctionRLoop(10000),
longRunningFunctionSApply(10000),
replications = 10
), format = "pipe"))| test | replications | elapsed | relative | user.self | sys.self | user.child | sys.child |
|---|---|---|---|---|---|---|---|
| longRunningFunctionCpp(10000) | 10 | 0.00 | NA | 0.00 | 0 | NA | NA |
| longRunningFunctionRLoop(10000) | 10 | 1.61 | NA | 1.55 | 0 | NA | NA |
| longRunningFunctionSApply(10000) | 10 | 0.11 | NA | 0.11 | 0 | NA | NA |
Siehe Efficient optimisation im Buch Efficient R programming.
Siehe z.B. Testing R Code.
Unbenutzte Funktionen identifizieren:
#install.packages("mvbutils")
library(mvbutils)
setwd("D:/R/external/Otartq_einzelansicht")
# start the app to load all functions into the workspace
# analyse functions
result <- foodweb(plotting = FALSE)
# get the number of times each function is called
res <- sapply(rownames(result$funmat), function(n) try(length(callers.of(n, fw = result))))
# get those functions that are never called
names(res[res==0])
Copyright © 2022 by Dr. Friedrich Pahlke. All rights reserved.