【问题标题】:How to replace a list of values in a numpy array?如何替换numpy数组中的值列表?
【发布时间】:2018-01-25 21:01:21
【问题描述】:

我有一个未排序的数字数组。

我需要用特定的替代品(也在相应的列表中给出)替换某些数字(在列表中给出)

我编写了以下代码(似乎有效):

import numpy as np

numbers = np.arange(0,40)
np.random.shuffle(numbers)
problem_numbers = [33, 23, 15]  # table, night_stand, plant
alternative_numbers = [12, 14, 26]  # desk, dresser, flower_pot

for i in range(len(problem_numbers)):
    idx = numbers == problem_numbers[i]
    numbers[idx] = alternative_numbers[i]

但是,这似乎非常低效(对于更大的数组,这需要完成数百万次)。

我发现this 问题回答了类似的问题,但在我的情况下,数字没有排序,它们需要保持原来的位置。

注意:numbers 可能在problem_numbers 中包含多个或不包含多个元素

【问题讨论】:

  • 为什么要编码idx = numbers == problem_numbers[i]problem_numbers 需要在数字范围内吗?
  • @itzikBenShabat 只是另一个问题:您是否想要级联替换,例如,如果问题编号是 [1, 3] 而替代方案是 [3, 5],您是否想要 numbers 中的 1 3 之后还是 5?您的解决方案将其转换为 5,而非级联替换将其替换为 3
  • 不需要级联。这不是我的数据的可能场景
  • @itzikBenShabat 我们可以假设problem_numbers 中的唯一数字吗?
  • @Divakar 是的。我们可以假设 problem_numbers 和 Alternative_numbers 中的唯一数字,但不能在数字中

标签: python arrays performance numpy


【解决方案1】:

编辑:我在 this answer 中实现了一个 TensorFlow 版本(几乎完全相同,除了替换是一个字典)。


这是一个简单的方法:

import numpy as np

numbers = np.arange(0,40)
np.random.shuffle(numbers)
problem_numbers = [33, 23, 15]  # table, night_stand, plant
alternative_numbers = [12, 14, 26]  # desk, dresser, flower_pot

# Replace values
problem_numbers = np.asarray(problem_numbers)
alternative_numbers = np.asarray(alternative_numbers)
n_min, n_max = numbers.min(), numbers.max()
replacer = np.arange(n_min, n_max + 1)
# Mask replacements out of range
mask = (problem_numbers >= n_min) & (problem_numbers <= n_max)
replacer[problem_numbers[mask] - n_min] = alternative_numbers[mask]
numbers = replacer[numbers - n_min]

只要numbers 中的值范围(最小和最大之间的差异)不是很大(例如,您没有类似1、@987654325 @ 和 10000000000)。

基准测试

我已经将 OP 中的代码与三个(截至目前)建议的解决方案进行了比较:

import numpy as np

def method_itzik(numbers, problem_numbers, alternative_numbers):
    numbers = np.asarray(numbers)
    for i in range(len(problem_numbers)):
        idx = numbers == problem_numbers[i]
        numbers[idx] = alternative_numbers[i]
    return numbers

def method_mseifert(numbers, problem_numbers, alternative_numbers):
    numbers = np.asarray(numbers)
    replacer = dict(zip(problem_numbers, alternative_numbers))
    numbers_list = numbers.tolist()
    numbers = np.array(list(map(replacer.get, numbers_list, numbers_list)))
    return numbers

def method_divakar(numbers, problem_numbers, alternative_numbers):
    numbers = np.asarray(numbers)
    problem_numbers = np.asarray(problem_numbers)
    problem_numbers = np.asarray(alternative_numbers)
    # Pre-process problem_numbers and correspondingly alternative_numbers
    # such that repeats and no matches are taken care of
    sidx_pn = problem_numbers.argsort()
    pn = problem_numbers[sidx_pn]
    mask = np.concatenate(([True],pn[1:] != pn[:-1]))
    an = alternative_numbers[sidx_pn]

    minN, maxN = numbers.min(), numbers.max()
    mask &= (pn >= minN) & (pn <= maxN)

    pn = pn[mask]
    an = an[mask]

    # Pre-pocessing done. Now, we need to use pn and an in place of
    # problem_numbers and alternative_numbers repectively. Map, index and assign.
    sidx = numbers.argsort()
    idx = sidx[np.searchsorted(numbers, pn, sorter=sidx)]
    valid_mask = numbers[idx] == pn
    numbers[idx[valid_mask]] = an[valid_mask]

