Самый быстрый способ создания np.arrays для каждого элемента из списка кортежей - PullRequest
0 голосов
/ 14 февраля 2019

Существует список кортежей l = [(x,y,z), (x,y,z), (x,y,z)] Идея состоит в том, чтобы найти самый быстрый способ создания различных массивов np.ar для каждого xs, ys, zs.Нужна помощь в поиске самого быстрого решения, чтобы сделать это.Для сравнения скорости я использую код, прикрепленный ниже

import time

def myfast():
   code

n = 1000000
t0 = time.time()
for i in range(n): myfast()
t1 = time.time()

total_n = t1-t0

1.  np.array([i[0] for i in l])
    np.array([i[1] for i in l])
    np.array([i[2] for i in l])

вывод: 0.9980638027191162

2.  array_x = np.zeros((len(l), 1), dtype="float")
    array_y  = np.zeros((len(l), 1), dtype="float")
    array_z  = np.zeros((len(l), 1), dtype="float")

    for i, zxc in enumerate(l):
        array_x[i] = zxc[0]
        array_y[i] = zxc[1]
        array_z[i] = zxc[2]

вывод 5.5509934425354

3. [np.array(x) for x in zip(*l)]

выход 2.5070037841796875

5. array_x, array_y, array_z = np.array(list(zip(*l)))

выход 2.725318431854248

Ответы [ 4 ]

0 голосов
/ 14 февраля 2019

Я полагаю, что большинство (но не все) ингредиентов этого ответа фактически присутствуют в других ответах, но на всех ответах до сих пор я не видел сравнения между яблоками, в том смысле, что некоторые подходыне возвращали список np.ndarray объектов, а скорее (удобно, на мой взгляд) сингл np.ndarray().

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

В конце концов,для небольших входных данных (3 x 10) список np.ndarray() s - это просто дополнительная нагрузка, которая значительно увеличивает время.Для больших входных данных (3 x 1000) и выше дополнительное вычисление больше не имеет значения, но подход, включающий в себя понимание и избегание создания большого массива numpy, может стать таким же быстрым, как (или даже быстрее чем) самые быстрые методы для меньших затрат.

Кроме того, весь код, который я представляю, работает для произвольных размеров кортежей / списка (если, конечно, все внутренние кортежи имеют одинаковый размер).

(РЕДАКТИРОВАТЬ: добавлен комментарий к окончательным результатам)


Проверены следующие методы:

import numpy as np


def to_arrays_zip(items):
    return np.array(list(zip(*items)))


def to_arrays_transpose(items):
    return np.array(items).transpose()


def to_arrays_zip_split(items):
    return [arr for arr in np.array(list(zip(*items)))]


def to_arrays_transpose_split(items):
    return [arr for arr in np.array(items).transpose()]


def to_arrays_comprehension(items):
    return [np.array([items[i][j] for i in range(len(items))]) for j in range(len(items[0]))]


def to_arrays_comprehension2(items):
    return [np.array([item[j] for item in items]) for j in range(len(items[0]))]

(Это удобная функция для проверки совпадения результатов.)

def test_equal(items1, items2):
    return all(np.all(x == y) for x, y in zip(items1, items2))

Для небольших входов:

N = 3
M = 10
ll = [tuple(range(N)) for _ in range(M)]

print(to_arrays_comprehension2(ll))

print('Returning `np.ndarray()`')
%timeit to_arrays_zip(ll)
# 2.82 µs ± 28 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit to_arrays_transpose(ll)
# 3.18 µs ± 30 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

print('Returning a list')
%timeit to_arrays_zip_split(ll)
# 3.71 µs ± 47 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit to_arrays_transpose_split(ll)
# 3.97 µs ± 42.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit to_arrays_comprehension(ll)
# 5.91 µs ± 96.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit to_arrays_comprehension2(ll)
# 5.14 µs ± 109 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Где подиум:

  1. to_arrays_zip_split() (не _splitесли вы в порядке с одним массивом)
  2. to_arrays_zip_transpose_split() (не-_split, если вы в порядке с одним массивом)
  3. to_arrays_comprehension2()

Для более крупных входов:

N = 3
M = 1000
ll = [tuple(range(N)) for _ in range(M)]

print('Returning `np.ndarray()`')
%timeit to_arrays_zip(ll)
# 146 µs ± 2.3 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit to_arrays_transpose(ll)
# 222 µs ± 2.01 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

print('Returning a list')
%timeit to_arrays_zip_split(ll)
# 147 µs ± 1.68 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit to_arrays_transpose_split(ll)
# 221 µs ± 2.58 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit to_arrays_comprehension(ll)
# 261 µs ± 2.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit to_arrays_comprehension2(ll)
# 212 µs ± 1.68 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Подиум становится:

  1. to_arrays_zip_split() (используете ли вы варианты _split или не _split, не имеет большого значения)
  2. to_arrays_comprehension2()
  3. to_arrays_zip_transpose_split() (используете ли вы варианты _split или не _split, не имеет большого значения)

