heute würde ich mich über Eure Einschätzung zur optimalen Repräsentation von Tabellen in R freuen. Und weil es bei "optimal" immer auf den konkreten Fall ankommt, habe ich zwei Beispiele dazu. Entnommen sind die nicht ganz zufällig aus den Empfehlungen zur Begutachtung der (Berufskrankheit) Lärmschwerhörigkeit. Hier braucht es eine Funktion, die verschiedene Messwerte als Argument annimmt und dann eine Minderung der Erwerbsfähigkeit oder einen Grad der Behinderung zurückgibt. Das geschieht händisch über ein paar einfache Rechenoperationen und das Nachschlagen in Tabellen, beispielsweise der hier dargestellten Tabelle 1:
Wenn man nun eine Funktion schreiben wollte, die erstmal in dieser Tabelle nachschlägt, wie würde man die Tabelle optimalerweise in R hinterlegen?
Die Anordnung in Zeilen und Spalten legt es nahe, die Daten in einer Matrix oder in einem Array abzulegen, aber wenn Ihr auf die Kriterien für die Zeilen achtet seht ihr schon, dass diese erstmal mit einem Sprung von 0 auf 20, dann in 15er-Schritten und dann in 25er-Schritten voranschreitet. Es wäre also bei einer solchen Matrix nicht ganz banal, in der richtigen Zeile nachzuschlagen. Das könnte man mit cut angehen oder bestimmt auch ganz anders. Dann stellt man schnell fest, dass sich in Zeilen und Spalten unglaublich viel Redundanz findet.
Da die Tabelle eine überschaubare Größe hat und sich nicht ändern wird, könnte ich mir tatsächlich eine große Folge von if-Verzweigungen vorstellen. Das ist maximal uneleganter Anfängerpfusch, aber letztlich verschwindet der hässliche Code in irgendeiner Funktion in einem package. Man könnte die Tabelle schön Zeile für Zeile durchgehen und es würde meines Erachtens gar nicht so schlecht zu lesen und ggf zu korrigieren sein:
Code: Alles auswählen
pHV <- function(ws, HVZ){
if(ws < 20) return(100)
if(ws < 35){ # ws ab 20
if(HVZ < 75)
return(95)
else
return(100)
}
if(ws < 50){ # ws ab 35
if(HVZ < 65)
return(90)
if(HVZ < 75)
return(95)
else
return(100)
# ...
}
}
#Tests
library(testthat)
expect_equal(pHV(25, 25), 95)
expect_equal(pHV(0, 0), 100)
expect_equal(pHV(45, 65), 95)
Code: Alles auswählen
pHV <- function(ws, HVZ){
if(ws < 20) return(100)
if(ws < 35) # ws ab 20
return(c(95, 100)[as.integer(cut(HVZ, c(-1, 75, 101), right = FALSE))])
if(ws < 50) # ws ab 35
return(c(90, 95, 100)[as.integer(cut(HVZ, c(-1, 65, 80, 101), right = FALSE))])
if(ws < 75) # ws ab 50
return(c(80, 90, 95)[as.integer(cut(HVZ, c(-1, 60, 65, 80), right = FALSE))])
# ...
}
#Tests
library(testthat)
expect_equal(pHV(0, 0), 100)
expect_equal(pHV(20, 0), 95)
expect_equal(pHV(20, 75), 100)
expect_equal(pHV(25, 25), 95)
expect_equal(pHV(45, 65), 95)
Man könnte einen Klassifikationsbaum trainieren - eine Baumstruktur wäre wahrscheinlich im Lookup sehr schnell, aber dafür würde viel Funktionalität in einer Blackbox verschwinden.
Bestimmt gibt es noch tausend andere Ansätze, wie man das angehen könnte. Vielleicht wird es ja eine ganz fruchtbare Diskussion wenn jeder mal schreibt, welche sinnvollen ihm einfallen, welche Ihr umsetzen würdet und warum.
In der Hoffnung auf einen spannenden Austausch,
Bernhard
Anhang
Gütekriterien:
1. Recheneffizienz ist immer gut - aber niemand hat tausend solcher Datensätze an einem Tag zu verarbeiten. Es macht also keinen Unterschied, ob ein Algorithmus O(n) oder O(n^2) ist, ob er etwas in 10 Nano-Sekunden oder in 50 Millisekunden nachschlägt.
2. Richtigkeit ist absolut führend. Die Datenstruktur/der Algorithmus, der sich am einfachsten fehlerfrei umsetzen lässt oder der am leichtesten auf Übertragungsfehlerfreiheit zu prüfen ist, ist der beste. Sofern alles andere einigermaßen vernünftig ist.
3. Weitergabe: Ich habe mit solchen Gutachten heute fast nichts mehr zu tun. Sowas umzusetzen würde nur lohnen, wenn man es dann auch als package weitergibt. Dann wäre es natürlich schön, wenn man den Empfänger eines entsprechenden package nicht nötigt, tausend umfangreiche Pakete herunterzuladen.
Google findet ein package "hashmap", was vielversprechend klingt. Es ist aber nicht über Version 0.2 hinausgekommen und wurde wohl letztes Jahr von CRAN gelöscht. Auch wenn Hashmaps grundsätzlich für Lookuptabellen super sein mögen, ist bestimmt klar geworden, dass ich sowas vermeiden wollte. Tricks, namespaces als Hashmap zu verwenden, wie hier beschrieben wären mir dagegen recht, wenn sie mit lesbarem Code einhergehen und sonst Vorteile bringen.
4. Universalität - mit den Ergebnissen dieser Tabelle geht es gleich in die nächste Tabelle. Als nachrangiges Ziel wäre es schön, ähnliche Implementierungen für alle Tabellen zu haben. Beispielhaft und zum Abschluss dieses Posts hänge ich Tabelle 3 noch mit an.