Каковы преимущества NumPy над регулярными списками Python?

Каковы преимущества NumPy перед обычными списками Python?

У меня около 100 серий финансовых рынков, и я собираюсь создать массив кубов размером 100x100x100 = 1 миллион ячеек. Я буду регрессировать (с 3 переменными) каждый x с каждым y и z, чтобы заполнить массив стандартными ошибками.

Я слышал, что для "больших матриц" я должен использовать NumPy, а не списки Python, из соображений производительности и масштабируемости. Дело в том, что я знаю списки Python, и они, кажется, работают для меня.

Каковы будут преимущества, если я перейду на NumPy?

Что если бы у меня было 1000 рядов (то есть 1 миллиард ячеек с плавающей запятой в кубе)?

Ответ 1

Массивы NumPy более компактны, чем списки Python - список списков, которые вы описываете, в Python, будет занимать не менее 20 МБ или около того, в то время как массив NumPy 3D с плавающей точкой с одинарной точностью в ячейках будет вписываться в 4 MB. Доступ к чтению и записи элементов также выполняется быстрее с помощью NumPy.

Возможно, вам все равно, что всего за миллион ячеек, но вы определенно хотели бы за миллиард ячеек - ни один подход не поместился бы в 32-битной архитектуре, но с 64-битными сборками NumPy уйдет с 4 GB или около того, одному Python понадобится по крайней мере около 12 ГБ (много указателей, размер которых вдвое больше) - гораздо более дорогостоящее устройство!

Разница в основном обусловлена ​​ "косвенностью" - список Python представляет собой массив указателей на объекты Python, по меньшей мере 4 байта на указатель плюс 16 байтов для даже самого маленького объекта Python (4 для указателя типа, 4 для справки count, 4 для значения - и распределители памяти округляются до 16). Массив NumPy представляет собой массив однородных значений - числа с одинарной точностью занимают по 4 байта, с двойной точностью - 8 байтов. Менее гибкий, но вы платите за гибкость стандартных списков Python!

Ответ 2

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 стоит усилий.

Ответ 3

Алекс упомянул об эффективности памяти, и Роберто упомянул удобство, и это оба хорошие моменты. В качестве еще нескольких идей я упомянул скорость и функциональность.

Функциональность: вы получаете много встроенных функций с NumPy, 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

Ответ 4

Вот хороший ответ из FAQ на сайт scipy.org:

Какие преимущества предлагают массивы NumPy над (вложенными) списками Python?

Списки Pythons - это эффективные контейнеры общего назначения. Они поддерживают (справедливо) эффективная вставка, удаление, добавление и конкатенация, и понимание Pythons-списков упрощает их создание и манипулировать. Однако они имеют определенные ограничения: они не поддерживают "векторизованные" операции, такие как элементное сложение и умножение, и тот факт, что они могут содержать объекты разных типов, означает что Python должен хранить информацию о типе для каждого элемента и должен введите код диспетчеризации типа при работе на каждом элементе. Эта также означает, что очень немногие операции с списками могут выполняться посредством эффективные C-циклы - каждая итерация потребует проверки типов и других Бухгалтерия API Python.

Ответ 5

Обратите также внимание на то, что существует поддержка таймсерий, основанных на NumPy, в записях времени:

http://pytseries.sourceforge.net

Для регрессии я уверен, что NumPy будет на порядок быстрее и удобнее списков даже для проблемы 100 ^ 3.

Ответ 6

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

  1. При создании Numpy массивы имеют фиксированный размер, в отличие от списков Python (которые могут динамически расти). Изменение размера ndarray создаст новый массив и удалит оригинал.

  2. Все элементы в массиве Numpy должны быть одного и того же типа данных (мы также можем иметь гетерогенный тип, но это не позволит вам выполнять математические операции) и, следовательно, иметь одинаковый размер в памяти

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

Ответ 7

Numpy предоставляет ряд очень мощных математических инструментов для работы с массивами или матрицами numpy, если хотите. Поскольку numpy очень хорошо оптимизирован и написан на C, он работает намного быстрее, чем если бы вы написали свой код на python. Не говоря уже о том, что было бы больно в задницу самому кодировать все функции.

Ответ 8

Скорее, я не уверен. Вот краткий пример: я создал функцию (из 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 секунд. Если кто-то может указать, почему я был бы очень благодарен.

BTW, чистая программа C, которая является более или менее копией версии функции NumPy функции, выполняется менее чем за 0,04 s. Скорость 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;
    }