Самый быстрый способ конвертировать список индексов в двумерный массив единиц - PullRequest
7 голосов
/ 19 июня 2019

У меня есть список индексов

a = [
  [1,2,4],
  [0,2,3],
  [1,3,4],
  [0,2]]

Какой самый быстрый способ преобразовать это в массив единиц, где каждый индекс показывает позицию, в которой будет 1?

т.е. что я хочу это:

output = array([
  [0,1,1,0,1],
  [1,0,1,1,0],
  [0,1,0,1,1],
  [1,0,1,0,0]])

Я заранее знаю максимальный размер массива. Я знаю, что мог бы перебирать каждый список и вставлять 1 в каждую позицию индекса, но есть ли более быстрый / векторизованный способ сделать это?

Мой вариант использования может содержать тысячи строк / столбцов, и мне нужно делать это тысячи раз, поэтому чем быстрее, тем лучше.

Ответы [ 6 ]

10 голосов
/ 19 июня 2019

Как насчет этого:

ncol = 5
nrow = len(a)
out = np.zeros((nrow, ncol), int)
out[np.arange(nrow).repeat([*map(len,a)]), np.concatenate(a)] = 1
out
# array([[0, 1, 1, 0, 1],
#        [1, 0, 1, 1, 0],
#        [0, 1, 0, 1, 1],
#        [1, 0, 1, 0, 0]])

Вот время для двоичного массива 1000x1000, обратите внимание, что я использую оптимизированную версию выше, см. Функцию pp ниже:

pp 21.717635259992676 ms
ts 37.10938713003998 ms
u9 37.32933565042913 ms

Код для получения времени:

import itertools as it
import numpy as np

def make_data(n,m):
    I,J = np.where(np.random.random((n,m))<np.random.random((n,1)))
    return [*map(np.ndarray.tolist, np.split(J, I.searchsorted(np.arange(1,n))))]

def pp():
    sz = np.fromiter(map(len,a),int,nrow)
    out = np.zeros((nrow,ncol),int)
    out[np.arange(nrow).repeat(sz),np.fromiter(it.chain.from_iterable(a),int,sz.sum())] = 1
    return out

def ts():
    out = np.zeros((nrow,ncol),int)
    for i, ix in enumerate(a):
        out[i][ix] = 1
    return out

def u9():
    out = np.zeros((nrow,ncol),int)
    for i, (x, y) in enumerate(zip(a, out)):
        y[x] = 1
        out[i] = y
    return out

nrow,ncol = 1000,1000
a = make_data(nrow,ncol)

from timeit import timeit
assert (pp()==ts()).all()
assert (pp()==u9()).all()

print("pp", timeit(pp,number=100)*10, "ms")
print("ts", timeit(ts,number=100)*10, "ms")
print("u9", timeit(u9,number=100)*10, "ms")
6 голосов
/ 19 июня 2019

Это может быть не самый быстрый способ.Вам нужно будет сравнить время выполнения этих ответов, используя большие массивы, чтобы найти самый быстрый способ.Вот мое решение

output = np.zeros((4,5))
for i, ix in enumerate(a):
    output[i][ix] = 1

# output -> 
#   array([[0, 1, 1, 0, 1],
#   [1, 0, 1, 1, 0],
#   [0, 1, 0, 1, 1],
#   [1, 0, 1, 0, 0]])
4 голосов
/ 19 июня 2019

Возможно, это не лучший способ, но единственный способ, которым я могу придумать:

output = np.zeros((4,5))
for i, (x, y) in enumerate(zip(a, output)):
    y[x] = 1
    output[i] = y
print(output)

Какие выходы:

[[ 0.  1.  1.  0.  1.]
 [ 1.  0.  1.  1.  0.]
 [ 0.  1.  0.  1.  1.]
 [ 1.  0.  1.  0.  0.]]
3 голосов
/ 19 июня 2019

Если вы можете и хотите использовать Cython , вы можете создать удобочитаемое и быстрое решение (по крайней мере, если вы не против печатать).

Здесь я используюIPython-привязки Cython для компиляции в блокнот Jupyter:

%load_ext cython
%%cython

cimport cython
cimport numpy as cnp
import numpy as np

