조건부 돌연변이에 dplyr 패키지를 사용할 수 있습니까?


178

돌연변이가 조건부 (특정 열 값의 값에 따라) 일 때 돌연변이를 사용할 수 있습니까?

이 예는 내가 의미하는 바를 보여줍니다.

structure(list(a = c(1, 3, 4, 6, 3, 2, 5, 1), b = c(1, 3, 4, 
2, 6, 7, 2, 6), c = c(6, 3, 6, 5, 3, 6, 5, 3), d = c(6, 2, 4, 
5, 3, 7, 2, 6), e = c(1, 2, 4, 5, 6, 7, 6, 3), f = c(2, 3, 4, 
2, 2, 7, 5, 2)), .Names = c("a", "b", "c", "d", "e", "f"), row.names = c(NA, 
8L), class = "data.frame")

  a b c d e f
1 1 1 6 6 1 2
2 3 3 3 2 2 3
3 4 4 6 4 4 4
4 6 2 5 5 5 2
5 3 6 3 3 6 2
6 2 7 6 7 7 7
7 5 2 5 2 6 5
8 1 6 3 6 3 2

dplyr 패키지를 사용하여 내 문제에 대한 해결책을 찾고 싶었습니다 (그리고 그래야 작동하는 코드가 아니라는 것을 알고 있지만 목적이 명확하다고 생각합니다) g :

 library(dplyr)
 df <- mutate(df,
         if (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)){g = 2},
         if (a == 0 | a == 1 | a == 4 | a == 3 |  c == 4) {g = 3})

내가 찾고있는 코드의 결과는이 특정 예제 에서이 결과를 가져야합니다.

  a b c d e f  g
1 1 1 6 6 1 2  3
2 3 3 3 2 2 3  3
3 4 4 6 4 4 4  3
4 6 2 5 5 5 2 NA
5 3 6 3 3 6 2 NA
6 2 7 6 7 7 7  2
7 5 2 5 2 6 5  2
8 1 6 3 6 3 2  3

dplyr 에서이 작업을 수행하는 방법에 대한 아이디어가 있습니까? 이 데이터 프레임은 단지 예일뿐입니다. 처리중인 데이터 프레임이 훨씬 큽니다. 속도 때문에 dplyr을 사용하려고했지만이 문제를 처리하는 다른 더 좋은 방법이 있습니까?


2
예, 그러나 dplyr::case_when()보다 명확합니다 ifelse.
smci

답변:


216

사용하다 ifelse

df %>%
  mutate(g = ifelse(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4), 2,
               ifelse(a == 0 | a == 1 | a == 4 | a == 3 |  c == 4, 3, NA)))

추가 - if_else : 주 dplyr 0.5에 있다는 것을 if_else대안 대체하는 것, 그래서 정의 함수 ifelse로는 if_else; 그러나 이후 if_else보다 더 엄격하기 때문에 ifelse(조건의 두 레그가 동일한 유형을 가져야 함) NA이 경우에로 대체해야 NA_real_합니다.

df %>%
  mutate(g = if_else(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4), 2,
               if_else(a == 0 | a == 1 | a == 4 | a == 3 |  c == 4, 3, NA_real_)))

추가 - case_when 이 질문을 추가했다 dplyr 게시 된 이후 case_when또 다른 대안이 될 수 있도록 :

df %>% mutate(g = case_when(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4) ~ 2,
                            a == 0 | a == 1 | a == 4 | a == 3 |  c == 4 ~ 3,
                            TRUE ~ NA_real_))

추가-산술 / na_if 값이 숫자이고 조건 (끝의 기본값 NA를 제외하고)이 상호 배타적 인 경우 문제의 경우와 같이 각 항이 곱해 지도록 산술 표현식을 사용할 수 있습니다 na_if끝에 0을 사용하여 NA로 대체 하는 원하는 결과 .

df %>%
  mutate(g = 2 * (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)) +
             3 * (a == 0 | a == 1 | a == 4 | a == 3 |  c == 4),
         g = na_if(g, 0))

3
대신에 NA조건을 충족하지 않는 행이 동일하게 유지되기를 원하는 경우 논리는 무엇입니까 ?
Nazer

