Я не нашел никакого метода проверки того, находятся ли элементы категориального значения вектора между другими элементами категориального значения. Дается фрейм данных:
id letter
1 B
2 A
3 B
4 B
5 C
6 B
7 A
8 B
9 C
Все, что я нашел, связано с числовыми значениями и с понятием общего порядка (а не с индексом элемента в конкретном векторе).
Я хочу добавить новый столбец с логическими значениями (1, если B находится между A и C; 0, если B находится между C и A) в фрейм данных,
id letter between
1 B 0
2 A NA
3 B 1
4 B 1
5 C NA
6 B 0
7 A NA
8 B 1
9 C NA
Последовательность упорядочена по времени. Желаемый результат - удалить строки df$between==0.
Комбинация rle
(кодирование длин серий) и zoo::rollapply
является одним из вариантов:
library(zoo)
d <- structure(list(id = 1:9,
letter = structure(c(2L, 1L, 2L, 2L, 3L, 2L, 1L, 2L, 3L),
.Label = c("A", "B", "C"),
class = "factor")),
class = "data.frame", row.names = c(NA, -9L))
rl <- rle(as.numeric(d$letter))
rep(rollapply(c(NA, rl$values, NA),
3,
function(x) if (x[2] == 2)
ifelse(x[1] == 1 && x[3] == 3, 1, 0)
else NA),
rl$lengths)
# [1] 0 NA 1 1 NA 0 NA 1 NA
Объяснение
rle
вы идентифицируете блоки последовательных значений.rollapply
вы «прокручиваете» функцию с заданным размером окна (здесь 3) по вектору.rl$values
содержит различные элементы, и функция, которую мы применяем к нему, довольно проста:
B
), верните NA
A
, а элемент 3 является C
, возвращает 1 и 0 в противном случаеВы можете использовать функции lead
и lag
, чтобы узнать буквы до и после, а затем mutate
, как показано ниже:
library(dplyr)
df %>%
mutate(letter_lag = lag(letter, 1),
letter_lead = lead(letter, 1)) %>%
mutate(between = case_when(letter_lag == "A" | letter_lead == "C" ~ 1,
letter_lag == "C" | letter_lead == "A" ~ 0,
TRUE ~ NA_real_)) %>%
select(id, letter, between)
id letter between
1 1 B 0
2 2 A NA
3 3 B 1
4 4 B 1
5 5 C NA
6 6 B 0
7 7 A NA
8 8 B 1
9 9 C NA
Этот код неверен. С этим изменением df$letters[5] <- "A"
вы получите неправильные результаты для строк 3
и 6
Другая tidyverse
возможность может быть:
df %>%
group_by(grp = with(rle(letter), rep(seq_along(lengths), lengths))) %>%
filter(row_number() == 1) %>%
ungroup() %>%
mutate(res = ifelse(lag(letter, default = first(letter)) == "A" &
lead(letter, default = last(letter)) == "C", 1, 0)) %>%
select(-letter, -grp) %>%
full_join(df, by = c("id" = "id")) %>%
arrange(id) %>%
fill(res) %>%
mutate(res = ifelse(letter != "B", NA, res))
id res letter
<int> <dbl> <chr>
1 1 0 B
2 2 NA A
3 3 1 B
4 4 1 B
5 5 NA C
6 6 0 B
7 7 NA A
8 8 1 B
9 9 NA C
В этом случае он сначала группируется по идентификатору серийного типа и сохраняет первые строки с заданным идентификатором. Во-вторых, проверяет состояние. В-третьих, он выполняет полное соединение с исходным df в столбце «id». Наконец, он упорядочивает по «id», заполняет пропущенные значения и присваивает NA строкам, где «буква» != B.
Вот одно решение, которое, я надеюсь, довольно просто концептуально. Для «особых» случаев, таких как B, находящийся вверху или внизу списка, или имеющий A или C с обеих сторон, я установил такие значения в 0.
# Create dummy data - you use your own
df <- data.frame(id=1:100, letter=sample(c("A", "B", "C"), 100, replace=T))
# Copy down info on whether A or C is above each B
acup <- df$letter
for(i in 2:nrow(df))
if (df$letter[i] == "B")
acup[i] <- acup[i-1]
# Copy up info on whether A or C is below each B
acdown <- df$letter
for(i in nrow(df):2 -1)
if (df$letter[i] == "B")
acdown[i] <- acdown[i+1]
# Set appropriate values for column 'between'
df$between <- NA
df$between[acup == "A" & acdown == "C"] <- 1
df$between[df$letter == "B" & is.na(df$between)] <- 0 # Includes special cases
Из вопроса неясно, должны ли чередоваться «A» и «C», хотя это подразумевается, потому что нет кодирования «B» между «A» и «A» или vv. Предположим, что да, для вектора
x = c("B", "A", "B", "B", "C", "B", "A", "B", "C")
сопоставьте числовые значения c(A=1, B=0, C=-1)
и сформируйте кумулятивную сумму
v = cumsum(c(A=1, B=0, C=-1)[x])
(увеличение на 1 при встрече с "A", уменьшение на единицу при "C"). Замените позиции, не соответствующие «B», на NA
v[x != "B"] = NA
давать
> v
B A B B C B A B C
0 NA 1 1 NA 0 NA 1 NA
Это может быть захвачено как функция
fun = function(x, map = c(A = 1, B = 0, C = -1)) {
x = map[x]
v = cumsum(x)
v[x != 0] = NA
v
}
и используется для преобразования data.frame или tibble, например,
tibble(x) %>% mutate(v = fun(x))
@markus спасибо; опечатка, но я хотел использовать x != "B"
, а не names(v)
, другой способ снять шкуру с кошки.
Каков желаемый результат для последовательности
... "A", "B", "B", "A" ...
?