Для еще больших входов:

N = 3
M = 1000000
ll = [tuple(range(N)) for _ in range(M)]

print('Returning `np.ndarray()`')
%timeit to_arrays_zip(ll)
# 215 ms ± 4.27 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit to_arrays_transpose(ll)
# 220 ms ± 4.62 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

print('Returning a list')
%timeit to_arrays_zip_split(ll)
# 218 ms ± 6.21 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit to_arrays_transpose_split(ll)
# 222 ms ± 3.48 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit to_arrays_comprehension(ll)
# 248 ms ± 3.55 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit to_arrays_comprehension2(ll)
# 186 ms ± 481 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

подиум становится:

  1. to_arrays_comprehension2()
  2. to_arrays_zip_split() (используете ли вы варианты _split или не _split, не имеет большого значения)
  3. to_arrays_zip_transpose_split() (используете ли вы варианты _split или не _split, не имеет большого значения)

, а варианты _zip и _transpose довольно близки

(я также пытался ускорить процесс с Numba, который не прошел хорошо)

0 голосов
/ 14 февраля 2019

Может быть, я что-то упускаю, но почему бы просто не передать список кортежей непосредственно np.array?Скажите, если:

n = 100
l = [(0, 1, 2) for _ in range(n)]

arr = np.array(l)
x = arr[:, 0]
y = arr[:, 1]
z = arr[:, 2]

Кстати, я предпочитаю использовать следующий код времени:

from timeit import default_timer as timer

t0 = timer()
do_heavy_calculation()
print("Time taken [sec]:", timer() - t0)
0 голосов
/ 14 февраля 2019

Здесь есть несколько действительно хороших вариантов, поэтому я суммировал их и сравнил скорость:

import numpy as np

def f1(input_data):
    array_x = np.array([elem[0] for elem in input_data])
    array_y = np.array([elem[1] for elem in input_data])
    array_z = np.array([elem[2] for elem in input_data])

    return array_x, array_y, array_z

def f2(input_data):
    array_x = np.zeros((len(input_data), ), dtype="float")
    array_y = np.zeros((len(input_data), ), dtype="float")
    array_z = np.zeros((len(input_data), ), dtype="float")

    for i, elem in enumerate(input_data):
        array_x[i] = elem[0]
        array_y[i] = elem[1]
        array_z[i] = elem[2]

    return array_x, array_y, array_z

def f3(input_data):
    return [np.array(elem) for elem in zip(*input_data)]

def f4(input_data):
    return np.array(list(zip(*input_data)))

def f5(input_data):
    return np.array(input_data).transpose()

def f6(input_data):
    array_all = np.array(input_data)
    array_x = array_all[:, 0]
    array_y = array_all[:, 1]
    array_z = array_all[:, 2]

    return array_x, array_y, array_z

Сначала я утверждал, что все они возвращают одни и те же данные (используя np.array_equal()):

data = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
for array_list in zip(f1(data), f2(data), f3(data), f4(data), f5(data), f6(data)):
    # print()
    # for i, arr in enumerate(array_list):
    #     print('array from function', i+1)
    #     print(arr)
    for i, arr in enumerate(array_list[:-1]):
        assert np.array_equal(arr, array_list[i+1])

И сравнение времени:

import timeit
for f in [f1, f2, f3, f4, f5, f6]:
    t = timeit.timeit('f(data)', 'from __main__ import data, f', number=100000)
    print('{:5s} {:10.4f} seconds'.format(f.__name__, t))

дает следующие результаты:

data = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]    # 3 tuples
timeit number=100000
f1        0.3184 seconds
f2        0.4013 seconds
f3        0.2826 seconds
f4        0.2091 seconds
f5        0.1732 seconds
f6        0.2159 seconds

data = [(1, 2, 3) for _ in range(10**6)]    # 1 millon tuples
timeit number=10
f1        2.2168 seconds
f2        2.8657 seconds
f3        2.0150 seconds
f4        1.9790 seconds
f5        2.6380 seconds
f6        2.6586 seconds

делает f5() самым быстрым вариантом для короткого ввода и f4() самым быстрым вариантомдля большого ввода.


Если количество элементов в каждом кортеже будет больше 3, то к этому случаю применимы только 3 функции (остальные жестко закодированы для 3 элементов в каждом кортеже):

data = [tuple(range(10**4)) for _ in range(10**3)]
timeit number=10
f3       11.8396 seconds
f4       13.4672 seconds
f5        4.6251 seconds

делая f5() снова самым быстрым вариантом для этих критериев.

0 голосов
/ 14 февраля 2019

вы можете попробовать:

import numpy
array_x, array_y, array_z = numpy.array(list(zip(*l)))

или просто:

numpy.array(list(zip(*l)))

и более элегантным способом:

numpy.array(l).transpose()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...