按组随机抽样

49

我有一个数据框,包含大约50,000行分布在15个不同的ID中(每个ID都有数千条观测记录)。数据框看起来像这样:

        ID  Year    Temp    ph
1       P1  1996    11.3    6.80
2       P1  1996    9.7     6.90
3       P1  1997    9.8     7.10
...
2000    P2  1997    10.5    6.90
2001    P2  1997    9.9     7.00
2002    P2  1997    10.0    6.93

我想对每个ID随机选择500行(例如,对于P1选取500行,对于P2再选取500行……)并创建一个新的数据框。我尝试了以下代码:

new_df<-df[df$ID %in% sample(unique(dfID),500),]

但是它只随机获取一个ID,而我需要每个ID随机获取500行。


4
如果你来这里是想要使用所有行,但从15个不同的ID中随机抽取一些的反向问题,请参考以下链接:https://dev59.com/11oU5IYBdhLWcg3wvIuq - Christopher Oezbek
9个回答

92

这在dplyr中作为slice_sample函数提供:

library(dplyr)
new_df <- df %>% group_by(ID) %>% slice_sample(n=500)

在旧版本的R中,该函数被称为sample_n,但已被弃用。


4
在大型数据框中表现良好。 - Martin Thøgersen
在函数内部的for循环中调用时无法正常工作,但在函数外部完美运行。有人知道原因吗? - Marina
2
非标准评估/标准评估问题:https://dev59.com/xlsX5IYBdhLWcg3wJMnb#34187076 - leerssej
我对这个解决方案唯一的问题是,你只能采集最小组别样本中的最大数量。例如某个ID有499行数据,但其他所有组别需要500行,那么程序会报错。 - HaplessEcologist
只是提醒一下,因为dplyr动词经常变化:在dplyr v.1中,这已被slice_sample取代。 - camille

19

试试这个:

library(plyr)
ddply(df,.(ID),function(x) x[sample(nrow(x),500),])

13

以下是基于R语言的一种方法。

首先,需要准备用于工作的样本数据:

set.seed(1)
mydf <- data.frame(ID = rep(1:3, each = 5), matrix(rnorm(45), ncol = 3))
mydf
#    ID         X1          X2          X3
# 1   1 -0.6264538 -0.04493361  1.35867955
# 2   1  0.1836433 -0.01619026 -0.10278773
# 3   1 -0.8356286  0.94383621  0.38767161
# 4   1  1.5952808  0.82122120 -0.05380504
# 5   1  0.3295078  0.59390132 -1.37705956
# 6   2 -0.8204684  0.91897737 -0.41499456
# 7   2  0.4874291  0.78213630 -0.39428995
# 8   2  0.7383247  0.07456498 -0.05931340
# 9   2  0.5757814 -1.98935170  1.10002537
# 10  2 -0.3053884  0.61982575  0.76317575
# 11  3  1.5117812 -0.05612874 -0.16452360
# 12  3  0.3898432 -0.15579551 -0.25336168
# 13  3 -0.6212406 -1.47075238  0.69696338
# 14  3 -2.2146999 -0.47815006  0.55666320
# 15  3  1.1249309  0.41794156 -0.68875569

其次,采样:

do.call(rbind, 
        lapply(split(mydf, mydf$ID), 
               function(x) x[sample(nrow(x), 3), ]))
#      ID         X1          X2         X3
# 1.2   1  0.1836433 -0.01619026 -0.1027877
# 1.1   1 -0.6264538 -0.04493361  1.3586796
# 1.5   1  0.3295078  0.59390132 -1.3770596
# 2.10  2 -0.3053884  0.61982575  0.7631757
# 2.9   2  0.5757814 -1.98935170  1.1000254
# 2.8   2  0.7383247  0.07456498 -0.0593134
# 3.13  3 -0.6212406 -1.47075238  0.6969634
# 3.12  3  0.3898432 -0.15579551 -0.2533617
# 3.15  3  1.1249309  0.41794156 -0.6887557

另外,sampling包中还有strata函数,当你想从每个组中抽取不同大小的样本时非常方便:


# install.packages("sampling")
library(sampling)
set.seed(1)
x <- strata(mydf, "ID", size = c(2, 3, 2), method = "srswor")
getdata(mydf, x)
#            X1          X2         X3 ID ID_unit Prob Stratum
# 2   0.1836433 -0.01619026 -0.1027877  1       2  0.4       1
# 5   0.3295078  0.59390132 -1.3770596  1       5  0.4       1
# 6  -0.8204684  0.91897737 -0.4149946  2       6  0.6       2
# 8   0.7383247  0.07456498 -0.0593134  2       8  0.6       2
# 9   0.5757814 -1.98935170  1.1000254  2       9  0.6       2
# 14 -2.2146999 -0.47815006  0.5566632  3      14  0.4       3
# 15  1.1249309  0.41794156 -0.6887557  3      15  0.4       3

13
如果你有大型数据集,一个 `data.table` 的解决方案可能是这样的:
library(data.table)

# Generate 26 mil rows random data
set.seed(2023-08-11) # anchor the  random number generator (RNG) state for reproducibility 
dt <- data.table(c1 = sample(length(LETTERS)*10^6), 
                 c2 = sample(LETTERS, replace = TRUE))

# For each letter, sample 500 rows
set.seed(2023-08-11) # anchor the RNG again, as we use `sample` again
dt_sample <- dt[, .SD[sample(x = .N, size = 500)], by = c2]

# We indeed sampled 500 rows for each letter
dt_sample[, .N, by = c2][order(c2)]
#>     c2   N
#>  1:  A 500
#>  2:  D 500
#>  3:  G 500
#>  4:  I 500
#>  5:  M 500
#>  6:  N 500
#>  7:  O 500
#>  8:  P 500
#>  9:  Q 500
#> 10:  R 500
#> 11:  S 500
#> 12:  T 500
#> 13:  U 500
#> 14:  V 500
#> 15:  W 500
#> 16:  Y 500
#> 17:  Z 500

2019-04-23创建,使用reprex包(v0.2.1)

如果你的数据不平衡,某些组的行数比你期望的样本大小要小,那么你需要设置一个防御性技巧,例如样本大小应该是min(500, .N) - 参见在data.table中对每个组进行随机抽样行。像这样:

dt[, .SD[sample(x = .N, size = min(500, .N))], by = c2]


1
太棒了!在调用dt_sample <- dt[, .SD[sample(x = .N, size = 500)], by = c2]之前,我是否应该设置种子以确保可复现性? - umbe1987
1
@umbe1987,是的,重新设置种子是更安全/良好的做法。感谢你指出这一点。我已经更新了代码。 - Valentin_Ștefan

2

如果其中一个ID小于500,则采用一种方法。这里我使用了mtcars数据集:

n <- 8
df <- mtcars
df$ID <- df$cyl

FUN <- function(x, n) {
    if (length(x) <= n) return(x)
    x[x %in% sample(x, n)]
}

df[unlist(lapply(split(1:nrow(df), df$ID), FUN, n = 8)), ]

1

以下是基于data.table的优雅解决方案。您可以通过三个简单的步骤从平衡或不平衡的面板数据集中随机抽取ID:

步骤1:将原始数据集中的唯一ID存储在向量中(我的数据集称为“main”,标识符称为“id”):

ids <- unique(main$id)

步骤2:从第1步的向量中随机抽取ID。 在下面的示例中,我从向量“ids”中随机抽取了50个ID,并将它们存储在新向量“draw”中:

draw <- ids %>% sample(50)

步骤三:根据第二步所绘制的ID匹配,对原始数据集中的行进行子集划分。

rsample <- main[main$id %in% draw, ]

0

虽然这不是非常优雅的解决方案,但它可能起作用。

library(data.table)
df <- data.table(df)
f <- list()
for(i in unique(df1$ID)){
 f[[i]] <- df1[id == i][sample(.N,(500))]
  }
 dfnew <- rbindlist(f)

0
library(data.table) #1
df <- data.table(df) #2
df[,group_num := sample(2,.N,replace = TRUE,prob = c(500,.N-500)/.N),by = "ID"] #3
df_sample = df[group_num == 1,] #4

或者您可以将第3行和第4行改为:

df[,random_num := sample(.N,.N),by="ID"]
df_sample  = df[random_num <=500,]

0
mydata1 is your original data(not tested)

mydata2<- split(mydata1,mydata1$ID)
names(mydata2)<-paste0("mydata2",1:length(levels(ID))) 
mysample<-Map(function(x) x[sample((1:nrow(x)),size=500,replace=FALSE),], mydata2)

library(plyr)# for rbinding the mysample
ldply(mysample)

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