在R中将数据分成最小和最大大小的组。

6

我想要将一组值(其中某些是重复的)随机分配到较少数量的组中,每个组至少分配两个值,但不超过四个值,并且同一个值不能被分配到同一个组。

示例数据:

values <- c(2499,2499,2522,2522,2522,2522,2648,2648,2652,2652,2670,2670,2689,2689,2690,2690,2693,2693,2700,2700,2706,2706,2714,2714,2730,2730,2738,2738,2740,2740,2765,2765,2768,2768,2773,2773,2783,2783,2794,2794,2798,2798,2807,2807,2812,2812,2831,2831,2831,2835,2835,2836,2836,2836,2844,2844,2844,2846,2846,2846,2883,2883,2964,2964)

groups <- 1:26

我试过了。
split(values, sample(groups, length(values), repl = TRUE))

这接近我想要的。但有时候这会导致只有一个值被分配给一个组,或者超过四个值。而且有时候相同的值(其中一个重复项)会被分配到同一组中。

期望的输出是将所有的值随机分布到各个组中,以确保每个组中的值都是唯一的(没有重复项),并且每个组中的值最少为两个,最多为四个。


这是一个多步骤的过程。首先,您需要删除重复项,然后选择第一组并从列表中删除这些值。然后,您需要将重复项添加回列表,并重复以上步骤。 - Dave2e
4个回答

3
为了随机分配组大小,我们的想法是从每个组ID的“库存”(在下面的函数中表示为size.max)开始。由于每个组至少要有2个成员(size.min),我们从库存中取出每个组ID的2个成员,并将它们放入与其ID对应的桶中。然后,我们从库存中随机抽取,直到桶中的项目总数等于length(values)。这可以通过使用sampletabulate非常快速地完成。
如果 R 是从 values 中每个唯一值的计数向量,S 是组大小的随机向量,则将 values 随机分配到组中,每个组中没有重复值的随机二进制矩阵与具有固定行和列和(RS)相对应,其中第 i 行和第 j 列的 1 表示第 i 个唯一值属于第 j 个组。它也对应于一个没有多重边或环边的随机二分图。来自 igraph 包的 sample_degseq 函数生成这些随机二分图。
library(igraph) # for sample_degseq

rsplit <- function(values, groups, size.min, size.max) {
  R <- table(values)
  S <- tabulate(
    sample(
      rep(groups, size.max - size.min),
      length(values) - size.min*length(groups)
    ),
    length(groups)
  ) + size.min

  d <- length(R) - length(S)
  
  with(
    as_data_frame(
      sample_degseq( # randomly assign values to groups
        c(R, integer(max(0, -d))),
        c(S, integer(max(0, d))),
        "simple.no.multiple.uniform"
      )
    ),
    split(as(names(R), class(values))[from], groups[to])
  )
}

在示例数据上运行它:

rsplit(values, groups, 2, 4)
#> $`1`
#> [1] 2846 2522
#> 
#> $`2`
#> [1] 2794 2964
#> 
#> $`3`
#> [1] 2773 2714
#> 
#> $`4`
#> [1] 2783 2740
#> 
#> $`5`
#> [1] 2693 2499 2844
#> 
#> $`6`
#> [1] 2846 2648
#> 
#> $`7`
#> [1] 2522 2812
#> 
#> $`8`
#> [1] 2798 2831
#> 
#> $`9`
#> [1] 2652 2690 2964 2670
#> 
#> $`10`
#> [1] 2738 2835 2844 2652
#> 
#> $`11`
#> [1] 2700 2670
#> 
#> $`12`
#> [1] 2499 2765
#> 
#> $`13`
#> [1] 2648 2846
#> 
#> $`14`
#> [1] 2807 2773 2690 2689
#> 
#> $`15`
#> [1] 2700 2883
#> 
#> $`16`
#> [1] 2883 2835 2812
#> 
#> $`17`
#> [1] 2807 2765
#> 
#> $`18`
#> [1] 2768 2844
#> 
#> $`19`
#> [1] 2706 2836
#> 
#> $`20`
#> [1] 2714 2522
#> 
#> $`21`
#> [1] 2706 2836
#> 
#> $`22`
#> [1] 2794 2836
#> 
#> $`23`
#> [1] 2738 2783
#> 
#> $`24`
#> [1] 2740 2693 2522 2730
#> 
#> $`25`
#> [1] 2689 2831
#> 
#> $`26`
#> [1] 2768 2831 2798 2730

