Подмножество фрейма данных на основе записи столбца (или ранга)

#r #subset

#r #подмножество

Вопрос:

У меня есть data.frame такой же простой, как этот:

 id group idu  value
1  1     1_1  34
2  1     2_1  23
3  1     3_1  67
4  2     4_2  6
5  2     5_2  24
6  2     6_2  45
1  3     1_3  34
2  3     2_3  67
3  3     3_3  76
  

откуда я хочу получить подмножество с первыми записями каждой группы; что-то вроде:

 id group idu value
1  1     1_1 34
4  2     4_2 6
1  3     1_3 34
  

идентификатор не уникален, поэтому подход не должен полагаться на него.

Могу ли я добиться этого, избегая циклов?

 data <- data.frame(
  id = c(1L, 2L, 3L, 4L, 5L, 6L, 1L, 2L, 3L),
  group = rep(1:3, each = 3L),
  idu = factor(c("1_1", "2_1", "3_1", "4_2", "5_2", "6_2", "1_3", "2_3", "3_3")),
  value = c(34L, 23L, 67L, 6L, 24L, 45L, 34L, 67L, 76L)
)
  

Ответ №1:

Используя df в миллион строк Гэвина:

 DF3 <- data.frame(id = sample(1000, 1000000, replace = TRUE),
                  group = factor(rep(1:1000, each = 1000)),
                  value = runif(1000000))
DF3 <- within(DF3, idu <- factor(paste(id, group, sep = "_")))
  

Я думаю, что самый быстрый способ — изменить порядок фрейма данных, а затем использовать duplicated :

 system.time({
  DF4 <- DF3[order(DF3$group), ]
  out2 <- DF4[!duplicated(DF4$group), ]
})
# user  system elapsed 
# 0.335   0.107   0.441
  

Это сопоставимо с 7 секундами для метода Гэвина fastet lapply split на моем компьютере.

Как правило, при работе с фреймами данных самым быстрым подходом обычно является генерация всех индексов, а затем выполнение одного подмножества.

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

1. Это хороший подход, но, чтобы добавить дополнительное примечание, реальные данные могут также повторять код группы, что требует дополнительного шага: добавьте реальный единый groupId ко всему набору данных, возможно, на основе столбца timestamp

2. как получилось, что !duplicated возвращает первое значение дублированной группы?

3. @zach Если вы прочитаете справку для duplicated , вы можете увидеть, что на самом деле это довольно конкретное определение «дубликата» — «дубликаты элементов с меньшим индексом». Таким образом, при первом обнаружении идентификатора группы R просматривает только предыдущие записи, которые она обработала, а не какие-либо из дубликатов впереди. Таким образом, оно возвращает FALSE , которое Хэдли инвертирует.

Ответ №2:

Обновление в свете комментария OP

При выполнении этого для более чем миллиона строк все предоставленные таким образом параметры будут медленными. Вот некоторые временные интервалы сравнения для фиктивного набора данных из 100 000 строк:

 set.seed(12)
DF3 <- data.frame(id = sample(1000, 100000, replace = TRUE),
                  group = factor(rep(1:100, each = 1000)),
                  value = runif(100000))
DF3 <- within(DF3, idu <- factor(paste(id, group, sep = "_")))

> system.time(out1 <- do.call(rbind, lapply(split(DF3, DF3["group"]), `[`, 1, )))
   user  system elapsed 
 19.594   0.053  19.984 
> system.time(out3 <- aggregate(DF3[,-2], DF3["group"], function (x) x[1]))
   user  system elapsed 
 12.419   0.141  12.788 
  

Я отказался от выполнения их с миллионом строк. Хотите верьте, хотите нет, но гораздо быстрее:

 out2 <- matrix(unlist(lapply(split(DF3[, -4], DF3["group"]), `[`, 1,)),
               byrow = TRUE, nrow = (lev <- length(levels(DF3$group))))
colnames(out2) <- names(DF3)[-4]
rownames(out2) <- seq_len(lev)
out2 <- as.data.frame(out2)
out2$group <- factor(out2$group)
out2$idu <- factor(paste(out2$id, out2$group, sep = "_"),
                   levels = levels(DF3$idu))
  

Выходные данные (фактически) одинаковы:

 > all.equal(out1, out2)
[1] TRUE
> all.equal(out1, out3[, c(2,1,3,4)])
[1] "Attributes: < Component 2: Modes: character, numeric >"              
[2] "Attributes: < Component 2: target is character, current is numeric >"
  

(разница между out1 (или out2 ) и out3 ( aggregate() версией) заключается только в именах строк компонентов.)

