Первый шаг - понять, что определение диапазонов целых чисел не будет работать. Вместо этого я пойду со списком пар чисел:
badData <- list(c(296,310), c(330,335), c(350,565))
с пониманием, что мы хотим проверить, чтобы каждый $wavelength
находился в любом из этих трех диапазонов. Поддерживаются другие диапазоны.
Второе, что мы можем сделать, это написать функцию, которая проверяет, находится ли вектор значений в одной или нескольких парах чисел. (В этом примере мы «знаем», что его будет не более одного, но это не критично.)
within_ranges <- function(x, lims) {
Reduce(`|`, lapply(lims, function(lim) lim[1] <= x & x <= lim[2]))
}
Чтобы понять, что это делает, давайте отладим его, позвоним и посмотрим, что происходит.
debugonce(within_ranges)
within_ranges(df$wavelength, badData)
# debugging in: within_ranges(df$wavelength, badData)
# debug at #1: {
# Reduce(`|`, lapply(lims, function(lim) lim[1] <= x & x <=
# lim[2]))
# }
Давайте просто запустим эту внутреннюю часть:
# Browse[2]>
lapply(lims, function(lim) lim[1] <= x & x <= lim[2])
# [[1]]
# [1] TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
# [[2]]
# [1] FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE
# [[3]]
# [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE
Таким образом, первый элемент (T, T, F, F, ...) состоит в том, попадают ли значения (x
) в первую пару чисел (от 296 до 310); второй элемент со второй парой (от 330 до 335); и т.д.
Часть Reduce(
вызывает первый аргумент, функцию, для первых двух аргументов, сохраняет возвращаемое значение, а затем выполняет ту же функцию для возврата и третьего аргумента. Он сохраняет его, затем выполняет ту же функцию для возврата и четвертого аргумента (если существует). Это повторяется по всей длине предоставленного списка.
В этом примере функция - это литерал |
(экранированный, поскольку он особенный), поэтому он "ИЛИ" использует вектор [[1]]
с вектором [[2]]
. Вы действительно можете увидеть, что происходит, если вы добавите accumulate=TRUE
:
# Browse[2]>
Reduce(`|`, lapply(lims, function(lim) lim[1] <= x & x <= lim[2]), accumulate=TRUE)
# [[1]]
# [1] TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
# [[2]]
# [1] TRUE TRUE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE
# [[3]]
# [1] TRUE TRUE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE TRUE TRUE
Первый возврат - первый вектор, без изменений. Второй элемент - это оригинальный [[2]]
вектор ИЛИ с предыдущим возвратом, который является этим [[1]]
вектором (который совпадает с исходным [[1]]
). Третий элемент - это оригинальный [[3]]
вектор ИЛИ с предыдущим возвратом, который равен this [[2]]
. Это приводит к трем группам TRUE
(1, 2, 7, 11, 12), которые вы ожидаете. Итак, нам нужен элемент [[3]]
, который мы получаем без накопления:
# Browse[2]>
Reduce(`|`, lapply(lims, function(lim) lim[1] <= x & x <= lim[2]))
# [1] TRUE TRUE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE TRUE TRUE
Хорошо, так что давайте Q
выйдем из отладчика и дадим ему полный ход:
within_ranges(df$wavelength, badData)
# [1] TRUE TRUE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE TRUE TRUE
Этот вывод выглядит знакомым.
(Кстати: внутри нашей функции мы могли бы также использовать
rowSums(sapply(lims, ...)) > 0
и это сработало бы так же хорошо. Для этого, однако, вы должны понимать, что sapply
должен возвращать matrix
с таким количеством столбцов, сколько у нас есть строк данных в df
, странно, если вы не знакомы.)
Теперь мы можем NA
, если нам нужно, либо dplyr
:
df %>%
mutate(
reflectance = if_else(within_ranges(wavelength, badData), NA_real_, reflectance)
)
# wavelength reflectance
# 1 300.0000 NA
# 2 305.0087 NA
# 3 310.0173 -11.01733
# 4 315.0260 -16.02600
# 5 320.0347 -21.03467
# 6 325.0433 -26.04333
# 7 330.0520 NA
# 8 335.0607 -36.06067
# 9 340.0693 -41.06934
# 10 345.0780 -46.07800
# 11 350.0867 NA
# 12 355.0953 NA
Редактировать : или другой dplyr
, используя вашу первую мысль о replace
(не моя первая по привычке, без причины):
df %>%
mutate(
reflectance = replace(reflectance, within_ranges(wavelength, badData), NA_real_)
)
или основание R:
df$reflectance <- ifelse(within_ranges(df$wavelength, badData), NA_real_, df$reflectance)
df
# wavelength reflectance
# 1 300.0000 NA
# 2 305.0087 NA
# 3 310.0173 -11.01733
# 4 315.0260 -16.02600
# 5 320.0347 -21.03467
# 6 325.0433 -26.04333
# 7 330.0520 NA
# 8 335.0607 -36.06067
# 9 340.0693 -41.06934
# 10 345.0780 -46.07800
# 11 350.0867 NA
# 12 355.0953 NA
Примечания:
- Я специально использую
NA_real
, как для ясности (знаете ли вы, что существуют разные типы NA
?), Так и отчасти потому, что при использовании dplyr::if_else
он будет жаловаться / не работать, если классы аргументы "истина" и "ложь" не совпадают (NA
технически logical
, а не numeric
, как ваш reflectance
);
- Я использую
dplyr::if_else
для первого примера, так как вы уже используете dplyr
, но в случае, если вы решите отказаться от dplyr
(или кто-то еще), тогда работает base-R ifelse
, тоже. (У него есть свои обязательства, но, похоже, он отлично работает здесь.)