使用dplyr按顺序获取序列中的第一行和最后一行

时间:2017-03-30 19:42:09

标签: r dplyr

我尝试使用dplyr按组取出重复值的第一行和最后一行。我出于效率原因这样做,尤其是图表更快。

这不是Select first and last row from grouped data的重复,因为我没有要求组中的严格的第一行和最后一行;我要求分组按级别(在我的情况下为1&0; s和0' s)中的第一行和最后一行可能出现在多个块中。

这是一个例子。假设我想从C列中删除所有冗余的1和0,同时保持A和B不变。

df = data.frame(
    A = rep(c("a", "b"), each = 10),
    B = rep(c(1:10), 2),
    C = c(1,0,0,0,0,0,1,1,1,1,0,0,0,1,0,0,0,0,0,1))

A  B C
a  1 1
a  2 0
a  3 0
a  4 0
a  5 0
a  6 0
a  7 1
a  8 1
a  9 1
a 10 1
b  1 0
b  2 0
b  3 0
b  4 1
b  5 0
b  6 0
b  7 0
b  8 0
b  9 0
b 10 1

最终结果应如下所示:

A  B C
a  1 1
a  2 0
a  6 0
a  7 1
a 10 1
b  1 0
b  3 0
b  4 1
b  5 0
b  9 0
b 10 1

使用unique将不会删除任何内容,或者只是选择1或0中的一个,而不保留我尝试实现的开始和结束质量。有没有办法在没有循环的情况下执行此操作,可能使用dplyrforcats

2 个答案:

答案 0 :(得分:2)

我认为slice应该让你接近:

df %>%
  group_by(A,C) %>%
  slice(c(1, n()))

给出

      A     B     C
  <chr> <int> <dbl>
1     a     2     0
2     a     6     0
3     a     1     1
4     a    10     1
5     b     1     0
6     b     9     0
7     b     4     1
8     b    10     1

虽然这与您的预期结果并不完全一致。 n()给出组中的最后一行。

在您编辑之后,很明显您没有在任何已建立的组中查找值(这是我以前的版本所做的)。您希望按1或0的运行进行分组。为此,您需要创建一个列来检查1&#39; s / 0的运行是否已更改,然后一个用于标识组。然后,slice将如前所述工作。但是,因为你的一些运行只有1行,所以如果它超过1,我们只需要包含n()(否则1行显示两次)。

df %>%
  mutate(groupChanged = (C != lag(C, default = C[1]))
         , toCutBy = cumsum(groupChanged)
         ) %>%
  group_by(toCutBy) %>%
  slice(c(1, ifelse(n() == 1, NA, n())))

给出

       A     B     C groupChanged toCutBy
   <chr> <int> <dbl>        <lgl>   <int>
1      a     1     1        FALSE       0
2      a     2     0         TRUE       1
3      a     6     0        FALSE       1
4      a     7     1         TRUE       2
5      a    10     1        FALSE       2
6      b     1     0         TRUE       3
7      b     3     0        FALSE       3
8      b     4     1         TRUE       4
9      b     5     0         TRUE       5
10     b     9     0        FALSE       5
11     b    10     1         TRUE       6

如果1或0的运行必须保持在A列的级别内,则还需要检查列A中的更改是否为该调用。在此示例中,它没有效果(因此返回完全相同的值),但在其他情况下可能需要。

df %>%
  mutate(groupChanged = (C != lag(C, default = C[1]) |
                           A != lag(A, default = A[1]))
         , toCutBy = cumsum(groupChanged)
  ) %>%
  group_by(toCutBy) %>%
  slice(c(1, ifelse(n() == 1, NA, n())))

答案 1 :(得分:0)

一个解决方案:

C_filter <- function(x) {
    !sapply(1:length(x), function(i) {
        identical(x[i], x[i-1])
    }) | !sapply(1:length(x), function(i) {
        identical(x[i], x[i+1])
    }) 
}
df %>% group_by(A) %>% filter(C_filter(C))

   A  B C
1  a  1 1
2  a  2 0
3  a  6 0
4  a  7 1
5  a 10 1
6  b  1 0
7  b  3 0
8  b  4 1
9  b  5 0
10 b  9 0
11 b 10 1