`Levels <-` (Was für eine Zauberei ist das?

114

In einer Antwort auf eine andere Frage hat @Marek die folgende Lösung veröffentlicht: https://stackoverflow.com/a/10432263/636656

dat <- structure(list(product = c(11L, 11L, 9L, 9L, 6L, 1L, 11L, 5L, 
                                  7L, 11L, 5L, 11L, 4L, 3L, 10L, 7L, 10L, 5L, 9L, 8L)), .Names = "product", row.names = c(NA, -20L), class = "data.frame")

`levels<-`(
  factor(dat$product),
  list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)
  )

Was als Ausgabe erzeugt:

 [1] Generic Generic Bayer   Bayer   Advil   Tylenol Generic Advil   Bayer   Generic Advil   Generic Advil   Tylenol
[15] Generic Bayer   Generic Advil   Bayer   Bayer  

Dies ist nur der Ausdruck eines Vektors. Um ihn zu speichern, können Sie das noch verwirrendere tun:

res <- `levels<-`(
  factor(dat$product),
  list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)
  )

Natürlich ist dies eine Art Aufruf der Level-Funktion, aber ich habe keine Ahnung, was hier gemacht wird. Was ist der Begriff für diese Art von Zauberei und wie erhöhe ich meine magischen Fähigkeiten in diesem Bereich?

Ari B. Friedman
quelle
1
Es gibt auch names<-und [<-.
Huon
1
Ich habe mich auch bei der anderen Frage darüber gewundert, aber nicht gefragt: Gibt es einen Grund für das structure(...)Konstrukt anstatt nur data.frame(product = c(11L, 11L, ..., 8L))? (Wenn dort etwas Magisches passiert, würde ich es auch gerne einsetzen!)
Huon
2
Es ist ein Aufruf der "levels<-"Funktion: Eine function (x, value) .Primitive("levels<-")Art Like X %in% Yist eine Abkürzung für "%in%"(X, Y).
BenBarnes
2
@dbaupp Sehr praktisch für reproduzierbare Beispiele: stackoverflow.com/questions/5963269/…
Ari B. Friedman
8
Ich habe keine Ahnung, warum jemand dafür gestimmt hat, dies als nicht konstruktiv zu schließen? Das Q hat eine sehr klare Antwort: Welche Bedeutung hat die im Beispiel verwendete Syntax und wie funktioniert dies in R?
Gavin Simpson

Antworten:

104

Die Antworten hier sind gut, aber es fehlt ein wichtiger Punkt. Lassen Sie mich versuchen, es zu beschreiben.

R ist eine funktionale Sprache und mutiert ihre Objekte nicht gern. Zuweisungsanweisungen mit Ersetzungsfunktionen sind jedoch zulässig:

levels(x) <- y

ist äquivalent zu

x <- `levels<-`(x, y)

Der Trick ist, dass dieses Umschreiben von durchgeführt wird <-; es wird nicht von gemacht levels<-.levels<-ist nur eine reguläre Funktion, die eine Eingabe nimmt und eine Ausgabe gibt; es mutiert nichts.

Eine Konsequenz davon ist, dass gemäß der obigen Regel <-rekursiv sein muss:

levels(factor(x)) <- y

ist

factor(x) <- `levels<-`(factor(x), y)

ist

x <- `factor<-`(x, `levels<-`(factor(x), y))

Es ist schön, dass diese rein funktionale Transformation (bis zum Ende, an dem die Aufgabe stattfindet) dem entspricht, was eine Aufgabe in einer imperativen Sprache wäre. Wenn ich mich richtig erinnere, wird dieses Konstrukt in funktionalen Sprachen als Linse bezeichnet.

Sobald Sie jedoch Ersatzfunktionen wie definiert haben levels<-, erhalten Sie einen weiteren, unerwarteten Zufall: Sie können nicht nur Zuweisungen vornehmen, sondern haben auch eine praktische Funktion, die einen Faktor berücksichtigt und einen anderen Faktor mit unterschiedlichen Ebenen ausgibt. Es gibt wirklich nichts "Aufgabe"!

Der Code, den Sie beschreiben, verwendet also nur diese andere Interpretation von levels<-. Ich gebe zu, dass der Name levels<-etwas verwirrend ist, weil er eine Aufgabe nahelegt, aber das ist nicht das, was vor sich geht. Der Code richtet einfach eine Art Pipeline ein:

  • Beginnen mit dat$product

  • Wandle es in einen Faktor um

  • Ändern Sie die Ebenen

  • Speichern Sie das in res

Persönlich finde ich diese Codezeile wunderschön;)

Owen
quelle
33

Keine Zauberei, so werden (Unter-) Zuweisungsfunktionen definiert. levels<-ist ein wenig anders, weil es ein Grundelement ist, die Attribute eines Faktors (unter) zuzuweisen, nicht die Elemente selbst. Es gibt viele Beispiele für diese Art von Funktion:

`<-`              # assignment
`[<-`             # sub-assignment
`[<-.data.frame`  # sub-assignment data.frame method
`dimnames<-`      # change dimname attribute
`attributes<-`    # change any attributes

Andere binäre Operatoren können auch so aufgerufen werden:

`+`(1,2)  # 3
`-`(1,2)  # -1
`*`(1,2)  # 2
`/`(1,2)  # 0.5

Jetzt, wo du das weißt, sollte dich so etwas wirklich umhauen:

Data <- data.frame(x=1:10, y=10:1)
names(Data)[1] <- "HI"              # How does that work?!? Magic! ;-)
Joshua Ulrich
quelle
1
Können Sie etwas mehr darüber erklären, wann es sinnvoll ist, Funktionen auf diese Weise aufzurufen, anstatt auf die übliche Weise? Ich arbeite an @ Mareks Beispiel in der verknüpften Frage, aber es wäre hilfreich, eine explizitere Erklärung zu haben.
Drew Steen
4
@DrewSteen: Aus Gründen der Codeklarheit / Lesbarkeit würde ich sagen, dass es niemals Sinn macht, weil `levels<-`(foo,bar)es dasselbe ist wie levels(foo) <- bar. Das Beispiel von @ Marek: `levels<-`(as.factor(foo),bar)ist dasselbe wie foo <- as.factor(foo); levels(foo) <- bar.
Joshua Ulrich
Schöne Liste. Glaubst du nicht, es levels<-ist wirklich nur eine Abkürzung dafür attr<-(x, "levels") <- value, oder zumindest war es wahrscheinlich so, bis es in ein Primitiv verwandelt und an C-Code übergeben wurde.
IRTFM
30

Der Grund für diese "Magie" ist, dass das "Zuweisungs" -Formular eine echte Variable haben muss, an der gearbeitet werden kann. Und das factor(dat$product)war nichts zugeordnet.

# This works since its done in several steps
x <- factor(dat$product)
levels(x) <- list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)
x

# This doesn't work although it's the "same" thing:
levels(factor(dat$product)) <- list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)
# Error: could not find function "factor<-"

# and this is the magic work-around that does work
`levels<-`(
  factor(dat$product),
  list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)
  )
Tommy
quelle
+1 Ich denke, es wäre sauberer, zuerst in Faktor umzuwandeln und dann die Ebenen über a within()und transform()call zu ersetzen, wenn das so modifizierte Objekt zurückgegeben und zugewiesen wird.
Gavin Simpson
4
@ GavinSimpson - Ich stimme zu, ich erkläre nur die Magie, ich verteidige sie nicht ;-)
Tommy
16

Für Benutzercode frage ich mich, warum solche Sprachmanipulationen so verwendet werden? Sie fragen, was Magie ist, und andere haben darauf hingewiesen, dass Sie die Ersatzfunktion mit dem Namen aufrufen levels<-. Für die meisten Menschen ist dies Magie und wirklich die beabsichtigte Verwendung ist levels(foo) <- bar.

Der angezeigte Anwendungsfall ist anders, da er productin der globalen Umgebung nicht vorhanden ist und daher immer nur in der lokalen Umgebung des Aufrufs vorhanden ist. levels<-Daher bleibt die gewünschte Änderung nicht bestehen - es gab keine Neuzuweisung von dat.

Unter diesen Umständen within() ist die ideale Funktion zu verwenden. Sie möchten natürlich schreiben

levels(product) <- bar

in R existiert aber natürlich productnicht als Objekt. within()Umgeht dies, weil es die Umgebung einrichtet, für die Sie Ihren R-Code ausführen möchten, und Ihren Ausdruck in dieser Umgebung auswertet. Das Zuweisen des Rückgabeobjekts aus dem Aufruf zu ist within()somit im ordnungsgemäß geänderten Datenrahmen erfolgreich.

Hier ist ein Beispiel (Sie müssen keine neuen erstellen datX- ich mache das einfach, damit die Zwischenschritte am Ende bleiben).

## one or t'other
#dat2 <- transform(dat, product = factor(product))
dat2 <- within(dat, product <- factor(product))

## then
dat3 <- within(dat2, 
               levels(product) <- list(Tylenol=1:3, Advil=4:6, 
                                       Bayer=7:9, Generic=10:12))

Welches gibt:

> head(dat3)
  product
1 Generic
2 Generic
3   Bayer
4   Bayer
5   Advil
6 Tylenol
> str(dat3)
'data.frame':   20 obs. of  1 variable:
 $ product: Factor w/ 4 levels "Tylenol","Advil",..: 4 4 3 3 2 1 4 2 3 4 ...

Ich habe Mühe zu sehen, wie Konstrukte wie das von Ihnen gezeigte in den meisten Fällen nützlich sind - wenn Sie die Daten ändern möchten, ändern Sie die Daten, erstellen Sie keine weitere Kopie und ändern Sie diese (was alles ist, was der levels<-Aufruf schließlich tut ).

Gavin Simpson
quelle