【问题标题】:How to extract values from a python list or numpy array using conditional checks by the application of numpy vectorization?如何通过应用 numpy 向量化的条件检查从 python 列表或 numpy 数组中提取值?
【发布时间】:2019-03-30 11:26:00
【问题描述】:

我有以下代码,我想根据给定条件从其他列表中提取某些值。但是我的数据集很大,每个列表中有 100 万个值。因此这种嵌套循环的方法耗时太长。是否有使用 Numpy 的矢量化或更快的方法,我可以使用它来加速我的代码并使用更少的内存?

import random
import numpy as np

x=[random.randrange(0,10) for _ in range(0,100)]
y=[random.randrange(0,10) for _ in range(0,100)]
z=[random.randrange(0,10) for _ in range(0,100)]

x_unique=np.unique(x)

xx_list=[]
y_list=[]
z_list=[]

for i in range(len(x_unique)):
    xx_list.append([])
    y_list.append([])
    z_list.append([])

for ii, i in enumerate(x_unique):
        for j,k in enumerate(x):
            if i == k:
                xx_list[ii].append(x[j])
                y_list[ii].append(y[j])
                z_list[ii].append(z[j])

[编辑:添加了预期的示例]

在列表:y_list 和 z_list 中,我想存储与 xx_list 中存储的相同索引号对应的值。

例如考虑以下示例列表:

x = [0.1,0.1,1,0.1,2,1,0.1]
y = [1.1,2.1,3,4,5,6,7]
z = [10,11,12,13.1,14,15,16]

因此,x_unique 如下:

x_unique = [0.1,1,2]

xx_list、y_list 和 z_list 应包含以下内容:

xx_list = [[0.1,0.1,0.1,0.1],[1,1],[2]]
y_list = [[1.1,2.1,4,7],[3,6],[5]]
z_list = [[10,11,13.1,16],[12,15],[14]]

【问题讨论】:

  • 您能描述一下您想要实现的目标吗(xx_listy_listz_list 中应该是什么)?
  • @cglacet 请检查我的编辑。我已经包含了一个我希望代码执行的示例。

标签: python list numpy vectorization nested-loops


【解决方案1】:

我找到了一个解决方案,它需要大约 400 毫秒来处理 Python 列表上的 1M 个项目。还有一个在处理 numpy 数组时需要 100 毫秒的解决方案。

Python

我使用它为每个输入列表(xyz)构建一个字典的策略。这些中的每一个都将作为一组标记的箱。对于每个输入列表,bin i 将包含它们在列表 x 中的对应索引等于 i 的项目。对应意味着它们在各自列表中的位置相同。

def compute_bins(x, y, z):
    # You can see this as an ordered-set:
    x_bin_indexes = {a:i for i, a in enumerate(sorted(set(x)))}

    # Each input list has its own set of labeled bins: 
    x_bins = defaultdict(list)
    y_bins = defaultdict(list)
    z_bins = defaultdict(list)

    for item_x, item_y, item_z in zip(x, y, z):
        index = x_bin_indexes[item_x]
        # Drop the item in the corresponding bin:
        x_bins[index].append(item_x)
        y_bins[index].append(item_y)
        z_bins[index].append(item_z)

    # Now we transform the result back to lists of list:
    x_bins = list(x_bins.values())
    y_bins = list(y_bins.values())
    z_bins = list(z_bins.values())
    return x_bins, y_bins, z_bins

这里的关键因素是我们在循环中进行的每个操作都是在恒定时间内进行的。函数可以这样调用:

>>> xx_list, y_list, z_list = compute_bins(x, y, z)
>>> xx_list
[[0, 0, 0, 0], [1, 1], [2]]
>>> y_list
[[1, 2, 4, 7], [3, 6], [5]]
>>> z_list
[[10, 11, 13, 16], [12, 15], [14]]

麻木

使用numpy,我想到了一个不同的策略:根据x中的项目对所有数组进行排序,然后根据x中连续相同值的数量对其进行拆分。这是代码(考虑到xyz 是numpy 数组):

import numpy as np

def compute_bins(x, *others):
    x_bin_indexes, bin_sizes = np.unique(x, return_counts=True)
    sort_order = np.argsort(x)
    split_rule = np.cumsum(bin_sizes)[:-1]
    return tuple(np.split(o[sort_order], split_rule) for o in (x, *others))

请注意,np.cumsum(bin_sizes)[:-1] 之所以存在,是因为split 采用了要切割的索引列表,而不是切割尺寸列表。如果我们想将[0, 0, 0, 1, 1, 2] 拆分为[[0, 0, 0], [1, 1], [2]],我们不会将[3, 2, 1] 传递给np.split,而是将[3, 5] 传递给[3, 5]

表演

关于性能,这是我机器上的表现:

from random import randint

test_size = int(1e6)
x = [randint(0, 100) for _ in range(test_size)]
y = [i+1 for i in range(test_size)]
z = [i+test_size+1 for i in range(test_size)]

%timeit xx_list, y_list, z_list = compute_bins(x, y, z)

python 版本的输出:

396 ms ± 5.98 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

numpy 版本的输出(xyznp.array):

105 ms ± 1.07 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

作为比较,您首先提出的解决方案给出:

19.7 s ± 282 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

【讨论】:

  • 感谢您的解决方案。恐怕我正在使用存储在 x、y 和 z 列表中的浮点数。您的解决方案(numpy 的)在“return tuple”语句中引发以下错误:“只有整数标量数组可以转换为标量索引”。我已经编辑了我的代码,以更好地反映我正在使用的列表的内容。
  • 奇怪,我没有那个错误,可能是numpy的版本。您可以尝试用np.cumsum(bin_sizes, dtype=np.int32)[:-1] 替换累积和行吗?也许我有一个更新版本的 numpy,它自己理解索引是整数,而你的版本还不知道:p。或者它可能不知道对整数求和只会产生整数。让我知道这是否有效。
  • 对不起,我意识到错误是因为我向函数发送列表而不是 numpy 数组。我还必须用 y,z 替换 *others。这是预期的吗?
  • 如果您在谈论调用compute_bins(x, y, z),那么是的,这很正常,如果您谈论函数的签名,那么不,您不应该修改它。基本上,函数签名中的*others 意味着将所有剩余的参数分组到一个名为others 的变量(元组)中。例如,如果我们将 f 定义为def f(x, *others): print(type(others), len(others)),那么我们可以调用f(1, 2, 3, 4, 5),它将打印<class 'tuple'> 4 或使用任意数量的参数调用f(至少一个将作为x 传递,那么与我们一样多想要),f(1) 打印 <class 'tuple'> 0
猜你喜欢
  • 2019-12-21
  • 2023-01-31
  • 2020-01-27
  • 2021-08-15
  • 2020-10-20
  • 2017-12-23
  • 1970-01-01
  • 2018-07-15
  • 2021-08-25
相关资源
最近更新 更多