R语言 如何基于多变量将数据集划分为平衡集

slmsl1lt  于 2022-12-25  发布在  其他
关注(0)|答案(2)|浏览(199)

我有一个大的数据集,我需要分为多个平衡集。
该集类似于以下内容:

> data<-matrix(runif(4000, min=0, max=10), nrow=500, ncol=8 )
> colnames(data)<-c("A","B","C","D","E","F","G","H")

例如,每个集合包含20行,需要在多个变量之间进行平衡,以便每个子集最终具有与所有其他子集相比包含在其子组中的B、C、D的相似平均值。
有没有办法用R做到这一点?任何建议都将不胜感激。提前感谢!

nhjlsmyf

nhjlsmyf1#

library(tidyverse)

# Reproducible data
set.seed(2)
data<-matrix(runif(4000, min=0, max=10), nrow=500, ncol=8 )
colnames(data)<-c("A","B","C","D","E","F","G","H")

data=as.data.frame(data)
    • 更新答案**

如果您希望将给定行的观测值放在一起,则可能无法在每列内的集合之间获得相似的均值。(就像你的样本数据一样),你需要25个20行的集合,其中每个A列集合的平均值相同,每个B列集合的平均值相同,等等。这是一个很大的约束。可以找到使集合均值的差最小化的集合成员资格分配调度的算法。
但是,如果您可以从每列中分别获取20个观测值,而不考虑它来自哪一行,那么这里有一个选项:
x一个一个一个一个x一个一个二个x

    • 原始答案**

一个三个一个x一个四个一个x一个五个一个x一个六个一个
如果数据框中的总行数不能被每个集合中所需的行数整除,则可以在创建集合时执行以下操作:

data = data %>% 
  mutate(set = sample(rep(1:ceiling(500/20), each=20))[1:n()])

在这种情况下,集合大小将稍微变化,因为数据行数不能被每个集合中所需的行数整除。

x7rlezfr

x7rlezfr2#

下面的方法可能值得一试的人在类似的立场。
它基于groupdata2fold()函数中的数值平衡,该函数允许创建具有单个列的平衡均值的组。通过标准化每个列并在数值上平衡它们的行总和,我们可能会增加在单个列中获得平衡均值的机会。
我将这种方法与随机分组并选择均值方差最小的分组进行了几次比较,结果似乎更好一些,但我不太相信这种方法在所有情况下都适用。

# Attach dplyr and groupdata2
library(dplyr)
library(groupdata2)

set.seed(1)

# Create the dataset
data <- matrix(runif(4000, min = 0, max = 10), nrow = 500, ncol = 8)
colnames(data) <- c("A", "B", "C", "D", "E", "F", "G", "H")
data <- dplyr::as_tibble(data)

# Standardize all columns and calculate row sums
data_std <- data %>% 
  dplyr::mutate_all(.funs = function(x){(x-mean(x))/sd(x)}) %>% 
  dplyr::mutate(total = rowSums(across(where(is.numeric))))

# Create groups (new column called ".folds")
# We numerically balance the "total" column 
data_std <- data_std %>% 
  groupdata2::fold(k = 25, num_col = "total")  # k = 500/20=25

# Transfer the groups to the original (non-standardized) data frame
data$group <- data_std$.folds

# Check the means
data %>% 
  dplyr::group_by(group) %>% 
  dplyr::summarise_all(.funs = mean)

> # A tibble: 25 x 9
>    group     A     B     C     D     E     F     G     H
>    <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
>  1 1      4.48  5.05  4.80  5.65  5.04  4.60  5.12  4.85
>  2 2      5.57  5.17  3.21  5.46  4.46  5.89  5.06  4.79
>  3 3      4.33  6.02  4.57  6.18  4.76  3.79  5.94  3.71
>  4 4      4.51  4.62  4.62  5.27  4.65  5.41  5.26  5.23
>  5 5      4.55  5.10  4.19  5.41  5.28  5.39  5.57  4.23
>  6 6      4.82  4.74  6.10  4.34  4.82  5.08  4.89  4.81
>  7 7      5.88  4.49  4.13  3.91  5.62  4.75  5.46  5.26
>  8 8      4.11  5.50  5.61  4.23  5.30  4.60  4.96  5.35
>  9 9      4.30  3.74  6.45  5.60  3.56  4.92  5.57  5.32
> 10 10     5.26  5.50  4.35  5.29  4.53  4.75  4.49  5.45
> # … with 15 more rows

# Check the standard deviations of the means
# Could be used to compare methods
data %>% 
  dplyr::group_by(group) %>% 
  dplyr::summarise_all(.funs = mean) %>% 
  dplyr::summarise(across(where(is.numeric), sd))

