R: сравнение идентификаторов в одном фрейме данных

#r #dataframe #group-by #dplyr

#r #фрейм данных #группировка по #dplyr

Вопрос:

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

 df <- data.frame(c(1,1,1,2,2,2,2,3,3,3,3,4,4,4,5,5,5), c("a","a","a","b","b","b","b","b","b","b","b",
                                                         "a","a","a","b","b","b"),
                 c(300,295,295,25,25,25,25,25,20,20,20,300,295,295,300, 295,295), 
                 c("c","d","e","f","g","h","i","j","l","m","n","o","p","q","r","s","t"))
colnames(df) <- c("ID", "Group", "Price", "OtherNumber")

> df
   ID Group Price OtherNumber
1   1     a   300           c
2   1     a   295           d
3   1     a   295           e
4   2     b    25           f
5   2     b    25           g
6   2     b    25           h
7   2     b    25           i
8   3     b    25           j
9   3     b    20           l
10  3     b    20           m
11  3     b    20           n
12  4     a   300           o
13  4     a   295           p
14  4     a   295           q
15  5     b   300           r
16  5     b   295           s
17  5     b   295           t
  

Я хочу сравнить первую цену последующих идентификаторов. Я хочу пометить их, только если два последующих идентификатора имеют одинаковую начальную цену и находятся в одной группе. На всякий случай, если это было не очень понятно, вот пример: я сравниваю первый и второй ID, но обе группы (a и b) и начальная цена является несоответствием (300 против 25). С другой стороны, между идентификаторами 2 и 3 они оба находятся в группе b и имеют одинаковую начальную цену 25 (ср. строки 4 и 8). Последующие цены на самом деле не имеют значения, поскольку они могут отличаться.

Я полагаю, я должен уметь работать с пакетом dplyr и определил очень приблизительное решение (которое пока не работает).

 # Load dplyr
library(dplyr)

# Assign row numbers within IDs
df1 <- df %>%
  group_by(ID) %>%
  mutate(subID = row_number())

# Isolate first observation in ID 
df2 <- df1[df1$subID == 1,]

# Set up loop to iterate through IDs
for (i in 2:length(df2)) {
  if (df2$Price[i] - df2$Price[i - 1] == 0) {
    df2$flag <- TRUE
  } else {
    df2$flag <- FALSE
  }
}

  

Если вы скажете мне, что это единственно возможное решение, я, очевидно, выделю на это больше ресурсов, но я уверен, что должно быть более простое решение. Я проверил SO и, возможно, что-то пропустил, но я не смог найти ничего, что шло бы в этом направлении. Спасибо!

Результат, который я хочу получить, выглядит примерно так:

    ID Group Price OtherNumber   flag
1   1     a   300           c  FALSE
2   1     a   295           d  FALSE
3   1     a   295           e  FALSE
4   2     b    25           f   TRUE
5   2     b    25           g   TRUE
6   2     b    25           h   TRUE
7   2     b    25           i   TRUE
8   3     b    25           j   TRUE
9   3     b    20           l   TRUE
10  3     b    20           m   TRUE
11  3     b    20           n   TRUE
12  4     a   300           o  FALSE
13  4     a   295           p  FALSE
14  4     a   295           q  FALSE
15  5     b   300           r  FALSE
16  5     b   295           s  FALSE
17  5     b   295           t  FALSE
  

Ответ №1:

Вот строка data.table oneliner… разрежьте на более мелкие фрагменты, чтобы просмотреть промежуточные результаты; также смотрите объяснение внизу ответа.

 dt <- as.data.table( df )
dt[ dt[ , .SD[1], ID][ ( Group == shift( Group, type = "lead") amp; Price == shift( Price, type = "lead") ) |
                   ( Group == shift( Group, type = "lag") amp; Price == shift( Price, type = "lag),
                   flag := TRUE][is.na(flag), flag := FALSE], flag := i.flag, on = .(ID)][]

#     ID Group Price OtherNumber  flag
#  1:  1     a   300           c FALSE
#  2:  1     a   295           d FALSE
#  3:  1     a   295           e FALSE
#  4:  2     b    25           f  TRUE
#  5:  2     b    25           g  TRUE
#  6:  2     b    25           h  TRUE
#  7:  2     b    25           i  TRUE
#  8:  3     b    25           j  TRUE
#  9:  3     b    20           l  TRUE
# 10:  3     b    20           m  TRUE
# 11:  3     b    20           n  TRUE
# 12:  4     a   300           o FALSE
# 13:  4     a   295           p FALSE
# 14:  4     a   295           q FALSE
# 15:  5     b   300           r FALSE
# 16:  5     b   295           s FALSE
# 17:  5     b   295           t FALSE
  

объяснение:
dt[ , .SD[1], ID] создайте data.table с первой строкой каждого ID

[ Group == shift( ... , flag := TRUE] устанавливает для столбца flag значение TRUE , когда следующая (или предыдущая) строка имеет совпадение Price и Group .

[is.na(flag), flag := FALSE] заполняет остальное (что неверно) значением `FALSE

..flag := i.flag, on = .(ID)] выполняет соединение по левому краю (по ссылке, поэтому это быстро и эффективно) в исходной data.table для получения конечного результата.

Комментарии:

1. Спасибо, это именно то, что я искал (также спасибо за подробное объяснение)! Просто краткое замечание: если я не ошибаюсь, в любой из двух совпадающих строк должно быть запаздывание, а не опережение.