【问题标题】:Excel VBA - N number of Random records per unique groupExcel VBA - 每个唯一组的 N 个随机记录
【发布时间】:2021-09-23 09:03:13
【问题描述】:

我正在开发一个系统健康检查工具来验证 4 个不同的系统是否同步。为此,我需要每天为来自主数据集中的每个唯一键/组合创建一个包含随机 N 条记录的样本数据集。将从该示例数据集中检查所有 4 个系统的记录,并使用条件格式突出显示任何差异。

我无法弄清楚如何使用上述标准从主数据集中提取样本数据集。

例如,我有一个包含 700 行的报表。 6 个字段 [Client-Contractor-Distribution Center-Service Level-Alert Value-Status] 的每个唯一组合(连接以创建一个键)有 100 条记录。这部分将是动态的。每个组合可以有任意数量的唯一组合和任意数量的记录。下图供参考。此处仅显示组,因为我无法在问题中粘贴 700 条记录。有 7 个不同的组,每个组有 100 条记录。

在 cmets 中存在一些问题,我在下面给出澄清: -组合/组=基本上是通过连接焦点列创建的键,以识别/定义记录可能属于的类别。例如连接名字和姓氏以创建一个人的唯一身份。

  • 所有记录都将在一张纸上。它是从系统下载的报告。
  • 每个分组的记录顺序:特定组的所有记录不会被捆绑在一起。所有记录都从系统中转储到报告中。我们通过连接焦点列来创建组/键。

假设我希望 7 个 GroupKey 中的每一个都有 5 个随机记录。本质上,我需要一种方法来获取随机选择的 35 条记录,每个唯一组合 5 条。 所需输出的示例如下所示。

我尝试过使用 RAND() 和 RANDBETWEEN() 公式。我确实得到随机记录。但问题是我无法确保每个组合获得 5 条记录,有时还会返回重复记录。我愿意接受任何方法(VBA/公式)来实现这一点。

对于像我这样最多只是 VBA 新手/初学者的人来说,这是一个非常复杂的问题。

【问题讨论】:

  • 你试过什么?仅更改您的问题标题不会为您提供答案。使用您的代码尝试编辑您的问题,解释问题所在,我们很乐意为您提供帮助
  • 您所说的“组”是在工作表上分组的吗?还是所有“组”元素都分布在所有这 700 行上?如果展开,您可以使用字典将它们简单地分组,并将特定列内容的串联作为键,并将每个项目行的数组作为项目。最后,您可以确定每个组包含多少个元素,创建一个随机数数组并将其与 rows 数组匹配,根据(随机)排名提取 5 个这样的行。
  • 那么,无法向我们显示 700 行,但您应该至少显示两个类别/组,它们是如何放置在要处理的工作表中的。如果每个组只有连续的行,那会容易一些,跳过分组的部分。如果您的图片还显示列标题也应该更好。我们是否应该认为它们在“A:G”范围内?
  • 第二组示例中是否有错误(客户 1 到 5,承包商 1 到 5 等等)?如果不是,那么唯一键是什么或选择它们?

标签: excel vba


【解决方案1】:

请测试下一个代码。它需要引用“Microsoft Scripting Runtime”:

Sub RandomRecPerGroup()
   Dim sh As Worksheet, shRet As Worksheet, lastR As Long, dict As New Scripting.Dictionary
   Dim arr, arrIt, i As Long, j As Long, f As Long, k As Long, count As Long, arrFin
   
   Set sh = ActiveSheet 'use here the sheet you need
   Set shRet = sh.Next  'use here the sheet you need (for testing reason, the next against the active one)
   shRet.Range("G1").EntireColumn.NumberFormat = "@"   'format the column to keep 'Reference number' as text
   lastR = sh.Range("A" & sh.rows.count).End(xlUp).row 'last row
   arr = sh.Range("A2:G" & lastR).Value ' place the range in an array for faster iteration
   ReDim arrFin(1 To 5, 1 To 7): k = 1  'reDim the array to keep each group
   For i = 1 To UBound(arr)             'iterate between the array elements:
        'create a dictionary key if not already existing, with the number of the row as item:
        If Not dict.Exists(arr(i, 1) & arr(i, 2) & arr(i, 3) & arr(i, 4) & arr(i, 5) & arr(i, 6)) Then
            dict.Add arr(i, 1) & arr(i, 2) & arr(i, 3) & arr(i, 4) & arr(i, 5) & arr(i, 6), i
        Else 'adding the number of row, separated by "|"
            dict(arr(i, 1) & arr(i, 2) & arr(i, 3) & arr(i, 4) & arr(i, 5) & arr(i, 6)) = _
                  dict(arr(i, 1) & arr(i, 2) & arr(i, 3) & arr(i, 4) & arr(i, 5) & arr(i, 6)) & "|" & i
        End If
   Next i
   Dim rndNo As Long              'a variable to receive the random number
   For i = 0 To dict.count - 1    'iterate between the dictionary elements:
        arrIt = Split(dict.items(i), "|"): ' split the item by "|" to obtain the same group existing rows
        For k = 1 To 5            'iterate to extract the 5 necessary sample rows of each group
                Randomize         'initialize the random numbers generation
                If UBound(arrIt) = -1 Then Exit For     'for the case of less than 5 rows per group
                rndNo = CLng(UBound(arrIt) * Rnd())     'give a value to the variable keeping the random numbers
                For f = 1 To 7                          'iterating to place in the array all 7 columns value
                    arrFin(k, f) = arr(arrIt(rndNo), f)
                Next f
                arrIt = Filter(arrIt, arrIt(rndNo), False) 'eliminate the element just placed in an array, to avoid doubling
        Next k
        lastR = shRet.Range("A" & sh.rows.count).End(xlUp).row + 1 'last empty row of the sheet where the result is returned
        shRet.Range("A" & lastR).Resize(5, 7).Value = arrFin       'drop the array content
   Next i
   MsgBox "Ready..."
