Python中对数组或列表中元素进行成对比较

11

让我用一个简单的例子来详细阐述我的问题。我有一个数组a=[a1,a2,a3,a4],其中所有ai都是数字。

我想得到数组'a'中的两两比较,例如I(a1>=a2), I(a1>=a3), I(a1>=a4), ,,,,I(a4>=a1), I(a4>=a2), I(a4>=a3),其中I是指示函数。因此我使用了以下代码。

res=[x>=y for x in a for y in a]

但它还会给出比较结果,如I(a1> = a1),..,I(a4> = a4),其始终为1。为了摆脱这些麻烦,我将res转换为一个numpy数组,并找到其非对角线元素。

res1=numpy.array(res)

这可以得出我想要的结果,但我认为应该有更有效或更简单的方法进行成对比较并提取除对角线元素之外的元素。你有任何想法吗?提前致谢。

5个回答

13

您可以使用NumPy广播 -

# Get the mask of comparisons in a vectorized manner using broadcasting
mask = a[:,None] >= a

# Select the elements other than diagonal ones
out = mask[~np.eye(a.size,dtype=bool)]

如果您更喜欢将mask中的对角元素设置为False,那么mask将成为输出,如下所示 -

mask[np.eye(a.size,dtype=bool)] = 0

示例运行 -

In [56]: a
Out[56]: array([3, 7, 5, 8])

In [57]: mask = a[:,None] >= a

In [58]: mask
Out[58]: 
array([[ True, False, False, False],
       [ True,  True,  True, False],
       [ True, False,  True, False],
       [ True,  True,  True,  True]], dtype=bool)

In [59]: mask[~np.eye(a.size,dtype=bool)] # Selecting non-diag elems
Out[59]: 
array([False, False, False,  True,  True, False,  True, False, False,
        True,  True,  True], dtype=bool)

In [60]: mask[np.eye(a.size,dtype=bool)] = 0 # Setting diag elems as False

In [61]: mask
Out[61]: 
array([[False, False, False, False],
       [ True, False,  True, False],
       [ True, False, False, False],
       [ True,  True,  True, False]], dtype=bool)

运行时测试

为什么要使用 NumPy 广播?因为它能提高性能!让我们来看一个大型数据集的例子 -

In [34]: def pairwise_comp(A): # Using NumPy broadcasting    
    ...:     a = np.asarray(A) # Convert to array if not already so
    ...:     mask = a[:,None] >= a
    ...:     out = mask[~np.eye(a.size,dtype=bool)]
    ...:     return out
    ...: 

In [35]: a = np.random.randint(0,9,(1000)).tolist() # Input list

In [36]: %timeit [x >= y for i,x in enumerate(a) for j,y in enumerate(a) if i != j]
1 loop, best of 3: 185 ms per loop # @Sixhobbits's loopy soln

In [37]: %timeit pairwise_comp(a)
100 loops, best of 3: 5.76 ms per loop

看起来很棒!感谢你。 - Sue

9
也许您想要:
 [x >= y for i,x in enumerate(a) for j,y in enumerate(a) if i != j]

这不会将任何项目与其自身进行比较,而是将其他每个项目相互比较。

1
[x >= y for i,x in enumerate(a) for j,y in enumerate(a) if i > j] 是什么意思? - Jean-François Fabre
1
@Jean-FrançoisFabre OP 希望也能得到相反的结果:I(a1>=a4)I(a4>=a1) - TemporalWolf
是的,我需要两个。 - Sue

4
我想把@Divakar的解决方案应用到pandas对象上。这里提供了两种计算成对绝对差异的方法。
(基于Python 3.6.2的IPython 6.1.0)
In [1]: import pandas as pd
   ...: import numpy as np
   ...: import itertools

In [2]: n = 256
   ...: labels = range(n)
   ...: ser = pd.Series(np.random.randn(n), index=labels)
   ...: ser.head()
Out[2]: 
0    1.592248
1   -1.168560
2   -1.243902
3   -0.133140
4   -0.714133
dtype: float64

循环语句

In [3]: %%time
   ...: result = dict()
   ...: for pair in itertools.combinations(labels, 2):
   ...:     a, b = pair
   ...:     a = ser[a]  # retrieve values
   ...:     b = ser[b]
   ...:     result[pair] = a - b

   ...: result = pd.Series(result).abs().reset_index()
   ...: result.columns = list('ABC')
   ...: df1 = result.pivot('A', 'B, 'C').reindex(index=labels, columns=labels)
   ...: df1 = df1.fillna(df1.T).fillna(0.)
CPU times: user 18.2 s, sys: 468 ms, total: 18.7 s
Wall time: 18.7 s

NumPy广播

In [4]: %%time
   ...: arr = ser.values
   ...: arr = arr[:, None] - arr
   ...: df2 = pd.DataFrame(arr, labels, labels).abs()
CPU times: user 816 µs, sys: 432 µs, total: 1.25 ms
Wall time: 675 µs

验证它们是否相等:

In [5]: df1.equals(df2)
Out[5]: True

使用循环比聪明的NumPy方法慢大约20000倍。NumPy有许多优化,但有时需要不同的思路。 :-)

2
您可以通过使用以下方法实现:

您可以通过以下方式来实现:

[x >= y for i,x in enumerate(a) for j,y in enumerate(a) if i != j]
你的代码问题

你正在对列表进行两次迭代。如果将你的推导式转换为循环,它将像这样工作:

for x in a:
    for y in a:
        x>=y # which is your condition

因此,执行顺序为:(a1, a1),(a1, a2),...,(a2, a1),(a2, a2),...,(a4, a4)。

1
为什么你担心 a1>=a1 比较呢?它可能是可预测的,但跳过它可能不值得额外的工作。
列出100个数字的清单。
In [17]: a=list(range(100))

将它们与简单的双重循环进行比较;生成10000个值(100*100)

In [18]: len([x>=y for x in a for y in a])
Out[18]: 10000
In [19]: timeit [x>=y for x in a for y in a]
1000 loops, best of 3: 1.04 ms per loop

现在使用@Moinuddin Quadri的枚举循环来跳过100个“eye”值:
In [20]: len([x>=y for i,x in enumerate(a) for j, y in enumerate(a) if i!=j])
Out[20]: 9900
In [21]: timeit [x>=y for i,x in enumerate(a) for j, y in enumerate(a) if i!=j]
100 loops, best of 3: 2.12 ms per loop

它需要的时间是原来的2倍。多出来的一半时间用于枚举,另一半用于if语句。
在这种情况下,使用numpy数组工作速度更快,即使包括创建数组的时间在内。
xa = np.array(x); Z = xa[:,None]>=xa

但是你无法摆脱对角线上的值。它们将保持为True;它们可以被反转为False,但这样做没有意义。在布尔数组中只有2个值。
最快的解决方案是编写一个指示函数,它不会受到这些对角线值的干扰。

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