将一个numpy数组中的数字转换为一组比它小的数字,每个数字被转换成一组比它小的数字。

9
考虑一组数字:
In [8]: import numpy as np

In [9]: x = np.array([np.random.random() for i in range(10)])

In [10]: x
Out[10]: 
array([ 0.62594394,  0.03255799,  0.7768568 ,  0.03050498,  0.01951657,
        0.04767246,  0.68038553,  0.60036203,  0.3617409 ,  0.80294355])

现在我想按照以下方式将这个集合转换为另一个集合y:对于x中的每个元素iy中相应的元素j将是i之前比i小的元素的数量。例如,上述给定的x会变成:

In [25]: y
Out[25]: array([ 6.,  2.,  8.,  1.,  0.,  3.,  7.,  5.,  4.,  9.])

现在,我可以使用简单的Python循环来完成这个任务:
In [16]: for i in range(len(x)):
    ...:     tot = 0
    ...:     for j in range(len(x)):
    ...:         if x[i] > x[j]: tot += 1
    ...:     y[i] = int(tot)

然而,当x的长度非常大时,代码会变得极其缓慢。我想知道是否有任何numpy魔法可以营救。例如,如果我必须过滤所有小于0.5的元素,我只需使用布尔掩码:

In [19]: z = x[x < 0.5]

In [20]: z
Out[20]: array([ 0.03255799,  0.03050498,  0.01951657,  0.04767246,  0.3617409 ])

可以使用类似的东西来实现更快速地达到相同的目的吗?


请注意,你的输入只是 np.random.rand(10) - Andras Deak -- Слава Україні
@AndrasDeak:我没听懂你的意思。 - Peaceful
尝试运行 x = np.random.rand(10),你会发现在列表推导式中不必调用 random() :) - Andras Deak -- Слава Україні
当然可以!谢谢 :) 不过我的真实数据并不是随机的。 - Peaceful
4个回答

12
您需要做的实际上是获取数组排序顺序的相反数
import numpy as np
x = np.random.rand(10)
y = np.empty(x.size,dtype=np.int64)
y[x.argsort()] = np.arange(x.size)

示例运行(在IPython中):

In [367]: x
Out[367]: 
array([ 0.09139335,  0.29084225,  0.43560987,  0.92334644,  0.09868977,
        0.90202354,  0.80905083,  0.4801967 ,  0.99086213,  0.00933582])

In [368]: y
Out[368]: array([1, 3, 4, 8, 2, 7, 6, 5, 9, 0])

或者,如果您想获得与x中的每个相应元素大于的元素数量,您需要将排序从升序更改为降序。其中一种可能的选项是交换索引的构造:

y_rev = np.empty(x.size,dtype=np.int64)
y_rev[x.argsort()] = np.arange(x.size)[::-1]

另一种方法,正如@unutbu在评论中建议的那样, 是将原始数组映射到新数组:

y_rev = x.size - y - 1

1
还是只用argsort两次:x.argsort().argsort() - Divakar
1
@Divakar:y[np.argsort(x)] = np.arange(x.size) 更快。 - unutbu
1
@AndrasDeak:或者,OP可以保留示例,但更改文本以便阅读,“y将是x中比i更大的其他元素数量”。 - unutbu
1
@AndrasDeak:我认为你的答案是正确的。它与OP问题的文本和他的“for-loop”代码一致。需要更改的是示例。 - unutbu
1
@Divakar 刚刚向我推荐了来自 https://dev59.com/sZ3ha4cB1Zd3GeqPZcRJ#41394980 的帖子。真是妙招。 - piRSquared
显示剩余7条评论

5

以下是使用np.searchsorted的一种方法 -

np.searchsorted(np.sort(x),x)

这是另一个基于@Andras Deak的解决方案使用argsort()的技术相关内容 -

x.argsort().argsort()

样例运行 -

In [359]: x
Out[359]: 
array([ 0.62594394,  0.03255799,  0.7768568 ,  0.03050498,  0.01951657,
        0.04767246,  0.68038553,  0.60036203,  0.3617409 ,  0.80294355])

In [360]: np.searchsorted(np.sort(x),x)
Out[360]: array([6, 2, 8, 1, 0, 3, 7, 5, 4, 9])

In [361]: x.argsort().argsort()
Out[361]: array([6, 2, 8, 1, 0, 3, 7, 5, 4, 9])

如果您感兴趣,我添加了一些时间记录。 - piRSquared

2
除了其他答案外,另一种使用布尔索引的解决方案可能是:

此外,使用布尔索引的另一个解决方案如下:

sum(x > i for i in x)

针对您的例子:

In [10]: x
Out[10]: 
array([ 0.62594394,  0.03255799,  0.7768568 ,  0.03050498,  0.01951657,
        0.04767246,  0.68038553,  0.60036203,  0.3617409 ,  0.80294355])

In [10]: y = sum(x > i for i in x)
In [11]: y
Out[10]: array([6, 2, 8, 1, 0, 3, 7, 5, 4, 9])

1
以向量化的方式:(x[:,None] > x).sum(1) - Divakar

2
我想通过对@Andras Deak的解决方案与argsort进行一些测试,来为这篇文章做出贡献。
似乎对于短数组来说,argsort 再次更快。简单的想法是评估我们看到平衡点发生变化的数组长度。
我将定义三个函数:
  • construct 是 Andras Deak 的解决方案
  • argsortagain 很明显
  • attempted_optimallen(a) == 400 时进行了权衡

函数

def argsortagain(s):
    return s.argsort()

def construct(s):
    u = np.empty(s.size, dtype=np.int64)
    u[s] = np.arange(s.size)

    return u

def attempted_optimal(s):
    return argsortagain(s) if len(s) < 400 else construct(s)

测试

results = pd.DataFrame(
    index=pd.RangeIndex(10, 610, 10, 'len'),
    columns=pd.Index(['construct', 'argsortagain', 'attempted_optimal'], name='function'))

for i in results.index:
    a = np.random.rand(i)
    s = a.argsort()
    for j in results.columns:
        results.set_value(
            i, j,
            timeit(
                '{}(s)'.format(j),
                'from __main__ import {}, s'.format(j),
                number=10000)
        )

results.plot()

enter image description here

结论:

结论

attempted_optimal 执行了它应该执行的任务。但是,在长度为 400 以下的数组范围内,其所获得的边际利益并不值得这样做。我完全支持只使用 constructed

这个分析帮助我得出这个结论。


2
好的中间地带! - Divakar

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