Ответ 1
Вероятно, существует более быстрый способ:
df %>%
group_by(id) %>%
arrange(stopSequence) %>%
filter(row_number()==1 | row_number()==n())
Вопрос
Используя dplyr
, как я могу выбрать верхние и нижние наблюдения/строки сгруппированных данных в одном выражении?
Данные и пример
С учетом кадра данных
df <- data.frame(id=c(1,1,1,2,2,2,3,3,3),
stopId=c("a","b","c","a","b","c","a","b","c"),
stopSequence=c(1,2,3,3,1,4,3,1,2))
Я могу получить верхние и нижние наблюдения из каждой группы, используя slice
, но используя две отдельные записи:
firstStop <- df %>%
group_by(id) %>%
arrange(stopSequence) %>%
slice(1) %>%
ungroup
lastStop <- df %>%
group_by(id) %>%
arrange(stopSequence) %>%
slice(n()) %>%
ungroup
Можно ли объединить эти два statmenets в один, который отображает как верхние, так и нижние наблюдения?
Вероятно, существует более быстрый способ:
df %>%
group_by(id) %>%
arrange(stopSequence) %>%
filter(row_number()==1 | row_number()==n())
Только для полноты: вы можете передать slice
вектор индексов:
df %>% arrange(stopSequence) %>% group_by(id) %>% slice(c(1,n()))
который дает
id stopId stopSequence
1 1 a 1
2 1 c 3
3 2 b 1
4 2 c 4
5 3 b 1
6 3 a 3
Не dplyr
, но гораздо более прямое использование data.table
:
library(data.table)
setDT(df)
df[ df[order(id, stopSequence), .I[c(1L,.N)], by=id]$V1 ]
# id stopId stopSequence
# 1: 1 a 1
# 2: 1 c 3
# 3: 2 b 1
# 4: 2 c 4
# 5: 3 b 1
# 6: 3 a 3
Более подробное объяснение:
# 1) get row numbers of first/last observations from each group
# * basically, we sort the table by id/stopSequence, then,
# grouping by id, name the row numbers of the first/last
# observations for each id; since this operation produces
# a data.table
# * .I is data.table shorthand for the row number
# * here, to be maximally explicit, I've named the variable V1
# as row_num to give other readers of my code a clearer
# understanding of what operation is producing what variable
first_last = df[order(id, stopSequence), .(row_num = .I[c(1L,.N)]), by=id]
idx = first_last$row_num
# 2) extract rows by number
df[idx]
Обязательно ознакомьтесь с Getting Started wiki для получения основных data.table
Что-то вроде:
library(dplyr)
df <- data.frame(id=c(1,1,1,2,2,2,3,3,3),
stopId=c("a","b","c","a","b","c","a","b","c"),
stopSequence=c(1,2,3,3,1,4,3,1,2))
first_last <- function(x) {
bind_rows(slice(x, 1), slice(x, n()))
}
df %>%
group_by(id) %>%
arrange(stopSequence) %>%
do(first_last(.)) %>%
ungroup
## Source: local data frame [6 x 3]
##
## id stopId stopSequence
## 1 1 a 1
## 2 1 c 3
## 3 2 b 1
## 4 2 c 4
## 5 3 b 1
## 6 3 a 3
С помощью do
вы можете в значительной степени выполнять любое количество операций в группе, но ответ @jeremycg более подходит для этой задачи.
Я знаю заданный вопрос dplyr
. Но, поскольку другие уже размещали решения с использованием других пакетов, я решил также использовать другие пакеты:
Базовый пакет:
df <- df[with(df, order(id, stopSequence, stopId)), ]
merge(df[!duplicated(df$id), ],
df[!duplicated(df$id, fromLast = TRUE), ],
all = TRUE)
data.table:
df <- setDT(df)
df[order(id, stopSequence)][, .SD[c(1,.N)], by=id]
sqldf:
library(sqldf)
min <- sqldf("SELECT id, stopId, min(stopSequence) AS StopSequence
FROM df GROUP BY id
ORDER BY id, StopSequence, stopId")
max <- sqldf("SELECT id, stopId, max(stopSequence) AS StopSequence
FROM df GROUP BY id
ORDER BY id, StopSequence, stopId")
sqldf("SELECT * FROM min
UNION
SELECT * FROM max")
В одном запросе:
sqldf("SELECT *
FROM (SELECT id, stopId, min(stopSequence) AS StopSequence
FROM df GROUP BY id
ORDER BY id, StopSequence, stopId)
UNION
SELECT *
FROM (SELECT id, stopId, max(stopSequence) AS StopSequence
FROM df GROUP BY id
ORDER BY id, StopSequence, stopId)")
Вывод:
id stopId StopSequence
1 1 a 1
2 1 c 3
3 2 b 1
4 2 c 4
5 3 a 3
6 3 b 1
Использование data.table
:
# convert to data.table
setDT(df)
# order, group, filter
df[order(stopSequence)][, .SD[c(1, .N)], by = id]
id stopId stopSequence
1: 1 a 1
2: 1 c 3
3: 2 b 1
4: 2 c 4
5: 3 b 1
6: 3 a 3
Другой подход с lapply и выражением dplyr. Мы можем применить произвольное число любых итоговых функций к одному и тому же утверждению:
lapply(c(first, last),
function(x) df %>% group_by(id) %>% summarize_all(funs(x))) %>%
bind_rows()
Например, вы можете быть заинтересованы в строках со значением max stopSequence и сделать:
lapply(c(first, last, max("stopSequence")),
function(x) df %>% group_by(id) %>% summarize_all(funs(x))) %>%
bind_rows()
Другая базовая альтернатива R будет заключаться в том, чтобы сначала order
использовать id
и stopSequence
, split
их, основываясь на id
, и для каждого id
мы выбираем только первый и последний индексы и подбираем кадр данных, используя те индексы.
df[sapply(with(df, split(order(id, stopSequence), id)), function(x)
c(x[1], x[length(x)])), ]
# id stopId stopSequence
#1 1 a 1
#3 1 c 3
#5 2 b 1
#6 2 c 4
#8 3 b 1
#7 3 a 3
Или аналогично, используя by
df[unlist(with(df, by(order(id, stopSequence), id, function(x)
c(x[1], x[length(x)])))), ]