Эти решения (1) поддерживают конвейер, (2) не перезаписывают входные данные и (3) требуют, чтобы условие было указано только один раз:
1a) mutate_cond Создайте простую функцию для фреймов данных или таблиц данных, которые могут быть включены в конвейеры. Эта функция похожа, mutate
но действует только на строки, удовлетворяющие условию:
mutate_cond <- function(.data, condition, ..., envir = parent.frame()) {
condition <- eval(substitute(condition), .data, envir)
.data[condition, ] <- .data[condition, ] %>% mutate(...)
.data
}
DF %>% mutate_cond(measure == 'exit', qty.exit = qty, cf = 0, delta.watts = 13)
1b) mutate_last Это альтернативная функция для фреймов данных или таблиц данных, которая снова похожа, mutate
но используется только внутри group_by
(как в примере ниже) и работает только с последней группой, а не с каждой группой. Обратите внимание, что TRUE> FALSE, поэтому if group_by
указывает условие, тогда mutate_last
будет работать только со строками, удовлетворяющими этому условию.
mutate_last <- function(.data, ...) {
n <- n_groups(.data)
indices <- attr(.data, "indices")[[n]] + 1
.data[indices, ] <- .data[indices, ] %>% mutate(...)
.data
}
DF %>%
group_by(is.exit = measure == 'exit') %>%
mutate_last(qty.exit = qty, cf = 0, delta.watts = 13) %>%
ungroup() %>%
select(-is.exit)
2) исключить условие Фактор условия, сделав его дополнительным столбцом, который позже удаляется. Затем используйте ifelse
, replace
или арифметику с логикой, как показано. Это также работает для таблиц данных.
library(dplyr)
DF %>% mutate(is.exit = measure == 'exit',
qty.exit = ifelse(is.exit, qty, qty.exit),
cf = (!is.exit) * cf,
delta.watts = replace(delta.watts, is.exit, 13)) %>%
select(-is.exit)
3) sqldf. Мы могли бы использовать SQL update
через пакет sqldf в конвейере для фреймов данных (но не таблиц данных, если мы их не конвертируем - это может представлять ошибку в dplyr. См. Dplyr issue 1579 ). Может показаться, что мы нежелательно изменяем ввод в этом коде из-за существования, update
но на самом деле update
он действует на копию ввода во временно сгенерированной базе данных, а не на фактический ввод.
library(sqldf)
DF %>%
do(sqldf(c("update '.'
set 'qty.exit' = qty, cf = 0, 'delta.watts' = 13
where measure = 'exit'",
"select * from '.'")))
4) row_case_when Также ознакомьтесь с row_case_when
определением в
разделе «Возврат тиббла»: как векторизовать с помощью case_when? . Он использует синтаксис, аналогичный, case_when
но применяется к строкам.
library(dplyr)
DF %>%
row_case_when(
measure == "exit" ~ data.frame(qty.exit = qty, cf = 0, delta.watts = 13),
TRUE ~ data.frame(qty.exit, cf, delta.watts)
)
Примечание 1: мы использовали это какDF
set.seed(1)
DF <- data.frame(site = sample(1:6, 50, replace=T),
space = sample(1:4, 50, replace=T),
measure = sample(c('cfl', 'led', 'linear', 'exit'), 50,
replace=T),
qty = round(runif(50) * 30),
qty.exit = 0,
delta.watts = sample(10.5:100.5, 50, replace=T),
cf = runif(50))
Примечание 2: проблема того, как легко указать обновление подмножества строк, также обсуждается в вопросах 134 , 631 , 1518 и 1573 dplyr , где 631 является основным потоком, а 1573 является обзором ответов здесь.
Вы можете сделать это с помощью
magrittr
двусторонней трубы%<>%
:library(dplyr) library(magrittr) dt[dt$measure=="exit",] %<>% mutate(qty.exit = qty, cf = 0, delta.watts = 13)
Это сокращает объем набора текста, но по-прежнему намного медленнее, чем
data.table
.источник
data.frame
/tibble
уже содержит столбец, определенный с помощьюmutate
. Это не сработает, если вы пытаетесь добавить новый столбец, например, при первом запуске цикла и изменении файлаdata.frame
.data.frame
. FWIW, я просто вернулся к использованию,data.table
а неdplyr
потому, что егоi
выражение легко справляется с этим - плюс общий цикл выполняется намного быстрее.Вот решение, которое мне нравится:
mutate_when <- function(data, ...) { dots <- eval(substitute(alist(...))) for (i in seq(1, length(dots), by = 2)) { condition <- eval(dots[[i]], envir = data) mutations <- eval(dots[[i + 1]], envir = data[condition, , drop = FALSE]) data[condition, names(mutations)] <- mutations } data }
Это позволяет вам писать такие вещи, как, например,
mtcars %>% mutate_when( mpg > 22, list(cyl = 100), disp == 160, list(cyl = 200) )
который вполне читается, хотя может быть не таким производительным, как мог бы.
источник
Как показано выше в eipi10, нет простого способа выполнить замену подмножества в dplyr, потому что DT использует семантику передачи по ссылке против dplyr с использованием передачи по значению. dplyr требует использования
ifelse()
всего вектора, тогда как DT будет выполнять подмножество и обновлять по ссылке (возвращая все DT). Итак, в этом упражнении DT будет значительно быстрее.В качестве альтернативы вы можете сначала подмножество, затем обновить и, наконец, рекомбинировать:
dt.sub <- dt[dt$measure == "exit",] %>% mutate(qty.exit= qty, cf= 0, delta.watts= 13) dt.new <- rbind(dt.sub, dt[dt$measure != "exit",])
Но DT будет значительно быстрее: (отредактировано для использования нового ответа eipi10)
library(data.table) library(dplyr) library(microbenchmark) microbenchmark(dt= {dt <- dt[measure == 'exit', `:=`(qty.exit = qty, cf = 0, delta.watts = 13)]}, eipi10= {dt[dt$measure=="exit",] %<>% mutate(qty.exit = qty, cf = 0, delta.watts = 13)}, alex= {dt.sub <- dt[dt$measure == "exit",] %>% mutate(qty.exit= qty, cf= 0, delta.watts= 13) dt.new <- rbind(dt.sub, dt[dt$measure != "exit",])}) Unit: microseconds expr min lq mean median uq max neval cld dt 591.480 672.2565 747.0771 743.341 780.973 1837.539 100 a eipi10 3481.212 3677.1685 4008.0314 3796.909 3936.796 6857.509 100 b alex 3412.029 3637.6350 3867.0649 3726.204 3936.985 5424.427 100 b
источник
Я просто наткнулся на это и мне очень нравится
mutate_cond()
@G. Гротендик, но подумал, что это может пригодиться и для обработки новых переменных. Итак, ниже есть два дополнения:Несвязанный: вторая последняя строка сделала немного больше
dplyr
, используяfilter()
Три новые строки в начале получают имена переменных для использования
mutate()
и инициализируют любые новые переменные во фрейме данных до того, как этоmutate()
произойдет. Новые переменные инициализируются до концаdata.frame
использованияnew_init
, для которого поNA
умолчанию установлено значение missing ( ).mutate_cond <- function(.data, condition, ..., new_init = NA, envir = parent.frame()) { # Initialize any new variables as new_init new_vars <- substitute(list(...))[-1] new_vars %<>% sapply(deparse) %>% names %>% setdiff(names(.data)) .data[, new_vars] <- new_init condition <- eval(substitute(condition), .data, envir) .data[condition, ] <- .data %>% filter(condition) %>% mutate(...) .data }
Вот несколько примеров использования данных радужной оболочки:
Измените
Petal.Length
на 88 гдеSpecies == "setosa"
. Это будет работать как в исходной функции, так и в этой новой версии.iris %>% mutate_cond(Species == "setosa", Petal.Length = 88)
То же, что и выше, но также создайте новую переменную
x
(NA
в строках, не включенных в условие). Раньше это было невозможно.iris %>% mutate_cond(Species == "setosa", Petal.Length = 88, x = TRUE)
То же, что и выше, но для строк, не включенных в условие
x
, установлено значение FALSE.iris %>% mutate_cond(Species == "setosa", Petal.Length = 88, x = TRUE, new_init = FALSE)
В этом примере показано, как
new_init
можно задать значение alist
для инициализации нескольких новых переменных с разными значениями. Здесь создаются две новые переменные, при этом исключенные строки инициализируются с использованием разных значений (x
инициализируются какFALSE
,y
какNA
)iris %>% mutate_cond(Species == "setosa" & Sepal.Length < 5, x = TRUE, y = Sepal.Length ^ 2, new_init = list(FALSE, NA))
источник
mutate_cond
функция выдает ошибку в моем наборе данных, а функция Гротендика - нет.Error: incorrect length (4700), expecting: 168
Кажется, связано с функцией фильтра.if_else
илиcase_when
.mutate_cond - отличная функция, но она выдает ошибку, если в столбце (ах), использованном для создания условия, есть NA. Я считаю, что условное изменение должно просто оставить такие строки в покое. Это соответствует поведению filter (), который возвращает строки, когда условие TRUE, но пропускает обе строки с FALSE и NA.
С этим небольшим изменением функция работает как шарм:
mutate_cond <- function(.data, condition, ..., envir = parent.frame()) { condition <- eval(substitute(condition), .data, envir) condition[is.na(condition)] = FALSE .data[condition, ] <- .data[condition, ] %>% mutate(...) .data }
источник
На самом деле я не вижу никаких изменений,
dplyr
которые бы сделали это намного проще.case_when
отлично подходит, когда есть несколько различных условий и результатов для одного столбца, но не помогает в этом случае, когда вы хотите изменить несколько столбцов на основе одного условия. Точно так жеrecode
экономится ввод текста, если вы заменяете несколько разных значений в одном столбце, но не помогает сделать это сразу в нескольких столбцах. В заключение,mutate_at
и т. Д. Применяются только условия к именам столбцов, а не к строкам в кадре данных. Вы могли бы потенциально написать функцию для mutate_at, которая бы это делала, но я не могу понять, как вы можете заставить ее вести себя по-разному для разных столбцов.Тем не менее, вот как я подхожу к этому, используя
nest
формуtidyr
иmap
изpurrr
.library(data.table) library(dplyr) library(tidyr) library(purrr) # Create some sample data set.seed(1) dt <- data.table(site = sample(1:6, 50, replace=T), space = sample(1:4, 50, replace=T), measure = sample(c('cfl', 'led', 'linear', 'exit'), 50, replace=T), qty = round(runif(50) * 30), qty.exit = 0, delta.watts = sample(10.5:100.5, 50, replace=T), cf = runif(50)) dt2 <- dt %>% nest(-measure) %>% mutate(data = if_else( measure == "exit", map(data, function(x) mutate(x, qty.exit = qty, cf = 0, delta.watts = 13)), data )) %>% unnest()
источник
nest(-measure)
чтобы избежатьgroup_by
Одним из кратких решений было бы произвести мутацию отфильтрованного подмножества, а затем добавить обратно невыпадающие строки таблицы:
library(dplyr) dt %>% filter(measure == 'exit') %>% mutate(qty.exit = qty, cf = 0, delta.watts = 13) %>% rbind(dt %>% filter(measure != 'exit'))
источник
При создании
rlang
возможна слегка измененная версия примера Гротендика 1a, устраняющая необходимость вenvir
аргументе, посколькуenquo()
захватывает среду, которая.p
создается автоматически.mutate_rows <- function(.data, .p, ...) { .p <- rlang::enquo(.p) .p_lgl <- rlang::eval_tidy(.p, .data) .data[.p_lgl, ] <- .data[.p_lgl, ] %>% mutate(...) .data } dt %>% mutate_rows(measure == "exit", qty.exit = qty, cf = 0, delta.watts = 13)
источник
Вы можете разделить набор данных и выполнить регулярный вызов изменения на
TRUE
детали.В dplyr 0.8 есть функция
group_split
разбиения по группам (и группы могут быть определены непосредственно в вызове), поэтому мы будем использовать ее здесь, но она такжеbase::split
работает.library(tidyverse) df1 %>% group_split(measure == "exit", keep=FALSE) %>% # or `split(.$measure == "exit")` modify_at(2,~mutate(.,qty.exit = qty, cf = 0, delta.watts = 13)) %>% bind_rows() # site space measure qty qty.exit delta.watts cf # 1 1 4 led 1 0 73.5 0.246240409 # 2 2 3 cfl 25 0 56.5 0.360315879 # 3 5 4 cfl 3 0 38.5 0.279966850 # 4 5 3 linear 19 0 40.5 0.281439486 # 5 2 3 linear 18 0 82.5 0.007898384 # 6 5 1 linear 29 0 33.5 0.392412729 # 7 5 3 linear 6 0 46.5 0.970848817 # 8 4 1 led 10 0 89.5 0.404447182 # 9 4 1 led 18 0 96.5 0.115594622 # 10 6 3 linear 18 0 15.5 0.017919745 # 11 4 3 led 22 0 54.5 0.901829577 # 12 3 3 led 17 0 79.5 0.063949974 # 13 1 3 led 16 0 86.5 0.551321441 # 14 6 4 cfl 5 0 65.5 0.256845013 # 15 4 2 led 12 0 29.5 0.340603733 # 16 5 3 linear 27 0 63.5 0.895166931 # 17 1 4 led 0 0 47.5 0.173088800 # 18 5 3 linear 20 0 89.5 0.438504370 # 19 2 4 cfl 18 0 45.5 0.031725246 # 20 2 3 led 24 0 94.5 0.456653397 # 21 3 3 cfl 24 0 73.5 0.161274319 # 22 5 3 led 9 0 62.5 0.252212124 # 23 5 1 led 15 0 40.5 0.115608182 # 24 3 3 cfl 3 0 89.5 0.066147321 # 25 6 4 cfl 2 0 35.5 0.007888337 # 26 5 1 linear 7 0 51.5 0.835458916 # 27 2 3 linear 28 0 36.5 0.691483644 # 28 5 4 led 6 0 43.5 0.604847889 # 29 6 1 linear 12 0 59.5 0.918838163 # 30 3 3 linear 7 0 73.5 0.471644760 # 31 4 2 led 5 0 34.5 0.972078100 # 32 1 3 cfl 17 0 80.5 0.457241602 # 33 5 4 linear 3 0 16.5 0.492500255 # 34 3 2 cfl 12 0 44.5 0.804236607 # 35 2 2 cfl 21 0 50.5 0.845094268 # 36 3 2 linear 10 0 23.5 0.637194873 # 37 4 3 led 6 0 69.5 0.161431896 # 38 3 2 exit 19 19 13.0 0.000000000 # 39 6 3 exit 7 7 13.0 0.000000000 # 40 6 2 exit 20 20 13.0 0.000000000 # 41 3 2 exit 1 1 13.0 0.000000000 # 42 2 4 exit 19 19 13.0 0.000000000 # 43 3 1 exit 24 24 13.0 0.000000000 # 44 3 3 exit 16 16 13.0 0.000000000 # 45 5 3 exit 9 9 13.0 0.000000000 # 46 2 3 exit 6 6 13.0 0.000000000 # 47 4 1 exit 1 1 13.0 0.000000000 # 48 1 1 exit 14 14 13.0 0.000000000 # 49 6 3 exit 7 7 13.0 0.000000000 # 50 2 4 exit 3 3 13.0 0.000000000
Если порядок строк имеет значение, используйте
tibble::rowid_to_column
сначала, затемdplyr::arrange
включитеrowid
и выберите его в конце.данные
df1 <- data.frame(site = sample(1:6, 50, replace=T), space = sample(1:4, 50, replace=T), measure = sample(c('cfl', 'led', 'linear', 'exit'), 50, replace=T), qty = round(runif(50) * 30), qty.exit = 0, delta.watts = sample(10.5:100.5, 50, replace=T), cf = runif(50), stringsAsFactors = F)
источник
Я думаю, что об этом ответе раньше не упоминалось. Он работает почти так же быстро, как и решение по умолчанию
data.table
.Использовать
base::replace()
df %>% mutate( qty.exit = replace( qty.exit, measure == 'exit', qty[ measure == 'exit'] ), cf = replace( cf, measure == 'exit', 0 ), delta.watts = replace( delta.watts, measure == 'exit', 13 ) )
replace перерабатывает заменяемое значение, поэтому, когда вы хотите, чтобы значения столбцов были
qty
введены в столбцыqty.exit
, вам также необходимо подмножествоqty
... следовательно,qty[ measure == 'exit']
в первой замене ..теперь вы, вероятно, не захотите
measure == 'exit'
все время вводить заново ... поэтому вы можете создать индекс-вектор, содержащий этот выбор, и использовать его в функциях выше.#build an index-vector matching the condition index.v <- which( df$measure == 'exit' ) df %>% mutate( qty.exit = replace( qty.exit, index.v, qty[ index.v] ), cf = replace( cf, index.v, 0 ), delta.watts = replace( delta.watts, index.v, 13 ) )
ориентиры
# Unit: milliseconds # expr min lq mean median uq max neval # data.table 1.005018 1.053370 1.137456 1.112871 1.186228 1.690996 100 # wimpel 1.061052 1.079128 1.218183 1.105037 1.137272 7.390613 100 # wimpel.index 1.043881 1.064818 1.131675 1.085304 1.108502 4.192995 100
источник
За счет отказа от обычного синтаксиса dplyr вы можете использовать
within
from base:dt %>% within(qty.exit[measure == 'exit'] <- qty[measure == 'exit'], delta.watts[measure == 'exit'] <- 13)
Кажется, он хорошо интегрируется с пайпом, и внутри него можно делать все, что угодно.
источник
dt %>% within({ delta.watts[measure == 'exit'] <- 13 ; qty.exit[measure == 'exit'] <- qty[measure == 'exit'] ; cf[measure == 'exit'] <- 0 })
то это действительно сработает