非常感谢您的帮助。两个答案都很有效。partitionsSample和sample_degseq函数可能有些晦涩,但也非常有用。再次感谢。 - James Daniel Johnston
1
有趣的igraph实现,加一! - ThomasIsCoding
@JamesDanielJohnston,我更新了抽样算法,我认为这是一个更好的算法,并且不使用RcppAlgos - jblood94
1
@ThomasIsCoding,实际上是从你那里得到了这个想法 - jblood94

1

根据您的设定的值和组,我从中抽取2至4个不重复的值,检查是否至少有2个值,若不满足条件则重新抽取直至满足要求,并返回一个以组名命名的列表。

library(tidyverse)
groups <- 1:26
map(
  .x = groups,
  .f = ~{
    # Sample variable length group size 2:4
    res <- sample(values, sample(2:4, 1), replace = TRUE)
    # remove duplicates
    res <- res[!duplicated(res)]
    # check for if length of no dups res is less than 2, if so, resample, repeat above
    while(length(res) < 2){
      res <- sample(values, sample(2:4, 1), replace = TRUE)
      res <- res[!duplicated(res)]
    }
    res 
  } 
) %>% # List with groups as names
  set_names(., groups)

1

想法

我认为你的问题涉及两个任务:随机分组大小和每个组内数值的唯一性。让我们深入了解一下。

1) 随机分组大小

首先,从数学角度来看,由于每个组都有相同的大小要求,即min=2max=4,可以分析出平均组大小肯定在这个区间内。

在这种情况下,一种贪婪且简单的思路是使所有组的大小尽可能接近。换句话说:

  1. 我们首先为每个组分配一个大小,以便所有组都能均匀地拥有相同的最大大小。
  2. 关于剩余的大小预算,我们通过所有组进行随机分配。

2) 每个组内数值的唯一性

鉴于随机组大小,下一步是根据大小分割向量values,并保持每个组内数值的唯一性。关键在于处理重复项。实际上,我们可以对重复项进行哈希处理,并尽可能远离彼此移动。我们可以尝试以下做法:
  1. 对数值(包括重复项)进行哈希和重新排序。
  2. 按组大小进行迭代,将重新排序后的数值分配给各组。

一个基本的R实现

f <- function(val, grp) {
    val <- sort(val)
    # min average size of each group
    p <- length(val) %/% length(grp)
    # number of remaining items to be assiggned
    q <- length(val) %% length(grp)
    # distribution of group sizes, by randomly assigning the remaining items
    g <- p + replace(rep(0, length(grp)), sample(seq_along(grp), q), 1)
    # split by given group sizes
    split(
        # reorder val
        unname(unlist(split(val, ave(val, val, FUN = seq_along)))),
        rep(grp, g)
    )
}
< p >和我们会看到< /p >
> f(values, groups)
$`1`
[1] 2499 2522 2648

$`2`
[1] 2652 2670

$`3`
[1] 2689 2690

$`4`
[1] 2693 2700

$`5`
[1] 2706 2714

$`6`
[1] 2730 2738

$`7`
[1] 2740 2765 2768

$`8`
[1] 2773 2783

$`9`
[1] 2794 2798 2807

$`10`
[1] 2812 2831 2835

$`11`
[1] 2836 2844

$`12`
[1] 2846 2883 2964

$`13`
[1] 2499 2522

$`14`
[1] 2648 2652

$`15`
[1] 2670 2689

