У меня есть два логических вектора во фрейме данных:
df <- data.frame(log1 = c(FALSE, FALSE, TRUE, FALSE, TRUE), log2 = c(TRUE, FALSE, FALSE, FALSE, TRUE))
Я хочу создать третий столбец, объединив эти два. Но этот новый столбец не должен просто содержать логические значения. Вместо этого ему следует присвоить третьему столбцу одно из трех значений: «высокое», «выброс» или «нормальное». «Высокий» имеет приоритет, поэтому в третьем столбце для строки 5 должно быть указано «высокий», а не «выходящий».
Я думаю, это можно сделать, используя if
и else
, но мне не удалось заставить это работать, используя следующий код:
df$new <- NA
if (df$log1 == TRUE){
df$new <- "high"
} else if (df$log2 == TRUE) {
df$new <- "outlier"
} else {
df$new <- "normal"
}
Кто-нибудь может помочь?
Речь идет о ifelse
и его производных.
ifelse(df$log1, "high", ifelse(df$log2, "outlier", "normal"))
# [1] "outlier" "normal" "high" "normal" "high"
Мы можем вкладывать dplyr::if_else
, но вложение обычно побуждает нас использовать case_when
.
library(dplyr)
df %>%
mutate(
new1 = if_else(log1, "high", if_else(log2, "outlier", "normal")),
new2 = case_when(log1 ~ "high", log2 ~ "outlier", TRUE ~ "normal")
)
# log1 log2 new1 new2
# 1 FALSE TRUE outlier outlier
# 2 FALSE FALSE normal normal
# 3 TRUE FALSE high high
# 4 FALSE FALSE normal normal
# 5 TRUE TRUE high high
Аналогично, fifelse
и fcase
:
library(data.table)
as.data.table(df)[, new1 := fifelse(log1, "high", fifelse(log2, "outlier", "normal"))
][, new2 := fcase(log1, "high", log2, "outlier", default = "normal")][]
# log1 log2 new1 new2
# <lgcl> <lgcl> <char> <char>
# 1: FALSE TRUE outlier outlier
# 2: FALSE FALSE normal normal
# 3: TRUE FALSE high high
# 4: FALSE FALSE normal normal
# 5: TRUE TRUE high high
Обратите внимание: хотя в dplyr::case_when
выше используются формулы-тильды, как в cond1 ~ value1, cond2 ~ value2
, в варианте fcase
используются чередующиеся аргументы, cond1, value1, cond2, value2, ...)
.
Кроме того, аргумент default=
работает, пока он является константой. Если требуется динамическое значение по умолчанию (т. е. на основе содержимого таблицы), то необходимо иметь полностью истинный вектор, как в fcase(..., rep(TRUE, .N), NEWVALUE)
.
Я ценю это... Обычно я тоже очень нерешителен, в прошлом я обнаружил некоторые изменения в своих ответах, добавленные выводы, которые я не говорил и не собирался делать, и это немного раздражало. Легко поправимо, конечно. Ваше редактирование было совершенно нормальным и поощрялось. Спасибо @jpsmith
Попробуйте это без if
/else
:
df$new <- "normal"
df[df$log1, ]$new <- "high"
df[!df$log1 & df$log2, ]$new <- "outlier"
log1 log2 new
1 FALSE TRUE outlier
2 FALSE FALSE normal
3 TRUE FALSE high
4 FALSE FALSE normal
5 TRUE TRUE high
Решение dplyr: попробуй это:
library(dplyr)
df %>%
mutate(new = ifelse(log1,
"high",
ifelse(log2,
"outlier",
"normal")
)
)
Это отличный альтернативный подход, и он отлично работает! Одно предложение: всякий раз, когда я использую этот подход в своем коде, я всегда ставлю переназначение с наивысшим приоритетом в последнюю очередь. Хотя логика здесь совершенно непересекающаяся (и, следовательно, никаких изменений в этих данных), в более обобщенном смысле, когда логика может быть не столь идеально дополняющей, то, что она последней, гарантирует, что она переопределяет любое другое значение.
Использование индексации:
df$new <- with(df, c("normal", "outlier", "high", "high")[2L*log1 + log2 + 1L])
df
#> log1 log2 new
#> 1 FALSE TRUE outlier
#> 2 FALSE FALSE normal
#> 3 TRUE FALSE high
#> 4 FALSE FALSE normal
#> 5 TRUE TRUE high
Решения data.table
, на которые указал @r2evans, являются самыми быстрыми и наиболее эффективными с точки зрения использования памяти. Индексирование выигрывает для базовых решений.
f1 <- function(df) {
within(df, new <- ifelse(log1, "high", ifelse(log2, "outlier", "normal")))
}
f2 <- function(df) {
df %>%
mutate(
new = if_else(log1, "high", if_else(log2, "outlier", "normal")),
)
}
f3 <- function(df) {
df %>%
mutate(
new = case_when(log1 ~ "high", log2 ~ "outlier", TRUE ~ "normal")
)
}
f4 <- function(df) {
setDT(df)[, new := fifelse(log1, "high", fifelse(log2, "outlier", "normal"))]
}
f5 <- function(df) {
setDT(df)[, new := fcase(log1, "high", log2, "outlier", default = "normal")]
}
f6 <- function(df) {
df$new <- "normal"
df[df$log1, ]$new <- "high"
df[!df$log1 & df$log2, ]$new <- "outlier"
df
}
f7 <- function(df) {
within(df, new <- c("normal", "outlier", "high", "high")[2L*log1 + log2 + 1L])
}
Тест:
df <- data.frame(log1 = sample(!0:1, 1e5, 1), log2 = sample(!0:1, 1e5, 1))
bench::mark(
ifelse = f1(df),
if_else = f2(df),
case_when = f3(df),
fifelse = f4(df),
fcase = f5(df),
subsetting = f6(df),
indexing = f7(df),
check = FALSE # mix of data.table and data.frame
)
#> # A tibble: 7 × 6
#> expression min median `itr/sec` mem_alloc `gc/sec`
#> <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl>
#> 1 ifelse 40.32ms 41.9ms 24.0 10.32MB 12.0
#> 2 if_else 8.13ms 8.92ms 111. 9.95MB 33.0
#> 3 case_when 6.58ms 7.09ms 137. 7.36MB 45.7
#> 4 fifelse 1.68ms 2.01ms 483. 3.42MB 18.0
#> 5 fcase 1.45ms 1.57ms 618. 1.2MB 22.6
#> 6 subsetting 6.99ms 8.2ms 120. 10.85MB 59.9
#> 7 indexing 1.57ms 2.54ms 389. 4.27MB 50.9
Проголосовал за. Я бы тоже предложил bitwShiftL(as.integer(log1), 1L) + as.integer(log2)
Или даже bitwOr(bitwShiftL(as.integer(T), 1L), as.integer(T))
df$new <- ifelse(df$log1, "high", ifelse(df$log2, "outlier", "normal"))
должно работать. Обратите внимание, что при работе с логикой вам не нуженx == TRUE
— R неявно читаетif (x){...}
как «если x ИСТИНА, сделайте ...» иif (!x){...}
как «если x ЛОЖЬ, сделайте ...».