10
mutate(g = ifelse(condition1, 2, ifelse(condition2, 3, g))
G. Grothendieck 2014 년

11
case_when이 sooooo 아름답고 그것이 실제로 거기에 있다는 것을 알아내는 데 너무 오래 걸렸습니다. 나는 이것이 가장 간단한 dplyr 튜토리얼에 있어야한다고 생각하지만, 데이터의 하위 집합에 대한 물건을 계산할 필요가 있지만 여전히 데이터를 완벽하게 유지하기를 원합니다.
Javier Fajardo

55

문제를 처리하는 다른 더 좋은 방법을 요청하므로 다음을 사용하는 다른 방법이 있습니다 data.table.

require(data.table) ## 1.9.2+
setDT(df)
df[a %in% c(0,1,3,4) | c == 4, g := 3L]
df[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]

조건 문의 순서가 g올바르게 되려면 반대 순서입니다 . g두 번째 과제 중에도 사본이 없습니다 . 대신 교체 됩니다 .

큰 데이터에이 사용하는 것보다 더 나은 성능을 것 중첩 if-else 으로, 이 모두 '예'와 '아니오'의 경우를 평가할 수 있습니다 , 그리고 중첩는 이럴을 유지 / 읽기 어려워 얻을 수 있습니다.


비교적 큰 데이터에 대한 벤치 마크는 다음과 같습니다.

# R version 3.1.0
require(data.table) ## 1.9.2
require(dplyr)
DT <- setDT(lapply(1:6, function(x) sample(7, 1e7, TRUE)))
setnames(DT, letters[1:6])
# > dim(DT) 
# [1] 10000000        6
DF <- as.data.frame(DT)

DT_fun <- function(DT) {
    DT[(a %in% c(0,1,3,4) | c == 4), g := 3L]
    DT[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]
}

DPLYR_fun <- function(DF) {
    mutate(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L, 
            ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}

BASE_fun <- function(DF) { # R v3.1.0
    transform(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L, 
            ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}

system.time(ans1 <- DT_fun(DT))
#   user  system elapsed 
#  2.659   0.420   3.107 

system.time(ans2 <- DPLYR_fun(DF))
#   user  system elapsed 
# 11.822   1.075  12.976 

system.time(ans3 <- BASE_fun(DF))
#   user  system elapsed 
# 11.676   1.530  13.319 

identical(as.data.frame(ans1), as.data.frame(ans2))
# [1] TRUE

identical(as.data.frame(ans1), as.data.frame(ans3))
# [1] TRUE

이것이 당신이 요구 한 대안인지 확실하지 않지만 도움이되기를 바랍니다.


4
좋은 코드 조각! G. Grotendieck의 대답은 효과가 있고 짧기 때문에 그 질문에 대한 답변으로 선택했습니다.하지만 귀하의 해결책에 감사드립니다. 나는이 방법도 시도 할 것입니다.
rdatasculptor

DT_fun입력을 제자리에서 수정하고 있기 때문에 벤치 마크는 상당히 공정하지 않을 수 있습니다. 두 번째 반복 앞으로 동일한 입력을받지 못했을뿐 아니라 ( DT$g이미 할당 된 이후 타이밍에 영향을 줄 수 있습니까?) 결과도 다시 전파되어 ans1( R의 최적화하다고 판단이 필요한 경우? 확실하지이에 ...)하지 않도록 다른 것을 복사 DPLYR_fun하고 BASE_fun필요 만들 수 있습니까?
Ken Williams

그럼에도 불구하고, 나는이 data.table솔루션이 훌륭 하다고 생각 하며 data.table실제로 테이블 작업을 위해 속도가 필요한 모든 곳에서 사용 하고 C ++로 가고 싶지 않습니다. 그래도 수정에주의해야합니다!
Ken Williams

나는 data.table에서 더 깔끔한 물건에 익숙해 지려고 노력하고 있으며, 이것은 data.table이 읽기 쉽고 효율적이라는 일반적인 사용 사례 중 하나입니다. 내 어휘에서 더 깔끔한 것을 개발하려는 주된 이유는 나 자신과 다른 사람들이 읽을 수 있기 때문이지만,이 경우에는 data.table이이기는 것처럼 보입니다.
Paul McMurdie

38

dplyr에는 이제 case_when벡터화 된 if를 제공 하는 함수 가 있습니다. 구문은 mosaic:::derivedFactor표준 dplyr 방식으로 변수에 액세스 할 수없고 NA 모드를 선언해야하므로 구문이 약간 이상 하지만 mosaic:::derivedFactor.

df %>%
mutate(g = case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L, 
                     a %in% c(0,1,3,4) | c == 4 ~ 3L, 
                     TRUE~as.integer(NA)))

편집 :dplyr::case_when() 패키지의 0.7.0 이전 버전을 사용 하는 경우 변수 이름 앞에 ' .$'를 붙여야합니다 (예 : .$a == 1내부 쓰기 case_when).

벤치 마크 : 벤치 마크 (Arun의 게시물에서 기능 재사용) 및 샘플 크기 축소 :

require(data.table) 
require(mosaic) 
require(dplyr)
require(microbenchmark)

set.seed(42) # To recreate the dataframe
DT <- setDT(lapply(1:6, function(x) sample(7, 10000, TRUE)))
setnames(DT, letters[1:6])
DF <- as.data.frame(DT)

DPLYR_case_when <- function(DF) {
  DF %>%
  mutate(g = case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L, 
                       a %in% c(0,1,3,4) | c==4 ~ 3L, 
                       TRUE~as.integer(NA)))
}

DT_fun <- function(DT) {
  DT[(a %in% c(0,1,3,4) | c == 4), g := 3L]
  DT[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]
}

DPLYR_fun <- function(DF) {
  mutate(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L, 
                    ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}

mosa_fun <- function(DF) {
  mutate(DF, g = derivedFactor(
    "2" = (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)),
    "3" = (a == 0 | a == 1 | a == 4 | a == 3 |  c == 4),
    .method = "first",
    .default = NA
  ))
}

perf_results <- microbenchmark(
  dt_fun <- DT_fun(copy(DT)),
  dplyr_ifelse <- DPLYR_fun(copy(DF)),
  dplyr_case_when <- DPLYR_case_when(copy(DF)),
  mosa <- mosa_fun(copy(DF)),
  times = 100L
)

이것은 다음을 제공합니다.

print(perf_results)
Unit: milliseconds
           expr        min         lq       mean     median         uq        max neval
         dt_fun   1.391402    1.560751   1.658337   1.651201   1.716851   2.383801   100
   dplyr_ifelse   1.172601    1.230351   1.331538   1.294851   1.390351   1.995701   100
dplyr_case_when   1.648201    1.768002   1.860968   1.844101   1.958801   2.207001   100
           mosa 255.591301  281.158350 291.391586 286.549802 292.101601 545.880702   100

case_when:도 같이 쓸 수있다df %>% mutate(g = with(., case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L, a %in% c(0,1,3,4) | c==4 ~ 3L, TRUE ~ NA_integer_)))
G. 그로 텐 디크

