Каковы преимущества NumPy перед обычными списками Python?
Каковы преимущества NumPy перед обычными списками Python?
У меня около 100 серий финансовых рынков, и я собираюсь создать массив кубов размером 100x100x100 = 1 миллион ячеек. Я буду регрессировать (с 3 переменными) каждый x с каждым y и z, чтобы заполнить массив стандартными ошибками.
Я слышал, что для "больших матриц" я должен использовать NumPy, а не списки Python, из соображений производительности и масштабируемости. Дело в том, что я знаю списки Python, и они, кажется, работают для меня.
Каковы будут преимущества, если я перейду на NumPy?
Что если бы у меня было 1000 рядов (то есть 1 миллиард ячеек с плавающей запятой в кубе)?
9 ответов
Массивы NumPy более компактны, чем списки Python - список списков, который вы описываете в Python, займет не менее 20 МБ или около того, в то время как 3D-массив NumPy с плавающей точкой одинарной точности в ячейках уместится в 4 МБ. Доступ к чтению и письму также быстрее с NumPy.
Может быть, вас не волнует только миллион ячеек, но вы определенно хотели бы иметь миллиард ячеек - ни один из подходов не подходит для 32-разрядной архитектуры, но с 64-разрядными сборками NumPy обходится без 4 ГБ или около того Для одного только Python потребуется по крайней мере около 12 ГБ (множество указателей, которые удваиваются в размере) - гораздо более дорогая часть оборудования!
Различие главным образом связано с "косвенностью" - список Python представляет собой массив указателей на объекты Python, по крайней мере 4 байта на указатель плюс 16 байтов даже для самого маленького объекта Python (4 для указателя типа, 4 для счетчика ссылок, 4 для значения - и распределители памяти округляются до 16). Массив NumPy - это массив одинаковых значений - числа одинарной точности занимают 4 байта каждое, числа двойной точности - 8 байтов. Менее гибкий, но вы платите существенно за гибкость стандартных списков Python!
NumPy не просто более эффективен; это также более удобно. Вы получаете множество векторных и матричных операций бесплатно, что иногда позволяет избежать ненужной работы. И они также эффективно реализованы.
Например, вы можете прочитать свой куб прямо из файла в массив:
x = numpy.fromfile(file=open("data"), dtype=float).reshape((100, 100, 100))
Сумма по второму измерению:
s = x.sum(axis=1)
Найдите, какие ячейки выше порога:
(x > 0.5).nonzero()
Удалите каждый четный фрагмент по третьему измерению:
x[:, :, ::2]
Кроме того, многие полезные библиотеки работают с массивами NumPy. Например, статистический анализ и визуализация библиотек.
Даже если у вас нет проблем с производительностью, изучение NumPy стоит усилий.
Алекс упомянул эффективность памяти, а Роберто упомянул удобство, и это хорошие моменты. Для еще нескольких идей я упомяну скорость и функциональность.
Функциональность: вы получаете много встроенного с помощью NumPy, FFT, сверток, быстрого поиска, базовой статистики, линейной алгебры, гистограмм и т. Д. И действительно, кто может жить без FFT?
Скорость: вот тест по суммированию по списку и массиву NumPy, показывающий, что сумма в массиве NumPy в 10 раз быстрее (в этом тесте пробег может варьироваться).
from numpy import arange
from timeit import Timer
Nelements = 10000
Ntimeits = 10000
x = arange(Nelements)
y = range(Nelements)
t_numpy = Timer("x.sum()", "from __main__ import x")
t_list = Timer("sum(y)", "from __main__ import y")
print("numpy: %.3e" % (t_numpy.timeit(Ntimeits)/Ntimeits,))
print("list: %.3e" % (t_list.timeit(Ntimeits)/Ntimeits,))
который в моих системах (пока я выполняю резервное копирование) дает:
numpy: 3.004e-05
list: 5.363e-04
Вот хороший ответ из FAQ на веб-сайте scipy.org:
Какие преимущества предлагают массивы NumPy перед (вложенными) списками Python?
Списки Python являются эффективными контейнерами общего назначения. Они поддерживают (достаточно) эффективную вставку, удаление, добавление и объединение, а понимание списков в Python позволяет легко создавать и манипулировать ими. Однако у них есть определенные ограничения: они не поддерживают "векторизованные" операции, такие как поэлементное сложение и умножение, и тот факт, что они могут содержать объекты разных типов, означает, что Python должен хранить информацию о типе для каждого элемента и должен выполнять код диспетчеризации типов. при работе на каждом элементе. Это также означает, что очень мало операций со списком может быть выполнено эффективными циклами Си - каждая итерация потребует проверки типов и другой бухгалтерии API Python.
Все они выдвинули на первый план почти все основные различия между массивом numpy и списком Python, я просто опишу их здесь:
При создании Numpy массивы имеют фиксированный размер, в отличие от списков Python (которые могут динамически расти). Изменение размера ndarray создаст новый массив и удалит оригинал.
Все элементы в массиве Numpy должны быть одного типа данных (мы также можем иметь гетерогенный тип, но это не позволит вам выполнять математические операции) и, следовательно, иметь одинаковый размер в памяти
Numpy массивы облегчают продвижение математических и других типов операций на большом количестве данных. Как правило, такие операции выполняются более эффективно и с меньшим количеством кода, чем это возможно при использовании последовательностей питонов
Также обратите внимание, что есть поддержка временных рядов на основе NumPy в scikits временных рядов:
http://pytseries.sourceforge.net/
Для регрессии я почти уверен, что NumPy будет на несколько порядков быстрее и удобнее, чем списки даже для задачи 100^3.
Стандартным изменяемым многоэлементным контейнером в Python является список. Благодаря динамической типизации Python мы можем даже создавать гетерогенные списки. Чтобы разрешить эти гибкие типы, каждый элемент в списке должен содержать информацию о собственном типе, счетчике ссылок и другую информацию. То есть каждый элемент является полным объектом Python. В особом случае, когда все переменные одного типа, большая часть этой информации является избыточной; гораздо эффективнее хранить данные в массиве фиксированного типа (в стиле NumPy). Массивы фиксированного типа в стиле NumPy лишены этой гибкости, но гораздо более эффективны для хранения данных и управления ими.
- NumPy - это не другой язык программирования, а модуль расширения Python. Он обеспечивает быстрые и эффективные операции с массивами однородных данных. Numpy имеет фиксированный размер создания.
- В Python: списки записываются в квадратных скобках. Эти списки могут быть однородными или разнородными.
- Основные преимущества использования Numpy Arrays Over Python Lists:
- Он потребляет меньше памяти.
- Быстро по сравнению со списком Python.
- Удобно использовать.
По скорости я не уверен в этом. Вот краткий пример: я создал функцию (из x), которая возвращает список простых чисел от 2 до x:
Обычная функция Python с использованием списков:
def findprimeupto(x): primes = [] n_primes = [] for i in range(2, x): if not (i in n_primes): primes.append(i) n_primes.append(i) for j in range(len(primes)): if i > n_primes[j]: n_primes[j] += primes[j] return primes import time start_time = time.time() findprimeupto(10000) print("--- %s seconds ---" % str(time.time() - start_time))
и C-подобная функция Python с использованием массивов NumPy:
import numpy def findprimeupto(x): primes = numpy.array(numpy.zeros(x), dtype=numpy.int32) n_primes = numpy.array(numpy.zeros(x), dtype=numpy.int32) primeslen = 0 for i in range(2, x): flag = 1 for j in range(primeslen): if n_primes[j] == i: flag = 0 break if flag: primes[primeslen] = i n_primes[primeslen] = i primeslen += 1 for j in range(primeslen): if i > n_primes[j]: n_primes[j] += primes[j] return [primeslen, primes] import time start_time = time.time() result = findprimeupto(10000) #for i in range(result[0]): # print('{:d} '.format(result[1][i]), end="") print() print("--- %s seconds ---" % str(time.time() - start_time))
Первая, предположительно медленная реализация с использованием списков, выполняется за 0,6 секунды, а более поздняя, предположительно быстрая реализация NumPy, требует 50 секунд. Если кто-то может указать, почему я очень ценю это.
Кстати, программа на чистом C, которая является более или менее копией версии функции NumPy, выполняется менее чем за 0,04 с. Скорость C еще более очевидна при большом x:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void findprimesupto(int n, int *primeslen, int *primes, int *n_primes) {
int i, j, flag;
*primeslen = 0;
for (i=2; i <= n; i++) {
for (j=0, flag=1; j < *primeslen; j++)
if (n_primes[j] == i) {
flag = 0;
break;
}
if (flag) {
primes[*primeslen] = i;
n_primes[*primeslen] = i;
(*primeslen)++;
}
for (j=0; j < *primeslen; j++)
if (i > n_primes[j])
n_primes[j] += primes[j];
}
}
int main() {
int n = 10000, primeslen = 0, i;
int *primes, *n_primes;
clock_t start, diff;
start = clock();
primes = malloc(n * sizeof(int));
n_primes = malloc(n * sizeof(int));
findprimesupto(n, &primeslen, primes, n_primes);
/* for (i=0; i < primeslen; i++)
printf("%d ", primes[i]);
printf("\n");
*/
diff = clock() - start;
printf("Time: %f s\n", (float) diff / (float) CLOCKS_PER_SEC);
free(primes);
free(n_primes);
return 0;
}