End Sub

代码可能在没有提到的参考的情况下工作(使用 labe 绑定),但我认为受益于智能感知建议应该是件好事。如果创建它看起来很复杂,请(首先)运行下一个将自动添加它的代码:

Sub addScrRunTimeRef()
  'Add a reference to 'Microsoft Scripting Runtime':
  'In case of error ('Programmatic access to Visual Basic Project not trusted'):
  'Options->Trust Center->Trust Center Settings->Macro Settings->Developer Macro Settings->
  '         check "Trust access to the VBA project object model"
  On Error Resume Next
  Application.VBE.ActiveVBProject.References.AddFromFile "C:\Windows\SysWOW64\scrrun.dll"
  If err.Number = 32813 Then
        err.Clear: On Error GoTo 0
        MsgBox "The reference already exists...": Exit Sub
  Else
        On Error GoTo 0
        MsgBox """Microsoft Scripting Runtime"" reference added successfully..."
  End If
End Sub

保存工作簿将保留参考。因此,无需再次运行代码...

【讨论】:

  • 这成功了!!!但是,我有几个问题。通过阅读代码并尽可能多地理解,看起来这段代码只有在数据集包含 7 个唯一组时才能工作。那是对的吗?因为在实际应用中,可能有 500 到 200 个不同的组,每个组中有数千条记录。
  • @Achal Desai 不是真的...它适用于工作表上存在的许多组。代码中的 7 表示在每个组上构建五个随机选择的行时要连接的列。它处理前六列之间的所有唯一连接,并为每个组创建一个包含所有出现的行的数组。然后,它随机返回其中的五个。该代码还为最多出现次数少于五次的情况准备了...
  • @Achal Desai 但是,即使没有经验,我也想记住,我们在这里,当有人回答我们的问题时,请勾选代码左侧的复选框,以便接受答案。这样,搜索类似问题的其他人就会知道该代码有效...
【解决方案2】:

所以基本上你有 700 种可能性,你想从中得到 5 个随机值,同时你确定你没有重复?

基本上有两种方法可以做到这一点:

  • 您创建了一个随机值的结果集合,您使用随机生成器生成从 1 到 700 的数字,但在将它们添加到您的集合之前,您需要验证它们是否已经存在于您的集合中。类似(伪代码):

      Dim col_Result as Collection
      Dim finished as Boolean = False;
      Dim r as integer;
    
      while (not finished){
           r = ConvertToInt(Random(700)) + 1;
           if not(col_Result.Contains(r))
           then col_Result.Add(r);
           finished = (col_Result.Count == 5);
      }
    
  • 您创建了一个从 1 到 700 的所有数字的集合,并从中检索了 5 次随机值,同时从集合中减去该值。类似的东西(又是伪代码):

      Dim col_Values as Collection = (1, 2, ..., 700);
      Dim col_Result as Collection;
    
      Dim r as integer;
    
      for (int i = 0; i < 5; i++){
          r = ConvertToInt(Random(700));
          col_Result.Add(r);
          col_Values.Subtract(r);
      }
    

使用最后一种方法时,从集合中减去一个值会转移其他值,这一点至关重要:(1,2,3,4,5).Subtract(2) 产生(1,3,4,5)

【讨论】:

  • 我认为这应该是 VBA 代码。 :) 看来您错过了与现有“组”相关的要点...
  • @FaneDuru:我的回答只是对正确方向的提示,它并不意味着是真正的代码(因此我的“伪代码”评论:-))。不要忘记作者提出了这个问题,再次提出了这个问题,但没有表现出任何努力来实现某些东西(因此我选择提供伪代码,而不是真实代码)。
  • 这个我可以理解,但恐怕OP对任何编程语言都不太了解。否则,他应该提供自己的尝试,正如我在他第一次发布类似内容时所建议的那样......通常,我不会回答一个没有显示 OP 自己完成这项工作的最低努力的问题。现在,看到你的回答和看起来很有挑战性的问题(根据我的口味),我敢于回答...... :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-15
  • 1970-01-01
  • 1970-01-01
  • 2014-08-04
  • 2021-01-12
  • 2021-06-18
相关资源
最近更新 更多