3
이 기준이 마이크로 초 / 밀리 초 / 일 단위입니까? 이 벤치 마크는 제공된 측정 단위가 없으면 의미가 없습니다. 또한 1e6보다 작은 데이터 세트에서 벤치마킹은 확장되지 않으므로 의미가 없습니다.
David Arenburg

3
Pls는 귀하의 답변을 수정, 당신은 .$더 이상 dplyr의 새로운 버전에서 필요가 없습니다
Amit Kohli

14

package 의 derivedFactor함수는 mosaic이것을 처리하도록 설계된 것 같습니다. 이 예제를 사용하면 다음과 같습니다.

library(dplyr)
library(mosaic)
df <- mutate(df, g = derivedFactor(
     "2" = (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)),
     "3" = (a == 0 | a == 1 | a == 4 | a == 3 |  c == 4),
     .method = "first",
     .default = NA
     ))

결과를 요인 대신 숫자 derivedFactor로 표시하려면 as.numeric통화를 줄 바꿈 할 수 있습니다 .

derivedFactor 임의의 수의 조건부에도 사용할 수 있습니다.


4
@hadley는 이것을 dplyr의 기본 구문으로 만들어야합니다. "ifelse"중첩 문을 필요로하는 다른 기능이 너무 좋아하기 때문에 주로의 경우 패키지의 단일 최악의 일부이다
rsoren

.asFactor = F옵션을 사용하거나 derivedVariable동일한 패키지에서 (유사한) 기능 을 사용 하여 결과가 요인이되는 것을 방지 할 수도 있습니다 .
Jake Fisher

recodedplyr 0.5 에서이 작업을 수행하는 것처럼 보입니다 . 아직 조사하지 않았습니다. blog.rstudio.org/2016/06/27/dplyr-0-5-0
Jake Fisher

12

case_when 다음과 같은 경우 SQL 스타일 사례를 매우 깨끗하게 구현합니다.

structure(list(a = c(1, 3, 4, 6, 3, 2, 5, 1), b = c(1, 3, 4, 
2, 6, 7, 2, 6), c = c(6, 3, 6, 5, 3, 6, 5, 3), d = c(6, 2, 4, 
5, 3, 7, 2, 6), e = c(1, 2, 4, 5, 6, 7, 6, 3), f = c(2, 3, 4, 
2, 2, 7, 5, 2)), .Names = c("a", "b", "c", "d", "e", "f"), row.names = c(NA, 
8L), class = "data.frame") -> df


df %>% 
    mutate( g = case_when(
                a == 2 | a == 5 | a == 7 | (a == 1 & b == 4 )     ~   2,
                a == 0 | a == 1 | a == 4 |  a == 3 | c == 4       ~   3
))

dplyr 0.7.4 사용

매뉴얼 : http://dplyr.tidyverse.org/reference/case_when.html

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