ifelse ()가 Date 객체를 숫자 객체로 바꾸는 것을 방지하는 방법


161

ifelse()날짜 벡터를 조작하는 함수 를 사용하고 있습니다 . 결과가 class Date일 것으로 예상하고 numeric대신 벡터 를 얻는 것에 놀랐습니다 . 예를 들면 다음과 같습니다.

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 패키지 에는 이제 ifelseDate 클래스의 올바른 클래스를 유지하면서 대체 할 수 있는 함수가 있습니다.이 함수 는 아래 에 최근 답변으로 게시되어 있습니다 . (이 의견으로) 앞서 언급 한 많은 다른 답변과 달리 CRAN 패키지로 단위 테스트되고 문서화 된 기능을 제공 하여이 문제를 해결함에 따라 여기에주의를 기울입니다.
Sam Firke

답변:


132

data.table::fifelse( data.table >= 1.12.3) 또는을 사용할 수 있습니다 dplyr::if_else.


data.table::fifelse

달리 ifelse, fifelse입력의 유형과 클래스를 유지합니다.

library(data.table)
dates <- fifelse(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"

dplyr::if_else

에서 dplyr 0.5.0릴리스 노트 :

[ 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" 

2
체크 표시가 풀리더라도 확실히 유용합니다. 현재 버전의 도움말 페이지에는 요인 인수에서 예상되는 내용이 나와 있지 않습니다. 내 투표는 수준의 조합이었다 레벨했다 요인 반환 개체에 대한 것 true's와 false의 수준을.
IRTFM

3
if_elseNA 의 주장 중 하나를 갖는 방법이 있습니까? 나는 논리적 인 NA_옵션을 시도했지만 아무것도 붙어 있지 않으며 나는NA_double_
roarkz

11
@Zak 하나의 가능성은 랩핑하는 것 NA입니다 as.Date.
Henrik

NA_real_@roarkz는. 그리고 @Henrik, 귀하의 의견은 내 문제를 해결했습니다.
BLT

63

이 문서화 된 관련 ifelse:

동일한 길이 (기준 및 "속성들을 포함하는 벡터 class와 같은") test의 값과 데이터 값 yes또는 no. 답의 모드는 논리 값에서 강요되어 먼저 값을 취한 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"

나중에 참고 : Hadley가 if_else데이터 형성 패키지의 magrittr / dplyr / tidyr 복합체에 내장되어 있음 을 알았습니다.


37
좀 더 우아한 버전 :safe.ifelse <- function(cond, yes, no) structure(ifelse(cond, yes, no), class = class(yes))
hadley

5
좋은. 이것이 기본 동작이 아닌 이유가 있습니까?
IRTFM

NA가 있고 작동하지 않기 때문에 "예"에 넣은 것을 조심하십시오. 클래스가 "yes"조건의 클래스라고 가정하는 것보다 클래스를 매개 변수로 전달하는 것이 좋습니다.
Denis

1
나는 이것이 마지막 의미인지 확실하지 않습니다. NA 값이 있다고해서 클래스를 가질 수 없다는 의미는 아닙니다.
IRTFM

이 문제가 발생한 지 8 년이 지났지 만 여전히 "안전ifelse() 하지 " 않습니다 .
M-- 19 :

16

DWin의 설명이 돋보입니다. 나는 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)

처음에 이것은 나에게 약간의 "해킹"을 느꼈다. 그러나 이제는 ifelse ()에서 얻은 성능 수익을 지불하는 작은 가격으로 생각합니다. 또한 루프보다 훨씬 간결합니다.


이 (좋은이 경우, 네, hackish) 기술은 사실과 또한 도움에 보인다 R의 for문 할당하는 의 항목 VECTORNAME,하지만 자신의 클래스 .
Greg Minshall

6

제안 된 방법은 요인 열에서 작동하지 않습니다. 이 개선을 제안하고 싶습니다.

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 sucks ... 큰 힘으로 큰 책임 이옵니다. 즉 1x1 행렬 및 / 또는 숫자 (예 : 추가해야 할 때)의 유형 변환은 괜찮지 만 ifelse의 유형 변환은 분명히 원하지 않습니다. 나는 지금 여러 번 ifelse의 동일한 '버그'에 부딪 쳤고 그것은 단지 내 시간을 훔치는 것을 계속합니다 :-(

FW


이것은 요인에 대해 나를 위해 작동하는 유일한 솔루션입니다.
bshor

나는 수준의 수준의 조합이됩니다 반환 할 것을 생각했을 것 yes등을 no먼저 그들이 두 요인이 있다고 확인한다고합니다. 문자로 변환 한 다음 "이온화"수준으로 다시 묶어야 할 수도 있습니다.
IRTFM

6

이것이 작동하지 않는 이유는 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 이외의 라이브러리가 필요하지 않습니다.


5

@ fabian-werner가 제공하는 대답은 훌륭하지만 객체에는 여러 클래스가있을 수 있으며 "인자"가 반드시 첫 번째로 반환되는 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 사용자의 두통을 줄일 수있는 이유에 대한 후속 논의를 제공했습니다. 아마도 해당 버그 스레드에서 "+1"은 R Core 팀이 다시 한 번 살펴볼 것을 권장합니다.

편집 : 다음은 "cond"(기본 ifelse () 동작), "yes", 위의 코드에 따른 동작 또는 "no"중 유지할 속성을 사용자가 지정할 수있는 더 나은 버전입니다. "아니오"값의 속성이 더 좋습니다.

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
inherits(y, "factor")"factor" %in% class.y
IRTFM

과연. inherits최고일지도 모른다.
Mekki MacAulay
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.