Подтвердить что ты не робот

Может ли пакет dplyr использоваться для условного мутирования?

Могут ли mutate использоваться, если мутация является условной (в зависимости от значений определенных значений столбца)?

Этот пример помогает показать, что я имею в виду.

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, но, возможно, есть и другие, более эффективные способы решения этой проблемы?

4b9b3361

Ответ 1

Используйте 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_))

Ответ 2

Поскольку вы запрашиваете другие способы решения этой проблемы, здесь другой способ использования 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, поскольку оно может оценивать как "да", так и "нет", а вложение может усложняться для чтения/поддержки IMHO.


Здесь сравнительный анализ относительно более крупных данных:

# 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

Не уверен, что это альтернатива, которую вы просили, но я надеюсь, что это поможет.

Ответ 3

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

Benchmark: Для эталона (повторное использование функций из сообщения Arun) и уменьшение размера выборки:

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

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

microbenchmark(
  DT_fun(DT),
  DPLYR_fun(DF),
  DPLYR_case_when(DF),
  mosa_fun(DF),
  times=20
)

Это дает:

            expr        min         lq       mean     median         uq        max neval
         DT_fun(DT)   1.503589   1.626971   2.054825   1.755860   2.292157   3.426192    20
      DPLYR_fun(DF)   2.420798   2.596476   3.617092   3.484567   4.184260   6.235367    20
DPLYR_case_when(DF)   2.153481   2.252134   6.124249   2.365763   3.119575  72.344114    20
       mosa_fun(DF) 396.344113 407.649356 413.743179 412.412634 416.515742 459.974969    20

Ответ 4

Функция 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 также может использоваться для произвольного числа условных обозначений.

Ответ 5

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