$`16`
[1] 2690 2693

$`17`
[1] 2700 2706 2714

$`18`
[1] 2730 2738 2740

$`19`
[1] 2765 2768

$`20`
[1] 2773 2783

$`21`
[1] 2794 2798 2807

$`22`
[1] 2812 2831 2835

$`23`
[1] 2836 2844 2846

$`24`
[1] 2883 2964

$`25`
[1] 2522 2831 2836

$`26`
[1] 2844 2846 2522

此外,如果你想检查每个组的唯一性,可以尝试。
> any(sapply(f(values, groups), anyDuplicated))
[1] FALSE

每个组中都没有重复项。

看起来组的大小有些随机(14个2和12个3随机分布),但是分配没有随机性。第一组总是c(2499, 2522)或者c(2499, 2522, 2648)。它似乎也不能保证组内的唯一性。试试在values <- c(values, rep(3e3, 5))上运行。 - jblood94
@jblood94 你说得对,这并不是一个“真正”的随机分组。正如我在回答中提到的,这是一种贪婪的方法,目的是使所有的小组大小几乎相同,而随机性来自剩余物品的随机分配。如果更喜欢混乱一些,我们总是可以重新洗牌结果的小组(包括每个小组的值和小组的顺序)。 - ThomasIsCoding

0
以下是一种更“老派”的方式(仅使用基本 R 和循环)来解决问题:
  1. 生成随机的组(或分区)大小,同时受到大小下限和上限的限制。
  2. values 的重复项随机分配给不同的分区(以避免每个分区内的重复项),顺序进行,直到所有条目都被分配完。
请注意,这个实现只是一个示例,用于展示其工作原理,但并未经过优化,在扩展时可能效率较低。
代码示例:
f <- function(values, groups, szmin = 2, szmax = 4) {
    # a helper function to create random partitions
    randgrp <- function(n, k, lb = szmin, ub = szmax) {
        res <- c()
        repeat {
            if (k == 0) {
                return(res)
            }
            v <- max(lb, n - (k - 1) * ub):min(ub, n - (k - 1) * lb)
            m <- v[sample(length(v), 1)]
            res <- append(res, m)
            n <- n - m
            k <- k - 1
        }
    }
    # group values
    vlst <- split(values, values)
    repeat {
        grp <- randgrp(length(values), length(groups))
        ids <- c()
        for (i in seq_along(vlst)) {
            p <- c()
            v <- vlst[[i]]
            for (j in seq_along(grp)) {
                if (grp[j] > 0) {
                    p <- append(p, j)
                    grp[j] <- grp[j] - 1
                }
                if (length(p) == length(v)) {
                    break
                }
            }
            ids <- append(ids, p)
        }
        if (length(ids) == length(values)) {
            break
        }
    }
    split(unname(unlist(vlst)), groups[ids])
}

我们可以得到类似的东西
> res 
$`1`
[1] 2499 2522 2648 2652

$`2`
[1] 2499 2522

$`3`
[1] 2522 2648

$`4`
[1] 2522 2652 2670

$`5`
[1] 2670 2689

$`6`
[1] 2689 2690

$`7`
[1] 2690 2693 2700

$`8`
[1] 2693 2700

$`9`
[1] 2706 2714

$`10`
[1] 2706 2714 2730 2738

$`11`
[1] 2730 2738 2740

$`12`
[1] 2740 2765

$`13`
[1] 2765 2768

$`14`
[1] 2768 2773

$`15`
[1] 2773 2783 2794

$`16`
[1] 2783 2794

$`17`
[1] 2798 2807

$`18`
[1] 2798 2807 2812

$`19`
[1] 2812 2831 2835 2836

$`20`
[1] 2831 2835

$`21`
[1] 2831 2836

$`22`
[1] 2836 2844 2846

$`23`
[1] 2844 2846

$`24`
[1] 2844 2846

$`25`
[1] 2883 2964

$`26`
[1] 2883 2964

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