我有一个数据框,其中有多列,其中尤其包含单词及其在句子中的位置。对于某些职位,行比其他职位多。这是一个模拟示例:
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
很明显,“位置”的各个部分的大小不同:
table(df$position)
1 2 3 4 5
15 15 17 28 25
为使不同批次更易于比较,我想在一个数据帧内的变量“位置”上绘制大小相等的样本。从理论上讲,这可以按如下步骤完成:
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循环。到目前为止,我已经尝试过此代码,但是返回的不是每个位置值的单个样本的组合,而是从“位置”的所有值中提取的单个样本:
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
此代码有什么问题,如何进行改进?
答案 0 :(得分:2)
考虑by
,并根据需要通过采样位置来分割数据帧。然后,rbind
将所有df与do.call()
一起放在循环之外。
df_list <- by(df, df$position, function(sub) sub[sample(1:nrow(sub), 3),])
final_df <- do.call(rbind, df_list)
当前,您在每次迭代中为整个(非子集)数据帧编制索引。另外,您正在rbind
循环内使用for
,这会占用大量内存,因此不建议使用。
具体地说,
by
是tapply
的面向对象的包装器,实际上是按因子将数据帧拆分为子集,并将每个子集传递到定义的函数中。在这里, sub 只是子集变量的名称(可以命名为任何名称)。这里的结果是一个数据帧列表。do.call
本质上跨多个元素运行扩展调用的压缩版本,其中rbind(df1, df2, df3)
等效于do.call(rbind, list(df1, df2, df3))
。这里要注意的关键是rbind
不是在循环内调用(避免在迭代内增长像数据框这样的复杂对象的危险),而是在循环外一次。答案 1 :(得分:0)
我们可以将data.table
与行索引sample
的{{1}}组成的组一起使用,并用它来对数据集进行子集化。这将非常有效
.I
或使用i1 <- setDT(df)[, sample(.I, 3), position]$V1
df[i1]
中的sample_n
tidyverse
或作为功能
library(tidyverse)
df %>%
group_by(position) %>%
sample_n(3)
答案 2 :(得分:0)
每次运行循环时,都会覆盖最后一个条目。试试:
uap5