`level <-` (Đây là phép thuật gì vậy?


114

Trong câu trả lời cho một câu hỏi khác, @Marek đã đăng giải pháp sau: 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)
  )

Sản xuất dưới dạng đầu ra:

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

Đây chỉ là bản in của một vectơ, vì vậy để lưu trữ nó, bạn có thể làm điều khó hiểu hơn:

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

Rõ ràng đây là một số kiểu gọi hàm các cấp, nhưng tôi không biết điều gì đang được thực hiện ở đây. Thuật ngữ cho loại ma thuật này là gì, và làm cách nào để tăng khả năng phép thuật của tôi trong lĩnh vực này?


1
Ngoài ra còn có names<-[<-.
huon

1
Ngoài ra, tôi tự hỏi về điều này trong câu hỏi khác nhưng không hỏi: có lý do nào cho structure(...)cấu trúc thay vì chỉ data.frame(product = c(11L, 11L, ..., 8L))không? (Nếu có phép thuật nào đó xảy ra ở đó, tôi cũng muốn sử dụng nó!)
huon

2
Đó là một lời gọi đến "levels<-"hàm function (x, value) .Primitive("levels<-"):, đại loại như X %in% Ylà một chữ viết tắt của "%in%"(X, Y).
BenBarnes

2
@dbaupp Rất hữu ích cho các ví dụ có thể tái tạo: stackoverflow.com/questions/5963269/…
Ari B. Friedman

8
Tôi không biết tại sao ai đó đã bỏ phiếu để đóng điều này là không mang tính xây dựng? Câu hỏi Q có một câu trả lời rất rõ ràng: ý nghĩa của cú pháp được sử dụng trong ví dụ là gì và nó hoạt động như thế nào trong R?
Gavin Simpson

Câu trả lời:


104

Các câu trả lời ở đây là tốt, nhưng chúng đang thiếu một điểm quan trọng. Hãy để tôi thử và mô tả nó.

R là một ngôn ngữ chức năng và không thích thay đổi các đối tượng của nó. Nhưng nó cho phép các câu lệnh gán, sử dụng các hàm thay thế:

levels(x) <- y

tương đương với

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

Bí quyết là, việc viết lại này được thực hiện bởi <-; nó không được thực hiện bởi levels<-. levels<-chỉ là một hàm thông thường nhận đầu vào và đưa ra đầu ra; nó không đột biến bất cứ điều gì.

Một hệ quả của điều đó là, theo quy tắc trên, <-phải là đệ quy:

levels(factor(x)) <- y

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

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

Thật tuyệt vời khi sự chuyển đổi thuần-chức năng này (cho đến khi kết thúc, nơi mà nhiệm vụ xảy ra) tương đương với những gì một nhiệm vụ sẽ là trong một ngôn ngữ mệnh lệnh. Nếu tôi nhớ không nhầm thì cấu trúc này trong ngôn ngữ chức năng được gọi là thấu kính.

Nhưng sau đó, một khi bạn đã xác định các chức năng thay thế như levels<-, bạn nhận được một cơn gió bất ngờ khác: bạn không chỉ có khả năng thực hiện bài tập, bạn có một chức năng tiện dụng lấy một hệ số và đưa ra một hệ số khác với các cấp độ khác nhau. Thực sự không có gì "phân công" về nó!

Vì vậy, đoạn mã bạn đang mô tả chỉ đang sử dụng cách diễn giải khác này levels<-. Tôi thừa nhận rằng cái tên levels<-này hơi khó hiểu vì nó gợi ý một nhiệm vụ, nhưng đây không phải là điều đang diễn ra. Mã chỉ đơn giản là thiết lập một loại đường dẫn:

  • Bắt đầu với dat$product

  • Chuyển đổi nó thành một hệ số

  • Thay đổi cấp độ

  • Lưu trữ nó trong res

Cá nhân tôi nghĩ rằng dòng mã đó là đẹp;)


33

