numpy:将1d数组的元素的索引作为2d数组获取

时间:2019-10-20 02:53:33

标签: python numpy numpy-ndarray

我有一个像这样的numpy数组: $validated = $request->validated(); $firstName = $request ->firstName; $lastName = $request ->lastName; $email = $request ->email; $userName = $request ->userName; $password = bcrypt($request ->password); $admissionDate_formated = Carbon::parse($request->admissionDate); $birthday_formated = Carbon::parse($request->birthday); $gender = $request ->gender; $address = $request ->address; $country = $request ->country; $state = $request ->state; $city = $request ->city; $postalCode = $request ->postalCode; $phoneNumber = $request ->phoneNumber; $nid_no = $request ->nid_no; $nid_image = 'nid_image'; $status = $request ->status; $date = Carbon::now()->toDateTimeString(); $randomString = Str::random(32); Mail::raw('echo"<a href="http://localhost:8080/emailConfirmation/'.$email.'/'.$randomString.'">Click Here</a>"', function ($message) use ($email,$randomString){ $message->to($email); }); DB::table('users')->insert( ['first_name' => $firstName, 'last_name' => $lastName, 'email' => $email,'userName' => $userName,'password' => $password,'joining_date' => $admissionDate_formated,'birthday' => $birthday_formated,'gender' => $gender,'address' => $address,'country' => $country,'state' => $state,'city' => $city,'postal_code' => $postalCode,'phone_number' => $phoneNumber,'image' => null,'department' => null,'short_biography' => null,'doctor_id' => null,'receptionist_id' => null,'admin_id' => null,'nid_no' => $nid_no,'nid_image' => $nid_image,'status' => $status,'role' => 4,'email_verified_at' => NULL,'remember_token' => NULL,'remember_token' => $randomString,'created_at' => $date,'updated_at' => $date,'created_by' => '1','updated_by' => '1'] ); }

是否可以将元素的索引获取为二维数组?例如,上述输入的答案为[1 2 2 0 0 1 3 5]

当前,我必须循环使用不同的值,并为每个值调用[[3 4], [0 5], [1 2], [6], [], [7]],这在输入足够大的情况下性能很差。

8 个答案:

答案 0 :(得分:7)

尽管请求的是numpy解决方案,但我还是决定看看是否有一个有趣的基于numba的解决方案。确实有!这是一种将分区列表表示为存储在单个预分配缓冲区中的参差不齐的数组的方法。这从Paul Panzer提出的argsort方法中获得了一些启发。 (对于性能较差但较简单的旧版本,请参见下文。)

@numba.jit(numba.void(numba.int64[:], 
                      numba.int64[:], 
                      numba.int64[:]), 
           nopython=True)
def enum_bins_numba_buffer_inner(ints, bins, starts):
    for x in range(len(ints)):
        i = ints[x]
        bins[starts[i]] = x
        starts[i] += 1

@numba.jit(nopython=False)  # Not 100% sure this does anything...
def enum_bins_numba_buffer(ints):
    ends = np.bincount(ints).cumsum()
    starts = np.empty(ends.shape, dtype=np.int64)
    starts[1:] = ends[:-1]
    starts[0] = 0

    bins = np.empty(ints.shape, dtype=np.int64)
    enum_bins_numba_buffer_inner(ints, bins, starts)

    starts[1:] = ends[:-1]
    starts[0] = 0
    return [bins[s:e] for s, e in zip(starts, ends)]

这将在75毫秒内处理一千万个项目列表,这几乎是使用纯Python编写的基于列表的版本的50倍。

对于一个较慢但可读性更高的版本,这是我之前的工作,它基于最近添加的对动态大小的“类型列表”的实验性支持,这使我们能够以无序方式填充每个垃圾箱很快。

这与numba的类型推断引擎有些冲突,我相信有更好的方法来处理该部分。事实证明,这也比上述方法慢了近10倍。

@numba.jit(nopython=True)
def enum_bins_numba(ints):
    bins = numba.typed.List()
    for i in range(ints.max() + 1):
        inner = numba.typed.List()
        inner.append(0)  # An awkward way of forcing type inference.
        inner.pop()
        bins.append(inner)

    for x, i in enumerate(ints):
        bins[i].append(x)

    return bins

我对以下各项进行了测试:

def enum_bins_dict(ints):
    enum_bins = defaultdict(list)
    for k, v in enumerate(ints):
        enum_bins[v].append(k)
    return enum_bins

def enum_bins_list(ints):
    enum_bins = [[] for i in range(ints.max() + 1)]
    for x, i in enumerate(ints):
        enum_bins[i].append(x)
    return enum_bins

def enum_bins_sparse(ints):
    M, N = ints.max() + 1, ints.size
    return sparse.csc_matrix((ints, ints, np.arange(N + 1)),
                             (M, N)).tolil().rows.tolist()

我还针对类似于enum_bins_numba_buffer的预编译cython版本(在下面进行了详细介绍)对它们进行了测试。

在一千万个随机整数(ints = np.random.randint(0, 100, 10000000))的列表中,我得到以下结果:

enum_bins_dict(ints)
3.71 s ± 80.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

enum_bins_list(ints)
3.28 s ± 52.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

enum_bins_sparse(ints)
1.02 s ± 34.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

enum_bins_numba(ints)
693 ms ± 5.81 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

enum_bins_cython(ints)
82.3 ms ± 1.77 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

enum_bins_numba_buffer(ints)
77.4 ms ± 2.06 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

令人印象深刻的是,即使关闭了边界检查,使用numba的这种方法也优于相同功能的cython版本。我对pythran尚不足够熟悉,无法使用它来测试这种方法,但是我很想看到一个比较。基于这种提速,看来pythran版本也可以通过这种方法更快一些。

这里是cython版本供参考,并带有一些构建说明。安装cython后,您将需要一个简单的setup.py文件,如下所示:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
import numpy

ext_modules = [
    Extension(
        'enum_bins_cython',
        ['enum_bins_cython.pyx'],
    )
]

setup(
    ext_modules=cythonize(ext_modules),
    include_dirs=[numpy.get_include()]
)

还有cython模块enum_bins_cython.pyx

# cython: language_level=3

import cython
import numpy
cimport numpy

@cython.boundscheck(False)
@cython.cdivision(True)
@cython.wraparound(False)
cdef void enum_bins_inner(long[:] ints, long[:] bins, long[:] starts) nogil:
    cdef long i, x
    for x in range(len(ints)):
        i = ints[x]
        bins[starts[i]] = x
        starts[i] = starts[i] + 1

def enum_bins_cython(ints):
    assert (ints >= 0).all()
    # There might be a way to avoid storing two offset arrays and
    # save memory, but `enum_bins_inner` modifies the input, and
    # having separate lists of starts and ends is convenient for
    # the final partition stage.
    ends = numpy.bincount(ints).cumsum()
    starts = numpy.empty(ends.shape, dtype=numpy.int64)
    starts[1:] = ends[:-1]
    starts[0] = 0

    bins = numpy.empty(ints.shape, dtype=numpy.int64)
    enum_bins_inner(ints, bins, starts)

    starts[1:] = ends[:-1]
    starts[0] = 0
    return [bins[s:e] for s, e in zip(starts, ends)]

将这两个文件放在您的工作目录中,运行以下命令:

python setup.py build_ext --inplace

然后可以使用from enum_bins_cython import enum_bins_cython导入函数。

答案 1 :(得分:6)

这确实是一种非常奇怪的方法,但这很可怕,但是我发现不共享-和所有numpy太有趣了!

out = np.array([''] * (x.max() + 1), dtype = object)
np.add.at(out, x, ["{} ".format(i) for i in range(x.size)])
[[int(i) for i in o.split()] for o in out]

Out[]:
[[3, 4], [0, 5], [1, 2], [6], [], [7]]

编辑:这是我沿着这条路可以找到的最佳方法。仍然比@PaulPanzer的argsort解决方案慢10倍:

out = np.empty((x.max() + 1), dtype = object)
out[:] = [[]] * (x.max() + 1)
coords = np.empty(x.size, dtype = object)
coords[:] = [[i] for i in range(x.size)]
np.add.at(out, x, coords)
list(out)

答案 2 :(得分:2)

取决于数据大小的一种可能选择是退出numpy并使用collections.defaultdict

In [248]: from collections import defaultdict

In [249]: d = defaultdict(list)

In [250]: l = np.random.randint(0, 100, 100000)

In [251]: %%timeit
     ...: for k, v in enumerate(l):
     ...:     d[v].append(k)
     ...:
