#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,])