Почему преобразование длинного 2D-списка в массив numpy так медленно?

У меня длинный список координат xy и хотел бы преобразовать его в массив numpy.

>>> import numpy as np
>>> xy = np.random.rand(1000000, 2).tolist()

Очевидным будет:

>>> a = np.array(xy) # Very slow...

Однако вышеупомянутый код необоснованно медленный. Интересно, что сначала перенести длинный список, преобразовать его в массив numpy, а затем переставить назад было бы намного быстрее (20x на моем ноутбуке).

>>> def longlist2array(longlist):
...     wide = [[row[c] for row in longlist] for c in range(len(longlist[0]))]
...     return np.array(wide).T
>>> a = longlist2array(xy) # 20x faster!

Является ли это ошибкой numpy?

EDIT:

Это список точек (с координатами xy), сгенерированных "на лету", поэтому вместо предварительного распределения массива и при необходимости увеличивая его или поддерживая два 1D-списка для x и y, я думаю, что текущее представление является наиболее естественным.

Почему цикл по второму индексу быстрее, чем первый индекс, учитывая, что мы итерируем через список python в обоих направлениях?

ИЗМЕНИТЬ 2:

На основе ответа @tiago и этот вопрос, я нашел следующий код в два раза быстрее, чем моя оригинальная версия:

>>> from itertools import chain
>>> def longlist2array(longlist):
...     flat = np.fromiter(chain.from_iterable(longlist), np.array(longlist[0][0]).dtype, -1) # Without intermediate list:)
...     return flat.reshape((len(longlist), -1))

Ответ 1

Реализация этого в Cython без дополнительной проверки, чтобы определить размерность и т.д., почти исключает разницу во времени, которую вы видите. Здесь файл .pyx, который я использовал для проверки этого.

from numpy cimport ndarray as ar
import numpy as np
cimport cython

@cython.boundscheck(False)
@cython.wraparound(False)
def toarr(xy):
    cdef int i, j, h=len(xy), w=len(xy[0])
    cdef ar[double,ndim=2] new = np.empty((h,w))
    for i in xrange(h):
        for j in xrange(w):
            new[i,j] = xy[i][j]
    return new

Я бы предположил, что дополнительное время тратится на проверку длины и содержимого каждого подсписок, чтобы определить тип данных, размер и размер требуемого массива. Когда есть только два подсписок, нужно проверить только две длины, чтобы определить количество столбцов в массиве, вместо того, чтобы проверять 1000000 из них.

Ответ 2

Это связано с тем, что самый быстрый переменный индекс вашего списка является последним, поэтому np.array() должен проходить массив много раз, потому что первый индекс намного больше. Если ваш список был транспонирован, np.array() будет быстрее вашего longlist2array:

In [65]: import numpy as np

In [66]: xy = np.random.rand(10000, 2).tolist()

In [67]: %timeit longlist2array(xy)
100 loops, best of 3: 3.38 ms per loop

In [68]: %timeit np.array(xy)
10 loops, best of 3: 55.8 ms per loop

In [69]: xy = np.random.rand(2, 10000).tolist()

In [70]: %timeit longlist2array(xy)
10 loops, best of 3: 59.8 ms per loop

In [71]: %timeit np.array(xy)
1000 loops, best of 3: 1.96 ms per loop

Нет волшебного решения вашей проблемы. Это как раз то, как Python хранит ваш список в памяти. Вам действительно нужно иметь список с этой формой? Вы не можете изменить его? (И вам действительно нужен список, учитывая, что вы конвертируете в numpy?)

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

from itertools import chain

def convertlist(longlist)
    tmp = list(chain.from_iterable(longlist))
    return np.array(tmp).reshape((len(longlist), len(longlist[0])))

Ответ 3

Если у вас pandas, вы можете использовать pandas.lib.to_object_array(), это самый быстрый метод:

import numpy as np
import pandas as pd
a = np.random.rand(100000, 2)
b = a.tolist()

%timeit np.array(b, dtype=float, ndmin=2)
%timeit np.array(b, dtype=object).astype(float)
%timeit np.array(zip(*b)).T
%timeit pd.lib.to_object_array(b).astype(float)

выходы:

1 loops, best of 3: 462 ms per loop
1 loops, best of 3: 192 ms per loop
10 loops, best of 3: 39.9 ms per loop
100 loops, best of 3: 13.7 ms per loop