【问题标题】:How to create a numpy array with a random entries that exclude one element in each index?如何创建一个随机条目的numpy数组,每个索引中排除一个元素?
【发布时间】:2020-03-20 15:12:38
【问题描述】:

我有一个包含可能值的数组val(例如val = [0, 1, 2, 3, 4, 5])和一个包含选定值的数组A(可能很长的列表)(例如A = [2, 3, 1, 0, 2, 1, ... , 2, 3, 1, 0, 4]

现在我想创建一个与A 长度相同的数组B,这样每个A[i]B[i]B[i] 不同,并且B 中的条目是随机选择的。如何使用 numpy 有效地做到这一点?

【问题讨论】:

  • 一种方法是先执行p=np.random.permutation(len(A)),然后执行B=A[p]。不过,这并不能保证每个 iA[i] != B[i]
  • A[i]!=B[i] 在我的情况下是必不可少的
  • val 是否总是按顺序排列的数字 0 to n 和连续数字?
  • @Divakar 实际上它们也可以是数组,但是有数字的解决方案就足够了。
  • 更具体地说,我的问题是这些数字是否总是按顺序排列的,或者您可以为val 提供类似[0,1,4,5,6,8,9] 的东西吗?

标签: python numpy random numpy-ndarray


【解决方案1】:

一种简单的方法是绘制 A 和 B 之间的差模 n,其中 n 是可能结果的数量。 A[i] != B[i] 表示这个差值不为零,因此我们从 1,...,n-1 开始:

n,N = 10,100
A = np.random.randint(0,n,N)

D = np.random.randint(1,n,N)
B = (A-D)%n

更新:虽然可以说是优雅的,但这个解决方案并不是最快的。我们可以通过将(慢)模运算符替换为仅测试负值并将 n 添加到它们来节省一些时间。

在这种形式下,这个解决方案开始看起来与@Divakar 的非常相似:两个可能的值块,一个需要移动。

但我们可以做得更好:我们可以只在 A[i] == B[i] 时才将它们换出,而不是平均移动一半的值。由于预计这种情况很少发生,除非允许的值列表非常短,因此代码运行得更快:

B = np.random.randint(1,n,N)
B[B==A] = 0

【讨论】:

    【解决方案2】:

    这有点浪费,因为它为A 中的每个项目创建了一个临时列表,但在其他方面满足了您的要求:

    from random import choice
    
    
    val = [0, 1, 2, 3, 4, 5]
    A = [2, 3, 1, 0, 2, 1, 2, 3, 1, 0, 4]
    
    val = set(val)
    B = [choice(list(val - {x})) for x in A]
    print(B) # -> [4, 2, 3, 2, 5, 4, 1, 5, 5, 4, 1]
    

    简而言之:

    发生的情况是val 被转换为setA 中的 current 项被从中删除。因此,从这个结果子集中随机选择一个项目并添加到B


    您也可以使用以下方法对其进行测试:

    print(all(x!=y for x, y in zip(A, B)))
    

    当然返回True


    最后,请注意上述方法仅适用于可散列项。因此,如果您可能有类似 val = [[1, 2], [2, 3], ..] 之类的内容,您将会遇到问题。

    【讨论】:

    • @JohanC 我不确定我是否同意。话虽如此,我确实相信有基于numpy 的解决方案可以更快
    【解决方案3】:

    这是一种矢量化方式 -

    def randnum_excludeone(A, val):
        n = val[-1]
        idx = np.random.randint(0,n,len(A))
        idx[idx>=A] += 1
        return idx
    

    我们的想法是我们为A 中的每个条目生成随机整数,覆盖val 减去1 的整个长度。然后,如果当前生成的随机数等于或大于当前A元素,我们添加1,否则我们保留它。因此,对于生成的任何小于当前A 数字的随机数,我们保留它。否则,加上1,我们将从当前的A 数字偏移。这是我们的最终输出 - idx

    让我们验证随机性并确保它在非 A 元素之间是一致的 -

    In [42]: A
    Out[42]: array([2, 3, 1, 0, 2, 1, 2, 3, 1, 0, 4])
    
    In [43]: val
    Out[43]: array([0, 1, 2, 3, 4, 5])
    
    In [44]: c = np.array([randnum_excludeone(A, val) for _ in range(10000)])
    
    In [45]: [np.bincount(i) for i in c.T]
    Out[45]: 
    [array([2013, 2018,    0, 2056, 1933, 1980]),
     array([2018, 1985, 2066,    0, 1922, 2009]),
     array([2032,    0, 1966, 1975, 2040, 1987]),
     array([   0, 2076, 1986, 1931, 2013, 1994]),
     array([2029, 1943,    0, 1960, 2100, 1968]),
     array([2028,    0, 2048, 2031, 1929, 1964]),
     array([2046, 2065,    0, 1990, 1940, 1959]),
     array([2040, 2003, 1935,    0, 2045, 1977]),
     array([2008,    0, 2011, 2030, 1937, 2014]),
     array([   0, 2000, 2015, 1983, 2023, 1979]),
     array([2075, 1995, 1987, 1948,    0, 1995])]
    

    大型阵列基准测试

    其他矢量化方法:

    # @Paul Panzer's solution
    def pp(A, val):
        n,N = val[-1]+1,len(A)    
        D = np.random.randint(1,n,N)
        B = (A-D)%n
        return B
    

    计时结果-

    In [66]: np.random.seed(0)
        ...: A = np.random.randint(0,6,100000)
    
    In [67]: %timeit pp(A,val)
    100 loops, best of 3: 3.11 ms per loop
    
    In [68]: %timeit randnum_excludeone(A, val)
    100 loops, best of 3: 2.53 ms per loop
    
    In [69]: np.random.seed(0)
        ...: A = np.random.randint(0,6,1000000)
    
    In [70]: %timeit pp(A,val)
    10 loops, best of 3: 39.9 ms per loop
    
    In [71]: %timeit randnum_excludeone(A, val)
    10 loops, best of 3: 25.9 ms per loop
    

    val的范围扩大到10 -

    In [60]: np.random.seed(0)
        ...: A = np.random.randint(0,10,1000000)
    
    In [61]: %timeit pp(A,val)
    10 loops, best of 3: 31.2 ms per loop
    
    In [62]: %timeit randnum_excludeone(A, val)
    10 loops, best of 3: 23.6 ms per loop
    

    【讨论】:

    • @JohanC 不,它独立于A 的分布。 B 根据问题的要求必须与 A 不同,这是这样做的。我误解了您的问题吗?
    • 避免逻辑索引,而是简单地将掩码添加到 idx 似乎快了几个百分点。事实上,随着这种变化,你和我的方法在我的笔记本电脑上并驾齐驱;-)
    • @PaulPanzer 现有的似乎要好很多。你的设置是什么?
    • @PaulPanzer 并且只需添加掩码似乎要好 2 倍以上。
    • @Divakar 你是对的,我只测试了一个小的(100 个条目)示例,你的方法可以更好地扩展。但是我关于避免花哨的索引的观点仍然存在。在我的笔记本电脑上,返回 idx + (idx>=a) 可使 1,000,000 数组的速度提高 25%。
    【解决方案4】:

    快速而肮脏,可以进行改进,但是就这样吧。 您的要求可以实现如下:

    val = [0, 1, 2, 3, 4, 5]
    A = [2, 3, 1, 0, 2, 1,4,4, 2, 3, 1, 0, 4]
    val_shifted = np.roll(val,1) 
    dic_val = {i:val_shifted[i] for i in range(len(val_shifted))}
    B = [dic_val[i] for i in A]
    

    哪个给出了满足您要求的结果

    A = [2, 3, 1, 0, 2, 1, 4, 4, 2, 3, 1, 0, 4]
    B = [1, 2, 0, 5, 1, 0, 3, 3, 1, 2, 0, 5, 3]
    

    【讨论】:

      【解决方案5】:

      这是另一种方法。 B 首先得到A 的随机洗牌。然后,AB 重叠的所有值都会被打乱。在所有重叠元素具有相同值的特殊情况下,它们会被随机良好的值交换。

      这种方法的有趣之处在于,当A 仅包含一组非常有限的不同值时,它也可以工作。与其他方法不同,BA 的精确洗牌,因此当A 没有均匀分布时它也可以工作。此外,B 是一个完全随机的随机播放,除了要求在相同的索引处不同。

      import random
      N = 10000
      A = [random.randrange(0,6) for _ in range(N)]
      B = a.copy()
      random.shuffle(b)
      print(A)
      print(B)
      
      while True:
          equal_vals = {i for i,j in zip(A, B) if i == j}
          print(len(equal_vals), equal_vals)
          if len(equal_vals) == 0:  # finished, no equal values on same positions
              break
          else:
              equal_ind = [k for k, (i, j) in enumerate(zip(A, B)) if i == j]
              # create a list of indices where A and B are equal
              random.shuffle(equal_ind)  # as the list was ordened, shuffle it to get a random order
      
              if len(equal_vals) == 1:  # special case, all equal indices have the same value
                  special_val = equal_vals.pop()
                  # find all the indices where the special_val could be placed without problems
                  good_ind = [k for k,(i,j) in enumerate(zip(A, B)) if i != special_val and j != special_val]
                  if len(good_ind) < len(equal_ind):
                      print("problem: there are too many equal values in list A")
                  else:
                      # swap each bad index with a random good index
                      chosen_ind = random.sample(good_ind, len(equal_ind))
                      for k1, k2 in zip(equal_ind, chosen_ind):
                          b[k1], b[k2] = b[k2], b[k1]  # swap
                  break
              elif len(equal_vals) >= 2:
      
                  # permute B via the lis of equal indices;
                  #   as there are at least 2 different values, at least two indices will get a desired value
                  prev = equal_ind[0]
                  old_first = B[prev]
                  for k in equal_ind[1:]:
                      B[prev] = B[k]
                      prev = k
                  B[prev] = old_first
      
      print(A)
      print(B)
      

      【讨论】:

        猜你喜欢
        • 2022-07-29
        • 2019-04-01
        • 2021-02-07
        • 2012-01-05
        • 2014-03-06
        • 2017-07-07
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多