с синхронизацией:

    user  system elapsed 
  0.163   0.001   0.168
  

проблема со 100 000 строками и проблема с миллионом строк:

 set.seed(12)
DF3 <- data.frame(id = sample(1000, 1000000, replace = TRUE),
                  group = factor(rep(1:1000, each = 1000)),
                  value = runif(1000000))
DF3 <- within(DF3, idu <- factor(paste(id, group, sep = "_")))
  

с синхронизацией

    user  system elapsed 
 11.916   0.000  11.925
  

Работа с матричным вариантом (который производит out2 ) позволяет быстрее обрабатывать миллион строк, по сравнению с другими версиями, которые решают задачу на 100 000 строк. Это просто показывает, что работа с матрицами действительно очень быстрая, и узким местом в моей do.call() версии является rbind() объединение результатов вместе.

Синхронизация проблемы с миллионом строк была выполнена с:

 system.time({out4 <- matrix(unlist(lapply(split(DF3[, -4], DF3["group"]),
                                          `[`, 1,)),
                            byrow = TRUE,
                            nrow = (lev <- length(levels(DF3$group))))
             colnames(out4) <- names(DF3)[-4]
             rownames(out4) <- seq_len(lev)
             out4 <- as.data.frame(out4)
             out4$group <- factor(out4$group)
             out4$idu <- factor(paste(out4$id, out4$group, sep = "_"),
                                levels = levels(DF3$idu))})
  

Оригинал

Скажем, если ваши данные находятся в DF , то:

 do.call(rbind, lapply(with(DF, split(DF, group)), head, 1))
  

будет делать то, что вы хотите:

 > do.call(rbind, lapply(with(DF, split(DF, group)), head, 1))
  idu group
1   1     1
2   4     2
3   7     3
  

Если новые данные находятся в DF2 , то мы получаем:

 > do.call(rbind, lapply(with(DF2, split(DF2, group)), head, 1))
  id group idu value
1  1     1 1_1    34
2  4     2 4_2     6
3  1     3 1_3    34
  

Но для скорости мы, вероятно, захотим подмножество вместо использования head() , и мы можем немного выиграть, не используя with() , например:

 do.call(rbind, lapply(split(DF2, DF2$group), `[`, 1, ))

> system.time(replicate(1000, do.call(rbind, lapply(split(DF2, DF2$group), `[`, 1, ))))
   user  system elapsed 
  3.847   0.040   4.044
> system.time(replicate(1000, do.call(rbind, lapply(split(DF2, DF2$group), head, 1))))
   user  system elapsed 
  4.058   0.038   4.111
> system.time(replicate(1000, aggregate(DF2[,-2], DF2["group"], function (x) x[1])))
   user  system elapsed 
  3.902   0.042   4.106
  

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

1. кажется, Гэвин работает. Я отредактировал содержимое этого вопроса, но это может не повлиять. Я должен протестировать его производительность с помощью data.frame объемом в 2 миллиона строк.

2. @Paulo Я обновил ответ, добавив некоторые временные интервалы сравнения для повторных запусков этого набора данных.

3. @Paulo Cardosa Я несколько раз настраивал время для решения большой задачи, и все варианты были медленными, поэтому я предоставил версию, которая работает с матрицей и намного быстрее. Включены тайминги для проблемы с миллионом строк.

4. Очень информативный Гэвин. Я попробую использовать реальные данные, чтобы посмотреть, как они ведут себя, когда в DF также есть больше столбцов. Все это важно, поскольку у меня есть объект с 20 миллионами строк для работы, и любая экономия времени окажет огромное влияние на конечные вычисления.

5. Одним из дополнительных требований было бы сохранение только записей, которые соответствуют ограничению выбора nrows (DF2$ групповые записи, которые соответствуют критериям). Может ли код учесть это?

Ответ №3:

Одно из решений с использованием plyr , предполагающее, что ваши данные находятся в объекте с именем zzz :

 ddply(zzz, "group", function(x) x[1 ,])
  

Другой вариант, который учитывает разницу между строками и должен работать быстрее, но зависит от объекта, который упорядочивается заранее. Это также предполагает, что у вас нет группового значения 0:

 zzz <- zzz[order(zzz$group) ,]

zzz[ diff(c(0,zzz$group)) != 0, ]
  

Ответ №4:

Я думаю, это поможет:

 aggregate(data["idu"], data["group"], function (x) x[1])
  

Для вашего обновленного вопроса я бы рекомендовал использовать ddply из plyr пакета:

 ddply(data, .(group), function (x) x[1,])