10 loops, best of 3: 22.8 ms per loop

然后,您将得到{value1: [index1, index2, ...], value2: [index3, index4, ...]}的字典。时间缩放与数组的大小非常接近线性关系,因此10,000,000在我的计算机上花费了约2.7s,这似乎是足够合理的。

答案 3 :(得分:2)

这是使用scipy.sparse的O(max(x)+ len(x))方法:

import numpy as np
from scipy import sparse

x = np.array("1 2 2 0 0 1 3 5".split(),int)
x
# array([1, 2, 2, 0, 0, 1, 3, 5])


M,N = x.max()+1,x.size
sparse.csc_matrix((x,x,np.arange(N+1)),(M,N)).tolil().rows.tolist()
# [[3, 4], [0, 5], [1, 2], [6], [], [7]]

这可以通过创建一个稀疏矩阵来实现,该稀疏矩阵的条目位于(x [0],0),(x [1],1),...的位置。使用CSC(压缩的稀疏列)格式是相当简单。然后将矩阵转换为LIL(链接列表)格式。这种格式将每一行的列索引作为列表存储在其rows属性中,因此我们需要做的就是将其转换为列表。

请注意,对于基于小型数组argsort的解决方案,速度可能会更快,但是在某些情况下(并非大得不可思议),这种解决方案将会越发流行。

答案 4 :(得分:2)

您可以通过制作数字字典来做到这一点,键将是数字,值应是该数字看到的索引,这是最快的方法之一,您可以看到下面的代码:

>>> import numpy as np
>>> a = np.array([1 ,2 ,2 ,0 ,0 ,1 ,3, 5])
>>> b = {}
# Creating an empty list for the numbers that exist in array a
>>> for i in range(np.min(a),np.max(a)+1):
    b[str(i)] = []

# Adding indices to the corresponding key
>>> for i in range(len(a)):
    b[str(a[i])].append(i)

# Resulting Dictionary
>>> b
{'0': [3, 4], '1': [0, 5], '2': [1, 2], '3': [6], '4': [], '5': [7]}

# Printing the result in the way you wanted.
>>> for i in sorted (b.keys()) :
     print(b[i], end = " ")

[3, 4] [0, 5] [1, 2] [6] [] [7] 

答案 5 :(得分:1)

这完全可以为您提供所需的信息,并且在我的计算机上,一千万种将花费大约2.5秒的时间:

import numpy as np
import timeit

# x = np.array("1 2 2 0 0 1 3 5".split(),int)
x = np.random.randint(0, 100, 100000)

def create_index_list(x):
    d = {}
    max_value = -1
    for i,v in enumerate(x):
        if v > max_value:
            max_value = v
        try:
            d[v].append(i)
        except:
            d[v] = [i]
    result_list = []
    for i in range(max_value+1):
        if i in d:
            result_list.append(d[i])
        else:
            result_list.append([])
    return result_list

# print(create_index_list(x))
print(timeit.timeit(stmt='create_index_list(x)', number=1, globals=globals()))

答案 6 :(得分:0)

伪代码:

  1. 通过从最大值减去numpy数组的最小值,然后加1,得到“ 2d数组中1d数组的数量”。在您的情况下,它将是5-0 + 1 = 6

  2. 初始化一个2d数组,其中包含1d数组的数量。在您的情况下,请初始化其中包含6个1d数组的2d数组。每个1d数组对应于numpy数组中的唯一元素,例如,第一个1d数组将对应于'0',第二个1d数组将对应于'1',...

  3. 循环遍历您的numpy数组,将元素的索引放入正确的对应1d数组中。在您的情况下,numpy数组中第一个元素的索引将放入第二个1d数组,numpy数组中第二个元素的索引将放入第三个1d数组,....

此伪代码将花费线性时间,因为它取决于numpy数组的长度。

答案 7 :(得分:0)

因此,给定一个元素列表,您想成对(元素,索引)。在线性时间内,可以这样完成:

hashtable = dict()
for idx, val in enumerate(mylist):
    if val not in hashtable.keys():
         hashtable[val] = list()
    hashtable[val].append(idx)
newlist = sorted(hashtable.values())

这应该花费O(n)时间。到目前为止,我还没有想到一个更快的解决方案,但是如果有的话,我会在这里更新。