`level <-` (Sihir apa ini?

114

Dalam jawaban untuk pertanyaan lain, @Marek memposting solusi berikut: 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)
  )

Yang menghasilkan sebagai keluaran:

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

Ini hanyalah cetakan dari sebuah vektor, jadi untuk menyimpannya Anda dapat melakukan yang lebih membingungkan:

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

Jelas ini adalah semacam panggilan ke fungsi level, tapi saya tidak tahu apa yang dilakukan di sini. Apa istilah untuk sihir jenis ini, dan bagaimana cara meningkatkan kemampuan sihir saya di domain ini?

Ari B. Friedman
sumber
1
Ada juga names<-dan [<-.
huon
1
Juga, saya bertanya-tanya tentang hal ini di pertanyaan lain tetapi tidak bertanya: apakah ada alasan untuk structure(...)konstruksi daripada hanya data.frame(product = c(11L, 11L, ..., 8L))? (Jika ada keajaiban terjadi di sana, saya ingin menggunakannya juga!)
hu
2
Ini adalah panggilan ke "levels<-"fungsi function (x, value) .Primitive("levels<-"):, semacam suka X %in% Yadalah singkatan dari "%in%"(X, Y).
BenBarnes
2
@dbaupp Sangat berguna untuk contoh yang dapat direproduksi: stackoverflow.com/questions/5963269/…
Ari B. Friedman
8
Saya tidak tahu mengapa seseorang memilih untuk menutup ini sebagai tidak konstruktif? Q memiliki jawaban yang sangat jelas: apa arti dari sintaks yang digunakan dalam contoh dan bagaimana cara kerjanya di R?
Gavin Simpson

Jawaban:

104

Jawabannya di sini bagus, tetapi mereka melewatkan satu poin penting. Izinkan saya mencoba menjelaskannya.

R adalah bahasa fungsional dan tidak suka mengubah objeknya. Tapi itu memungkinkan pernyataan penugasan, menggunakan fungsi pengganti:

levels(x) <- y

setara dengan

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

Triknya adalah, penulisan ulang ini dilakukan dengan <-; itu tidak dilakukan oleh levels<-. levels<-hanyalah fungsi biasa yang mengambil masukan dan memberikan keluaran; itu tidak mengubah apapun.

Salah satu konsekuensinya adalah, menurut aturan di atas, <-harus rekursif:

levels(factor(x)) <- y

adalah

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

adalah

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

Agak indah bahwa transformasi fungsional murni ini (hingga akhir, di mana penugasan terjadi) setara dengan apa yang akan dilakukan penugasan dalam bahasa imperatif. Jika saya ingat dengan benar konstruksi ini dalam bahasa fungsional disebut lensa.

Tetapi kemudian, setelah Anda menentukan fungsi pengganti seperti levels<-, Anda mendapatkan rejeki nomplok lain yang tidak terduga: Anda tidak hanya memiliki kemampuan untuk membuat tugas, Anda memiliki fungsi praktis yang memperhitungkan faktor, dan memberikan faktor lain dengan level yang berbeda. Benar-benar tidak ada "tugas" tentang itu!

Jadi, kode yang Anda gambarkan hanya memanfaatkan interpretasi lain ini levels<-. Saya akui bahwa namanya levels<-agak membingungkan karena menunjukkan suatu tugas, tetapi bukan ini yang terjadi. Kode tersebut hanya menyiapkan semacam pipeline:

  • Dimulai dari dat$product

  • Ubah menjadi faktor

  • Ubah levelnya

  • Simpan itu di res

Secara pribadi, saya pikir baris kode itu indah;)

Owen
sumber
33

Tidak ada sihir, begitulah (sub) fungsi penugasan didefinisikan. levels<-sedikit berbeda karena primitif untuk (sub) menetapkan atribut faktor, bukan elemen itu sendiri. Ada banyak contoh dari jenis fungsi ini:

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

Operator biner lain juga bisa disebut seperti itu:

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

Sekarang setelah Anda mengetahuinya, sesuatu seperti ini seharusnya benar-benar membuat Anda terkagum-kagum:

Data <- data.frame(x=1:10, y=10:1)
names(Data)[1] <- "HI"              # How does that work?!? Magic! ;-)
Joshua Ulrich
sumber
1
Dapatkah Anda menjelaskan lebih banyak tentang kapan masuk akal untuk memanggil fungsi dengan cara itu, daripada cara yang biasa? Saya sedang mengerjakan contoh @ Marek dalam pertanyaan terkait, tetapi penjelasan yang lebih eksplisit akan membantu.
Drew Steen
4
@DrewSteen: untuk alasan kejelasan kode / keterbacaan, menurut saya itu tidak pernah masuk akal karena `levels<-`(foo,bar)sama dengan levels(foo) <- bar. Menggunakan contoh @ Marek: `levels<-`(as.factor(foo),bar)sama dengan foo <- as.factor(foo); levels(foo) <- bar.
Joshua Ulrich
Daftar yang bagus. Tidakkah menurutmu levels<-itu benar-benar hanya singkatan attr<-(x, "levels") <- value, atau setidaknya mungkin sampai itu berubah menjadi primitif dan diserahkan ke C-code.
IRTFM
30

Alasan untuk "keajaiban" itu adalah bahwa formulir "tugas" harus memiliki variabel nyata untuk dikerjakan. Dan factor(dat$product)itu tidak ditugaskan untuk apa pun.

# 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
sumber
+1 Saya pikir akan lebih bersih untuk mengonversi ke faktor terlebih dahulu, kemudian mengganti level melalui panggilan within()dan transform()jika objek yang dimodifikasi demikian dikembalikan dan ditetapkan.
Gavin Simpson
4
@GavinSimpson - Saya setuju, saya hanya menjelaskan keajaiban, saya tidak mempertahankannya ;-)
Tommy
16

Untuk kode pengguna, saya bertanya-tanya mengapa manipulasi bahasa seperti itu digunakan begitu? Anda bertanya sihir apa ini dan yang lain telah menunjukkan bahwa Anda memanggil fungsi pengganti yang memiliki nama itu levels<-. Bagi kebanyakan orang ini adalah sihir dan sebenarnya tujuan penggunaan adalah levels(foo) <- bar.

Kasus penggunaan yang Anda tunjukkan berbeda karena producttidak ada di lingkungan global sehingga hanya pernah ada di lingkungan lokal panggilan levels<-sehingga perubahan yang ingin Anda buat tidak bertahan - tidak ada penugasan ulang dat.

Dalam keadaan ini, within() adalah fungsi yang ideal untuk digunakan. Anda tentu ingin menulis

levels(product) <- bar

di R tapi tentu saja producttidak ada sebagai objek. within()mengatasi ini karena itu mengatur lingkungan yang Anda inginkan untuk menjalankan kode R Anda dan mengevaluasi ekspresi Anda dalam lingkungan itu. Menetapkan objek yang dikembalikan dari panggilan ke within()dengan demikian berhasil dalam bingkai data yang dimodifikasi dengan benar.

Berikut ini contohnya (Anda tidak perlu membuat yang baru datX- saya hanya melakukannya agar langkah perantara tetap berada di akhir)

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

Pemberian yang mana:

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

Saya kesulitan untuk melihat bagaimana konstruksi seperti yang Anda tunjukkan berguna dalam sebagian besar kasus - jika Anda ingin mengubah data, mengubah data, jangan membuat salinan lain dan mengubahnya (bagaimanapun juga semua levels<-panggilan itu lakukan ).

Gavin Simpson
sumber