можно ли использовать пакет dplyr для условной мутации?
можно ли использовать мутацию, когда мутация является условной (в зависимости от значений определенных значений столбцов)?
этот пример помогает показать, что я имею в виду.
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, но, возможно, есть другие, лучшие способы справиться с этой проблемой?
5 ответов:
использовать
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_))
поскольку вы просите другие лучшие способы решения проблемы, вот еще один способ использования
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
не уверен, что это альтернатива, которую вы просили, но я надеюсь, что это поможет.
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)))
EDIT: если вы используете
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
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