在数据框中,根据指定列的数值,重复每一行的数据。

203
df <- data.frame(var1 = c('a', 'b', 'c'), var2 = c('d', 'e', 'f'),
                 freq = 1:3)

最简单的方法是扩展上面数据框的每一行的前两列,以便每一行重复“freq”列中指定的次数?

换句话说,从这个开始:

df
  var1 var2 freq
1    a    d    1
2    b    e    2
3    c    f    3

变成这样:

df.expanded
  var1 var2
1    a    d
2    b    e
3    b    e
4    c    f
5    c    f
6    c    f
10个回答

202

这是一个解决方案:

df.expanded <- df[rep(row.names(df), df$freq), 1:2]

结果:

    var1 var2
1      a    d
2      b    e
2.1    b    e
3      c    f
3.1    c    f
3.2    c    f

2
太好了!我总是忘记可以那样使用方括号。我一直认为索引只用于子集或重新排序。我有另一个解决方案,远不如这个优雅,无疑也不如这个高效。我可能还是会发布出来,让其他人可以比较。 - wkmor1
27
对于大的data.frame,更有效率的方法是将row.names(df)替换为seq.int(1,nrow(df))或者 seq_len(nrow(df)) - Marek
这对于一个大数据框非常有效--150万行,5列,速度非常快。谢谢! - gabe
6
1:2硬编码了这个例子的解决方案,1:ncol(df)将适用于任意数据框。 (注:其中的“1:2”和“1:ncol(df)”指的是代码中的语法操作,表示从第一个列到第二个列和从第一个列到最后一个列,分别对应于特定示例和任意数据框。) - vladiim

115

在tidyverse中更新动词的旧问题:

library(tidyr) # version >= 0.8.0
df <- data.frame(var1=c('a', 'b', 'c'), var2=c('d', 'e', 'f'), freq=1:3)
df %>% 
  uncount(freq)

    var1 var2
1      a    d
2      b    e
2.1    b    e
3      c    f
3.1    c    f
3.2    c    f

太棒了!真是救命稻草,谢谢!! - avallecam

49
使用 splitstackshape 包中的 expandRows():
library(splitstackshape)
expandRows(df, "freq")

简单易懂的语法,非常快速,在 data.framedata.table 上使用。

    var1 var2
1      a    d
2      b    e
2.1    b    e
3      c    f
3.1    c    f
3.2    c    f

28

@neilfws的解决方案适用于data.frame,但不适用于data.table,因为它们缺少row.names属性。以下方法适用于两种情况:

df.expanded <- df[rep(seq(nrow(df)), df$freq), 1:2]

data.table 的代码略微更清洁:

# convert to data.table by reference
setDT(df)
df.expanded <- df[rep(seq(.N), freq), !"freq"]

6
另一个可选方案:df[rep(seq(.N), freq)][, freq := NULL]。该代码会将数据框中的行按照频率重复,并删除频率列,同时保持原始含义不变。 - Jaap
1
另一种替代方案是 df[rep(1:.N, freq)][, freq:=NULL] - Dale Kube

9

使用 slice 实现与 dplyr 类似的功能,不同的是我们可以将每个行号重复 freq 次。

library(dplyr)

df %>%  
  slice(rep(seq_len(n()), freq)) %>% 
  select(-freq)

#  var1 var2
#1    a    d
#2    b    e
#3    b    e
#4    c    f
#5    c    f
#6    c    f

seq_len(n())这部分可以替换为以下任何一种方式。

df %>% slice(rep(1:nrow(df), freq)) %>% select(-freq)
#Or
df %>% slice(rep(row_number(), freq)) %>% select(-freq)
#Or
df %>% slice(rep(seq_len(nrow(.)), freq)) %>% select(-freq)

8

我知道这并不是情况,但如果你需要保留原始的频率列,你可以使用另一个 tidyverse 方法与 rep 结合使用:

library(purrr)

df <- data.frame(var1 = c('a', 'b', 'c'), var2 = c('d', 'e', 'f'), freq = 1:3)

df %>% 
  map_df(., rep, .$freq)
#> # A tibble: 6 x 3
#>   var1  var2   freq
#>   <fct> <fct> <int>
#> 1 a     d         1
#> 2 b     e         2
#> 3 b     e         2
#> 4 c     f         3
#> 5 c     f         3
#> 6 c     f         3