@cython.boundscheck(False)  # remove this if you cannot guarantee that nrow/ncol are correct
@cython.wraparound(False)
cpdef cnp.int_t[:, :] mseifert(list a, int nrow, int ncol):
    cdef cnp.int_t[:, :] out = np.zeros([nrow, ncol], dtype=int)
    cdef list subl
    cdef int row_idx
    cdef int col_idx
    for row_idx, subl in enumerate(a):
        for col_idx in subl:
            out[row_idx, col_idx] = 1
    return out

Для сравнения производительности представленных здесь решений я использую свою библиотеку simple_benchmark:

enter image description here

Обратите внимание, что здесь используется логарифмическая ось, чтобы одновременно показать различия для маленьких и больших массивов.Согласно моему бенчмарку, моя функция на самом деле самая быстрая из решений, однако стоит также отметить, что все решения не так уж и далеки.

Вот полный код, который я использовал для теста:

import numpy as np
from simple_benchmark import BenchmarkBuilder, MultiArgument
import itertools

b = BenchmarkBuilder()

@b.add_function()
def pp(a, nrow, ncol):
    sz = np.fromiter(map(len, a), int, nrow)
    out = np.zeros((nrow, ncol), int)
    out[np.arange(nrow).repeat(sz), np.fromiter(itertools.chain.from_iterable(a), int, sz.sum())] = 1
    return out

@b.add_function()
def ts(a, nrow, ncol):
    out = np.zeros((nrow, ncol), int)
    for i, ix in enumerate(a):
        out[i][ix] = 1
    return out

@b.add_function()
def u9(a, nrow, ncol):
    out = np.zeros((nrow, ncol), int)
    for i, (x, y) in enumerate(zip(a, out)):
        y[x] = 1
        out[i] = y
    return out

b.add_functions([mseifert])

@b.add_arguments("number of rows/columns")
def argument_provider():
    for n in range(2, 13):
        ncols = 2**n
        a = [
            sorted(set(np.random.randint(0, ncols, size=np.random.randint(0, ncols)))) 
            for _ in range(ncols)
        ]
        yield ncols, MultiArgument([a, ncols, ncols])

r = b.run()
r.plot()
1 голос
/ 20 июня 2019

В зависимости от вашего варианта использования, вы можете изучить использование разреженных матриц. Входная матрица выглядит подозрительно, как матрица Compressed Sparse Row (CSR) . Возможно что-то вроде

import numpy as np
from scipy.sparse import csr_matrix
from itertools import accumulate


def ragged2csr(inds):
    offset = len(inds[0])
    lens = [len(x) for x in inds]
    indptr = list(accumulate(lens))
    indptr = np.array([x - offset for x in indptr])
    indices = np.array([val for sublist in inds for val in sublist])
    n = indices.size
    data = np.ones(n)
    return csr_matrix((data, indices, indptr))

Опять же, если он подходит в вашем случае использования, разреженная матрица позволила бы масштабировать элементарные / маскирующие операции с количеством ненулевых элементов, а не с количеством элементов (строк * столбцов), что может привести к значительному ускорению (для достаточно разреженная матрица).

Другим хорошим введением в матрицы CSR является раздел 3.4 Итерационные методы . В этом случае data равно aa, indices равно ja и indptr равно ia. Этот формат также очень популярен среди различных пакетов / библиотек.

0 голосов
/ 19 июня 2019

Как насчет использования индексации массива?Если бы вы знали больше о ваших входных данных, вы могли бы избавиться от штрафа за необходимость сначала преобразовывать в линейный массив.

import numpy as np


def main():
    row_count = 4
    col_count = 5
    a = [[1,2,4],[0,2,3],[1,3,4],[0,2]]

    # iterate through each row, concatenate all indices and convert them to linear

    # numpy append performs copy even if you don't want it, list append is faster
    b = []
    for row_idx, row in enumerate(a):
        b.append(np.array(row, dtype=np.int64) + (row_idx * col_count))

    linear_idxs = np.hstack(b)
    #could skip previous steps if given index inputs well before hand, or in linear index order. 
    c = np.zeros(row_count * col_count)
    c[linear_idxs] = 1
    c = c.reshape(row_count, col_count)
    print(c)


if __name__ == "__main__":
    main()

#output
# [[0. 1. 1. 0. 1.]
#  [1. 0. 1. 1. 0.]
#  [0. 1. 0. 1. 1.]
#  [1. 0. 1. 0. 0.]]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...