按组用第一个观测值替换所有数值

5

对于每个由 'id' 定义的组,我想选择列 'x' 和 'y' 的第一行中的值,并用那个第一个值替换所有后续的值。

一些数据:

id    Visit   x        y
1      1      0        1
1      2      1        2
1      3      2        8
2      9      1       11
2      10     12      14

我希望:

id    Visit   x        y
1      1      0        1
1      2      0        1  # <- x & y replaced with first values of 'id' 1 
1      3      0        1  # 
2      9      1        11  
2      10     1        11 # <- x & y replaced with first values of 'id' 2 

我尝试了这个:

df1 <- df %>%
  arrange(id, Visit) %>%
  group_by(id) %>%
  fill(x, y, 
       .direction = 'down',)

不过,似乎这样做还不够。有人能帮忙吗?


tidyr::fill的作用是填充缺失值,而不是向下填充现有值。换句话说,它只在存在NA时起作用。 - r2evans
哦,我明白了。谢谢你的澄清! - D. Fowler
6个回答

6
使用 duplicated 函数作为 base 函数的替代方案:
df[, c("x", "y")] = df[(i = !duplicated(df$id)), c("x", "y")][cumsum(i), ]
#   id Visit x  y
# 1  1     1 0  1
# 2  1     2 0  1
# 3  1     3 0  1
# 4  2     9 1 11
# 5  2    10 1 11

使用 data.table 的滚动连接(rolling join)来“填充”每个组中的第一个值(对大型数据快速处理):
library(data.table)
setDT(df)
df[ , c("x", "y") := df[!duplicated(id)][.SD, on = .(id, Visit), .(x, y), roll = Inf]]
df
#    id Visit x  y
# 1:  1     1 0  1
# 2:  1     2 0  1
# 3:  1     3 0  1
# 4:  2     9 1 11
# 5:  2    10 1 11

5
如果您想使用基本的R语言版本来实现@akrun提供的精彩答案,请参考以下内容:
df[c("x","y")] <- lapply(df[c("x","y")], function(z) ave(z, df$id, FUN = function(y) y[1]))
df
#   id Visit x  y
# 1  1     1 0  1
# 2  1     2 0  1
# 3  1     3 0  1
# 4  2     9 1 11
# 5  2    10 1 11

(我故意避免使用 dplyr::firstdata.table::first,因为这样做会违背使用基本 R 版本的初衷。)

或者使用 data.table 变量:

library(data.table)
setDT(df)
df[, c("x","y") := lapply(.SD[,c("x","y")], first), by = .(id)]

正如@Henrik所提到的,这里最好使用.SDcols

df[, c("x","y") := lapply(.SD, first), by = .(id), .SDcols = c("x","y")]

1
嗨@r2evans!你应该避免使用.SD[...]:对于by中的每次迭代,它都会调用[.data.table,这会带来很大的开销。相反,使用df[, c("x","y") := lapply(.SD, first), .SDcols = c("x", "y"), by = id]在处理更大的数据时速度更快。干杯 - Henrik
@Henrik,你知道是否有关于在LHS中使用.SDcols名称而不预定义向量的讨论吗?例如,df[, (.SDcolnms) := lapply(.SD,...), .SDcols=c('x','y')]。我知道可以通过预先分配nms <- c('x','y')并在两个位置使用它来轻松处理...只是好奇。再次感谢。 - r2evans
1
关于变量名,有一个热门功能请求:names(.SD) := ...应该可行。敬请期待! - Henrik
很好的讨论链,可惜已经讨论了多年,没有什么最新的进展。尽管在 names(.SD):=.SDcols:= 上有一些共识,但仍然没有达成一致意见。(你说“敬请期待”的时候是在讽刺这个七年的问题,还是你有更深入的理解?) - r2evans
1
有关不要在FAQ中使用.SD[...]的相关说明:(链接: https://cran.r-project.org/web/packages/data.table/vignettes/datatable-faq.html#how-can-i-avoid-writing-a-really-long-j-expression-youve-said-that-i-should-use-the-column-names-but-ive-got-a-lot-of-columns) - Henrik
显示剩余2条评论

3
我们可以在 mutate 上使用 across 的同时使用 first
library(dplyr)
df %>%
    arrange(id, Visit) %>% 
    group_by(id) %>% 
    mutate(across(c(x, y), first)) %>%
    ungroup

2

另一个基于R的选项

with(
  df,
  cbind(
    df[c("id", "Visit")],
    cbind(x, y)[ave(1:nrow(df), id, FUN = function(x) head(x, 1)), ]
  )
)

提供

  id Visit x  y
1  1     1 0  1
2  1     2 0  1
3  1     3 0  1
4  2     9 1 11
5  2    10 1 11

1
使用dplyr,也可以使用head()
library(dplyr)
df %>%
    group_by(id) %>% 
    mutate(x = head(x, 1), y = head(x,1)) %>%
    ungroup

0

在基本的R中,另一个选项是通过split()lapply()do.call()rbind()结合使用的分割-应用-组合范式:

df <- data.frame(id = c(1,1,1,2,2), Visit = c(1,2,3,9,10), x = c(0,1,2,1,12), y = c(1,2,8,11,14))

df <- do.call(rbind, 
        lapply(split(df, df$id), 
          function(z) {
                z$x <- z[1, "x"]
                z$y <- z[1, "y"]
                return(z)}))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接