2011-07-12 27 views
84

我正在使用函數ifelse()來操作日期向量。我預計結果是類Date,並且很驚訝地得到一個numeric向量。下面是一個例子:如何防止將日期對象轉換爲數字對象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) 

這是特別令人驚訝,因爲在整個向量執行所述操作返回一個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) 

我應該使用一些其他功能來操作Date載體嗎?如果是這樣,什麼功能?如果不是,我該如何強制ifelse返回與輸入相同類型的向量?

ifelse的幫助頁面表示這是一個功能,而不是一個錯誤,但我仍然努力尋找一個解釋,我發現這是令人驚訝的行爲。

+4

現在有一個函數'if_else()'在dplyr包可以代替'ifelse'同時保留正確的類Date對象的作爲最近的回答,它是[下面發佈](http://stackoverflow.com/a/38093096/4470365)。我在這裏關注它,因爲它通過提供一個在CRAN包中進行單元測試和記錄的函數來解決這個問題,而不像其他許多答案(就此評論而言)排名靠前。 –

回答

51

您可以使用dplyr::if_else

dplyr 0.5.0 release notes:「[if_else]有更嚴格的語義ifelse():所述truefalse參數必須是相同的類型這給出了一個令人驚奇的少的返回類型,並保存S3矢量等日期」。

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

絕對有用,即使它讓我鬆了一個複選標記。當前版本的幫助頁面沒有說明期望因子參數。我的投票將會是一個因素返回對象,它的水平是「真」和「假」水平的聯合。 –

+1

有沒有辦法讓'if_else'的參數之一成爲NA?我已經嘗試了邏輯'NA_'選項並沒有什麼是堅持,我不相信有一個'NA_double_' – roarkz

+2

@Zak一種可能性是包裝'NA'在'as.Date'。 – Henrik

55

它涉及的ifelse所記錄

相同長度的向量和屬性(包括尺寸和「class」)從yesno的值作爲test和數據值。答案的模式將從邏輯上被強制,以適應從yes獲取的任何值,然後從no獲取任何值。

歸因於它的影響,ifelse使因素失去他們的水平和日期失去他們的類,只有他們的模式(「數字」)被恢復。試試這個:

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" 

您可以創建一個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" 

稍後注:我看到哈德利已經建立了一個if_else到數據整形包的的magrittr/dplyr/tidyr複雜。

+30

稍微更優雅的版本:'safe.ifelse < - 函數(cond,是,否)結構(ifelse(cond,yes,no),class = class(yes))' – hadley

+5

不錯。你看到有什麼理由不是默認行爲嗎? –

+7

不,我不明白爲什麼ifelse的工作方式。 – hadley

12

迪文的解釋是現貨。我擺弄以及與此打了一會兒我才意識到我可以簡單地強制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) 

起初,這感覺有點「hackish的」給我。但現在我只是把它看作是我從ifelse()獲得的性能回報的一個小代價。再加上它比循環更簡潔。

5

建議的方法不適用於因子列。我還想提出這一改進:

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

順便說一句:ifelse很爛......與大國意味着巨大的責任,1x1的矩陣和/或NUMERICS [時,他們應該例如添加]是的,即類型轉換對我來說好,但是這種類型轉換在ifelse中顯然是不需要的。我碰上ifelse多次的非常相同的「錯誤」,現在,它只是不斷偷了我的時間:-(

FW

+0

這是唯一適用於我的因素解決方案。 – bshor

+0

我會認爲要返回的級別是「是」和「否」級別的聯合,並且您首先會檢查它們是否都是因素。你可能需要轉換爲角色,然後用「工會化」級別重新組合。 –

5

通過@法比安 - 沃納提供的答案是偉大的,但對象可以有多個類和「因子」未必是通過class(yes)返回的第一個,所以我建議這個小的修改,以檢查所有類的屬性:

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

我也提交了請求,使用R的開發團隊加入記錄的選項讓base :: ifelse()根據用戶選擇保留哪些屬性來保留屬性。請求在這裏:https://bugs.r-project.org/bugzilla/show_bug.cgi?id=16609 - 它已經被標記爲「WONTFIX」,理由是它一直是它現在的樣子,但我已經提供了一個後續的論點,說明爲什麼簡單的添加可能會節省大量的R用戶頭疼。也許你在bug線程中的「+1」會鼓勵R核心團隊再次看看。

編輯:這是一個更好的版本,允許用戶指定要保留的屬性,或者「cond」(默認ifelse()行爲),「是」,行爲按照上面的代碼或「否」對於「否」值屬性較好的情況:

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

'繼承(y,「因子」)'可能比'「因子」%in%class.y'更爲正確「 –

+0

確實。 '繼承'可能是最好的。 –

4

這將不起作用的原因是,ifelse()函數將值轉換爲因子。一個很好的解決方法是在評估之前將其轉換爲字符。

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

這將不需要從基R.開任何庫

相關問題