Из такого фрейма данных
test <- data.frame('id'= rep(1:5,2), 'string'= LETTERS[1:10])
test <- test[order(test$id), ]
rownames(test) <- 1:10
> test
id string
1 1 A
2 1 F
3 2 B
4 2 G
5 3 C
6 3 H
7 4 D
8 4 I
9 5 E
10 5 J
Я хочу создать новый с первой строкой каждой пары id / string. Если sqldf принимает внутри себя код R, запрос может выглядеть так:
res <- sqldf("select id, min(rownames(test)), string
from test
group by id, string")
> res
id string
1 1 A
3 2 B
5 3 C
7 4 D
9 5 E
Есть ли решение, кроме создания нового столбца, например
test$row <- rownames(test)
и выполнить тот же запрос sqldf с min (row)?
Ответы:
Вы можете использовать
duplicated
для этого очень быстро.Тесты для фанатов скорости:
ju <- function() test[!duplicated(test$id),] gs1 <- function() do.call(rbind, lapply(split(test, test$id), head, 1)) gs2 <- function() do.call(rbind, lapply(split(test, test$id), `[`, 1, )) jply <- function() ddply(test,.(id),function(x) head(x,1)) jdt <- function() { testd <- as.data.table(test) setkey(testd,id) # Initial solution (slow) # testd[,lapply(.SD,function(x) head(x,1)),by = key(testd)] # Faster options : testd[!duplicated(id)] # (1) # testd[, .SD[1L], by=key(testd)] # (2) # testd[J(unique(id)),mult="first"] # (3) # testd[ testd[,.I[1L],by=id] ] # (4) needs v1.8.3. Allows 2nd, 3rd etc } library(plyr) library(data.table) library(rbenchmark) # sample data set.seed(21) test <- data.frame(id=sample(1e3, 1e5, TRUE), string=sample(LETTERS, 1e5, TRUE)) test <- test[order(test$id), ] benchmark(ju(), gs1(), gs2(), jply(), jdt(), replications=5, order="relative")[,1:6] # test replications elapsed relative user.self sys.self # 1 ju() 5 0.03 1.000 0.03 0.00 # 5 jdt() 5 0.03 1.000 0.03 0.00 # 3 gs2() 5 3.49 116.333 2.87 0.58 # 2 gs1() 5 3.58 119.333 3.00 0.58 # 4 jply() 5 3.69 123.000 3.11 0.51
Давайте попробуем это еще раз, но только с участниками первого заезда, с большим количеством данных и повторений.
set.seed(21) test <- data.frame(id=sample(1e4, 1e6, TRUE), string=sample(LETTERS, 1e6, TRUE)) test <- test[order(test$id), ] benchmark(ju(), jdt(), order="relative")[,1:6] # test replications elapsed relative user.self sys.self # 1 ju() 100 5.48 1.000 4.44 1.00 # 2 jdt() 100 6.92 1.263 5.70 1.15
источник
!duplicated(x)
находит первую в каждой группе, даже если она не отсортирована, iiuc.Я предпочитаю подход dplyr.
group_by(id)
за которым следует либоfilter(row_number()==1)
или жеslice(1)
или жеslice_head(1)
# (dplyr => 1.0)top_n(n = -1)
top_n()
внутренне использует функцию ранжирования. Отрицательный выбирает из нижней части ранга.В некоторых случаях может потребоваться размещение идентификаторов после group_by.
library(dplyr) # using filter(), top_n() or slice() m1 <- test %>% group_by(id) %>% filter(row_number()==1) m2 <- test %>% group_by(id) %>% slice(1) m3 <- test %>% group_by(id) %>% top_n(n = -1)
Все три метода возвращают одинаковый результат
# A tibble: 5 x 2 # Groups: id [5] id string <int> <fct> 1 1 A 2 2 B 3 3 C 4 4 D 5 5 E
источник
slice
.slice(x)
это ярлык дляfilter(row_number() %in% x)
.data.table
в a,data.frame
чтобы это работало?data.table
унаследовано от,data.frame
поэтому во многих случаях вы можете использовать команды dplyr для файлаdata.table
. Пример выше, например, также работает, еслиtest
это файлdata.table
. См , например stackoverflow.com/questions/13618488/... для более глубокого explanantionЧто о
DT <- data.table(test) setkey(DT, id) DT[J(unique(id)), mult = "first"]
редактировать
Также есть уникальный метод, для
data.tables
которого будет возвращена первая строка по ключуjdtu <- function() unique(DT)
Я думаю, что если вы заказываете
test
за пределами эталонного теста, вы также можете удалитьsetkey
иdata.table
преобразование из эталонного теста (поскольку setkey в основном сортируется по идентификатору, так же, какorder
).set.seed(21) test <- data.frame(id=sample(1e3, 1e5, TRUE), string=sample(LETTERS, 1e5, TRUE)) test <- test[order(test$id), ] DT <- data.table(DT, key = 'id') ju <- function() test[!duplicated(test$id),] jdt <- function() DT[J(unique(id)),mult = 'first'] library(rbenchmark) benchmark(ju(), jdt(), replications = 5) ## test replications elapsed relative user.self sys.self ## 2 jdt() 5 0.01 1 0.02 0 ## 1 ju() 5 0.05 5 0.05 0
и с большим количеством данных
** Редактировать уникальным методом **
set.seed(21) test <- data.frame(id=sample(1e4, 1e6, TRUE), string=sample(LETTERS, 1e6, TRUE)) test <- test[order(test$id), ] DT <- data.table(test, key = 'id') test replications elapsed relative user.self sys.self 2 jdt() 5 0.09 2.25 0.09 0.00 3 jdtu() 5 0.04 1.00 0.05 0.00 1 ju() 5 0.22 5.50 0.19 0.03
Уникальный метод здесь самый быстрый.
источник
unique(DT,by="id")
работает напрямуюdata.table
версии> = 1.9.8, по умолчаниюby
аргументunique
являетсяby = seq_along(x)
(все столбцы), вместо ранее по умолчаниюby = key(x)
Вариант простой
ddply
:ddply(test,.(id),function(x) head(x,1))
Если скорость является проблемой, можно применить аналогичный подход
data.table
:testd <- data.table(test) setkey(testd,id) testd[,.SD[1],by = key(testd)]
или это может быть значительно быстрее:
testd[testd[, .I[1], by = key(testd]$V1]
источник
теперь для
dplyr
добавления отдельного счетчика.df %>% group_by(aa, bb) %>% summarise(first=head(value,1), count=n_distinct(value))
Вы создаете группы, они объединяются в группы.
Если данные числовые, вы можете использовать:
first(value)
[также естьlast(value)
] вместоhead(value, 1)
см. http://cran.rstudio.com/web/packages/dplyr/vignettes/introduction.html
Полный:
> df Source: local data frame [16 x 3] aa bb value 1 1 1 GUT 2 1 1 PER 3 1 2 SUT 4 1 2 GUT 5 1 3 SUT 6 1 3 GUT 7 1 3 PER 8 2 1 221 9 2 1 224 10 2 1 239 11 2 2 217 12 2 2 221 13 2 2 224 14 3 1 GUT 15 3 1 HUL 16 3 1 GUT > library(dplyr) > df %>% > group_by(aa, bb) %>% > summarise(first=head(value,1), count=n_distinct(value)) Source: local data frame [6 x 4] Groups: aa aa bb first count 1 1 1 GUT 2 2 1 2 SUT 2 3 1 3 SUT 3 4 2 1 221 3 5 2 2 217 3 6 3 1 GUT 2
источник
dplyr
, не требующие написания оператора для каждого отдельного столбца, который нужно включить (см., Например, ответ атомщика ниже). Also I'm not sure what *"if data is numeric"* has anything to do with whether or not one would use
first (value) `vshead(value)
(or justvalue[1]
)(1) SQLite имеет встроенный
rowid
псевдостолбец, поэтому это работает:sqldf("select min(rowid) rowid, id, string from test group by id")
давая:
rowid id string 1 1 1 A 2 3 2 B 3 5 3 C 4 7 4 D 5 9 5 E
(2) Также
sqldf
естьrow.names=
аргумент:sqldf("select min(cast(row_names as real)) row_names, id, string from test group by id", row.names = TRUE)
давая:
id string 1 1 A 3 2 B 5 3 C 7 4 D 9 5 E
(3) Третья альтернатива, сочетающая элементы двух вышеупомянутых, может быть даже лучше:
sqldf("select min(rowid) row_names, id, string from test group by id", row.names = TRUE)
давая:
id string 1 1 A 3 2 B 5 3 C 7 4 D 9 5 E
Обратите внимание, что все три из них полагаются на расширение SQLite для SQL, где использование
min
илиmax
гарантированно приведет к тому, что другие столбцы будут выбраны из той же строки. (В других базах данных на основе SQL это не может быть гарантировано.)источник
Базовый вариант R - это идиома
split()
-lapply()
-do.call()
:> do.call(rbind, lapply(split(test, test$id), head, 1)) id string 1 1 A 2 2 B 3 3 C 4 4 D 5 5 E
Более прямой вариант является
lapply()
в[
функции:> do.call(rbind, lapply(split(test, test$id), `[`, 1, )) id string 1 1 A 2 2 B 3 3 C 4 4 D 5 5 E
Пробел
1, )
в концеlapply()
вызова имеет важное значение, поскольку это эквивалентно вызову[1, ]
для выбора первой строки и всех столбцов.источник