Гибридизированные опции dplyr теперь примерно на 30% быстрее, чем переназначение подгруппы Base R.На 100-мегапиксельном кадре данных mutate_all(~replace(., is.na(.), 0))
работает на полсекунды быстрее, чем базовая опция R d[is.na(d)] <- 0
.Чего конкретно хочется избежать, так это использования ifelse()
или if_else()
.(Полный 600-кратный пробный анализ занял более 4,5 часов, в основном из-за включения этих подходов.) См. Полный анализ результатов ниже.
Если вы боретесь с массивными фреймами данных, data.table
- самый быстрый вариант.всего: на 40% быстрее, чем стандартный подход Base R .Он также изменяет данные на месте, эффективно позволяя работать с почти вдвое большим количеством данных за один раз.
Кластеризация других полезных подходов к замене в обратном направлении
В местном масштабе:
- index
mutate_at(c(5:10), ~replace(., is.na(.), 0))
- прямая ссылка
mutate_at(vars(var5:var10), ~replace(., is.na(.), 0))
- фиксированное соответствие
mutate_at(vars(contains("1")), ~replace(., is.na(.), 0))
- или вместо
contains()
, попробуйте ends_with()
, starts_with()
- сопоставление с шаблоном
mutate_at(vars(matches("\\d{2}")), ~replace(., is.na(.), 0))
Условно:
(изменить только цифры (столбцы) и оставить только строку (столбцы).)
- целых чисел
mutate_if(is.integer, ~replace(., is.na(.), 0))
- удваивается
mutate_if(is.numeric, ~replace(., is.na(.), 0))
- строки
mutate_if(is.character, ~replace(., is.na(.), 0))
Полный анализ -
Обновлено для dplyr 0.8.0: функции используют формат purrr ~
символы: заменаустарел funs()
аргументы.
Подходы проверены:
# Base R:
baseR.sbst.rssgn <- function(x) { x[is.na(x)] <- 0; x }
baseR.replace <- function(x) { replace(x, is.na(x), 0) }
baseR.for <- function(x) { for(j in 1:ncol(x))
x[[j]][is.na(x[[j]])] = 0 }
# tidyverse
## dplyr
dplyr_if_else <- function(x) { mutate_all(x, ~if_else(is.na(.), 0, .)) }
dplyr_coalesce <- function(x) { mutate_all(x, ~coalesce(., 0)) }
## tidyr
tidyr_replace_na <- function(x) { replace_na(x, as.list(setNames(rep(0, 10), as.list(c(paste0("var", 1:10)))))) }
## hybrid
hybrd.ifelse <- function(x) { mutate_all(x, ~ifelse(is.na(.), 0, .)) }
hybrd.replace_na <- function(x) { mutate_all(x, ~replace_na(., 0)) }
hybrd.replace <- function(x) { mutate_all(x, ~replace(., is.na(.), 0)) }
hybrd.rplc_at.idx<- function(x) { mutate_at(x, c(1:10), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.nse<- function(x) { mutate_at(x, vars(var1:var10), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.stw<- function(x) { mutate_at(x, vars(starts_with("var")), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.ctn<- function(x) { mutate_at(x, vars(contains("var")), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.mtc<- function(x) { mutate_at(x, vars(matches("\\d+")), ~replace(., is.na(.), 0)) }
hybrd.rplc_if <- function(x) { mutate_if(x, is.numeric, ~replace(., is.na(.), 0)) }
# data.table
library(data.table)
DT.for.set.nms <- function(x) { for (j in names(x))
set(x,which(is.na(x[[j]])),j,0) }
DT.for.set.sqln <- function(x) { for (j in seq_len(ncol(x)))
set(x,which(is.na(x[[j]])),j,0) }
DT.fnafill <- function(x) { fnafill(df, fill=0)}
DT.setnafill <- function(x) { setnafill(df, fill=0)}
TКод для этого анализа:
library(microbenchmark)
# 20% NA filled dataframe of 10 Million rows and 10 columns
set.seed(42) # to recreate the exact dataframe
dfN <- as.data.frame(matrix(sample(c(NA, as.numeric(1:4)), 1e7*10, replace = TRUE),
dimnames = list(NULL, paste0("var", 1:10)),
ncol = 10))
# Running 600 trials with each replacement method
# (the functions are excecuted locally - so that the original dataframe remains unmodified in all cases)
perf_results <- microbenchmark(
hybrid.ifelse = hybrid.ifelse(copy(dfN)),
dplyr_if_else = dplyr_if_else(copy(dfN)),
hybrd.replace_na = hybrd.replace_na(copy(dfN)),
baseR.sbst.rssgn = baseR.sbst.rssgn(copy(dfN)),
baseR.replace = baseR.replace(copy(dfN)),
dplyr_coalesce = dplyr_coalesce(copy(dfN)),
tidyr_replace_na = tidyr_replace_na(copy(dfN)),
hybrd.replace = hybrd.replace(copy(dfN)),
hybrd.rplc_at.ctn= hybrd.rplc_at.ctn(copy(dfN)),
hybrd.rplc_at.nse= hybrd.rplc_at.nse(copy(dfN)),
baseR.for = baseR.for(copy(dfN)),
hybrd.rplc_at.idx= hybrd.rplc_at.idx(copy(dfN)),
DT.for.set.nms = DT.for.set.nms(copy(dfN)),
DT.for.set.sqln = DT.for.set.sqln(copy(dfN)),
times = 600L
)
Сводка результатов
> print(perf_results)
Unit: milliseconds
expr min lq mean median uq max neval
hybrd.ifelse 6171.0439 6339.7046 6425.221 6407.397 6496.992 7052.851 600
dplyr_if_else 3737.4954 3877.0983 3953.857 3946.024 4023.301 4539.428 600
hybrd.replace_na 1497.8653 1706.1119 1748.464 1745.282 1789.804 2127.166 600
baseR.sbst.rssgn 1480.5098 1686.1581 1730.006 1728.477 1772.951 2010.215 600
baseR.replace 1457.4016 1681.5583 1725.481 1722.069 1766.916 2089.627 600
dplyr_coalesce 1227.6150 1483.3520 1524.245 1519.454 1561.488 1996.859 600
tidyr_replace_na 1248.3292 1473.1707 1521.889 1520.108 1570.382 1995.768 600
hybrd.replace 913.1865 1197.3133 1233.336 1238.747 1276.141 1438.646 600
hybrd.rplc_at.ctn 916.9339 1192.9885 1224.733 1227.628 1268.644 1466.085 600
hybrd.rplc_at.nse 919.0270 1191.0541 1228.749 1228.635 1275.103 2882.040 600
baseR.for 869.3169 1180.8311 1216.958 1224.407 1264.737 1459.726 600
hybrd.rplc_at.idx 839.8915 1189.7465 1223.326 1228.329 1266.375 1565.794 600
DT.for.set.nms 761.6086 915.8166 1015.457 1001.772 1106.315 1363.044 600
DT.for.set.sqln 787.3535 918.8733 1017.812 1002.042 1122.474 1321.860 600
График результатов
ggplot(perf_results, aes(x=expr, y=time/10^9)) +
geom_boxplot() +
xlab('Expression') +
ylab('Elapsed Time (Seconds)') +
scale_y_continuous(breaks = seq(0,7,1)) +
coord_flip()
![Boxplot Comparison of Elapsed Time](https://i.stack.imgur.com/6BoFk.png)
Диаграмма рассеяния испытаний с цветовой кодировкой (с осью Y на логарифмической шкале)
qplot(y=time/10^9, data=perf_results, colour=expr) +
labs(y = "log10 Scaled Elapsed Time per Trial (secs)", x = "Trial Number") +
coord_cartesian(ylim = c(0.75, 7.5)) +
scale_y_log10(breaks=c(0.75, 0.875, 1, 1.25, 1.5, 1.75, seq(2, 7.5)))
![Scatterplot of All Trial Times](https://i.stack.imgur.com/x1uaK.png)
Примечание о других высоких показателях
Когда наборы данных становятся больше, Tidyr '* replace_na
исторически вырывается вперед.Благодаря текущему набору 50M точек данных, он работает почти точно так же, как Base R For Loop.Мне любопытно посмотреть, что происходит с различными размерами фреймов данных.
Дополнительные примеры вариантов функций mutate
и summarize
_at
и _all
можно найти здесь: https://rdrr.io/cran/dplyr/man/summarise_all.html ДополнительноЯ нашел полезные демонстрации и коллекции примеров здесь: https://blog.exploratory.io/dplyr-0-5-is-awesome-heres-why-be095fd4eb8a
Атрибуты и благодарности
С особой благодарностью:
- Тайлер Ринкер и Акрун для демонстрации микробенчмарка.
- alexis_laz за работу, помогающую мне понять использование
local()
, и (с помощью Фрэнка, тоже пациента) роль, которую тихое принуждение играет в ускорении многих из этих подходов. - ArthurYip для мешка, чтобы добавить более новую функцию
coalesce()
и обновить анализ. - Грегор для толчка, чтобы выяснить функции
data.table
достаточно хорошо, чтобы наконец включить их вмодельный ряд. - База R Для цикла: alexis_laz
- data.table Для циклов: Matt_Dowle
(Конечно, пожалуйста, подойдите и дайте им голосование, если вы находите эти подходы полезными.)
Примечание по использованию чисел: Если у вас есть чистый набор целочисленных данных, все ваши функции будут работать быстрее.Пожалуйста, см. работа alexiz_laz для получения дополнительной информации.Я не помню, чтобы IRL встречал набор данных, содержащий более 10-15% целых чисел, поэтому я запускаю эти тесты на полностью числовых фреймах данных.
Используемое оборудование 3,9 ГГц процессор с 24 ГБ оперативной памяти