Как перекодировать диапазон строк между двумя конкретными значениями - PullRequest
9 голосов
/ 09 мая 2019

У меня есть следующий фрейм данных:

a <- seq(1:14)
b <- c(0, 0, "start", 0, 0, 0, "end", 0, 0, "start", 0, "end", 0, 0)
df <- data.frame(a, b)

 df
a      b
1      0
2      0
3   start
4      0
5      0
6      0
7    end
8      0
9      0
10  start
11     0
12   end
13     0
14     0

Теперь я хочу перекодировать значения в b между «start» и «end» так, чтобы:

 df
a      b
1      0
2      0
3   start
4      1
5      1
6      1
7    end
8      0
9      0
10  start
11     1
12   end
13     0
14     0

Пока у меня нет рабочего кода.Я пробовал что-то с which() и between() и inrange() из пакета data.table, но я не мог понять это.Есть идеи как это решить?

Ответы [ 2 ]

12 голосов
/ 09 мая 2019

С учетом

df <- data.frame(a, b, stringsAsFactors = FALSE)
#                      ^^^^^^^^^^^^^^^^^^^^^^^^

Мы можем сделать

idx <- (cumsum(b == "start") - cumsum(b == "end") - (b == "start")) == 1
df <- transform(df, b = replace(b, idx, "1"))
df
#    a     b
#1   1     0
#2   2     0
#3   3 start
#4   4     1
#5   5     1
#6   6     1
#7   7   end
#8   8     0
#9   9     0
#10 10 start
#11 11     1
#12 12   end
#13 13     0
#14 14     0

idx равно TRUE для элементов между «началом» и «концом».

Когда мы звоним cumsum(b == "start") - cumsum(b == "end"), мы почти на месте

cumsum(b == "start") - cumsum(b == "end")
# [1] 0 0 1 1 1 1 0 0 0 1 1 0 0 0

Нам нужно только установить позиции на ноль, где b == "start", т.е.

cumsum(b == "start") - cumsum(b == "end") - b == "start"
# [1] 0 0 0 1 1 1 0 0 0 0 1 0 0 0

Проверьте, равен ли этот вектор 1, чтобы сделать его логичным

idx <- (cumsum(b == "start") - cumsum(b == "end") - (b == "start")) == 1

Результат

idx
[1] FALSE FALSE FALSE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE

Мы используем этот логический вектор для замены соответствующих элементов b на "1".

4 голосов
/ 09 мая 2019

Более компактный ответ из комментария @RonakShah:

df$b[unlist(mapply(`:`, which(df$b == "start") + 1, which(df$b == "end") - 1))] <- 1

Оригинальный ответ

Логика, аналогичная приведенному выше компактному ответу, с использованием lapply здесьмы находим начальную и конечную позиции, сопоставляем это со списком и находим индекс, затем заменяем индекс на 1,

starting <- which(b == "start")
ending <- which(b == "end")
my.ls <- lapply(Map(c, starting, ending), function(x) (x[1]+1):(x[2]-1))

index <- unlist(my.ls)
b[index] <- 1


df <- data.frame(a, b)
df
a     b
1   1     0
2   2     0
3   3 start
4   4     1
5   5     1
6   6     1
7   7   end
8   8     0
9   9     0
10 10 start
11 11     1
12 12   end
13 13     0
14 14     0

Ответ по старому циклу

Youмогут использовать функции which следующим образом: сначала определить все начальные и конечные точки, затем выполнить цикл и изменить их на 1 ...

a <- seq(1:14)
b <- c(0, 0, "start", 0, 0, 0, "end", 0, 0, "start", 0, "end", 0, 0)

starting <- which(b == "start")
ending <- which(b == "end")

for (i in 1:length(starting)){
  index <- (starting[i]+1):(ending[i]-1)
  b[index] <- 1
}
df <- data.frame(a, b)
df
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...