Python 更改numpy数组中一致值的比例

Python 更改numpy数组中一致值的比例,python,arrays,numpy,Python,Arrays,Numpy,我有个问题一直在想。假设我有一个如下所示的numpy数组(在实际实现中,len(array)大约为4500): 由此,我尝试生成一个新的(无序)数组,其中随机符合array的值的比例是一个特定的比例p。那么让我们假设p=.5。然后,一个新的数组示例如下 array = [0, 0, 1, 1, 2, 2] new_array = [0, 1, 2, 1, 0, 2] 您可以看到new_array中正好有50%的值与array中的值一致。输出阵列的要求如下: np.count\u非零(数

我有个问题一直在想。假设我有一个如下所示的numpy数组(在实际实现中,
len(array)
大约为4500):

由此,我尝试生成一个新的(无序)数组,其中随机符合
array
的值的比例是一个特定的比例
p
。那么让我们假设
p=.5
。然后,一个新的数组示例如下

    array = [0, 0, 1, 1, 2, 2]
new_array = [0, 1, 2, 1, 0, 2]
您可以看到
new_array
中正好有50%的值与
array
中的值一致。输出阵列的要求如下:

np.count\u非零(数组-新数组)/len(数组)=p
,和
set(np.unique(数组))==set(np.unique(新数组))

我所说的“同意”是指同意索引的
array[I]==new\u array[I]
new_数组
中的所有值应与
数组
中的值相同,只是乱序而已

我相信有一种优雅的方式可以做到这一点——有人能想出什么吗


谢谢

您可以尝试以下方法

    array = [0, 0, 1, 1, 2, 2]
new_array = [0, 1, 2, 1, 0, 2]
随机导入
p=0.5
arr=np.数组([0,0,1,1,2,2])
#所需的类似元件数量
num_sim_元素=圆形(len(arr)*p)
#创建相似元素的索引
hp={}
对于枚举中的i,e(arr):
如果(hp中的e):
hp[e].附加(i)
其他:
hp[e]=[i]
#打印(hp)
out_map=[]
k=列表(hp.keys())
v=列表(hp.values())
索引=0
while(len(out\u map)!=num\u sim\u元素):
如果(len(v[index])>0:
k_uk=k[索引]
随机。随机(v[索引])
v_uv=v[index].pop()
out_map.append((k_,v_))
指数+=1
指数%=len(k)
#打印(输出地图)
out\u unique=set([i[0]表示i in-out\u映射])
out_索引=[i[-1]表示i in-out_映射]
out_arr=arr.copy()
#对于输入输出图:
#out_arr[i[-1]]=i[0]
对于集合中的i(范围(len(arr))。差异(out_指数):
out\u arr[i]=random.choice(列表(out\u unique.difference([out\u arr[i]]))
打印(arr)
打印(输出)
断言1-(np.count\u nonzero(arr-out\u arr)/len(arr))==p
断言集(np.unique(arr))==集(np.unique(out_arr))

以下是一个可能更容易理解的版本:

import math, random

# generate array of random values
a = np.random.rand(4500)

# make a utility list of every position in that array, and shuffle it
indices = [i for i in range(0, len(a))]
random.shuffle(indices)

# set the proportion you want to keep the same
proportion = 0.5

# make two lists of indices, the ones that stay the same and the ones that get shuffled
anchors = indices[0:math.floor(len(a)*proportion)]
not_anchors = indices[math.floor(len(a)*proportion):]

# get values of non-anchor indices, and shuffle them
not_anchor_values = [a[i] for i in not_anchors]
random.shuffle(not_anchor_values)

# loop original array, if an anchor position, keep original value
# if not an anchor, draw value from shuffle non-anchor value list and increment the count 
final_list = []
count = 0
for e,i in enumerate(a):
    if e in not_anchors:
        final_list.append(i)
    else:
        final_list.append(not_anchor_values[count])
        count +=1

# test proportion of matches and non-matches in output

match = []
not_match = []
for e,i in enumerate(a):
    if i == final_list[e]:
        match.append(True)
    else:
        not_match.append(True)
len(match)/(len(match)+len(not_match))
代码中的注释解释了该方法。

编辑了,以包含不同且更准确的方法)

需要注意的是,并非所有的混洗分数
p
(混洗元素数除以元素总数)的值都是可访问的。
p
的可能值取决于输入的大小和重复元素的数量

尽管如此,我可以提出两种可能的方法:

  • 将输入拆分为大小正确的固定索引和未固定索引,然后将未固定索引混洗
  • 将numpy导入为np
    def部分_混洗(arr,p=1.0):
    n=阵列尺寸
    k=圆形(n*p)
    洗牌=np.arange(n)
    洗牌=np.random.choice(n,k,replace=False)
    洗牌[洗牌]=np.排序(洗牌)
    返回arr[洗牌]
    
    方法(1)的主要优点是,它可以很容易地以矢量化的形式实现,并使用高级索引。 另一方面,只要您愿意接受由于重复值或仅仅因为混洗索引意外地与未混洗的索引重合,某些混洗可能会返回一些未混洗的元素,这一点就行得通。 这会导致
    p
    的请求值通常大于观察到的实际值。 如果需要相对更精确的
    p
    ,可以尝试搜索
    p
    参数,在输出上给出所需的值,或者进行反复试验

  • 实现一个变体,其中您:(a)拒绝交换值相同的位置,(b)只选择随机位置交换尚未访问的位置
  • def partial_shuffle_eff(arr,p=1.0,inplace=False,trys=2.0):
    如果不到位:
    arr=arr.copy()
    n=阵列尺寸
    k=圆形(n*p)
    尝试=圆形(n*次尝试)
    seen=set()
    i=l=t=0
    当i
    虽然这种方法得到了更准确的
    p
    ,但它仍然受到以下事实的限制:掉期的目标数量必须是偶数。 此外,对于具有大量唯一性的输入,第二个
    while
    while j in seen…
    )可能是一个无限循环,因此应设置尝试次数的上限。 最后,您需要使用显式循环,从而导致执行速度大大降低,除非您可以使用Numba的JIT编译,这将显著加快执行速度

    import numba as nb
    
    
    partial_shuffle_eff_nb = nb.njit(partial_shuffle_eff)
    partial_shuffle_eff_nb.__name__ = 'partial_shuffle_eff_nb'
    

    为了测试部分洗牌的准确性,我们可以使用(百分比):

    def-hamming_距离(a,b):
    断言(a.shape==b.shape)
    返回np.count_非零(a==b)
    def哈明距离百分比(a,b):
    返回汉明顿距离(a,b)/len(a)
    def混洗分数(a、b):
    返回1%的哈明距离(a,b)
    
    我们可以观察到类似的行为:

    funcs = (
        partial_shuffle,
        partial_shuffle_eff,
        partial_shuffle_eff_nb
    )
    
    n = 12
    m = 3
    arrs = (
        np.zeros(n, dtype=int),
        np.arange(n),
        np.repeat(np.arange(m), n // m),
        np.repeat(np.arange(3), 2),
        np.repeat(np.arange(3), 3),
    )
    
    np.random.seed(0)
    for arr in arrs:
        print(" " * 24, arr)
        for func in funcs:
            shuffled = func(arr, 0.5)
            print(f"{func.__name__:>24s}", shuffled, shuffling_fraction(arr, shuffled))
    #                          [0 0 0 0 0 0 0 0 0 0 0 0]
    #          partial_shuffle [0 0 0 0 0 0 0 0 0 0 0 0] 0.0
    #      partial_shuffle_eff [0 0 0 0 0 0 0 0 0 0 0 0] 0.0
    #   partial_shuffle_eff_nb [0 0 0 0 0 0 0 0 0 0 0 0] 0.0
    #                          [ 0  1  2  3  4  5  6  7  8  9 10 11]
    #          partial_shuffle [ 0  8  2  3  6  5  7  4  9  1 10 11] 0.5
    #      partial_shuffle_eff [ 3  8 11  0  4  5  6  7  1  9 10  2] 0.5
    #   partial_shuffle_eff_nb [ 9 10 11  3  4  5  6  7  8  0  1  2] 0.5
    #                          [0 0 0 0 1 1 1 1 2 2 2 2]
    #          partial_shuffle [0 0 2 0 1 2 1 1 2 2 1 0] 0.33333333333333337
    #      partial_shuffle_eff [1 1 1 0 0 1 0 0 2 2 2 2] 0.5
    #   partial_shuffle_eff_nb [1 2 1 0 1 0 0 1 0 2 2 2] 0.5
    #                          [0 0 1 1 2 2]
    #          partial_shuffle [0 0 1 1 2 2] 0.0
    #      partial_shuffle_eff [1 1 0 0 2 2] 0.6666666666666667
    #   partial_shuffle_eff_nb [1 2 0 1 0 2] 0.6666666666666667
    #                          [0 0 0 1 1 1 2 2 2]
    #          partial_shuffle [0 0 1 1 0 1 2 2 2] 0.2222222222222222
    #      partial_shuffle_eff [0 1 2 1 0 1 2 2 0] 0.4444444444444444
    #   partial_shuffle_eff_nb [0 0 1 0 2 1 2 1 2] 0.4444444444444444
    
    或者,对于更接近您的用例的输入:

    n = 4500
    m = 3
    arr = np.repeat(np.arange(m), n // m)
    
    np.random.seed(0)
    for func in funcs:
        shuffled = func(arr, 0.5)
        print(f"{func.__name__:>24s}", shuffling_fraction(arr, shuffled))
    #          partial_shuffle 0.33777777777777773
    #      partial_shuffle_eff 0.5
    #   partial_shuffle_eff_nb 0.5
    

    最后是一些小型基准测试:

    n = 4500
    m = 3
    arr = np.repeat(np.arange(m), n // m)
    
    np.random.seed(0)
    for func in funcs:
        print(f"{func.__name__:>24s}", end=" ")
        %timeit func(arr, 0.5)
    #          partial_shuffle  213 µs ± 6.36 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    #      partial_shuffle_eff  10.9 ms ± 194 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    #   partial_shuffle_eff_nb 172 µs ± 1.79 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    

    基于索引的同意?你所说的同意是什么意思?这有点含糊不清。@wwii中的“同意”,我的意思是
    array[I]==new_array[I]
    @Polkaguy6000是的。这很有趣,但我看到了一个问题,如果你有数组
    [1,1,1,2]
    和所需的匹配比.25,你的输出不可能是
    [2,1,1,1]
    (a.5匹配)或
    [1,1,2]
    (1.0匹配)。Sor
    n = 4500
    m = 3
    arr = np.repeat(np.arange(m), n // m)
    
    np.random.seed(0)
    for func in funcs:
        print(f"{func.__name__:>24s}", end=" ")
        %timeit func(arr, 0.5)
    #          partial_shuffle  213 µs ± 6.36 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    #      partial_shuffle_eff  10.9 ms ± 194 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    #   partial_shuffle_eff_nb 172 µs ± 1.79 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)