2011-07-12 35 views
84

Tôi đang sử dụng hàm ifelse() để thao tác với một vectơ ngày tháng. Tôi mong đợi kết quả là của lớp Date, và đã rất ngạc nhiên khi nhận được một vector numeric thay thế. Dưới đây là một ví dụ:Cách ngăn ifelse() chuyển đối tượng Ngày thành đối tượng số

dates <- as.Date(c('2011-01-01', '2011-01-02', '2011-01-03', '2011-01-04', '2011-01-05')) 
dates <- ifelse(dates == '2011-01-01', dates - 1, dates) 
str(dates) 

Điều này đặc biệt đáng ngạc nhiên vì thực hiện các hoạt động trên toàn bộ vector trả về một đối tượng Date.

dates <- as.Date(c('2011-01-01', '2011-01-02', '2011-01-03', '2011-01-04','2011-01-05')) 
dates <- dates - 1 
str(dates) 

Tôi có nên sử dụng một số chức năng khác để hoạt động trên Date vectơ không? Nếu có thì chức năng gì? Nếu không, làm cách nào để buộc ifelse trả về một vectơ cùng loại với đầu vào?

Trang trợ giúp cho ifelse cho biết đây là một tính năng chứ không phải lỗi, nhưng tôi vẫn đang cố gắng tìm giải thích cho những gì tôi thấy là hành vi đáng ngạc nhiên.

+4

