从 R 中数据帧的不同大小的子层中绘制大小相同的样本
Drawing equally-sized samples from differently-sized substrata of a dataframe in R
我有一个包含多个列的数据框,其中包含单词及其在句子中的位置。对于某些职位,行数比其他职位多。这是一个模拟示例:
df <- data.frame(
word = sample(LETTERS, 100, replace = T),
position = sample(1:5, 100, replace = T)
)
head(df)
word position
1 K 1
2 R 5
3 J 2
4 Y 5
5 Z 5
6 U 4
显然,'position' 的各个部分大小不同:
table(df$position)
1 2 3 4 5
15 15 17 28 25
为了使不同的部分更容易比较,我想在一个数据帧内的变量 'position' 上绘制相同大小的样本。这在理论上可以分步完成,例如:
df_pos1 <- df[df$position==1,]
df_pos1_sample <- df_pos1[sample(1:nrow(df_pos1), 3),]
df_pos2 <- df[df$position==2,]
df_pos2_sample <- df_pos2[sample(1:nrow(df_pos2), 3),]
df_pos3 <- df[df$position==3,]
df_pos3_sample <- df_pos3[sample(1:nrow(df_pos3), 3),]
df_pos4 <- df[df$position==4,]
df_pos4_sample <- df_pos4[sample(1:nrow(df_pos4), 3),]
df_pos5 <- df[df$position==5,]
df_pos5_sample <- df_pos5[sample(1:nrow(df_pos5), 3),]
依此类推,最终将各个样本合并到一个数据帧中:
df_samples <- rbind(df_pos1_sample, df_pos2_sample, df_pos3_sample, df_pos4_sample, df_pos5_sample)
但是这个过程很麻烦而且容易出错。更经济的解决方案可能是 for 循环。到目前为止,我已经尝试过这段代码,但是,returns 不是每个位置值的单个样本的组合,而是从 'position':
的所有值中提取的单个样本
df_samples <-c()
for(i in unique(df$position)){
df_samples <- rbind(df[sample(1:nrow(df[df$position==i,]), 3),])
}
df_samples
word position
13 D 2
2 R 5
12 G 3
4 Y 5
16 Z 3
11 S 3
6 U 4
14 J 3
9 O 5
1 K 1
这段代码有什么问题,如何改进?
我们可以将 data.table
与行索引 .I
的 sample
分组一起使用,并使用它来对数据集进行子集化。这会非常有效
i1 <- setDT(df)[, sample(.I, 3), position]$V1
df[i1]
或使用 sample_n
来自 tidyverse
library(tidyverse)
df %>%
group_by(position) %>%
sample_n(3)
或作为函数
f1 <- function(data) {
data as.data.table(data)
i1 <- data[, sample(.I, 3), by = position]$V1
data[i1]
}
考虑 by
将数据帧按 位置 拆分,并根据需要进行采样。然后 rbind
所有 dfs 在循环外与 do.call()
.
df_list <- by(df, df$position, function(sub) sub[sample(1:nrow(sub), 3),])
final_df <- do.call(rbind, df_list)
目前您在每次迭代中索引整个(非子集)数据框。此外,您在 for
循环中使用 rbind
,这是内存密集型的,不建议这样做。
具体来说,
by
是 tapply
的面向对象包装器,本质上是按因子将数据帧拆分为子集,并将每个子集传递给定义的函数。这里 sub 只是子集变量的名称(可以命名为任何名称)。这里的结果是一个数据框列表。
do.call
本质上运行跨多个元素的扩展调用的紧凑版本,其中 rbind(df1, df2, df3)
等同于 do.call(rbind, list(df1, df2, df3))
。这里要注意的关键是 rbind
不是在循环内调用(避免在迭代内增加复杂对象(如数据框)的危险),而是在循环外调用 once。
每次 运行 循环都会覆盖最后一个条目。尝试:
df_samples <- data.frame()
df_samples <- rbind(df_samples, df[sample(1:nrow(df[df$position==i,]), 3),])
我有一个包含多个列的数据框,其中包含单词及其在句子中的位置。对于某些职位,行数比其他职位多。这是一个模拟示例:
df <- data.frame(
word = sample(LETTERS, 100, replace = T),
position = sample(1:5, 100, replace = T)
)
head(df)
word position
1 K 1
2 R 5
3 J 2
4 Y 5
5 Z 5
6 U 4
显然,'position' 的各个部分大小不同:
table(df$position)
1 2 3 4 5
15 15 17 28 25
为了使不同的部分更容易比较,我想在一个数据帧内的变量 'position' 上绘制相同大小的样本。这在理论上可以分步完成,例如:
df_pos1 <- df[df$position==1,]
df_pos1_sample <- df_pos1[sample(1:nrow(df_pos1), 3),]
df_pos2 <- df[df$position==2,]
df_pos2_sample <- df_pos2[sample(1:nrow(df_pos2), 3),]
df_pos3 <- df[df$position==3,]
df_pos3_sample <- df_pos3[sample(1:nrow(df_pos3), 3),]
df_pos4 <- df[df$position==4,]
df_pos4_sample <- df_pos4[sample(1:nrow(df_pos4), 3),]
df_pos5 <- df[df$position==5,]
df_pos5_sample <- df_pos5[sample(1:nrow(df_pos5), 3),]
依此类推,最终将各个样本合并到一个数据帧中:
df_samples <- rbind(df_pos1_sample, df_pos2_sample, df_pos3_sample, df_pos4_sample, df_pos5_sample)
但是这个过程很麻烦而且容易出错。更经济的解决方案可能是 for 循环。到目前为止,我已经尝试过这段代码,但是,returns 不是每个位置值的单个样本的组合,而是从 'position':
的所有值中提取的单个样本df_samples <-c()
for(i in unique(df$position)){
df_samples <- rbind(df[sample(1:nrow(df[df$position==i,]), 3),])
}
df_samples
word position
13 D 2
2 R 5
12 G 3
4 Y 5
16 Z 3
11 S 3
6 U 4
14 J 3
9 O 5
1 K 1
这段代码有什么问题,如何改进?
我们可以将 data.table
与行索引 .I
的 sample
分组一起使用,并使用它来对数据集进行子集化。这会非常有效
i1 <- setDT(df)[, sample(.I, 3), position]$V1
df[i1]
或使用 sample_n
来自 tidyverse
library(tidyverse)
df %>%
group_by(position) %>%
sample_n(3)
或作为函数
f1 <- function(data) {
data as.data.table(data)
i1 <- data[, sample(.I, 3), by = position]$V1
data[i1]
}
考虑 by
将数据帧按 位置 拆分,并根据需要进行采样。然后 rbind
所有 dfs 在循环外与 do.call()
.
df_list <- by(df, df$position, function(sub) sub[sample(1:nrow(sub), 3),])
final_df <- do.call(rbind, df_list)
目前您在每次迭代中索引整个(非子集)数据框。此外,您在 for
循环中使用 rbind
,这是内存密集型的,不建议这样做。
具体来说,
by
是tapply
的面向对象包装器,本质上是按因子将数据帧拆分为子集,并将每个子集传递给定义的函数。这里 sub 只是子集变量的名称(可以命名为任何名称)。这里的结果是一个数据框列表。do.call
本质上运行跨多个元素的扩展调用的紧凑版本,其中rbind(df1, df2, df3)
等同于do.call(rbind, list(df1, df2, df3))
。这里要注意的关键是rbind
不是在循环内调用(避免在迭代内增加复杂对象(如数据框)的危险),而是在循环外调用 once。
每次 运行 循环都会覆盖最后一个条目。尝试:
df_samples <- data.frame()
df_samples <- rbind(df_samples, df[sample(1:nrow(df[df$position==i,]), 3),])