根据不同的行条件删除组行
Removing rows of groups based on different row conditions
这就是我的数据框的样子。
dt <- read.table(text='
Name ActivityType GrpID
John Sale 1
John Sale 2
John Webinar 3
Kyle Email 1
Kyle Seminar 2
Kyle Sale 3
Kyle Webinar 4
Kyle Sale 5
Tom Email 1
Tom Video 2
Tom Seminar 3
', header=T, row.names = NULL)
我想做 3 件事。
- 删除组(名称是组)的第一个 ActivityType 是 "Sale" 的行。这将删除 Name = John
的行
- 删除没有 ActivityType = Sale 的行。这会删除 Name = Tom
的行
Return 剩余的组,其中第一个 ActivityType 不是 "Sale" 但在后面的行中有一个 ActivityType = "Sale"(如 1 & 2) 并且仅显示 ActivityType = Sale 的第一个实例之前的结果。所以它应该显示
Name ActivityType GrpID
Kyle Email 1
Kyle Seminar 2
Kyle Sale 3
不必像描述的那样是 3 个步骤。我只需要最终输出。我正在考虑在 data.table 中使用 SD 功能,但不知道如何添加这些条件。非常感谢您的帮助。
在data.table
中:
setDT(dt)
sl <- "Sale" #since we re-use it so much...
#1)
dt[ , if (!ActivityType[1L] == sl) .SD, by = Name]
#2)
dt[ , if (any(ActivityType == sl)) .SD, by = Name]
#3)
dt[ , {x <- ActivityType == sl; if(!x[1] & any(x)) .SD[1:which.max(x)]}, by = Name]
(请注意,第三种情况包含前两种情况,所以我假设您需要三种不同的输出...否则只坚持最后一种)
使用 dplyr
这将适用于您上面的示例
dt %>%
group_by(Name) %>%
filter( sum((GrpID == 1 & ActivityType=='Sale')) == 0 ) %>%
filter( sum(ActivityType=='Sale') > 0 ) %>%
filter( GrpID <= min(GrpID[ActivityType == 'Sale'])) %>%
ungroup
#Source: local data frame [3 x 3]
#
# Name ActivityType GrpID
# (fctr) (fctr) (int)
#1 Kyle Email 1
#2 Kyle Seminar 2
#3 Kyle Sale 3
虽然可能有更简洁的方法来执行此操作。
编辑: 我添加了输出和 ungroup
以删除分组。
编辑 2: 根据 MichaelChirico
的建议
dt %>%
group_by(Name) %>%
filter( !any(ActivityType == 'Sale' & GrpID == 1) ) %>% # 1
filter( any(ActivityType == 'Sale') ) %>% # 2
filter( GrpID <= min(GrpID[ActivityType == 'Sale'])) %>% # 3
ungroup
上述解决方案使用 any
而不是 sum
(%>%
是管道运算符)。但这并不是说不能提高效率。如果有人建议更有效和/或更具可读性的解决方案,我将很乐意再次更新此 dplyr
解决方案。
编辑 3
下面是第 3 项的替代解决方案,该解决方案基于@MichaelChirico 的comment/solution。这将所有 3 个条件组合在一个过滤语句中(不使用上面的渐进式过滤)。
dt %>%
group_by(Name) %>%
mutate(x = (ActivityType == 'Sale') ) %>%
filter( !x[1],
any(x),
row_number() <= which.max(x)) %>%
ungroup %>%
select(-x)
# For those who prefer to roll their own
result.list <- by(dt, dt$Name, FUN = function(x) {
f <- match("Sale", x$ActivityType)
if(!is.na(f) & (f != 1) ) return(head(x, f))
})
result.df <- do.call(rbind, result.list)
这就是我的数据框的样子。
dt <- read.table(text='
Name ActivityType GrpID
John Sale 1
John Sale 2
John Webinar 3
Kyle Email 1
Kyle Seminar 2
Kyle Sale 3
Kyle Webinar 4
Kyle Sale 5
Tom Email 1
Tom Video 2
Tom Seminar 3
', header=T, row.names = NULL)
我想做 3 件事。
- 删除组(名称是组)的第一个 ActivityType 是 "Sale" 的行。这将删除 Name = John 的行
- 删除没有 ActivityType = Sale 的行。这会删除 Name = Tom 的行
Return 剩余的组,其中第一个 ActivityType 不是 "Sale" 但在后面的行中有一个 ActivityType = "Sale"(如 1 & 2) 并且仅显示 ActivityType = Sale 的第一个实例之前的结果。所以它应该显示
Name ActivityType GrpID Kyle Email 1 Kyle Seminar 2 Kyle Sale 3
不必像描述的那样是 3 个步骤。我只需要最终输出。我正在考虑在 data.table 中使用 SD 功能,但不知道如何添加这些条件。非常感谢您的帮助。
在data.table
中:
setDT(dt)
sl <- "Sale" #since we re-use it so much...
#1)
dt[ , if (!ActivityType[1L] == sl) .SD, by = Name]
#2)
dt[ , if (any(ActivityType == sl)) .SD, by = Name]
#3)
dt[ , {x <- ActivityType == sl; if(!x[1] & any(x)) .SD[1:which.max(x)]}, by = Name]
(请注意,第三种情况包含前两种情况,所以我假设您需要三种不同的输出...否则只坚持最后一种)
使用 dplyr
这将适用于您上面的示例
dt %>%
group_by(Name) %>%
filter( sum((GrpID == 1 & ActivityType=='Sale')) == 0 ) %>%
filter( sum(ActivityType=='Sale') > 0 ) %>%
filter( GrpID <= min(GrpID[ActivityType == 'Sale'])) %>%
ungroup
#Source: local data frame [3 x 3]
#
# Name ActivityType GrpID
# (fctr) (fctr) (int)
#1 Kyle Email 1
#2 Kyle Seminar 2
#3 Kyle Sale 3
虽然可能有更简洁的方法来执行此操作。
编辑: 我添加了输出和 ungroup
以删除分组。
编辑 2: 根据 MichaelChirico
的建议dt %>%
group_by(Name) %>%
filter( !any(ActivityType == 'Sale' & GrpID == 1) ) %>% # 1
filter( any(ActivityType == 'Sale') ) %>% # 2
filter( GrpID <= min(GrpID[ActivityType == 'Sale'])) %>% # 3
ungroup
上述解决方案使用 any
而不是 sum
(%>%
是管道运算符)。但这并不是说不能提高效率。如果有人建议更有效和/或更具可读性的解决方案,我将很乐意再次更新此 dplyr
解决方案。
编辑 3
下面是第 3 项的替代解决方案,该解决方案基于@MichaelChirico 的comment/solution。这将所有 3 个条件组合在一个过滤语句中(不使用上面的渐进式过滤)。
dt %>%
group_by(Name) %>%
mutate(x = (ActivityType == 'Sale') ) %>%
filter( !x[1],
any(x),
row_number() <= which.max(x)) %>%
ungroup %>%
select(-x)
# For those who prefer to roll their own
result.list <- by(dt, dt$Name, FUN = function(x) {
f <- match("Sale", x$ActivityType)
if(!is.na(f) & (f != 1) ) return(head(x, f))
})
result.df <- do.call(rbind, result.list)