`livelli <-` (Che stregoneria è questa?

In una risposta ad un’altra domanda, @Marek ha pubblicato la seguente soluzione: 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) ) 

Che produce come output:

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

Questa è solo la stampa di un vettore, quindi per memorizzarlo puoi fare anche più confusione:

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

Chiaramente questa è una sorta di chiamata alla funzione dei livelli, ma non ho idea di cosa si stia facendo qui. Qual è il termine per questo tipo di stregoneria, e come posso aumentare la mia abilità magica in questo dominio?

Le risposte qui sono buone, ma mancano di un punto importante. Fammi provare e descriverlo.

R è un linguaggio funzionale e non gli piace mutare i suoi oggetti. Ma consente le dichiarazioni di assegnazione, utilizzando le funzioni di sostituzione:

 levels(x) < - y 

è equivalente a

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

Il trucco è che questa riscrittura è fatta da < - ; non è fatto dai levels< - . levels< - è solo una funzione regolare che prende un input e dà un output; non muta nulla.

Una conseguenza di ciò è che, secondo la regola precedente, < - deve essere ricorsivo:

 levels(factor(x)) < - y 

è

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

è

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

È bello che questa trasformazione puramente funzionale (fino alla fine, dove avviene l'incarico) sia equivalente a ciò che un incarico sarebbe in un linguaggio imperativo. Se ricordo bene, questo costrutto in linguaggi funzionali è chiamato lente.

Ma poi, una volta definite le funzioni di sostituzione come i levels< - , ottieni un altro colpo inaspettato: non hai solo la possibilità di fare assegnazioni, hai una funzione a portata di mano che prende in considerazione un fattore e dà un altro fattore con diversi livelli. Non c'è davvero niente "assegnazione" a riguardo!

Quindi, il codice che stai descrivendo si sta solo servendo di quest'altra interpretazione dei levels< - . Ammetto che i levels< - nome levels< - sono un po 'confusi perché suggeriscono un compito, ma questo non è quello che sta succedendo. Il codice sta semplicemente impostando una sorta di pipeline:

  • Inizia con il dat$product

  • Convertilo in un fattore

  • Cambia i livelli

  • Memorizzalo in res

Personalmente, penso che la linea di codice sia bella;)

La ragione di questa “magia” è che il modulo “assegnazione” deve avere una variabile reale su cui lavorare. E il factor(dat$product) non è stato assegnato a nulla.

 # 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) ) 

Nessuna stregoneria, ecco come vengono definite le (sotto) funzioni di assegnazione. levels< - è un po 'diverso perché è un primitivo (sub) assegnare gli attributi di un fattore, non gli elementi stessi. Ci sono molti esempi di questo tipo di funzione:

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

Altri operatori binari possono essere chiamati così:

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

Ora che lo sai, qualcosa del genere dovrebbe davvero farti saltare la testa:

 Data < - data.frame(x=1:10, y=10:1) names(Data)[1] <- "HI" # How does that work?!? Magic! ;-) 

Per il codice utente mi chiedo perché tali manipolazioni del linguaggio siano utilizzate così? Chiedete che cos’è la magia e altri hanno sottolineato che state chiamando la funzione di sostituzione con i levels< - nome levels< - . Per la maggior parte delle persone questa è magia e in realtà l'uso previsto è levels(foo) < - bar .

Il caso d'uso che mostri è diverso perché il product non esiste nell'ambiente globale quindi esiste sempre e solo nell'ambiente locale della chiamata ai levels< - quindi la modifica che vuoi fare non persiste - non c'è stata alcuna riassegnazione di dat .

In queste circostanze, within() è la funzione ideale da utilizzare. Vorrebbe naturalmente scrivere

 levels(product) < - bar 

in R ma ovviamente il product non esiste come object. within() aggira questo perché imposta l'ambiente in cui desideri eseguire il tuo codice R e valuta la tua espressione all'interno di quell'ambiente. Assegnando l'object di ritorno dalla chiamata a within() quindi, si ottiene il frame di dati opportunamente modificato.

Ecco un esempio (non è necessario creare un nuovo datX - lo faccio solo così i passaggi intermedi rimangono alla fine)

 ## 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)) 

Che dà:

 > 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 ... 

Fatico a vedere come i costrutti come quello che mostri siano utili nella maggior parte dei casi - se vuoi cambiare i dati, cambiare i dati, non creare un'altra copia e cambiarla (che è tutti i levels< - chiamata è facendo dopotutto).