【问题标题】:Using dplyr to make sample from data frame使用 dplyr 从数据框中制作样本
【发布时间】:2014-04-23 07:52:59
【问题描述】:

我有一个非常大的数据框(150.000.000 行),格式如下:

df = data.frame(pnr = rep(500+2*(1:15),each=3), x = runif(3*15))

pnr 是个人 ID,x 是一些数据。我想对 10% 的人进行抽样。在 dplyr 中有没有快速的方法来做到这一点?

以下是一个解决方案,但是由于合并语句,它很慢

prns = as.data.frame(unique(df$prn))
names(prns)[1] = "prn"
prns$s = rbinom(nrow(prns),1,0.1)

df = merge(df,prns)
df2 = df[df$s==1,]

【问题讨论】:

  • 您真的需要为此使用dplyr吗?你想对行进行采样吗?还是只是列?向上还是向下舍入 10%?
  • 另一种选择也不错:) 数据框很大(15.000.000 行)
  • 使用semi_join 而不是merge

标签: r dplyr


【解决方案1】:

我实际上会为此建议使用“data.table”包而不是“dplyr”。这是一个包含一些大样本数据的示例(不比您自己的 1500 万行小很多)。

我还将展示一些正确和错误的做事方式:-)

这是示例数据。

library(data.table)
library(dplyr)
library(microbenchmark)
set.seed(1)
mydf <- DT <- data.frame(person = sample(10000, 1e7, TRUE),
                   value = runif(1e7))

我们还将创建一个“data.table”并将键设置为“person”。创建“data.table”不需要很长时间,但设置键可以。

system.time(setDT(DT))
#    user  system elapsed 
#   0.001   0.000   0.001 

## Setting the key takes some time, but is worth it
system.time(setkey(DT, person)) 
#    user  system elapsed 
#   0.620   0.025   0.646

我想不出比以下更有效的方法来选择您的“人”值,所以我从基准中删除了这些——它们对所有方法都是通用的。

## Common to all tests...
A <- unique(mydf$person)
B <- sample(A, ceiling(.1 * length(A)), FALSE)

为方便起见,不同的测试以函数形式呈现...

## Base R #1
fun1a <- function() {
  mydf[mydf$person %in% B, ]
}

## Base R #2--sometimes using `which` makes things quicker
fun1b <- function() {
  mydf[which(mydf$person %in% B), ]
}

## `filter` from "dplyr"
fun2 <- function() {
  filter(mydf, person %in% B)
}

## The "wrong" way to do this with "data.table"
fun3a <- function() {
  DT[which(person %in% B)]
}

## The "right" (I think) way to do this with "data.table"
fun3b <- function() {
  DT[J(B)]
}

现在,我们可以进行基准测试了:

## The benchmarking
microbenchmark(fun1a(), fun1b(), fun2(), fun3a(), fun3b(), times = 20)
# Unit: milliseconds
#     expr       min        lq    median        uq       max neval
#  fun1a() 382.37534 394.27968 396.76076 406.92431 494.32220    20
#  fun1b() 401.91530 413.04710 416.38470 425.90150 503.83169    20
#   fun2() 381.78909 394.16716 395.49341 399.01202 417.79044    20
#  fun3a() 387.35363 397.02220 399.18113 406.23515 413.56128    20
#  fun3b()  28.77801  28.91648  29.01535  29.37596  42.34043    20

看看我们以正确的方式使用“data.table”所获得的性能!不过,所有其他方法都非常快。


summary 显示结果相同。 (“data.table”解决方案的行顺序会有所不同,因为它已经过排序。)

summary(fun1a())
#      person         value         
#  Min.   :  16   Min.   :0.000002  
#  1st Qu.:2424   1st Qu.:0.250988  
#  Median :5075   Median :0.500259  
#  Mean   :4958   Mean   :0.500349  
#  3rd Qu.:7434   3rd Qu.:0.749601  
#  Max.   :9973   Max.   :1.000000  

summary(fun2())
#      person         value         
#  Min.   :  16   Min.   :0.000002  
#  1st Qu.:2424   1st Qu.:0.250988  
#  Median :5075   Median :0.500259  
#  Mean   :4958   Mean   :0.500349  
#  3rd Qu.:7434   3rd Qu.:0.749601  
#  Max.   :9973   Max.   :1.000000  

summary(fun3b())
#      person         value         
#  Min.   :  16   Min.   :0.000002  
#  1st Qu.:2424   1st Qu.:0.250988  
#  Median :5075   Median :0.500259  
#  Mean   :4958   Mean   :0.500349  
#  3rd Qu.:7434   3rd Qu.:0.749601  
#  Max.   :9973   Max.   :1.000000 

【讨论】:

    【解决方案2】:

    在基数 R 中,对 10% 的行进行采样,向上舍入到下一行

    > df[sample(nrow(df), ceiling(0.1*nrow(df)), FALSE), ]
    ##    pnr         x
    ## 16 512 0.9781232
    ## 21 514 0.5279925
    ## 33 522 0.8332834
    ## 14 510 0.7989481
    ## 4  504 0.7825318
    

    或向下舍入到下一行

    > df[sample(nrow(df), floor(0.1*nrow(df)), FALSE), ]
    ##    pnr           x
    ## 43 530 0.449985180
    ## 35 524 0.996350657
    ## 2  502 0.499871966
    ## 25 518 0.005199058
    

    或对pnr 列的 10% 进行抽样,向上取整

    > sample(df$pnr, ceiling(0.1*length(df$pnr)), FALSE)
    ## [1] 530 516 526 518 514 
    

    添加:

    如果您希望对 10% 的人(唯一 pnr ID)进行抽样,并返回这些人及其各自的数据,我想您想要

    > S <- sample(unique(df$pnr), ceiling(0.1*length(unique(df$pnr))), FALSE)
    > df[df$pnr %in% S, ]
    ##    pnr         x
    ## 1  502 0.7630667
    ## 2  502 0.4998720
    ## 3  502 0.4839460
    ## 22 516 0.8248153
    ## 23 516 0.5795991
    ## 24 516 0.1572472
    

    PS:我会等待dplyr 的答复。在 1500 万行上它可能会更快。

    【讨论】:

    • 谢谢。但我认为这不能解决我的问题。我需要 1) 人员样本,以及 2) 对于被抽样人员的所有 x 数据
    • 最后做了一个补充。我想这就是你想要的。
    • 再次感谢!这行得通。但我认为 df$pnr %in% S 会很慢。可能有一个 dplyr 方式。顺便说一句:我在戴维斯呆了半年(我想是 1991 年)
    • @PeterS,在我对 1e7 行的快速测试中,base R 的方法需要 2 秒,而来自 "dplyr" 的 filter 需要 1 秒。
    • @PeterS,实际上,我收回了这一点。通过对基本 R 方法进行一些优化,它几乎没有运行得更快。很快将尝试链接到一些基准。
    【解决方案3】:

    如果你不一定想要一个完全随机的样本,那么你可以这样做

    filter(df, pnr %% 10 ==0).
    

    每 10 个人需要一次(您可以通过更改为 ==1,...获得 10 个不同的样本)。您可以通过随机重新分配 ID 来使其随机化——在玩具示例中使用 sample(15)[(df$pnr-500)/2] 来做到这一点相当简单——将pnr 的映射反转到适合sample 的集合上对于实际来说可能不太容易——世界案例。

    【讨论】:

      猜你喜欢
      • 2014-05-31
      • 2014-02-10
      • 1970-01-01
      • 2020-07-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-21
      • 2018-01-16
      相关资源
      最近更新 更多