def method_jdehesa(numbers, problem_numbers, alternative_numbers):
    numbers = np.asarray(numbers)
    problem_numbers = np.asarray(problem_numbers)
    alternative_numbers = np.asarray(alternative_numbers)
    n_min, n_max = numbers.min(), numbers.max()
    replacer = np.arange(n_min, n_max + 1)
    # Mask replacements out of range
    mask = (problem_numbers >= n_min) & (problem_numbers <= n_max)
    replacer[problem_numbers[mask] - n_min] = alternative_numbers[mask]
    numbers = replacer[numbers - n_min]
    return numbers

结果:

import numpy as np

np.random.seed(100)

MAX_NUM = 100000
numbers = np.random.randint(0, MAX_NUM, size=100000)
problem_numbers = np.unique(np.random.randint(0, MAX_NUM, size=500))
alternative_numbers = np.random.randint(0, MAX_NUM, size=len(problem_numbers))

%timeit method_itzik(numbers, problem_numbers, alternative_numbers)
10 loops, best of 3: 63.3 ms per loop

# This method expects lists
problem_numbers_l = list(problem_numbers)
alternative_numbers_l = list(alternative_numbers)
%timeit method_mseifert(numbers, problem_numbers_l, alternative_numbers_l)
10 loops, best of 3: 20.5 ms per loop

%timeit method_divakar(numbers, problem_numbers, alternative_numbers)
100 loops, best of 3: 9.45 ms per loop

%timeit method_jdehesa(numbers, problem_numbers, alternative_numbers)
1000 loops, best of 3: 822 µs per loop

【讨论】:

  • 这假定numbers 中的元素覆盖了从最小值到最大值的整个序列。
  • @Divakar 不,它不需要numbers 覆盖整个范围,replacer 中不在numbers 中的值将不会被使用(这就是我说的原因在numbers 中存在巨大的“差距”可能会导致这种效率低下,至少在内存方面)。
  • 这是我试图获得一个不覆盖范围内所有数字的随机数组:numbers = np.unique(np.random.randint(0,100,(50)))numbers[10] = 23 ; numbers[20] = 15 ; numbers[30] = 33 然后随机播放。这种方法的价值不匹配。
  • @Divakar 哦,对了,代码有错误,谢谢。
  • 当您对执行一些就地操作的函数进行计时时,您应该使用在每次计时运行之前执行的setup 代码。这样可以确保后续运行不会因为第一次运行已经修改了输入而产生偏差。
【解决方案2】:

如果不是所有problem_values 都在numbers 中,甚至可能出现多次:

在这种情况下,我将只使用 dict 来保留要替换的值并使用 dict.get 来翻译有问题的数字:

replacer = dict(zip(problem_numbers, alternative_numbers))
numbers_list = numbers.tolist()
numbers = np.array(list(map(replacer.get, numbers_list, numbers_list)))

即使它必须“通过 Python”,这几乎是不言自明的,它并不比 NumPy 解决方案慢多少(可能)。

如果每个problem_value 都实际存在于numbers 数组中并且一次:

如果你有numpy_indexed 包,你可以简单地使用numpy_indexed.indices

>>> import numpy_indexed as ni
>>> numbers[ni.indices(numbers, problem_numbers)] = alternative_numbers

即使对于大型数组,这也应该非常有效。

【讨论】:

  • map 中有一个额外的参数。在numbers 上使用map(您可以直接将其作为数组传递,而不是将其转换为list)可能并不比迭代problem_numbers 好,后者显然更小,就像OP 所做的那样。
  • @jdehesa 迭代 array 的常数因子远高于迭代列表(即使必须使用 tolist)所以如果性能是一个问题并且 python 迭代是必需始终转换为列表
  • @jdehesa 额外的参数是因为dict.get 有两个参数。
猜你喜欢
  • 2012-11-14
  • 2013-09-11
  • 2023-03-20
  • 2019-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-26
相关资源
最近更新 更多