Подмножество фрейма данных на основе записи в столбце (или ранга) - PullRequest
13 голосов
/ 27 апреля 2011

У меня есть 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

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

Можно ли этого избежать, избегая петель?

dput() данных:

structure(list(id = c(1L, 2L, 3L, 4L, 5L, 6L, 1L, 2L, 3L), group = c(1L, 
1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L), idu = structure(c(1L, 3L, 5L, 
7L, 8L, 9L, 2L, 4L, 6L), .Label = c("1_1", "1_3", "2_1", "2_3", 
"3_1", "3_3", "4_2", "5_2", "6_2"), class = "factor"), value = c(34L, 
23L, 67L, 6L, 24L, 45L, 34L, 67L, 76L)), .Names = c("id", "group", 
"idu", "value"), class = "data.frame", row.names = c(NA, -9L))

Ответы [ 4 ]

10 голосов
/ 28 апреля 2011

Использование строки миллиона Гэвина 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 секундами.для метода Gavin fastet lapply + split на моем компьютере.

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

5 голосов
/ 27 апреля 2011

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

Если сделать это с миллионами строк, все предоставленные параметры будут работать медленно. Вот некоторые временные параметры сравнения для фиктивного набора данных из 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 голос
/ 27 апреля 2011

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

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

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

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

zzz[ diff(c(0,zzz$group)) != 0, ]
1 голос
/ 27 апреля 2011

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

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

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

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