> # A tibble: 1 x 8
>       A     B     C     D     E     F     G     H
>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
> 1 0.496 0.546 0.764 0.669 0.591 0.611 0.690 0.475

最好是比较不同方法对 * 标准化 * 数据的平均值和平均方差(或上述标准差),在这种情况下,可以计算方差之和并使其最小化。

data_std %>% 
  dplyr::select(-total) %>% 
  dplyr::group_by(.folds) %>% 
  dplyr::summarise_all(.funs = mean) %>% 
  dplyr::summarise(across(where(is.numeric), sd)) %>% 
  sum()

> 1.643989

比较多个平衡拆分

fold()函数允许一次创建多个 unique 分组因子(拆分)。因此,在这里,我将执行20次数字平衡拆分,并找到均值标准差之和最小的分组。我将进一步将其转换为函数。

create_multi_balanced_groups <- function(data, cols, k, num_tries){
  
  # Extract the variables of interest
  # We assume these are numeric but we could add a check
  data_to_balance <- data[, cols]
  
  # Standardize all columns
  # And calculate rowwise sums
  data_std <- data_to_balance %>% 
    dplyr::mutate_all(.funs = function(x){(x-mean(x))/sd(x)}) %>% 
    dplyr::mutate(total = rowSums(across(where(is.numeric))))
  
  # Create `num_tries` unique numerically balanced splits
  data_std <- data_std %>% 
    groupdata2::fold(
      k = k, 
      num_fold_cols = num_tries,
      num_col = "total"
    )
  
  # The new fold column names ".folds_1", ".folds_2", etc.
  fold_col_names <- paste0(".folds_", seq_len(num_tries))
  
  # Remove total column
  data_std <- data_std %>% 
    dplyr::select(-total)
  
  # Calculate score for each split
  # This could probably be done more efficiently without a for loop
  variance_scores <- c()
  for (fcol in fold_col_names){
    score <- data_std %>% 
      dplyr::group_by(!!as.name(fcol)) %>% 
      dplyr::summarise(across(where(is.numeric), mean)) %>% 
      dplyr::summarise(across(where(is.numeric), sd)) %>% 
      sum()
    
    variance_scores <- append(variance_scores, score)
  }
  
  # Get the fold column with the lowest score
  lowest_fcol_index <- which.min(variance_scores)
  best_fcol <- fold_col_names[[lowest_fcol_index]]
  
  # Add the best fold column / grouping factor to the original data
  data[["group"]] <- data_std[[best_fcol]]
  
  # Return the original data and the score of the best fold column
  list(data, min(variance_scores))
  
}

# Run with 20 splits
set.seed(1)
data_grouped_and_score <- create_multi_balanced_groups(
  data = data,
  cols = c("A", "B", "C", "D", "E", "F", "G", "H"),
  k = 25,
  num_tries = 20
)

# Check data
data_grouped_and_score[[1]]

> # A tibble: 500 x 9
>         A     B     C     D     E      F     G     H group
>     <dbl> <dbl> <dbl> <dbl> <dbl>  <dbl> <dbl> <dbl> <fct>
>  1 5.86   6.54  0.500 2.88  5.70  9.67    2.29 3.01  2    
>  2 0.0895 4.69  5.71  0.343 8.95  7.73    5.76 9.58  1    
>  3 2.94   1.78  2.06  6.66  9.54  0.600   4.26 0.771 16   
>  4 2.77   1.52  0.723 8.11  8.95  1.37    6.32 6.24  7    
>  5 8.14   2.49  0.467 8.51  0.889 6.28    4.47 8.63  13   
>  6 2.60   8.23  9.17  5.14  2.85  8.54    8.94 0.619 23   
>  7 7.24   0.260 6.64  8.35  8.59  0.0862  1.73 8.10  5    
>  8 9.06   1.11  6.01  5.35  2.01  9.37    7.47 1.01  1    
>  9 9.49   5.48  3.64  1.94  3.24  2.49    3.63 5.52  7    
> 10 0.731  0.230 5.29  8.43  5.40  8.50    3.46 1.23  10   
> # … with 490 more rows

# Check score
data_grouped_and_score[[2]]

> 1.552656

通过注解掉num_col = "total"行,我们可以在没有数值平衡的情况下运行它。对我来说,这给出了1.615257的分数。
免责声明:我是groupdata2包的作者。fold()函数还可以平衡分类列(cat_col),并将所有ID相同的数据点保存在同一个文件夹(id_col)中(例如,避免交叉验证中的泄漏)。还有一个非常类似的partition()函数。

相关问题