如何根据范围对列表元素进行分组/计数

4
如果我的 x 列表和 y 列表如下:
x = [10,20,30]
y = [1,2,3,15,22,27]

我希望返回一个字典,其中包含小于 x 值的元素计数:

{
    10:3,
    20:1,
    30:2,
}

我有一个非常大的列表,所以我希望有一种更好的方法来处理它,而不涉及缓慢的嵌套for循环。我已经查看了collections.Counter和itertools,但似乎没有提供分组的方法。是否有内置的函数可以实现这个?

4个回答

8
你可以使用模块和:
>>> import bisect
>>> from collections import Counter
>>> Counter(x[bisect.bisect_left(x, item)] for item in y)
Counter({10: 3, 30: 2, 20: 1})

5

如果您愿意使用numpy,基本上您正在请求一个直方图:

x = [10,20,30]
y = [1,2,3,15,22,27]

np.histogram(y,bins=[0]+x)
#(array([3, 1, 2]), array([ 0, 10, 20, 30]))

将其转换为字典格式:
b = np.histogram(y,bins=[0]+x)[0]
d = { k:v for k,v in zip(x, b)}

对于短列表,这并不值得,但如果您的列表很长,这可能是有意义的:

In [292]: y = np.random.randint(0, 30, 1000)

In [293]: %%timeit
   .....: b = np.histogram(y, bins=[0]+x)[0]
   .....: d = { k:v for k,v in zip(x, b)}
   .....: 
1000 loops, best of 3: 185 µs per loop

In [294]: y = list(y)

In [295]: timeit Counter(x[bisect.bisect_left(x, item)] for item in y)
100 loops, best of 3: 3.84 ms per loop

In [311]: timeit dict(zip(x, [[n_y for n_y in y if n_y < n_x] for n_x in x]))
100 loops, best of 3: 3.75 ms per loop

1
我的原始目标是制作一个直方图,所以非常感谢您的回答。虽然我会选择更一般的答案作为最佳答案,但我肯定也会使用这个答案! - pyInTheSky

1
短答案:
dict(zip(x, [[n_y for n_y in y if n_y < n_x] for n_x in x]))

长答案

首先,我们需要遍历y的值来检查哪个成员小于某个值。如果我们对10进行操作,我们会得到以下结果:

>>> [n_y for n_y in y if n_y < 10]
[1, 2, 3]

然后我们需要将那个 '10' 变成一个变量,查看 x 的内容:
>>> [[n_y for n_y in y if n_y < n_x] for n_x in x]
[[1, 2, 3], [1, 2, 3, 15], [1, 2, 3, 15, 22, 27]]

最后,我们需要将这些结果与原始的x相加。这就是zip派上用场的地方:
>>> zip(x, [[n_y for n_y in y if n_y < n_x] for n_x in x])
[(10, [1, 2, 3]), (20, [1, 2, 3, 15]), (30, [1, 2, 3, 15, 22, 27])]

这将给出一个元组列表,因此我们应该对其进行字典转换以获得最终结果:
>>> dict(zip(x, [[n_y for n_y in y if n_y < n_x] for n_x in x]))
{10: [1, 2, 3], 20: [1, 2, 3, 15], 30: [1, 2, 3, 15, 22, 27]}

0
如果在 x 中的值之间的步长始终为 10,我会这样做:
>>> y = [1,2,3,15,22,27]
>>> step = 10
>>> from collections import Counter
>>> Counter(n - n%step + step for n in y)
Counter({10: 3, 30: 2, 20: 1})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接