Datenstrukturen für Tabellen

Wie rufe ich R-Funktionen auf, wie selektiere ich Daten, ich weiß nicht genau ....

Moderatoren: EDi, jogo

Antworten
bigben
Beiträge: 2771
Registriert: Mi Okt 12, 2016 9:09 am

Datenstrukturen für Tabellen

Beitrag von bigben »

Hallo Leute,

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:
Tabelle1.png
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)
Verkürzen könnte man das, indem man die Zeile mit if und die Spalte mit cut abbildet:

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.
Tabelle3.png
---
Programmiere stets so, dass die Maxime Deines Programmierstils Grundlage allgemeiner Gesetzgebung sein könnte
Athomas
Beiträge: 768
Registriert: Mo Feb 26, 2018 8:19 pm

Re: Datenstrukturen für Tabellen

Beitrag von Athomas »

Sehe ich das richtig, dass Du das mehrfache Vorkommen von Werten in einer Tabelle als "unglaublich viel Redundanz" bezeichnest :lol: ?
bigben
Beiträge: 2771
Registriert: Mi Okt 12, 2016 9:09 am

Re: Datenstrukturen für Tabellen

Beitrag von bigben »

Ja. War wohl schlecht ausgedrückt. Wenn ich sagen kann, dass jedes w_s < 20 zum Ergebnis 100 führt, ist es dann sinnvoll, in 14 Spalten jeweils den Wert 100 einzutragen? Ist das nicht Redundanz? Ist das nicht mit einem if besser ausgedrückt?

LG,
Bernhard
---
Programmiere stets so, dass die Maxime Deines Programmierstils Grundlage allgemeiner Gesetzgebung sein könnte
Antworten