Có bây giờ là một chức năng 'if_else() 'trong gói dplyr có thể thay thế cho' ifelse' trong khi giữ lại lớp học đúng của các đối tượng ngày - đó là [được đăng bên dưới] (http://stackoverflow.com/a/38093096/4470365) như một câu trả lời gần đây. Tôi đang chú ý đến nó ở đây vì nó giải quyết vấn đề này bằng cách cung cấp một hàm được kiểm tra đơn vị và được ghi lại trong gói CRAN, không giống như nhiều câu trả lời khác (như nhận xét này) được xếp hạng trước. –

Trả lời

51

Bạn có thể sử dụng dplyr::if_else.

Từ dplyr 0.5.0 release notes: "[if_else] có ngữ nghĩa chặt chẽ hơn mà ifelse():. Các truefalse đối số phải là cùng loại này đưa ra một kiểu trả về ít ngạc nhiên, và bảo tồn vectơ S3 như ngày".

library(dplyr) 
dates <- if_else(dates == '2011-01-01', dates - 1, dates) 
str(dates) 
# Date[1:5], format: "2010-12-31" "2011-01-02" "2011-01-03" "2011-01-04" "2011-01-05" 
+1

Chắc chắn hữu ích ngay cả khi nó làm tôi mất dấu kiểm. Phiên bản hiện tại của trang trợ giúp không nói những gì mong đợi từ các đối số yếu tố. Phiếu bầu của tôi sẽ dành cho đối tượng trả về yếu tố có các mức là sự kết hợp của các mức 'true' và' false'. –

+1

Có cách nào để có một đối số của 'if_else' là NA không?Tôi đã cố gắng hợp lý 'NA_' tùy chọn và không có gì là gắn bó và tôi không tin rằng có một' NA_double_' – roarkz

+2

@Zak Một khả năng là để bọc 'NA' trong' as.Date'. – Henrik

55

Nó liên quan đến tài liệu Value của ifelse:

Một vector của cùng độ dài và thuộc tính (bao gồm cả kích thước và "class") như test và các giá trị dữ liệu từ các giá trị của yes hoặc no. Phương thức của câu trả lời sẽ được ép buộc từ lô-gic để chứa trước bất kỳ giá trị nào được lấy từ yes và sau đó bất kỳ giá trị nào được lấy từ no.

Đun sôi theo ý nghĩa của nó, ifelse làm cho các yếu tố bị mất cấp và ngày mất lớp và chỉ chế độ của họ ("số") được khôi phục. Hãy thử điều này thay vì:

dates[dates == '2011-01-01'] <- dates[dates == '2011-01-01'] - 1 
str(dates) 
# Date[1:5], format: "2010-12-31" "2011-01-02" "2011-01-03" "2011-01-04" "2011-01-05" 

Bạn có thể tạo một safe.ifelse:

safe.ifelse <- function(cond, yes, no){ class.y <- class(yes) 
            X <- ifelse(cond, yes, no) 
            class(X) <- class.y; return(X)} 

safe.ifelse(dates == '2011-01-01', dates - 1, dates) 
# [1] "2010-12-31" "2011-01-02" "2011-01-03" "2011-01-04" "2011-01-05" 

Một lưu ý sau: Tôi thấy rằng Hadley đã xây dựng một if_else vào khu phức hợp các magrittr/dplyr/tidyr các gói dữ liệu định hình .

+30

Phiên bản trang nhã hơn: 'safe.ifelse <- function (cond, yes, no) structure (ifelse (cond, yes, no), class = class (yes))' – hadley

+5

Tốt. Bạn có thấy lý do nào đó không phải là hành vi mặc định? –

+7

Không, tôi không hiểu tại sao ifelse hoạt động theo cách đó. – hadley

12

Giải thích của DWin được bật. Tôi fiddled và chiến đấu với điều này trong một thời gian trước khi tôi nhận ra tôi chỉ đơn giản là có thể buộc các lớp sau khi tuyên bố ifelse:

dates <- as.Date(c('2011-01-01','2011-01-02','2011-01-03','2011-01-04','2011-01-05')) 
dates <- ifelse(dates=='2011-01-01',dates-1,dates) 
str(dates) 
class(dates)<- "Date" 
str(dates) 

Lúc đầu này cảm thấy một chút "hackish" đối với tôi. Nhưng bây giờ tôi chỉ nghĩ về nó như là một mức giá nhỏ để trả cho lợi nhuận hiệu suất mà tôi nhận được từ ifelse(). Cộng với nó vẫn còn ngắn gọn hơn nhiều so với một vòng lặp.

5

Phương pháp được đề xuất không hoạt động với các cột yếu tố. Id muốn đề nghị cải tiến này:

safe.ifelse <- function(cond, yes, no) { 
    class.y <- class(yes) 
    if (class.y == "factor") { 
    levels.y = levels(yes) 
    } 
    X <- ifelse(cond,yes,no) 
    if (class.y == "factor") { 
    X = as.factor(X) 
    levels(X) = levels.y 
    } else { 
    class(X) <- class.y 
    } 
    return(X) 
} 

Bằng cách này: ifelse sucks ... với sức mạnh lớn đến trách nhiệm lớn lao, tức là loại chuyển đổi của ma trận 1x1 và/hoặc numerics [khi họ nên được bổ sung ví dụ] là ok với tôi nhưng chuyển đổi loại này trong ifelse rõ ràng là không mong muốn.Tôi tình cờ gặp rất giống 'lỗi' của ifelse nhiều lần bây giờ và nó chỉ cần giữ trên ăn cắp thời gian của tôi :-(

FW

+0

Đây là giải pháp duy nhất phù hợp với tôi về các yếu tố. – bshor

+0

Tôi đã nghĩ rằng các mức được trả về sẽ là sự kết hợp của các mức 'có' và 'không' và trước tiên bạn sẽ kiểm tra xem chúng có phải cả hai yếu tố hay không. Bạn có lẽ sẽ cần phải chuyển đổi thành ký tự và sau đó rebundle với các "-unionized" -levels. –

5

Câu trả lời được cung cấp bởi @ fabian-Werner là rất tốt, nhưng đối tượng có thể có nhiều lớp học, và "yếu tố" có thể không nhất thiết phải là người đầu tiên trả về bởi class(yes), vì vậy tôi đề nghị sửa đổi nhỏ này để kiểm tra tất cả các lớp thuộc tính:

safe.ifelse <- function(cond, yes, no) { 
     class.y <- class(yes) 
     if ("factor" %in% class.y) { # Note the small condition change here 
     levels.y = levels(yes) 
     } 
     X <- ifelse(cond,yes,no) 
     if ("factor" %in% class.y) { # Note the small condition change here 
     X = as.factor(X) 
     levels(X) = levels.y 
     } else { 
     class(X) <- class.y 
     } 
     return(X) 
    } 

tôi cũng đã gửi yêu cầu với đội ngũ phát triển R để thêm một tài liệu tùy chọn để có cơ sở :: ifelse() bảo tồn các thuộc tính dựa trên lựa chọn người dùng của các thuộc tính để bảo tồn . Yêu cầu là ở đây: https://bugs.r-project.org/bugzilla/show_bug.cgi?id=16609 - Nó đã được gắn cờ là "WONTFIX" với lý do nó luôn luôn là cách nó bây giờ, nhưng tôi đã cung cấp một đối số tiếp theo về lý do tại sao một bổ sung đơn giản có thể tiết kiệm rất nhiều R người dùng nhức đầu. Có lẽ "+1" của bạn trong chuỗi lỗi đó sẽ khuyến khích nhóm R Core xem xét lại. EDIT: Đây là một phiên bản tốt hơn cho phép người dùng chỉ định các thuộc tính cần lưu giữ, "cond" (hành vi ifelse mặc định), "có", hành vi theo mã ở trên hoặc "không", trong trường hợp các thuộc tính của giá trị "không" tốt hơn:

safe_ifelse <- function(cond, yes, no, preserved_attributes = "yes") { 
    # Capture the user's choice for which attributes to preserve in return value 
    preserved   <- switch(EXPR = preserved_attributes, "cond" = cond, 
                   "yes" = yes, 
                   "no" = no); 
    # Preserve the desired values and check if object is a factor 
    preserved_class  <- class(preserved); 
    preserved_levels <- levels(preserved); 
    preserved_is_factor <- "factor" %in% preserved_class; 

    # We have to use base::ifelse() for its vectorized properties 
    # If we do our own if() {} else {}, then it will only work on first variable in a list 
    return_obj <- ifelse(cond, yes, no); 

    # If the object whose attributes we want to retain is a factor 
    # Typecast the return object as.factor() 
    # Set its levels() 
    # Then check to see if it's also one or more classes in addition to "factor" 
    # If so, set the classes, which will preserve "factor" too 
    if (preserved_is_factor) { 
     return_obj   <- as.factor(return_obj); 
     levels(return_obj) <- preserved_levels; 
     if (length(preserved_class) > 1) { 
      class(return_obj) <- preserved_class; 
     } 
    } 
    # In all cases we want to preserve the class of the chosen object, so set it here 
    else { 
     class(return_obj) <- preserved_class; 
    } 
    return(return_obj); 

} # End safe_ifelse function 
+1

'thừa kế (y," yếu tố ")' có thể "chính xác hơn" so với "yếu tố"% trong% class.y' –

+0

Thực vậy. 'kế thừa' có thể là tốt nhất. –

4

Lý do tại sao điều này không hiệu quả là vì hàm ifelse() chuyển đổi giá trị thành các thừa số. Cách giải quyết tốt nhất là chuyển đổi nó thành ký tự trước khi đánh giá nó.

dates <- as.Date(c('2011-01-01','2011-01-02','2011-01-03','2011-01-04','2011-01-05')) 
dates_new <- dates - 1 
dates <- as.Date(ifelse(dates =='2011-01-01',as.character(dates_new),as.character(dates))) 

này sẽ không yêu cầu bất kỳ thư viện ngoài R. cơ sở

Các vấn đề liên quan