如何为以下数据框中的每个唯一id
选择第一行和最后一行?
tmp <- structure(list(id = c(15L, 15L, 15L, 15L, 21L, 21L, 22L, 22L,
22L, 23L, 23L, 23L, 24L, 24L, 24L, 24L), d = c(1L, 1L, 1L, 1L,
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), gr = c(2L, 1L,
1L, 1L, 1L, 2L, 1L, 1L, 2L, 1L, 1L, 2L, 1L, 1L, 1L, 2L), mm = c(3.4,
4.9, 4.4, 5.5, 4, 3.8, 4, 4.9, 4.6, 2.7, 4, 3, 3, 2, 4, 2), area = c(1L,
2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 3L, 2L, 3L)), .Names = c("id",
"d", "gr", "mm", "area"), class = "data.frame", row.names = c(NA,
-16L))
tmp
#> id d gr mm area
#> 1 15 1 2 3.4 1
#> 2 15 1 1 4.9 2
#> 3 15 1 1 4.4 1
#> 4 15 1 1 5.5 2
#> 5 21 1 1 4.0 2
#> 6 21 1 2 3.8 2
#> 7 22 1 1 4.0 2
#> 8 22 1 1 4.9 2
#> 9 22 1 2 4.6 2
#> 10 23 1 1 2.7 2
#> 11 23 1 1 4.0 2
#> 12 23 1 2 3.0 2
#> 13 24 1 1 3.0 2
#> 14 24 1 1 2.0 3
#> 15 24 1 1 4.0 2
#> 16 24 1 2 2.0 3
答案 0 :(得分:80)
快速而简短的data.table
解决方案:
tmp[, .SD[c(1,.N)], by=id]
其中.SD
代表(D)ata的每个(S)ubset,.N
是每个组中的行数,tmp
是data.table
;例如默认情况下由fread()
提供或使用data.frame
转换setDT()
。
请注意,如果一个组只包含一行,那么该行将在输出中出现两次,因为该行既是该组的第一行又是最后一行。为了避免在这种情况下重复,感谢@Thell:
tmp[, .SD[unique(c(1,.N))], by=id]
或者,以下内容使.N==1
特殊情况的逻辑显式为:
tmp[, if (.N==1) .SD else .SD[c(1,.N)], by=id]
.SD[1]
的第一部分中不需要if
,因为在这种情况下.N
是1
所以.SD
必须只有一行无论如何。
如果您愿意,可以将j
包裹在{}
中并在{}
内包含整页代码。只要{}
中的最后一个表达式返回一个类似list
的对象(例如普通list
,data.table
或data.frame
)。
tmp[, { ...; if (.N==1) .SD else .SD[c(1,.N)] } , by=id]
答案 1 :(得分:26)
plyr解决方案(tmp
是您的数据框):
library("plyr")
ddply(tmp, .(id), function(x) x[c(1, nrow(x)), ])
# id d gr mm area
# 1 15 1 2 3.4 1
# 2 15 1 1 5.5 2
# 3 21 1 1 4.0 2
# 4 21 1 2 3.8 2
# 5 22 1 1 4.0 2
# 6 22 1 2 4.6 2
# 7 23 1 1 2.7 2
# 8 23 1 2 3.0 2
# 9 24 1 1 3.0 2
# 10 24 1 2 2.0 3
library("dplyr")
tmp %>%
group_by(id) %>%
slice(c(1, n())) %>%
ungroup()
# # A tibble: 10 × 5
# id d gr mm area
# <int> <int> <int> <dbl> <int>
# 1 15 1 2 3.4 1
# 2 15 1 1 5.5 2
# 3 21 1 1 4.0 2
# 4 21 1 2 3.8 2
# 5 22 1 1 4.0 2
# 6 22 1 2 4.6 2
# 7 23 1 1 2.7 2
# 8 23 1 2 3.0 2
# 9 24 1 1 3.0 2
# 10 24 1 2 2.0 3
答案 2 :(得分:4)
以下是基础R
的解决方案。如果有多个组具有相同的id
,则此代码将返回每个组的第一行和最后一行。
编辑:2017年1月12日
这个解决方案可能比我在下面的其他答案更直观一些:
lmy.df = read.table(text = '
id d gr mm area
15 1 2 3.40 1
15 1 1 4.90 2
15 1 1 4.40 1
15 1 1 5.50 2
21 1 1 4.00 2
21 1 2 3.80 2
22 1 1 4.00 2
23 1 1 2.70 2
23 1 1 4.00 2
23 1 2 3.00 2
24 1 1 3.00 2
24 1 1 2.00 3
24 1 1 4.00 2
24 1 2 2.00 3
', header = TRUE)
head <- aggregate(lmy.df, by=list(lmy.df$id), FUN = function(x) { first = head(x,1) } )
tail <- aggregate(lmy.df, by=list(lmy.df$id), FUN = function(x) { last = tail(x,1) } )
head$order = 'first'
tail$order = 'last'
my.output <- rbind(head, tail)
my.output
# Group.1 id d gr mm area order
#1 15 15 1 2 3.4 1 first
#2 21 21 1 1 4.0 2 first
#3 22 22 1 1 4.0 2 first
#4 23 23 1 1 2.7 2 first
#5 24 24 1 1 3.0 2 first
#6 15 15 1 1 5.5 2 last
#7 21 21 1 2 3.8 2 last
#8 22 22 1 1 4.0 2 last
#9 23 23 1 2 3.0 2 last
#10 24 24 1 2 2.0 3 last
编辑:2016年6月18日
自发布原始答案以来,我了解到使用lapply
比使用apply
更好。这是因为如果每个组具有相同的行数,则apply
不起作用。见这里:Error when numbering rows by group
lmy.df = read.table(text = '
id d gr mm area
15 1 2 3.40 1
15 1 1 4.90 2
15 1 1 4.40 1
15 1 1 5.50 2
21 1 1 4.00 2
21 1 2 3.80 2
22 1 1 4.00 2
23 1 1 2.70 2
23 1 1 4.00 2
23 1 2 3.00 2
24 1 1 3.00 2
24 1 1 2.00 3
24 1 1 4.00 2
24 1 2 2.00 3
', header = TRUE)
lmy.seq <- rle(lmy.df$id)$lengths
lmy.df$first <- unlist(lapply(lmy.seq, function(x) seq(1,x)))
lmy.df$last <- unlist(lapply(lmy.seq, function(x) seq(x,1,-1)))
lmy.df
lmy.df2 <- lmy.df[lmy.df$first==1 | lmy.df$last == 1,]
lmy.df2
# id d gr mm area first last
#1 15 1 2 3.4 1 1 4
#4 15 1 1 5.5 2 4 1
#5 21 1 1 4.0 2 1 2
#6 21 1 2 3.8 2 2 1
#7 22 1 1 4.0 2 1 1
#8 23 1 1 2.7 2 1 3
#10 23 1 2 3.0 2 3 1
#11 24 1 1 3.0 2 1 4
#14 24 1 2 2.0 3 4 1
以下是每个组有两行的示例:
lmy.df = read.table(text = '
id d gr mm area
15 1 2 3.40 1
15 1 1 4.90 2
21 1 1 4.00 2
21 1 2 3.80 2
22 1 1 4.00 2
22 1 1 6.00 2
23 1 1 2.70 2
23 1 2 3.00 2
24 1 1 3.00 2
24 1 2 2.00 3
', header = TRUE)
lmy.seq <- rle(lmy.df$id)$lengths
lmy.df$first <- unlist(lapply(lmy.seq, function(x) seq(1,x)))
lmy.df$last <- unlist(lapply(lmy.seq, function(x) seq(x,1,-1)))
lmy.df
lmy.df2 <- lmy.df[lmy.df$first==1 | lmy.df$last == 1,]
lmy.df2
# id d gr mm area first last
#1 15 1 2 3.4 1 1 2
#2 15 1 1 4.9 2 2 1
#3 21 1 1 4.0 2 1 2
#4 21 1 2 3.8 2 2 1
#5 22 1 1 4.0 2 1 2
#6 22 1 1 6.0 2 2 1
#7 23 1 1 2.7 2 1 2
#8 23 1 2 3.0 2 2 1
#9 24 1 1 3.0 2 1 2
#10 24 1 2 2.0 3 2 1
原始回答:
my.seq <- data.frame(rle(my.df$id)$lengths)
my.df$first <- unlist(apply(my.seq, 1, function(x) seq(1,x)))
my.df$last <- unlist(apply(my.seq, 1, function(x) seq(x,1,-1)))
my.df2 <- my.df[my.df$first==1 | my.df$last == 1,]
my.df2
id d gr mm area first last
1 15 1 2 3.4 1 1 4
4 15 1 1 5.5 2 4 1
5 21 1 1 4.0 2 1 2
6 21 1 2 3.8 2 2 1
7 22 1 1 4.0 2 1 3
9 22 1 2 4.6 2 3 1
10 23 1 1 2.7 2 1 3
12 23 1 2 3.0 2 3 1
13 24 1 1 3.0 2 1 4
16 24 1 2 2.0 3 4 1
答案 3 :(得分:1)
使用dplyr
的另一种方法可能是:
tmp %>%
group_by(id) %>%
filter(1:n() %in% range(1:n()))
id d gr mm area
<int> <int> <int> <dbl> <int>
1 15 1 2 3.4 1
2 15 1 1 5.5 2
3 21 1 1 4 2
4 21 1 2 3.8 2
5 22 1 1 4 2
6 22 1 2 4.6 2
7 23 1 1 2.7 2
8 23 1 2 3 2
9 24 1 1 3 2
10 24 1 2 2 3
或者与使用row_number()
的想法相同:
tmp %>%
group_by(id) %>%
filter(row_number() %in% range(row_number()))
或使用slice()
执行操作:
tmp %>%
group_by(id) %>%
slice(c(which.min(1:n()), which.max(1:n())))
答案 4 :(得分:0)
我们还可以在基数R中使用$res1 = $this->db->select('SUM(cl_one) as total, COUNT(cl_one) as count').from('Table1').get().row();
$res2 = $this->db->select('SUM(cl_2) as total, COUNT(cl_2) as count').from('Table2').get().row();
$total_amount = $res1[0]['total'] + $res2[0]['total'];
$total_count = $res1[0]['count'] + $res2[0]['count'];
。对于每个ave
,我们选择第一行和最后一行。
id
较短的版本将使用tmp[as.logical(with(tmp,ave(d, id, FUN = function(x)
seq_along(x) %in% c(1L, length(x))))), ]
# id d gr mm area
#1 15 1 2 3.4 1
#4 15 1 1 5.5 2
#5 21 1 1 4.0 2
#6 21 1 2 3.8 2
#7 22 1 1 4.0 2
#9 22 1 2 4.6 2
#10 23 1 1 2.7 2
#12 23 1 2 3.0 2
#13 24 1 1 3.0 2
#16 24 1 2 2.0 3
,range
返回向量的最小值和最大值
range
我们也可以对tmp[as.logical(with(tmp, ave(seq_along(d), id,FUN = function(x) x %in% range(x)))),]
使用split
+ sapply
方法
range
使用tmp[c(sapply(split(seq_len(nrow(tmp)), tmp$id), range)), ]
,尽管我更喜欢@rcs所示的dplyr
方法,但这是使用slice
的一种方法,它类似于filter
解决方案,其中我们创建了一个比较ave
row_number()
在上述所有解决方案中,我们也可以使用library(dplyr)
tmp %>% group_by(id) %>% filter(row_number() %in% c(1L, n()))
代替match
,因为%in%
只是%in%
的包装。
答案 5 :(得分:0)
slice_head()
和slice_tail()
library(tidyverse)
tmp <- structure(list(id = c(15L, 15L, 15L, 15L, 21L, 21L, 22L, 22L,
22L, 23L, 23L, 23L, 24L, 24L, 24L, 24L), d = c(1L, 1L, 1L, 1L,
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), gr = c(2L, 1L,
1L, 1L, 1L, 2L, 1L, 1L, 2L, 1L, 1L, 2L, 1L, 1L, 1L, 2L), mm = c(3.4,
4.9, 4.4, 5.5, 4, 3.8, 4, 4.9, 4.6, 2.7, 4, 3, 3, 2, 4, 2), area = c(1L,
2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 3L, 2L, 3L)), class = "data.frame", row.names = c(NA,
-16L))
tmp %>%
group_by(id) %>%
slice_head()
# A tibble: 5 x 5
# Groups: id [5]
id d gr mm area
<int> <int> <int> <dbl> <int>
1 15 1 2 3.4 1
2 21 1 1 4 2
3 22 1 1 4 2
4 23 1 1 2.7 2
5 24 1 1 3 2
tmp %>%
group_by(id) %>%
slice_tail()
# A tibble: 5 x 5
# Groups: id [5]
id d gr mm area
<int> <int> <int> <dbl> <int>
1 15 1 1 5.5 2
2 21 1 2 3.8 2
3 22 1 2 4.6 2
4 23 1 2 3 2
5 24 1 2 2 3
默认情况下,slice_head()
和slice_tail()
返回1行,但是您也可以指定n
和prop
的参数,将切片分成若干行或一部分分别。有关更多详细信息,请参见?slice
。