这段内容创建于2019年12月21日,使用了reprex包 (v0.3.0)


5
еңЁuncount()дёӯпјҢжӮЁеҸҜд»ҘдҪҝз”Ё.remove = FALSEжқҘдҝқз•ҷйҮҚеӨҚзҡ„иЎҢгҖӮ - user10917479

7

如果您需要在非常大的数据框上执行此操作,我建议将其转换为数据表并使用以下方法,这样应该可以运行得更快:

library(data.table)
dt <- data.table(df)
dt.expanded <- dt[ ,list(freq=rep(1,freq)),by=c("var1","var2")]
dt.expanded[ ,freq := NULL]
dt.expanded

看看这个解决方案有多快:

df <- data.frame(var1=1:2e3, var2=1:2e3, freq=1:2e3)
system.time(df.exp <- df[rep(row.names(df), df$freq), 1:2])
##    user  system elapsed 
##    4.57    0.00    4.56
dt <- data.table(df)
system.time(dt.expanded <- dt[ ,list(freq=rep(1,freq)),by=c("var1","var2")])
##    user  system elapsed 
##    0.05    0.01    0.06

1
我遇到了一个错误:Error in rep(1, freq) : invalid 'times' argument。鉴于已经有了一个data.table的答案,您可能想描述一下您的方法与当前data.table答案的不同之处或更好的情况。或者如果没有主要区别,您可以将其作为评论添加到现有答案中。 - Sam Firke
@SamFirke:感谢您的评论。奇怪的是,我刚刚再次尝试了一下,没有出现这样的错误。您是否使用了原始的OP问题中的“df”?我的答案更好,因为另一个答案在使用“data.frame”语法时有点滥用“data.table”包,请参阅“data.table”的FAQ:“通常不应该按数字而不是名称引用列。” - vonjd
1
感谢您的解释。您的代码在OP发布的示例df上对我有效,但是当我尝试在更大的数据框上进行基准测试时,我遇到了错误。我使用的数据框是:set.seed(1) dfbig <- data.frame(var1=sample(letters, 1000, replace = TRUE), var2=sample(LETTERS, 1000, replace = TRUE), freq=sample(1:10, 1000, replace = TRUE)) 在小型数据框中,基本答案在我的基准测试中表现良好,但它无法很好地扩展到更大的数据框。其他三个答案在这个更大的数据框上成功运行。 - Sam Firke
@SamFirke:这确实很奇怪,它应该在那里也能工作,我不知道为什么不能。你想把它变成一个问题吗?还是我来做? - vonjd
好主意。你能行吗?我不熟悉data.table语法,所以不应该评判答案。 - Sam Firke
显示剩余3条评论

5
另一种可能性是使用 tidyr::expand:
library(dplyr)
library(tidyr)

df %>% group_by_at(vars(-freq)) %>% expand(temp = 1:freq) %>% select(-temp)

#> # A tibble: 6 x 2
#> # Groups:   var1, var2 [3]
#>   var1  var2 
#>   <fct> <fct>
#> 1 a     d    
#> 2 b     e    
#> 3 b     e    
#> 4 c     f    
#> 5 c     f    
#> 6 c     f

vonjd的回答的一行版本:

library(data.table)

setDT(df)[ ,list(freq=rep(1,freq)),by=c("var1","var2")][ ,freq := NULL][]

#>    var1 var2
#> 1:    a    d
#> 2:    b    e
#> 3:    b    e
#> 4:    c    f
#> 5:    c    f
#> 6:    c    f

reprex package (v0.2.1) 于2019年5月21日创建


3

我在这个精彩答案的讨论中提供了一个新的补充!使用tidyr包(包含在tidyverse中)可以实现一行代码解决:

df %>% tidyr::uncount(weights = freq)

0
事实上,使用向量和索引的方法,我们也可以实现相同的结果,并且更易于理解:
rawdata <- data.frame('time' = 1:3, 
           'x1' = 4:6,
           'x2' = 7:9,
           'x3' = 10:12)

rawdata[rep(1, time=2), ] %>% remove_rownames()
#  time x1 x2 x3
# 1    1  4  7 10
# 2    1  4  7 10



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