Không có phép thuật, đó chỉ là cách các hàm gán (phụ) được định nghĩa. levels<-hơi khác một chút vì nó là nguyên thủy để (con) gán các thuộc tính của một yếu tố, không phải chính các yếu tố đó. Có rất nhiều ví dụ về loại chức năng này:

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

Các toán tử nhị phân khác cũng có thể được gọi như vậy:

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

Bây giờ bạn biết điều đó, một cái gì đó như thế này thực sự sẽ làm bạn suy nghĩ:

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

1
Bạn có thể giải thích thêm một chút về thời điểm hợp lý khi gọi các hàm theo cách đó, thay vì cách thông thường? Tôi đang làm việc thông qua ví dụ của @ Marek trong câu hỏi được liên kết, nhưng sẽ hữu ích nếu có một lời giải thích rõ ràng hơn.
Drew Steen

4
@DrewSteen: vì lý do mã rõ ràng / dễ đọc, tôi sẽ nói rằng nó không bao giờ có ý nghĩa vì `levels<-`(foo,bar)nó giống như levels(foo) <- bar. Sử dụng ví dụ của @ Marek: `levels<-`(as.factor(foo),bar)giống như foo <- as.factor(foo); levels(foo) <- bar.
Joshua Ulrich

Danh sách đẹp. Bạn không nghĩ rằng levels<-nó thực sự chỉ là viết tắt của nó attr<-(x, "levels") <- value, hoặc ít nhất nó có thể là cho đến khi nó được chuyển thành một nguyên thủy và chuyển giao cho C-code.
IRTFM

30

Lý do cho sự "kỳ diệu" đó là biểu mẫu "gán" phải có một biến thực để làm việc. Và factor(dat$product)không được chỉ định cho bất cứ điều gì.

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

+1 Tôi nghĩ sẽ rõ ràng hơn nếu chuyển đổi sang hệ số trước, sau đó thay thế các cấp thông qua a within()transform()cuộc gọi là đối tượng được sửa đổi do đó được trả về và gán.
Gavin Simpson

4
@GavinSimpson - Tôi đồng ý, tôi chỉ giải thích điều kỳ diệu, tôi không bảo vệ nó ;-)
Tommy

16

Đối với mã người dùng, tôi tự hỏi tại sao các thao tác ngôn ngữ lại được sử dụng như vậy? Bạn hỏi đây là phép thuật gì và những người khác đã chỉ ra rằng bạn đang gọi hàm thay thế có tên levels<-. Đối với hầu hết mọi người, đây là ma thuật và thực sự mục đích sử dụng là như vậy levels(foo) <- bar.

Trường hợp sử dụng bạn hiển thị khác vì productkhông tồn tại trong môi trường toàn cầu nên nó chỉ tồn tại trong môi trường cục bộ của lệnh gọi levels<-do đó thay đổi bạn muốn thực hiện không tồn tại - không có sự chỉ định lại dat.

Trong những trường hợp này, within() là chức năng lý tưởng để sử dụng. Bạn sẽ tự nhiên muốn viết

levels(product) <- bar

trong R nhưng tất nhiên productkhông tồn tại như một đối tượng. within()giải quyết được điều này vì nó thiết lập môi trường bạn muốn chạy mã R của mình và đánh giá biểu thức của bạn trong môi trường đó. Như vậy, việc gán đối tượng trả về từ cuộc gọi đến within()sẽ thành công trong khung dữ liệu được sửa đổi đúng cách.

Đây là một ví dụ (bạn không cần tạo mới datX- tôi chỉ làm như vậy để các bước trung gian vẫn ở cuối)

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

Cái nào mang lại:

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

Tôi đấu tranh để xem các cấu trúc như bạn hiển thị hữu ích như thế nào trong phần lớn các trường hợp - nếu bạn muốn thay đổi dữ liệu, hãy thay đổi dữ liệu, đừng tạo một bản sao khác và thay đổi điều đó (suy cho cùng thì đó là tất cả những gì levels<-gọi là ).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.