Заполните NA в R нулем, если следующая действительная точка данных находится на расстоянии более 2 интервалов
У меня есть несколько векторов с NA и мое намерение заполнить NA, которые находятся более чем в 2 интервалах от действительной точки данных, с 0. Например:
x <- c(3, 4, NA, NA, NA, 3, 3)
Ожидаемый результат есть,
3, 4, NA, 0, NA, 3, 3
Ответы
Ответ 1
Может быть, есть более простые решения, но этот работает.
na2zero <- function(x){
ave(x, cumsum(abs(c(0, diff(is.na(x))))), FUN = function(y){
if(anyNA(y)){
if(length(y) > 2) y[-c(1, length(y))] <- 0
}
y
})
}
na2zero(x)
#[1] 3 4 NA 0 NA 3 3
X <- list(x, c(x, x), c(3, 4, NA, NA, NA, NA, 3, 3))
lapply(X, na2zero)
Ответ 2
Обновить -
Здесь, наверное, одно из самых простых и быстрых решений (спасибо ответу Г. Гротендика). Просто знание, является ли значение NA
с обеих сторон любого NA
является достаточной информацией. Поэтому, используя lead
и lag
от пакета dplyr
-
na2zero <- function(x) {
x[is.na(lag(x, 1, 0)) & is.na(lead(x, 1, 0)) & is.na(x)] <- 0
x
}
na2zero(x = c(3, 4, NA, NA, NA, 3, 3))
[1] 3 4 NA 0 NA 3 3
na2zero(x = c(3, 4, NA, NA, NA, NA, NA, 3, 3))
[1] 3 4 NA 0 0 0 NA 3 3
na2zero(x = c(3, 4, NA, NA, NA, 3, 3, NA, NA, 1, NA, 0, 0, rep(NA, 4L)))
[1] 3 4 NA 0 NA 3 3 NA NA 1 NA 0 0 NA 0 0 NA
Предыдущий ответ (также быстрый) -
Здесь один способ с использованием rle
и replace
из базы R. Этот метод превращает каждый NA
, который не является конечной точкой в рабочей длине, в 0
-
na2zero <- function(x) {
run_lengths <- rle(is.na(x))$lengths
replace(x,
sequence(run_lengths) != 1 &
sequence(run_lengths) != rep(run_lengths, run_lengths) &
is.na(x),
0)
}
na2zero(x = c(3, 4, NA, NA, NA, 3, 3))
[1] 3 4 NA 0 NA 3 3
na2zero(x = c(3, 4, NA, NA, NA, NA, NA, 3, 3))
[1] 3 4 NA 0 0 0 NA 3 3
Обновленные тесты -
set.seed(2)
x <- c(3, 4, NA, NA, NA, 3, 3)
x <- sample(x, 1e5, T)
microbenchmark(
Rui(x),
Shree_old(x), Shree_new(x),
markus(x),
IceCreamT(x),
Uwe1(x), Uwe2(x), Uwe_Reduce(x),
Grothendieck(x),
times = 50
)
all.equal(Shree_dplyr(x), Rui(x)) # [1] TRUE
all.equal(Shree_dplyr(x), Shree_rle(x)) # [1] TRUE
all.equal(Shree_dplyr(x), markus(x)) # [1] TRUE
all.equal(Shree_dplyr(x), Uwe1(x)) # [1] TRUE
all.equal(Shree_dplyr(x), Uwe2(x)) # [1] TRUE
all.equal(Shree_dplyr(x), Uwe_Reduce(x)) # [1] TRUE
all.equal(Shree_dplyr(x), Grothendieck(x)) # [1] TRUE
Unit: milliseconds
expr min lq mean median uq max neval
Rui(x) 286.026540 307.586604 342.620266 318.404731 363.844258 518.03330 50
Shree_rle(x) 51.556489 62.038875 85.348031 65.012384 81.882141 327.57514 50
Shree_dplyr(x) 3.996918 4.258248 17.210709 6.298946 10.335142 207.14732 50
markus(x) 853.513854 885.419719 1001.450726 919.930389 1018.353847 1642.25435 50
IceCreamT(x) 12.162079 13.773873 22.555446 15.021700 21.271498 199.08993 50
Uwe1(x) 162.536980 183.566490 225.801038 196.882049 269.020395 439.17737 50
Uwe2(x) 83.582360 93.136277 115.608342 99.165997 115.376903 309.67290 50
Uwe_Reduce(x) 1.732195 1.871940 4.215195 2.016815 4.842883 25.91542 50
Grothendieck(x) 620.814291 688.107779 767.749387 746.699435 850.442643 982.49094 50
PS: Изучите ответ TiredSquirell, который выглядит как базовая версия ответа Uwe Lag Lag, но несколько быстрее (не тестировался выше).
Ответ 3
Здесь опция data.table
library(data.table)
na0_dt <- function(x){
replace(x, rowid(r <- rleid(xna <- is.na(x))) > 1 & rev(rowid(rev(r))) > 1 & xna, 0)
}
Ответ 4
Для полноты изложения приведем еще три подхода data.table:
x <- c(3, 4, NA, NA, NA, 3, 3, NA, NA, 1, NA, 0, 0, rep(NA, 4L))
library(data.table)
data.table(x)[, x := replace(x, which(is.na(x))[-c(1L, .N)], 0), by =.(rleid(is.na(x)))]$x
[1] 3 4 NA 0 NA 3 3 NA NA 1 NA 0 0 NA 0 0 NA
x[data.table(x)[, .I[is.na(x)][-c(1L, .N)], by =.(rleid(is.na(x)))]$V1] <- 0
x
[1] 3 4 NA 0 NA 3 3 NA NA 1 NA 0 0 NA 0 0 NA
shift()
и Reduce()
Я был так сосредоточен на поиске правильного способа создания групп, что начал думать о прямолинейном подходе довольно поздно. Правило довольно простое:
Замените все NA на ноль, которым предшествует и следует другой NA.
Это можно сделать с помощью zoo::rollapply()
как в ответе Г. Гротендика, или с помощью lag()
и lead()
как в последнем редактировании Shree.
Тем не менее, мой собственный тест (не опубликованный здесь, чтобы избежать дублирования с data.table::shift()
Шри) показывает, что data.table::shift()
и Reduce()
пока самый быстрый метод.
isnax <- is.na(x)
x[Reduce('&', data.table::shift(isnax, -1:1))] <- 0
x
Это также немного быстрее, чем использование lag()
и lead()
(обратите внимание, что это отличается от версии Shree, так как is.na()
вызывается только один раз):
isnax <- is.na(x)
x[isnax & dplyr::lag(isnax) & dplyr::lead(isnax)] <- 0
x
Ответ 5
Исходя из примера, я предполагаю, что вы имеете в виду, что если значение равно NA, а смежные значения в обоих направлениях равны NA (или в одном направлении, если значение является первым или последним), то замените значение на 0. Использование центрированного скользящего окна длины 3 возвращает TRUE, если все это NA, а затем заменяет TRUE на 0. Это дает следующую однострочную строку
library(zoo)
replace(x, rollapply(c(TRUE, is.na(x), TRUE), 3, all), 0)
## [1] 3 4 NA 0 NA 3 3
Ответ 6
Вот "тупо простое" решение:
is_na <- is.na(x) # Vector telling you whether each position in x is NA
na_before <- c(F,is_na[1:(length(x)-1)]) # Whether each position has an NA before it
na_after <- c(is_na[2:length(x),F) # Whether each position has an NA after it
x[is_na & na_before & na_after] <- 0 # Set to 0 if all three are true
Создание na_before и na_after основано на смещении одного вправо или одного влево. Чтобы проиллюстрировать, как это работает, рассмотрим буквы ниже (я пишу T и F как 1 и 0, чтобы их было легче различить):
A B C D E
is_vowel 1 0 0 0 1
vowel_before 0 1 0 0 0
vowel_after 0 0 0 1 0
Когда вы делаете vowel_before, вы берете последовательность "10001" is_vowel и сдвигаете ее на один вправо (потому что каждая буква теперь ссылается на букву слева). Вы отбрасываете последнюю 1 (вам не важно, что у F есть гласная перед ним, потому что F не включена), и вы добавляете 0 в начале (первая буква не имеет буквы перед ней, и поэтому не может иметь гласный перед этим). vowel_after создается с той же логикой.
Редактировать. (Добавлено Руи Баррадасом)
Это решение, по моим оценкам, самое быстрое.
Как функция:
TiredSquirrel <- function(x){
is_na <- is.na(x)
na_before <- c(FALSE, is_na[1:(length(x) - 1)])
na_after <- c(is_na[2:length(x)], FALSE)
x[is_na & na_before & na_after] <- 0
x
}
И эталон.
x <- c(3, 4, NA, NA, NA, 3, 3)
r <- na2zero(x)
all.equal(r, TiredSquirrel(x))
#[1] TRUE
x <- sample(x, 1e3, TRUE)
r <- na2zero(x)
all.equal(r, TiredSquirrel(x))
#[1] TRUE
microbenchmark(
Rui = na2zero(x),
Uwe_Reduce = Uwe_Reduce(x),
TiredSquirrel = TiredSquirrel(x)
)
#Unit: microseconds
# expr min lq mean median uq max neval cld
# Rui 3134.293 3198.8180 3365.70736 3263.7980 3391.7900 5593.111 100 b
# Uwe_Reduce 99.895 104.3510 125.81417 113.9995 146.7335 244.280 100 a
# TiredSquirrel 65.205 67.4365 72.41129 70.6430 75.8315 122.061 100 a
Ответ 7
Еще один базовый подход
x <- c(3, 4, NA, NA, NA, 3, 3, NA, 3, NA, NA, NA, NA, 1)
Создать группирующую переменную
grp <- with(rle(is.na(x)), rep(seq_along(lengths), lengths)) # same as rleid(is.na(x))
Для каждой группы вычислите параллельный минимум cumsum(is.na(x))
и его обратный (который будет больше единицы для значений, "которые находятся более чем в 2 интервалах от действительной точки данных" на расстоянии)
tmp <- ave(x, grp, FUN = function(x) pmin(cumsum(is.na(x)), rev(cumsum(is.na(x)))))
Наконец, используйте это как идентификатор для замены желаемых значений в x
replace(x, tmp > 1, 0)
# [1] 3 4 NA 0 NA 3 3 NA 3 NA 0 0 NA 1
Написано как функция
f <- function(x, gap = 1) {
stopifnot(gap >= 0)
if (gap == 0) {
x[is.na(x)] <- 0
x } else {
grp <- with(rle(is.na(x)), rep(seq_along(lengths), times = lengths))
tmp <- ave(x, grp, FUN = function(x) pmin(cumsum(is.na(x)), rev(cumsum(is.na(x)))))
replace(x, tmp > gap, 0)
}
}
f(x)
Ответ 8
Как насчет этого:
library(tidyverse)
x <- as.data.frame(x)
x %>% group_by(x) %>%
mutate(y = cumsum(is.na(x)), z = ifelse(y > 1 & y < max(y),0,x)) %>%
pull(z)
[1] 3 4 NA 0 NA 3 3