【问题标题】:Conditionally selecting last N values within a group by another column using R使用 R 有条件地通过另一列选择组内的最后 N 个值
【发布时间】:2017-07-31 17:30:51
【问题描述】:

此问题类似于按列here 选择组内的前 N ​​个值。

但是,我想按组选择最后 N 个值,其中 N 取决于相应计数列的值。计数表示特定名称的出现次数。如果 count >3,我只想要最后三个条目,但如果它小于 3,我只想要最后一个条目。

# Sample data
df <- data.frame(Name = c("x","x","x","x","y","y","y","z","z"), Value = c(1,2,3,4,5,6,7,8,9))

# Obtain count for each name
count <- df %>%
  group_by(Name) %>%
  summarise(Count = n_distinct(Value))

# Merge dataframe with count
merge(df, count, by=c("Name"))

# Delete the first entry for x and the first entry for z

# Desired output
data.frame(Name = c("x","x","x","y","y","y","z"), Value = c(2,3,4,5,6,7,9))

【问题讨论】:

  • 您应该包含library(dplyr) 并相应地标记您的问题

标签: r dataframe dplyr


【解决方案1】:

另一种dplyrish方式:

df %>% group_by(Name) %>% slice(tail(row_number(), 
  if (n_distinct(Value) < 3) 1 else 3
))

# A tibble: 7 x 2
# Groups:   Name [3]
    Name Value
  <fctr> <dbl>
1      x     2
2      x     3
3      x     4
4      y     5
5      y     6
6      y     7
7      z     9

data.table 中的类比是...

library(data.table)
setDT(df)
df[, tail(.SD, if (uniqueN(Value) < 3) 1 else 3), by=Name]

base R 中最接近的东西是...

with(df, {
  len = tapply(Value, Name, FUN = length)
  nv  = tapply(Value, Name, FUN = function(x) length(unique(x)))
  df[ sequence(len) > rep(nv - ifelse(nv < 3, 1, 3), len), ]
})

...这比它应该的更难想出。

【讨论】:

  • @C8H10N4O2 它假设 OP 已经按照他们的意愿对其进行了排序,因为他们只要求“按组排列的最后 N 个值”。
  • 好的,那么这绝对是最好的解决方案。
  • 谢谢 :) 根据需要排序,我同意你的方法是要走的路。
【解决方案2】:

另一种可能性:

library(tidyverse)

df %>%
  split(.$Name) %>%
  map_df(~ if (n_distinct(.x) >= 3) tail(.x, 3) else tail(.x, 1))

这给出了:

#  Name Value
#1    x     2
#2    x     3
#3    x     4
#4    y     5
#5    y     6
#6    y     7
#7    z     9

【讨论】:

  • 这开始变得深奥了...不过我喜欢它
  • 如果要相信 OP 的代码,可以使用 n_distinct
  • @Frank 哦,是的。我认为这不是 OP 评论的问题:此外,这些值都是独一无二的,因此无需担心关系,但我很欣赏它的彻底性!
【解决方案3】:

在基础 R 中,首先将 df 除以 df$Name。然后,对于每个子组,检查行数并有条件地提取最后 3 行或最后 1 行。

do.call(rbind, lapply(split(df, df$Name), function(a)
    a[tail(sequence(NROW(a)), c(3,1)[(NROW(a) < 3) + 1]),]))

或者

do.call(rbind, lapply(split(df, df$Name), function(a)
    a[tail(sequence(NROW(a)), ifelse(NROW(a) < 3, 1, 3)),]))
#    Name Value
#x.2    x     2
#x.3    x     3
#x.4    x     4
#y.5    y     5
#y.6    y     6
#y.7    y     7
#z      z     9

对于三个条件值

do.call(rbind, lapply(split(df, df$Name), function(a)
      a[tail(sequence(NROW(a)), ifelse(NROW(a) >= 6, 6, ifelse(NROW(a) >= 3, 3, 1))),]))

【讨论】:

  • 谢谢@d.b!有没有办法扩展它以将其分成三个不同的子组?我想它会有 c(6,3,1) (如果计数 6 是我的下一个断点)但我无法真正弄清楚 NROW 输入
  • 这看起来不错,但由于某种原因,我没有得到预期的输出。如果我有df &lt;- data.frame(Name = c("x","x","x","x","y","y","y","z","z","a","a","a","a","a","a","a"), Value = c(1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7)),那么我的预期输出是data.frame(Name = c("x","x","x","y","y","y","z","a","a","a","a","a","a"), Value = c(2,3,4,5,6,7,9,2,3,4,5,6,7)) 但是,我得到的是data.frame(Name = c("a","a","a","x","x","x","y","y","y","z"), Value = c(5,6,7,2,3,4,5,6,7,9))
  • @Anna,试试do.call(rbind, lapply(split(df, df$Name), function(a) a[tail(sequence(NROW(a)), ifelse(NROW(a) &gt;= 6, 6, ifelse(NROW(a) &gt;= 3, 3, 1))),]))
【解决方案4】:

如果您已经在使用 dplyr,自然的方法是:

library(dplyr)

# Sample data
df <- data.frame(Name = c("x","x","x","x","y","y","y","z","z"), 
                 Value = c(1,2,3,4,5,6,7,8,9))

df %>%
  group_by(Name) %>%
  mutate(Count = n_distinct(Value),
         Rank = dense_rank(desc(Value))) %>% 
  filter((Count>= 3 & Rank <= 3) | (Rank==1)) %>%
  select(-c(Count,Rank))

不需要merge,因为您只是在按名称定义的组进行计数和排名。然后,您对计数和排名要求应用过滤器,并(可选地,为了清理)删除计数和排名。

【讨论】:

  • 请参阅?ranking 了解您的排名选项——您的问题并未完全清楚您将如何打破平局。
  • 谢谢@C8H10N4O2!我如何将其扩展到三个不同组的过滤器?例如,如果我想打破 6、3 或 1 个计数,我将计算计数的“下限”。此外,这些价值观都是独一无二的,因此无需担心关系,但我很欣赏它的彻底性!
  • @Anna 你只需要弄清楚如何将你的选择写成一个逻辑语句,然后把它放在filter() 中。另外,请注意,此答案将按值对您的数据进行排名,而 Frank 仅按组取最后 N 行。因此,如果您确信您的数据集已经排序,您应该接受 Frank 的回答。
猜你喜欢
  • 2020-07-07
  • 1970-01-01
  • 2022-09-30
  • 1970-01-01
  • 2012-10-01
  • 2019-04-14
  • 2019-11-06
  • 2020-04-06
  • 2011-05-09
相关资源
最近更新 更多