NumPy — это библиотека Python, которая предоставляет простую, но мощную структуру данных: n-мерный массив. Это фундамент, на котором построена почти вся мощь инструментария Python для обработки данных, а изучение NumPy — это первый шаг на пути любого ученого-специалиста по Python. Этот урок предоставит вам знания, необходимые для использования NumPy и библиотек более высокого уровня, которые на него полагаются.

В этом руководстве вы узнаете:

  • Какие основные концепции науки о данных стали возможны с помощью NumPy.
  • Как создавать массивы NumPy разными методами.
  • Как манипулировать массивами NumPy для выполнения полезных вычислений.
  • Как применить эти новые навыки к реальным проблемам.

Чтобы получить максимальную отдачу от урока по NumPy, вы должны быть знакомы с написанием кода Python. «Курс молодого бойца — Python» — отличный способ убедиться, что у вас есть базовые навыки. Знания матричной математики так-же будут полезно. Однако, ничего не нужно знать о науке о данных. Вы узнаете это здесь.

Содержание

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

Выбор NumPy: преимущества

Поскольку вы уже знаете Python, вы можете спросить себя, действительно ли вам нужно изучать совершенно новую парадигму, чтобы заниматься наукой о данных. Циклы for в Python потрясающие! Чтение и запись файлов CSV можно выполнять с помощью традиционного кода. Однако есть несколько убедительных аргументов в пользу изучения новой парадигмы.

Вот четыре основных преимущества, которые NumPy может дать вашему коду:

  1. Больше скорости: NumPy использует алгоритмы, написанные на C, которые выполняются за наносекунды, а не за секунды.
  2. Меньше циклов: NumPy помогает сократить количество циклов и не запутаться в индексах итераций.
  3. Более четкий код: без циклов,ваш код будет больше похож на уравнения, которые вы пытаетесь вычислить.
  4. Лучшее качество: тысячи участников работают над тем, чтобы NumPy оставался быстрым, дружелюбным и свободным от ошибок.

Благодаря этим преимуществам NumPy является стандартом де-факто для многомерных массивов в науке о данных Python, и многие из самых популярных библиотек построены на его основе. Изучение NumPy — отличный способ заложить прочную основу, поскольку вы расширяете свои знания в более конкретные области науки о данных.

Установка NumPy

Пришло время все настроить, чтобы вы могли начать учиться работать с NumPy. Есть несколько способов сделать это, и вы не ошибетесь, следуя инструкциям на веб-сайте NumPy. Но есть некоторые дополнительные детали, о которых следует помнить, которые изложены ниже.

Вы также будете устанавливать Matplotlib. Вы будете использовать его в одном из следующих примеров, чтобы изучить, как другие библиотеки используют NumPy.

Использование Repl.it в качестве онлайн-редактора

Если вы просто хотите начать с некоторых примеров, следуйте инструкциям по этому руководству и начните наращивать мышечную память с помощью NumPy, тогда Repl.it — ​​отличный вариант для редактирования в браузере. Вы можете зарегистрироваться и запустить среду Python за считанные минуты. Слева есть вкладка для пакетов. Вы можете добавить столько, сколько хотите.В этом руководстве по NumPy используйте текущие версии NumPy и Matplotlib.

Установка NumPy с Anaconda

Дистрибутив Anaconda — это набор стандартных инструментов Python для обработки данных, связанных с менеджером пакетов, который помогает управлять вашими виртуальными средами и зависимостями проекта. Он построен на основе conda, которая фактически является менеджером пакетов. Это метод, рекомендованный проектом NumPy,особенно если вы начинаете заниматься наукой о данных в Python, еще не настроив сложную среду разработки.

Если у вас уже есть рабочий процесс, который вам нравится, в котором используются pip, Pipenv, Poetry или какой-либо другой набор инструментов, тогда, возможно, лучше не добавлять conda в микс. Репозиторий пакетов conda отделен от PyPI, а сама conda создает отдельный маленький островок пакетов на вашем компьютере, поэтому управление путями и запоминание того, какой пакет живет где, может быть кошмаром.

После того, как вы установили conda, вы можете запустить команду установки необходимых вам библиотек:

$ conda install numpy matplotlib

Это установит все, что вам нужно для этого руководства по NumPy, и все будет готово.

Установка NumPy с помощью pip

Хотя проект NumPy рекомендует использовать conda, если вы начинаете с нуля, нет ничего плохого в том, чтобы самостоятельно управлять своей средой и просто использовать старый добрый pip, Pipenv, Poetry или любую другую альтернативу pip, которая вам больше всего нравится.

Вот команды для настройки с помощью pip:

$ mkdir numpy-tutorial
$ cd numpy-tutorial
$ python3 -m venv .numpy-tutorial-venv
$ source .numpy-tutorial-venv/bin/activate

(.numpy-tutorial-venv)
$ pip install numpy matplotlib
Collecting numpy
  Downloading numpy-1.19.1-cp38-cp38-macosx_10_9_x86_64.whl (15.3 MB)
     |████████████████████████████████| 15.3 MB 2.7 MB/s
Collecting matplotlib
  Downloading matplotlib-3.3.0-1-cp38-cp38-macosx_10_9_x86_64.whl (11.4 MB)
     |████████████████████████████████| 11.4 MB 16.8 MB/s
...

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

Использование IPython, Notebooks или JupyterLab

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

IPython — это обновленный цикл чтения-оценки-печати Python (REPL), который делает редактирование кода в сеансе реального интерпретатора более простым и красивым. Вот как выглядит сеанс REPL IPython:


In [2]: digits = np.array([
   ...:     [1, 2, 3],
   ...:     [4, 5, 6],
   ...:     [6, 7, 9],
   ...: ])

In [3]: digits
Out[3]:
array([ [1, 2, 3],
       [4, 5, 6],
       [6, 7, 9] ])

Он имеет несколько отличий от базового REPL Python, включая номера строк, использование цветов и качество визуализации массивов. Также есть много бонусов для пользовательского опыта, которые делают более приятным ввод, повторный ввод и редактирование кода.

Вы можете установить IPython отдельно:

$ pip install ipython

В качестве альтернативы, если вы подождете и установите любой из последующих инструментов, они будут включать копию IPython.

Чуть более функциональная альтернатива REPL — это ноутбук. Однако записные книжки — это немного другой стиль написания Python, чем стандартные сценарии. Вместо традиционного файла Python они предоставляют вам серию мини-файлов. Сценарии, называемые ячейками, которые вы можете запускать и повторно запускать в любом порядке, в том же сеансе Python.

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

Вот как это выглядит:

Самым популярным предложением ноутбуков, вероятно, является Jupyter Notebook, но nteract — это еще один вариант, который объединяет функциональность Jupyter и пытается сделать его более доступным и мощным.

Однако, если вы смотрите на Jupyter Notebook и думаете, что ему нужно больше возможностей, подобных IDE, то другой вариант — JupyterLab. Вы можете настраивать текстовые редакторы, записные книжки, терминалы и пользовательские компоненты в интерфейсе на основе браузера. Вероятно, это будет удобнее для людей, пришедших из MatLab. Это самое молодое из предложений, но его версия 1.0 была выпущена еще в 2019 году, поэтому она должна быть стабильной и полнофункциональной.

Вот как выглядит интерфейс:

Какой бы вариант вы ни выбрали, после его установки вы будете готовы запустить свои первые строки кода NumPy. Пришло время для первого примера.

Здравствуйте, NumPy: руководство по выставлению оценок за тест на изгиб

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

  • Создание массивов с использованием numpy.array()
  • Обработка полных массивов как отдельных значений, чтобы сделать векторизованные вычисления более удобочитаемыми
  • Использование встроенных функций NumPy для изменения и агрегирования данных

Эти концепции составляют основу эффективного использования NumPy.

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

Однако это будет относительно элементарное исправление. Вы возьмете любой средний балл и объявите, что это C. Кроме того, вы убедитесь, что кривая случайно не повредит успеваемость ваших учеников и не поможет настолько, что ученик успеет лучше, чем 100%.

Введите этот код в свой REPL:

>>> import numpy as np
>>> CURVE_CENTER = 80
>>> grades = np.array([72, 35, 64, 88, 51, 90, 74, 12])
>>> def curve(grades):
...     average = grades.mean()
...     change = CURVE_CENTER - average
...     new_grades = grades + change
...     return np.clip(new_grades, grades, 100)
...
>>> curve(grades)
array([ 91.25,  54.25,  83.25, 100.  ,  70.25, 100.  ,  93.25,  31.25])

Исходные оценки были увеличены в зависимости от того, где они находились в колоде, но ни один из них не был повышен выше 100%.

Вот важные моменты:

  • Строка 1 импортирует NumPy с использованием псевдонима np, что является общепринятым соглашением, позволяющим сэкономить несколько нажатий клавиш.
  • Строка 3 создает ваш первый массив NumPy, который является одномерным и имеет форму (8,) и тип данных int64. Пока не стоит особо беспокоиться об этих деталях. Вы изучите их более подробно позже в этом руководстве.
  • В строке 5 берется среднее значение всех оценок с использованием .mean(). У массивов много методов.

В строке 7 вы сразу пользуетесь двумя важными концепциями:

  1. Векторизация
  2. Вещание или Broadcasting

Векторизация — это процесс выполнения одной и той же операции одинаковым образом для каждого элемента в массиве. Это удаляет циклы for из вашего кода, но дает тот же результат.

Broadcasting — это процесс расширения двух массивов разной формы и выяснения того, как выполнять векторизованные вычисления между ними. Помните, Grades — это массив чисел формы (8,), а изменение — это скаляр или одно число, по существу, имеющее форму (1,). В этом случае NumPy добавляет скаляр к каждому элементу в массиве и возвращает новый массив с результатами.

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

В этом случае, вам нужна функция, которая принимает массив и следит за тем, чтобы значения не превышали заданный минимум или максимум. clip() делает именно это.

Строка 8 также представляет собой еще один пример широковещательной передачи. Для второго аргумента clip() вы передаете оценки, гарантируя, что каждая новая оценка не будет ниже исходной. Но для третьего аргументавы передаете единственное значение: 100. NumPy принимает это значение и передает его каждому элементу в new_grades, гарантируя, что ни одна из недавно изогнутых оценок не превышает идеального результата.

Переход к форме: формы и оси массивов

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

Векторы, представляющие собой одномерные массивы чисел, легче всего отслеживать. Два измерения тоже не так уж и плохи, потому что они похожи на электронные таблицы. Но все начинает усложняться в трех измерениях, а визуализировать четыре? Забудь об этом.

Освоение формы

Форма — это ключевая концепция при использовании многомерных массивов. В какой-то момент легче забыть о визуализации формы ваших данных и вместо этого следовать некоторым мысленным правилам и доверять NumPy, чтобы сказать вам правильную форму.

Все массивы имеют свойство с именем .shape, которое возвращает кортеж размера в каждом измерении. Менее важно, какое измерение является каким, но очень важно, чтобы массивы, которые вы передаете функциям, имели форму, которую ожидают функции. Обычный способ убедиться, что ваши данные имеют правильную форму, — это распечатать данные и их форму до тех пор, пока вы не убедитесь, что все работает так, как вы ожидаете.

Следующий пример покажет этот процесс. Вы создадите массив сложной формы, проверите его и измените порядок, чтобы он выглядел так, как должен:

In [1]: import numpy as np

In [2]: temperatures = np.array([
   ...:     29.3, 42.1, 18.8, 16.1, 38.0, 12.5,
   ...:     12.6, 49.9, 38.6, 31.3, 9.2, 22.2
   ...: ]).reshape(2, 2, 3)

In [3]: temperatures.shape
Out[3]: (2, 2, 3)

In [4]: temperatures
Out[4]:
array([ [ [29.3, 42.1, 18.8],
        [16.1, 38. , 12.5] ],

       [ [12.6, 49.9, 38.6],
        [31.3,  9.2, 22.2] ] ])

In [5]: np.swapaxes(temperatures, 1, 2)
Out[5]:
array([ [ [29.3, 16.1],
        [42.1, 38. ],
        [18.8, 12.5] ],

       [ [12.6, 31.3],
        [49.9,  9.2],
        [38.6, 22.2] ] ])

Здесь вы используете метод numpy.ndarray с именем .reshape() для формирования блока данных размером 2 × 2 × 3. Когда вы проверяете форму вашего массива во входных данных 3, это именно то, что вы ему сказали. Однако вы можете видеть, как быстро становится трудно визуализировать напечатанные массивы в трех или более измерениях. После того, как вы поменяете оси с помощью .swapaxes (), становится немного яснее, какое измерение есть какое. Подробнее об осях вы узнаете в следующем разделе.

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

Что такое топоры

Приведенный выше пример показывает, насколько важно знать не только, в какой форме находятся ваши данные, но и какие данные находятся на какой оси. В массивах NumPy оси имеют нулевой индекс и определяют, какое измерение является каким. Например, двумерный массив имеет вертикальную ось (ось 0) и горизонтальную ось (ось 1). Многие функции и команды в NumPy изменяют свое поведение в зависимости от того, какую ось вы им приказываете обрабатывать.

В этом примере будет показано, как .max() ведет себя по умолчанию, без аргумента оси, и как он меняет функциональность в зависимости от того, какую ось вы укажете, когда действительно указываете аргумент:

In [1]: import numpy as np

In [2]: table = np.array([
   ...:     [5, 3, 7, 1],
   ...:     [2, 6, 7 ,9],
   ...:     [1, 1, 1, 1],
   ...:     [4, 3, 2, 0],
   ...: ])

In [3]: table.max()
Out[3]: 9

In [4]: table.max(axis=0)
Out[4]: array([5, 6, 7, 9])

In [5]: table.max(axis=1)
Out[5]: array([7, 9, 1, 4])

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

Фактически, многие функции NumPy ведут себя следующим образом: если ось не указана, они выполняют операцию со всем набором данных. Иначе, они выполняют операцию по оси.

Broadcasting

До сих пор вы видели несколько небольших примеров Broadcasting, но чем больше примеров вы увидите, тем яснее станет эта тема. По сути, он работает вокруг одного правила: массивы могут транслироваться друг против друга, если их размеры совпадают или если один из массивов имеет размер 1.

Если массивы совпадают по размеру вдоль оси,тогда элементы будут обрабатываться поэлементно, подобно тому, как работает встроенная функция Python zip(). Если один из массивов имеет размер 1 по оси, то это значение будет транслироваться по этой оси или дублироваться столько раз, сколько необходимо, чтобы соответствовать количеству элементов вдоль этой оси в другом массиве.

Вот небольшой пример. Массив A имеет форму (4, 1, 8), а массив B имеет форму (1, 6, 8). Основываясь на приведенных выше правилах, вы можете работать с этими массивами вместе:

  • На оси 0 у A есть 4, а у B — 1, поэтому B может транслироваться по этой оси.
  • На оси 1 у A есть 1, а у B — 6, поэтому A может транслироваться по этой оси.
  • На оси 2 два массива имеют совпадающие размеры,чтобы они могли успешно работать.

Все три оси успешно следуют правилу.

Вы можете настроить массивы следующим образом:

In [1]: import numpy as np

In [2]: A = np.arange(32).reshape(4, 1, 8)

In [3]: A
Out[3]:
array([ [ [ 0,  1,  2,  3,  4,  5,  6,  7] ],

       [ [ 8,  9, 10, 11, 12, 13, 14, 15] ],

       [ [16, 17, 18, 19, 20, 21, 22, 23] ],

       [ [24, 25, 26, 27, 28, 29, 30, 31] ] ])

In [4]: B = np.arange(48).reshape(1, 6, 8)

In [5]: B
Out[5]:
array([ [ [ 0,  1,  2,  3,  4,  5,  6,  7],
        [ 8,  9, 10, 11, 12, 13, 14, 15],
        [16, 17, 18, 19, 20, 21, 22, 23],
        [24, 25, 26, 27, 28, 29, 30, 31],
        [32, 33, 34, 35, 36, 37, 38, 39],
        [40, 41, 42, 43, 44, 45, 46, 47] ] ])

У A 4 плоскости, каждая с 1 строкой и 8 столбцами. B имеет только 1 плоскость с 6 рядами и 8 столбцами. Посмотрите, что NumPy делает для вас, когда вы пытаетесь произвести расчет между ними!

Сложите два массива вместе:

In [7]: A + B
Out[7]:
array([ [ [ 0,  2,  4,  6,  8, 10, 12, 14],
        [ 8, 10, 12, 14, 16, 18, 20, 22],
        [16, 18, 20, 22, 24, 26, 28, 30],
        [24, 26, 28, 30, 32, 34, 36, 38],
        [32, 34, 36, 38, 40, 42, 44, 46],
        [40, 42, 44, 46, 48, 50, 52, 54] ],

       [ [ 8, 10, 12, 14, 16, 18, 20, 22],
        [16, 18, 20, 22, 24, 26, 28, 30],
        [24, 26, 28, 30, 32, 34, 36, 38],
        [32, 34, 36, 38, 40, 42, 44, 46],
        [40, 42, 44, 46, 48, 50, 52, 54],
        [48, 50, 52, 54, 56, 58, 60, 62] ],

       [ [16, 18, 20, 22, 24, 26, 28, 30],
        [24, 26, 28, 30, 32, 34, 36, 38],
        [32, 34, 36, 38, 40, 42, 44, 46],
        [40, 42, 44, 46, 48, 50, 52, 54],
        [48, 50, 52, 54, 56, 58, 60, 62],
        [56, 58, 60, 62, 64, 66, 68, 70] ],

       [ [24, 26, 28, 30, 32, 34, 36, 38],
        [32, 34, 36, 38, 40, 42, 44, 46],
        [40, 42, 44, 46, 48, 50, 52, 54],
        [48, 50, 52, 54, 56, 58, 60, 62],
        [56, 58, 60, 62, 64, 66, 68, 70],
        [64, 66, 68, 70, 72, 74, 76, 78] ] ])

Принцип работы broadcasting заключается в том, что NumPy дублирует плоскость в B три раза, так что у вас всего четыре, что соответствует количеству плоскостей в A. Он также дублирует одну строку в A пять раз, всего шесть, что соответствует числу. строк в B. Затем он добавляет каждый элемент во вновь развернутом массиве A к его аналогу в том же месте в B. Результат каждого вычисления отображается в соответствующем месте вывода.

Примечание. Это хороший способ создать массив из диапазона с помощью arange()!

Еще раз, даже если вы можете использовать такие слова, как «плоскость», «строка» и «столбец», чтобы описать, как фигуры в этом примере транслируются для создания совпадающих трехмерных фигур, все становится сложнее в более высоких измерениях. Часто вам придется просто соблюдать правила вещания и много печатать, чтобы убедиться, что все работает по плану.

Понимание broadcasting — важная часть освоения векторизованных вычислений, а векторизованные вычисления — это способ написать чистый идиоматический код NumPy.

Операции обработки данных: фильтр, порядок, агрегирование

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

Индексирование

Индексирование использует многие из тех идиом, которые использует обычный код Python. Вы можете использовать положительные или отрицательные индексы для индексации спереди или сзади массива. Вы можете использовать двоеточие (:), чтобы указать «остальное» или «все», и вы даже можете использовать два двоеточия для пропуска элементов, как в обычных списках Python.

В чем разница: в массивах NumPy между осями используются запятые, поэтому вы можете индексировать несколько осей в одном наборе квадратных скобок. Пример — самый простой способ показать это. Пора подтвердить магический квадрат Дюрера!

Числовой квадрат ниже обладает некоторыми удивительными свойствами. Если сложить строки, столбцы или диагонали, получится то же число — 34. То же самое вы получите, если сложите каждый из четырех квадрантов, четыре центральных квадрата, четыре угловых квадрата или четыре угловых квадрата любой из имеющихся сеток 3 × 3. Вы собираетесь это доказать!

Интересный факт: в нижнем ряду числа 15 и 14 находятся посередине, представляя год, когда Дюрер создал этот квадрат. Цифры 1 и 4 также находятся в этом ряду, представляя первую и четвертую буквы алфавита, A и D, которые являются инициалами создателя квадрата, Альбрехта Дюрера!

Введите в REPL следующее:

In [1]: import numpy as np

In [2]: square = np.array([
   ...:     [16, 3, 2, 13],
   ...:     [5, 10, 11, 8],
   ...:     [9, 6, 7, 12],
   ...:     [4, 15, 14, 1]
   ...: ])

In [3]: for i in range(4):
   ...:     assert square[:, i].sum() == 34
   ...:     assert square[i, :].sum() == 34
   ...:

In [4]: assert square[:2, :2].sum() == 34

In [5]: assert square[2:, :2].sum() == 34

In [6]: assert square[:2, 2:].sum() == 34

In [7]: assert square[2:, 2:].sum() == 34

Внутри цикла for вы проверяете, что сумма всех строк и всех столбцов равна 34. После этого, используя выборочную индексацию, вы проверяете, что каждый из квадрантов также составляет 34.

И последнее, что следует отметить, это то, что вы можете взять сумму любого массива, чтобы сложить все его элементы глобально с помощью square.sum(). Этот метод также может принимать аргумент оси, чтобы вместо этого выполнять суммирование по осям.

Маскирование и фильтрация

Выбор на основе индекса — это хорошо, но что, если вы хотите отфильтровать данные на основе более сложных, неоднородных или непоследовательных критериев? Здесь в игру вступает концепция маски.

Маска — это массив, который имеет ту же форму, что и ваши данные, но вместо ваших значений он содержит логические значения: True или False. Вы можете использовать этот массив масок для индексации в массиве данных нелинейными и сложными способами. Он вернет все элементы, в которых логический массив имеет значение True.

Вот пример, показывающий процесс сначала в замедленной съемке, а затем, как это обычно делается, в одной строке:

In [1]: import numpy as np

In [2]: numbers = np.linspace(5, 50, 24, dtype=int).reshape(4, -1)

In [3]: numbers
Out[3]:
array([ [ 5, 6,  8, 10, 12, 14],
       [16, 18, 20, 22, 24, 26],
       [28, 30, 32, 34, 36, 38],
       [40, 42, 44, 46, 48, 50] ])

In [4]: mask = numbers % 4 == 0

In [5]: mask
Out[5]:
array([ [False, False,  True, False,  True, False],
       [ True, False,  True, False,  True, False],
       [ True, False,  True, False,  True, False],
       [ True, False,  True, False,  True, False] ])

In [6]: numbers[mask]
Out[6]: array([ 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48])

In [7]: by_four = numbers[numbers % 4 == 0]

In [8]: by_four
Out[8]: array([ 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48])

Вы увидите объяснение новых приемов создания массива в In [2], а пока сосредоточимся на сути примера. Это важные части:

  • In [4] создает маску, выполняя векторизованное логическое вычисление, беря каждый элемент и проверяя, делится ли на четыре без остатка. Возвращается массив масок той же формы с поэлементными результатами вычислений.
  • In [6] использует эту маску для индексации исходного массива чисел. Это приводит к тому, что массив теряет свою исходную форму, уменьшая его до одного измерения, но вы по-прежнему получаете данные, которые ищете.
  • In [7] обеспечивает более традиционный, идиоматический замаскированный выбор, который вы можете увидеть в природе, с анонимным фильтрующим массивом, созданным встроенным в скобки выбора. Этот синтаксис аналогичен использованию в языке программирования R.

Возвращаясь ко In [2], вы сталкиваетесь с тремя новыми концепциями:

  • Использование np.linspace() для создания равномерно распределенного массива
  • Установка dtype вывода
  • Изменение формы массива с -1

np.linspace() генерирует n чисел, равномерно распределенных между минимумом и максимумом, что полезно для равномерно распределенной выборки при научном построении графиков.

Благодаря конкретному вычислению в этом примере, наличие целых чисел в массиве чисел упрощает жизнь. Но поскольку пространство между 5 и 50 не делится равномерно на 24, результирующие числа будут числами с плавающей запятой. Вы указываете dtype типа int, чтобы функция округлялась в меньшую сторону и выдавала целые числа. Позже вы увидите более подробное обсуждение типов данных.

Наконец, array.reshape() может принимать -1 в качестве одного из размеров своих измерений. Это означает, что NumPy должен просто выяснить, насколько большой должна быть эта конкретная ось, исходя из размера других осей. В этом случае с 24 значениями и размером 4 по оси 0 ось 1 заканчивается размером 6.

Вот еще один пример, демонстрирующий возможности маскированной фильтрации.Нормальное распределение — это распределение вероятностей, в котором примерно 95,45% значений находятся в пределах двух стандартных отклонений от среднего.

Вы можете проверить это с небольшой помощью модуля NumPy random для генерации случайных значений:

In [1]: import numpy as np

In [2]: from numpy.random import default_rng

In [3]: rng = default_rng()

In [4]: values = rng.standard_normal(10000)

In [5]: values[:5]
Out[5]: array([ .9779210858,  1.8361585253,  -.3641365235,
               -.1311344527, 1.286542056 ])

In [6]: std = values.std()

In [7]: std
Out[7]: .9940375551073492

In [8]: filtered = values[(values > -2 * std) & (values < 2 * std)]

In [9]: filtered.size
Out[9]: 9565

In [10]: values.size
Out[10]: 10000

In [11]: filtered.size / values.size
Out[11]: 0.9565

Здесь вы используете потенциально странно выглядящий синтаксис для объединения условий фильтрации: бинарный оператор &. Почему так? Это потому, что NumPy обозначает & и | как векторизованные поэлементные операторы для объединения логических значений. Если вы попытаетесь выполнить A и B, то получите предупреждение о том, насколько странно истинное значение для массива, потому что и работает с истинным значением всего массива, а не поэлементно.

Транспонирование, сортировка и объединение

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

Вот перенос массива:

In [1]: import numpy as np

In [2]: a = np.array([
   ...:     [1, 2],
   ...:     [3, 4],
   ...:     [5, 6],
   ...: ])

In [3]: a.T
Out[3]:
array(1, 3, 5],
       [2, 4, 6)

In [4]: a.transpose()
Out[4]:
array(1, 3, 5],
       [2, 4, 6)

Когда вы вычисляете транспонирование массива, индексы строки и столбца каждого элемента меняются местами. Например, элемент [0, 2] становится элементом [2, 0]. Вы также можете использовать a.T как псевдоним для a.transpose().

Следующий блок кода показывает сортировку, но вы также увидите более мощный метод сортировки в следующем разделе, посвященном структурированным данным:

In [1]: import numpy as np

In [2]: data = np.array([
   ...:     [7, 1, 4],
   ...:     [8, 6, 5],
   ...:     [1, 2, 3]
   ...: ])

In [3]: np.sort(data)
Out[3]:
array([ [1, 4, 7],
       [5, 6, 8],
       [1, 2, 3] ])

In [4]: np.sort(data, axis=None)
Out[4]: array([1, 1, 2, 3, 4, 5, 6, 7, 8])

In [5]: np.sort(data, axis=0)
Out[5]:
array([ [1, 1, 3],
       [7, 2, 4],
       [8, 6, 5] ])

Отсутствие аргумента оси автоматически выбирает последнее и самое внутреннее измерение, которым в этом примере являются строки. Использование None сглаживает массив и выполняет глобальную сортировку. В противном случае вы можете указать, какую ось хотите. В Out[5] каждый столбец массива по-прежнему содержит все его элементы, но они отсортированы по возрастанию внутри этого столбца..

Наконец, вот пример конкатенации. Хотя существует функция np.concatenate(), есть также ряд вспомогательных функций, которые иногда легче читать.

Вот некоторые примеры:

In [1]: import numpy as np

In [2]: a = np.array([
   ...:     [4, 8],
   ...:     [6, 1]
   ...: ])

In [3]: b = np.array([
   ...:     [3, 5],
   ...:     [7, 2],
   ...: ])

In [4]: np.hstack((a, b))
Out[4]:
array([ [4, 8, 3, 5],
       [6, 1, 7, 2] ])

In [5]: np.vstack((b, a))
Out[5]:
array([ [3, 5],
       [7, 2],
       [4, 8],
       [6, 1] ])

In [6]: np.concatenate((a, b))
Out[6]:
array([ [4, 8],
       [6, 1],
       [3, 5],
       [7, 2] ])

In [7]: np.concatenate((a, b), axis=None)
Out[7]: array([4, 8, 6, 1, 3, 5, 7, 2])

In [4] и In [5] показывают чуть более интуитивно понятные функции hstack() и vstack(). In [6] и In [7] показывают более общий метод concatenate(), сначала без аргумента оси, а затем с axis=None. Это поведение сглаживания похоже по форме на то, что вы только что видели с помощью sort(). Следует отметить один важный камень преткновения: все эти функции в качестве первого аргумента принимают кортеж массивов, а не переменное количество аргументов, как вы могли ожидать. Вы можете это сказать, потому что здесь есть лишняя пара круглых скобок.

Агрегирование

Ваша последняя остановка в этом туре по функциональности перед тем, как погрузиться в более сложные темы и примеры, - это агрегирование. Вы уже видели довольно много методов агрегирования, включая .sum(), .max (), .mean () и .std (). Вы можете ссылаться на более обширную библиотеку функций NumPy, чтобы увидеть больше. Многие математические, финансовые,а статистические функции используют агрегирование, чтобы помочь вам уменьшить количество измерений в ваших данных.

Практический пример 1. Реализация серии Маклорена

Пришло время увидеть реалистичный пример использования навыков, представленных в разделах выше: реализация уравнения.

Одна из самых сложных вещей при преобразовании математических уравнений в код без NumPy заключается в том, что многие визуальные сходства отсутствуют, из-за чего трудно определить, на какую часть уравнения вы смотрите, читая код. Суммирования преобразуются в более подробные циклы for, а оптимизация пределов в конечном итоге выглядит как циклы while.

Использование NumPy позволяет вам приблизиться к однозначному представлению от уравнения к коду.

В следующем примере, например, закодируйте серию Маклорена для e^x. Ряды Маклорена - это способ приближения более сложных функций с помощью бесконечного ряда суммированных членов с центром около нуля.

Например, ряд Маклорена представляет собой следующее суммирование:

e^x = \sum_{n=0}^{\infty}{\dfrac{x^n}{n!}} = 1 + x + \dfrac{x^2}{2} + \dfrac{x^3}{3} \dots

Вы складываете члены, начиная с нуля и теоретически до бесконечности. Каждый n-й член будет возведен в x до n и разделен на n!, что является обозначением факториальной операции.

Теперь пришло время поместить это в код NumPy. Создайте файл с именем maclaurin.py:

from math import e, factorial

import numpy as np

fac = np.vectorize(factorial)

def e_x(x, terms=10):
    """Approximates e^x using a given number of terms of
    the Maclaurin series
    """
    n = np.arange(terms)
    return np.sum((x ** n) / fac(n))

if __name__ == "__main__":
    print("Значение:", e ** 3)  # Using e from the standard library

    print("N (ряд)\t\tПриближение\tПогрешность")

    for n in range(1, 14):
        maclaurin = e_x(3, terms=n)
        print(f"{n}\t\t{maclaurin:.03f}\t\t{e**3 - maclaurin:.03f}")

Когда вы запустите скрипт, то должны увидеть следующий результат:

Значение: 20.085536923187664
N (ряд)		Приближение	Погрешность
1		1.000		19.086
2		4.000		16.086
3		8.500		11.586
4		13.000		7.086
5		16.375		3.711
6		18.400		1.686
7		19.412		0.673
8		19.846		0.239
9		20.009		0.076
10		20.063		0.022
11		20.080		0.006
12		20.084		0.001
13		20.085		0.000

По мере увеличения количества членов ряда ваше значение Маклорена становится все ближе и ближе к фактическому значению, а ошибка становится все меньше и меньше.

Вычисление каждого члена включает в себя взятие x в степени n и деление на n!, или факториал числа n. Сложение, суммирование и возведение в степень - это все операции, которые NumPy может векторизовать автоматически и быстро, но не для factorial().

Чтобы использовать factorial() в векторизованных вычислениях, вы должны использовать np.vectorize() для создания векторизованной версии. Документация для np.vectorize() утверждает, что это не более чем тонкая оболочка, которая применяет цикл for к заданной функции. Использование этого кода вместо обычного кода Python не дает реальных преимуществ в производительности и потенциально может привести к некоторым штрафам за накладные расходы. Однако, как вы сейчас увидите, удобство чтения огромно. Как только ваш векторизованный факториал готов, фактический код для вычисления всего ряда Маклорена становится шокирующе коротким. Это также читабельно. Что наиболее важно, это почти точно соответствует тому, как выглядит математическое уравнение:

n = np.arange(terms)
return np.sum((x ** n) / fac(n))

Это настолько важная идея, что ее следует повторить. За исключением дополнительной строки для инициализации n, код читается почти так же, как исходное математическое уравнение. Нет циклов for, временных переменных i, j, k. Просто математика.

Точно так же вы используете NumPy для математического программирования! Для дополнительной практики попробуйте выбрать одну из других серий Maclaurin и реализовать ее аналогичным образом.

Оптимизация хранилища: типы данных

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

Однако в NumPy есть еще кое-что, что нужно осветить. NumPy использует код C под капотом для оптимизации производительности, и он не может этого сделать, если все элементы в массиве не относятся к одному типу. Это не означает только тот же тип Python. Они должны быть одного и того же базового типа C, с одинаковой формой и размером в битах!

Числовые типы: int, bool, float и complex

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

В таблице ниже приведены подробные сведения об этих типах:

Название Кол-во бит Тип Python Тип NumPy
Integer 64 int np.int_
Booleans 8 bool np.bool_
Float 64 float np.float_
Complex 128 complex np.complex_

Это просто типы, которые соответствуют существующим типам Python. В NumPy также есть типы для версий каждого меньшего размера, такие как 8-, 16- и 32-битные целые числа, 32-битные числа с плавающей запятой одинарной точности и 64-битные комплексные числа одинарной точности. В документации они перечислены полностью.

Чтобы указать тип при создании массива, можно указать аргумент dtype:

In [1]: import numpy as np

In [2]: a = np.array([1, 3, 5.5, 7.7, 9.2], dtype=np.single)

In [3]: a
Out[3]: array([1. , 3. , 5.5, 7.7, 9.2], dtype=float32)

In [4]: b = np.array([1, 3, 5.5, 7.7, 9.2], dtype=np.uint8)

In [5]: b
Out[5]: array([1, 3, 5, 7, 9], dtype=uint8)

NumPy автоматически преобразует ваш независимый от платформы тип np.single в любой тип фиксированного размера, поддерживаемый вашей платформой для этого размера. В этом случае он использует np.float32. Если предоставленные вами значения не соответствуют форме указанного вами dtype, NumPy либо исправит это за вас, либо выдаст ошибку.

Типы строк: Unicode с измененным размером

Строки ведут себя немного странно в коде NumPy, потому что NumPy нужно знать, сколько байтов следует ожидать, что обычно не является фактором программирования на Python. К счастью, NumPy неплохо справляется с менее сложными случаями:

In [1]: import numpy as np

In [2]: names = np.array(["bob", "amy", "han"], dtype=str)

In [3]: names
Out[3]: array(['bob', 'amy', 'han'], dtype='<U')

In [4]: names.itemsize
Out[4]: 12

In [5]: names = np.array( ["bob", "amy", "han"] )

In [6]: names
Out[6]: array( ['bob', 'amy', 'han'], dtype='<U' )

In [7]: more_names = np.array( ["bobo", "jehosephat"] )

In [8]: 
 

NumPy - это библиотека Python, которая предоставляет простую, но мощную структуру данных: n-мерный массив. Это фундамент, на котором построена почти вся мощь инструментария Python для обработки данных, а изучение NumPy - это первый шаг на пути любого ученого-специалиста по Python.Это руководство предоставит вам знания, необходимые для использования NumPy и библиотек более высокого уровня, которые на него полагаются.

В этом руководстве вы узнаете:
  • Какие основные концепции науки о данных стали возможны с помощью NumPy
  • Как создавать массивы NumPy разными методами
  • Как манипулировать массивами NumPy для выполнения полезных вычислений
  • Как применить эти новые навыки к реальным проблемам
Чтобы получить максимальную отдачу от этого руководства по NumPy, вы должны быть знакомы с написанием кода Python. Прохождение курса «Введение в Python» - отличный способ убедиться, что у вас есть базовые навыки. Если вы знакомы с матричной математикой,тогда это тоже будет полезно. Однако вам не нужно ничего знать о науке о данных. Вы узнаете это здесь.

Содержание

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

Выбор NumPy: преимущества

Поскольку вы уже знаете Python, вы можете спросить себя, действительно ли вам нужно изучать совершенно новую парадигму, чтобы заниматься наукой о данных. Циклы for в Python потрясающие! Чтение и запись файлов CSV можно выполнять с помощью традиционного кода. Однако есть несколько убедительных аргументов в пользу изучения новой парадигмы. Вот четыре основных преимущества, которые NumPy может дать вашему коду:
  1. Больше скорости: NumPy использует алгоритмы, написанные на C, которые выполняются за наносекунды, а не за секунды.
  2. Меньше циклов: NumPy помогает сократить количество циклов и не запутаться в индексах итераций.
  3. Более четкий код: без циклов,ваш код будет больше похож на уравнения, которые вы пытаетесь вычислить.
  4. Лучшее качество: тысячи участников работают над тем, чтобы NumPy оставался быстрым, дружелюбным и свободным от ошибок.
Благодаря этим преимуществам NumPy является стандартом де-факто для многомерных массивов в науке о данных Python, и многие из самых популярных библиотек построены на его основе. Изучение NumPy - отличный способ заложить прочную основу, поскольку вы расширяете свои знания в более конкретные области науки о данных.

Установка NumPy

Пришло время все настроить, чтобы вы могли начать учиться работать с NumPy. Есть несколько способов сделать это, и вы не ошибетесь, следуя инструкциям на веб-сайте NumPy. Но есть некоторые дополнительные детали, о которых следует помнить, которые изложены ниже. Вы также будете устанавливать Matplotlib. Вы будете использовать его в одном из следующих примеров, чтобы изучить, как другие библиотеки используют NumPy.

Использование Repl.it в качестве онлайн-редактора

Если вы просто хотите начать с некоторых примеров, следуйте инструкциям по этому руководству и начните наращивать мышечную память с помощью NumPy, тогда Repl.it - ​​отличный вариант для редактирования в браузере. Вы можете зарегистрироваться и запустить среду Python за считанные минуты. Слева есть вкладка для пакетов. Вы можете добавить столько, сколько хотите.В этом руководстве по NumPy используйте текущие версии NumPy и Matplotlib.

Установка NumPy с Anaconda

Дистрибутив Anaconda - это набор стандартных инструментов Python для обработки данных, связанных с менеджером пакетов, который помогает управлять вашими виртуальными средами и зависимостями проекта. Он построен на основе conda, которая фактически является менеджером пакетов. Это метод, рекомендованный проектом NumPy,особенно если вы начинаете заниматься наукой о данных в Python, еще не настроив сложную среду разработки. Если у вас уже есть рабочий процесс, который вам нравится, в котором используются pip, Pipenv, Poetry или какой-либо другой набор инструментов, тогда, возможно, лучше не добавлять conda в микс. Репозиторий пакетов conda отделен от PyPI, а сама conda создает отдельный маленький островок пакетов на вашем компьютере, поэтому управление путями и запоминание того, какой пакет живет где, может быть кошмаром. После того, как вы установили conda, вы можете запустить команду установки необходимых вам библиотек:
$ conda install numpy matplotlib

Это установит все, что вам нужно для этого руководства по NumPy, и все будет готово.

Установка NumPy с помощью pip

Хотя проект NumPy рекомендует использовать conda, если вы начинаете с нуля, нет ничего плохого в том, чтобы самостоятельно управлять своей средой и просто использовать старый добрый pip, Pipenv, Poetry или любую другую альтернативу pip, которая вам больше всего нравится.

Вот команды для настройки с помощью pip:

$ mkdir numpy-tutorial
$ cd numpy-tutorial
$ python3 -m venv .numpy-tutorial-venv
$ source .numpy-tutorial-venv/bin/activate

(.numpy-tutorial-venv)
$ pip install numpy matplotlib
Collecting numpy
  Downloading numpy-1.19.1-cp38-cp38-macosx_10_9_x86_64.whl (15.3 MB)
     |████████████████████████████████| 15.3 MB 2.7 MB/s
Collecting matplotlib
  Downloading matplotlib-3.3.0-1-cp38-cp38-macosx_10_9_x86_64.whl (11.4 MB)
     |████████████████████████████████| 11.4 MB 16.8 MB/s
...

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

Использование IPython, Notebooks или JupyterLab

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

IPython - это обновленный цикл чтения-оценки-печати Python (REPL), который делает редактирование кода в сеансе реального интерпретатора более простым и красивым. Вот как выглядит сеанс REPL IPython:


In [2]: digits = np.array([
   ...:     [1, 2, 3],
   ...:     [4, 5, 6],
   ...:     [6, 7, 9],
   ...: ])

In [3]: digits
Out[3]:
array([ [1, 2, 3],
       [4, 5, 6],
       [6, 7, 9] ])

Он имеет несколько отличий от базового REPL Python, включая номера строк, использование цветов и качество визуализации массивов. Также есть много бонусов для пользовательского опыта, которые делают более приятным ввод, повторный ввод и редактирование кода.

Вы можете установить IPython отдельно:

$ pip install ipython

В качестве альтернативы, если вы подождете и установите любой из последующих инструментов, они будут включать копию IPython.

Чуть более функциональная альтернатива REPL - это ноутбук. Однако записные книжки - это немного другой стиль написания Python, чем стандартные сценарии. Вместо традиционного файла Python они предоставляют вам серию мини-файлов. Сценарии, называемые ячейками, которые вы можете запускать и повторно запускать в любом порядке, в том же сеансе Python.

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

Вот как это выглядит:

Самым популярным предложением ноутбуков, вероятно, является Jupyter Notebook, но nteract - это еще один вариант, который объединяет функциональность Jupyter и пытается сделать его более доступным и мощным.

Однако, если вы смотрите на Jupyter Notebook и думаете, что ему нужно больше возможностей, подобных IDE, то другой вариант - JupyterLab. Вы можете настраивать текстовые редакторы, записные книжки, терминалы и пользовательские компоненты в интерфейсе на основе браузера. Вероятно, это будет удобнее для людей, пришедших из MatLab. Это самое молодое из предложений, но его версия 1.0 была выпущена еще в 2019 году, поэтому она должна быть стабильной и полнофункциональной.

Вот как выглядит интерфейс:

Какой бы вариант вы ни выбрали, после его установки вы будете готовы запустить свои первые строки кода NumPy. Пришло время для первого примера.

Здравствуйте, NumPy: руководство по выставлению оценок за тест на изгиб

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

  • Создание массивов с использованием numpy.array()
  • Обработка полных массивов как отдельных значений, чтобы сделать векторизованные вычисления более удобочитаемыми
  • Использование встроенных функций NumPy для изменения и агрегирования данных

Эти концепции составляют основу эффективного использования NumPy.

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

Однако это будет относительно элементарное исправление. Вы возьмете любой средний балл и объявите, что это C. Кроме того, вы убедитесь, что кривая случайно не повредит успеваемость ваших учеников и не поможет настолько, что ученик успеет лучше, чем 100%.

Введите этот код в свой REPL:

>>> import numpy as np
>>> CURVE_CENTER = 80
>>> grades = np.array([72, 35, 64, 88, 51, 90, 74, 12])
>>> def curve(grades):
...     average = grades.mean()
...     change = CURVE_CENTER - average
...     new_grades = grades + change
...     return np.clip(new_grades, grades, 100)
...
>>> curve(grades)
array([ 91.25,  54.25,  83.25, 100.  ,  70.25, 100.  ,  93.25,  31.25])

Исходные оценки были увеличены в зависимости от того, где они находились в колоде, но ни один из них не был повышен выше 100%.

Вот важные моменты:

  • Строка 1 импортирует NumPy с использованием псевдонима np, что является общепринятым соглашением, позволяющим сэкономить несколько нажатий клавиш.
  • Строка 3 создает ваш первый массив NumPy, который является одномерным и имеет форму (8,) и тип данных int64. Пока не стоит особо беспокоиться об этих деталях. Вы изучите их более подробно позже в этом руководстве.
  • В строке 5 берется среднее значение всех оценок с использованием .mean(). У массивов много методов.

В строке 7 вы сразу пользуетесь двумя важными концепциями:

  1. Векторизация
  2. Вещание или Broadcasting

Векторизация - это процесс выполнения одной и той же операции одинаковым образом для каждого элемента в массиве. Это удаляет циклы for из вашего кода, но дает тот же результат.

Broadcasting - это процесс расширения двух массивов разной формы и выяснения того, как выполнять векторизованные вычисления между ними. Помните, Grades - это массив чисел формы (8,), а изменение - это скаляр или одно число, по существу, имеющее форму (1,). В этом случае NumPy добавляет скаляр к каждому элементу в массиве и возвращает новый массив с результатами.

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

В этом случае, вам нужна функция, которая принимает массив и следит за тем, чтобы значения не превышали заданный минимум или максимум. clip() делает именно это.

Строка 8 также представляет собой еще один пример широковещательной передачи. Для второго аргумента clip() вы передаете оценки, гарантируя, что каждая новая оценка не будет ниже исходной. Но для третьего аргументавы передаете единственное значение: 100. NumPy принимает это значение и передает его каждому элементу в new_grades, гарантируя, что ни одна из недавно изогнутых оценок не превышает идеального результата.

Переход к форме: формы и оси массивов

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

Векторы, представляющие собой одномерные массивы чисел, легче всего отслеживать. Два измерения тоже не так уж и плохи, потому что они похожи на электронные таблицы. Но все начинает усложняться в трех измерениях, а визуализировать четыре? Забудь об этом.

Освоение формы

Форма - это ключевая концепция при использовании многомерных массивов. В какой-то момент легче забыть о визуализации формы ваших данных и вместо этого следовать некоторым мысленным правилам и доверять NumPy, чтобы сказать вам правильную форму.

Все массивы имеют свойство с именем .shape, которое возвращает кортеж размера в каждом измерении. Менее важно, какое измерение является каким, но очень важно, чтобы массивы, которые вы передаете функциям, имели форму, которую ожидают функции. Обычный способ убедиться, что ваши данные имеют правильную форму, - это распечатать данные и их форму до тех пор, пока вы не убедитесь, что все работает так, как вы ожидаете.

Следующий пример покажет этот процесс. Вы создадите массив сложной формы, проверите его и измените порядок, чтобы он выглядел так, как должен:

In [1]: import numpy as np

In [2]: temperatures = np.array([
   ...:     29.3, 42.1, 18.8, 16.1, 38.0, 12.5,
   ...:     12.6, 49.9, 38.6, 31.3, 9.2, 22.2
   ...: ]).reshape(2, 2, 3)

In [3]: temperatures.shape
Out[3]: (2, 2, 3)

In [4]: temperatures
Out[4]:
array([ [ [29.3, 42.1, 18.8],
        [16.1, 38. , 12.5] ],

       [ [12.6, 49.9, 38.6],
        [31.3,  9.2, 22.2] ] ])

In [5]: np.swapaxes(temperatures, 1, 2)
Out[5]:
array([ [ [29.3, 16.1],
        [42.1, 38. ],
        [18.8, 12.5] ],

       [ [12.6, 31.3],
        [49.9,  9.2],
        [38.6, 22.2] ] ])

Здесь вы используете метод numpy.ndarray с именем .reshape() для формирования блока данных размером 2 × 2 × 3. Когда вы проверяете форму вашего массива во входных данных 3, это именно то, что вы ему сказали. Однако вы можете видеть, как быстро становится трудно визуализировать напечатанные массивы в трех или более измерениях. После того, как вы поменяете оси с помощью .swapaxes (), становится немного яснее, какое измерение есть какое. Подробнее об осях вы узнаете в следующем разделе.

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

Что такое топоры

Приведенный выше пример показывает, насколько важно знать не только, в какой форме находятся ваши данные, но и какие данные находятся на какой оси. В массивах NumPy оси имеют нулевой индекс и определяют, какое измерение является каким. Например, двумерный массив имеет вертикальную ось (ось 0) и горизонтальную ось (ось 1). Многие функции и команды в NumPy изменяют свое поведение в зависимости от того, какую ось вы им приказываете обрабатывать.

В этом примере будет показано, как .max() ведет себя по умолчанию, без аргумента оси, и как он меняет функциональность в зависимости от того, какую ось вы укажете, когда действительно указываете аргумент:

In [1]: import numpy as np

In [2]: table = np.array([
   ...:     [5, 3, 7, 1],
   ...:     [2, 6, 7 ,9],
   ...:     [1, 1, 1, 1],
   ...:     [4, 3, 2, 0],
   ...: ])

In [3]: table.max()
Out[3]: 9

In [4]: table.max(axis=0)
Out[4]: array([5, 6, 7, 9])

In [5]: table.max(axis=1)
Out[5]: array([7, 9, 1, 4])

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

Фактически, многие функции NumPy ведут себя следующим образом: если ось не указана, они выполняют операцию со всем набором данных. Иначе, они выполняют операцию по оси.

Broadcasting

До сих пор вы видели несколько небольших примеров Broadcasting, но чем больше примеров вы увидите, тем яснее станет эта тема. По сути, он работает вокруг одного правила: массивы могут транслироваться друг против друга, если их размеры совпадают или если один из массивов имеет размер 1.

Если массивы совпадают по размеру вдоль оси,тогда элементы будут обрабатываться поэлементно, подобно тому, как работает встроенная функция Python zip(). Если один из массивов имеет размер 1 по оси, то это значение будет транслироваться по этой оси или дублироваться столько раз, сколько необходимо, чтобы соответствовать количеству элементов вдоль этой оси в другом массиве.

Вот небольшой пример. Массив A имеет форму (4, 1, 8), а массив B имеет форму (1, 6, 8). Основываясь на приведенных выше правилах, вы можете работать с этими массивами вместе:

  • На оси 0 у A есть 4, а у B - 1, поэтому B может транслироваться по этой оси.
  • На оси 1 у A есть 1, а у B - 6, поэтому A может транслироваться по этой оси.
  • На оси 2 два массива имеют совпадающие размеры,чтобы они могли успешно работать.

Все три оси успешно следуют правилу.

Вы можете настроить массивы следующим образом:

In [1]: import numpy as np

In [2]: A = np.arange(32).reshape(4, 1, 8)

In [3]: A
Out[3]:
array([ [ [ 0,  1,  2,  3,  4,  5,  6,  7] ],

       [ [ 8,  9, 10, 11, 12, 13, 14, 15] ],

       [ [16, 17, 18, 19, 20, 21, 22, 23] ],

       [ [24, 25, 26, 27, 28, 29, 30, 31] ] ])

In [4]: B = np.arange(48).reshape(1, 6, 8)

In [5]: B
Out[5]:
array([ [ [ 0,  1,  2,  3,  4,  5,  6,  7],
        [ 8,  9, 10, 11, 12, 13, 14, 15],
        [16, 17, 18, 19, 20, 21, 22, 23],
        [24, 25, 26, 27, 28, 29, 30, 31],
        [32, 33, 34, 35, 36, 37, 38, 39],
        [40, 41, 42, 43, 44, 45, 46, 47] ] ])

У A 4 плоскости, каждая с 1 строкой и 8 столбцами. B имеет только 1 плоскость с 6 рядами и 8 столбцами. Посмотрите, что NumPy делает для вас, когда вы пытаетесь произвести расчет между ними!

Сложите два массива вместе:

In [7]: A + B
Out[7]:
array([ [ [ 0,  2,  4,  6,  8, 10, 12, 14],
        [ 8, 10, 12, 14, 16, 18, 20, 22],
        [16, 18, 20, 22, 24, 26, 28, 30],
        [24, 26, 28, 30, 32, 34, 36, 38],
        [32, 34, 36, 38, 40, 42, 44, 46],
        [40, 42, 44, 46, 48, 50, 52, 54] ],

       [ [ 8, 10, 12, 14, 16, 18, 20, 22],
        [16, 18, 20, 22, 24, 26, 28, 30],
        [24, 26, 28, 30, 32, 34, 36, 38],
        [32, 34, 36, 38, 40, 42, 44, 46],
        [40, 42, 44, 46, 48, 50, 52, 54],
        [48, 50, 52, 54, 56, 58, 60, 62] ],

       [ [16, 18, 20, 22, 24, 26, 28, 30],
        [24, 26, 28, 30, 32, 34, 36, 38],
        [32, 34, 36, 38, 40, 42, 44, 46],
        [40, 42, 44, 46, 48, 50, 52, 54],
        [48, 50, 52, 54, 56, 58, 60, 62],
        [56, 58, 60, 62, 64, 66, 68, 70] ],

       [ [24, 26, 28, 30, 32, 34, 36, 38],
        [32, 34, 36, 38, 40, 42, 44, 46],
        [40, 42, 44, 46, 48, 50, 52, 54],
        [48, 50, 52, 54, 56, 58, 60, 62],
        [56, 58, 60, 62, 64, 66, 68, 70],
        [64, 66, 68, 70, 72, 74, 76, 78] ] ])

Принцип работы broadcasting заключается в том, что NumPy дублирует плоскость в B три раза, так что у вас всего четыре, что соответствует количеству плоскостей в A. Он также дублирует одну строку в A пять раз, всего шесть, что соответствует числу. строк в B. Затем он добавляет каждый элемент во вновь развернутом массиве A к его аналогу в том же месте в B. Результат каждого вычисления отображается в соответствующем месте вывода.

Примечание. Это хороший способ создать массив из диапазона с помощью arange()!

Еще раз, даже если вы можете использовать такие слова, как «плоскость», «строка» и «столбец», чтобы описать, как фигуры в этом примере транслируются для создания совпадающих трехмерных фигур, все становится сложнее в более высоких измерениях. Часто вам придется просто соблюдать правила вещания и много печатать, чтобы убедиться, что все работает по плану.

Понимание broadcasting - важная часть освоения векторизованных вычислений, а векторизованные вычисления - это способ написать чистый идиоматический код NumPy.

Операции обработки данных: фильтр, порядок, агрегирование

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

Индексирование

Индексирование использует многие из тех идиом, которые использует обычный код Python. Вы можете использовать положительные или отрицательные индексы для индексации спереди или сзади массива. Вы можете использовать двоеточие (:), чтобы указать «остальное» или «все», и вы даже можете использовать два двоеточия для пропуска элементов, как в обычных списках Python.

В чем разница: в массивах NumPy между осями используются запятые, поэтому вы можете индексировать несколько осей в одном наборе квадратных скобок. Пример - самый простой способ показать это. Пора подтвердить магический квадрат Дюрера!

Числовой квадрат ниже обладает некоторыми удивительными свойствами. Если сложить строки, столбцы или диагонали, получится то же число - 34. То же самое вы получите, если сложите каждый из четырех квадрантов, четыре центральных квадрата, четыре угловых квадрата или четыре угловых квадрата любой из имеющихся сеток 3 × 3. Вы собираетесь это доказать!

Интересный факт: в нижнем ряду числа 15 и 14 находятся посередине, представляя год, когда Дюрер создал этот квадрат. Цифры 1 и 4 также находятся в этом ряду, представляя первую и четвертую буквы алфавита, A и D, которые являются инициалами создателя квадрата, Альбрехта Дюрера!

Введите в REPL следующее:

In [1]: import numpy as np

In [2]: square = np.array([
   ...:     [16, 3, 2, 13],
   ...:     [5, 10, 11, 8],
   ...:     [9, 6, 7, 12],
   ...:     [4, 15, 14, 1]
   ...: ])

In [3]: for i in range(4):
   ...:     assert square[:, i].sum() == 34
   ...:     assert square[i, :].sum() == 34
   ...:

In [4]: assert square[:2, :2].sum() == 34

In [5]: assert square[2:, :2].sum() == 34

In [6]: assert square[:2, 2:].sum() == 34

In [7]: assert square[2:, 2:].sum() == 34

Внутри цикла for вы проверяете, что сумма всех строк и всех столбцов равна 34. После этого, используя выборочную индексацию, вы проверяете, что каждый из квадрантов также составляет 34.

И последнее, что следует отметить, это то, что вы можете взять сумму любого массива, чтобы сложить все его элементы глобально с помощью square.sum(). Этот метод также может принимать аргумент оси, чтобы вместо этого выполнять суммирование по осям.

Маскирование и фильтрация

Выбор на основе индекса - это хорошо, но что, если вы хотите отфильтровать данные на основе более сложных, неоднородных или непоследовательных критериев? Здесь в игру вступает концепция маски.

Маска - это массив, который имеет ту же форму, что и ваши данные, но вместо ваших значений он содержит логические значения: True или False. Вы можете использовать этот массив масок для индексации в массиве данных нелинейными и сложными способами. Он вернет все элементы, в которых логический массив имеет значение True.

Вот пример, показывающий процесс сначала в замедленной съемке, а затем, как это обычно делается, в одной строке:

In [1]: import numpy as np

In [2]: numbers = np.linspace(5, 50, 24, dtype=int).reshape(4, -1)

In [3]: numbers
Out[3]:
array([ [ 5, 6,  8, 10, 12, 14],
       [16, 18, 20, 22, 24, 26],
       [28, 30, 32, 34, 36, 38],
       [40, 42, 44, 46, 48, 50] ])

In [4]: mask = numbers % 4 == 0

In [5]: mask
Out[5]:
array([ [False, False,  True, False,  True, False],
       [ True, False,  True, False,  True, False],
       [ True, False,  True, False,  True, False],
       [ True, False,  True, False,  True, False] ])

In [6]: numbers[mask]
Out[6]: array([ 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48])

In [7]: by_four = numbers[numbers % 4 == 0]

In [8]: by_four
Out[8]: array([ 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48])

Вы увидите объяснение новых приемов создания массива в In [2], а пока сосредоточимся на сути примера. Это важные части:

  • In [4] создает маску, выполняя векторизованное логическое вычисление, беря каждый элемент и проверяя, делится ли на четыре без остатка. Возвращается массив масок той же формы с поэлементными результатами вычислений.
  • In [6] использует эту маску для индексации исходного массива чисел. Это приводит к тому, что массив теряет свою исходную форму, уменьшая его до одного измерения, но вы по-прежнему получаете данные, которые ищете.
  • In [7] обеспечивает более традиционный, идиоматический замаскированный выбор, который вы можете увидеть в природе, с анонимным фильтрующим массивом, созданным встроенным в скобки выбора. Этот синтаксис аналогичен использованию в языке программирования R.

Возвращаясь ко In [2], вы сталкиваетесь с тремя новыми концепциями:

  • Использование np.linspace() для создания равномерно распределенного массива
  • Установка dtype вывода
  • Изменение формы массива с -1

np.linspace() генерирует n чисел, равномерно распределенных между минимумом и максимумом, что полезно для равномерно распределенной выборки при научном построении графиков.

Благодаря конкретному вычислению в этом примере, наличие целых чисел в массиве чисел упрощает жизнь. Но поскольку пространство между 5 и 50 не делится равномерно на 24, результирующие числа будут числами с плавающей запятой. Вы указываете dtype типа int, чтобы функция округлялась в меньшую сторону и выдавала целые числа. Позже вы увидите более подробное обсуждение типов данных.

Наконец, array.reshape() может принимать -1 в качестве одного из размеров своих измерений. Это означает, что NumPy должен просто выяснить, насколько большой должна быть эта конкретная ось, исходя из размера других осей. В этом случае с 24 значениями и размером 4 по оси 0 ось 1 заканчивается размером 6.

Вот еще один пример, демонстрирующий возможности маскированной фильтрации.Нормальное распределение - это распределение вероятностей, в котором примерно 95,45% значений находятся в пределах двух стандартных отклонений от среднего.

Вы можете проверить это с небольшой помощью модуля NumPy random для генерации случайных значений:

In [1]: import numpy as np

In [2]: from numpy.random import default_rng

In [3]: rng = default_rng()

In [4]: values = rng.standard_normal(10000)

In [5]: values[:5]
Out[5]: array([ .9779210858,  1.8361585253,  -.3641365235,
               -.1311344527, 1.286542056 ])

In [6]: std = values.std()

In [7]: std
Out[7]: .9940375551073492

In [8]: filtered = values[(values > -2 * std) & (values < 2 * std)]

In [9]: filtered.size
Out[9]: 9565

In [10]: values.size
Out[10]: 10000

In [11]: filtered.size / values.size
Out[11]: 0.9565

Здесь вы используете потенциально странно выглядящий синтаксис для объединения условий фильтрации: бинарный оператор &. Почему так? Это потому, что NumPy обозначает & и | как векторизованные поэлементные операторы для объединения логических значений. Если вы попытаетесь выполнить A и B, то получите предупреждение о том, насколько странно истинное значение для массива, потому что и работает с истинным значением всего массива, а не поэлементно.

In [1]: import numpy as np In [2]: a = np.array([ ...: [1, 2], ...: [3, 4], ...: [5, 6], ...: ]) In [3]: a.T Out[3]: array(1, 3, 5], [2, 4, 6) In [4]: a.transpose() Out[4]: array(1, 3, 5], [2, 4, 6)

Когда вы вычисляете транспонирование массива, индексы строки и столбца каждого элемента меняются местами. Например, элемент [0, 2] становится элементом [2, 0]. Вы также можете использовать a.T как псевдоним для a.transpose().

Следующий блок кода показывает сортировку, но вы также увидите более мощный метод сортировки в следующем разделе, посвященном структурированным данным:

In [1]: import numpy as np

In [2]: data = np.array([
   ...:     [7, 1, 4],
   ...:     [8, 6, 5],
   ...:     [1, 2, 3]
   ...: ])

In [3]: np.sort(data)
Out[3]:
array([ [1, 4, 7],
       [5, 6, 8],
       [1, 2, 3] ])

In [4]: np.sort(data, axis=None)
Out[4]: array([1, 1, 2, 3, 4, 5, 6, 7, 8])

In [5]: np.sort(data, axis=0)
Out[5]:
array([ [1, 1, 3],
       [7, 2, 4],
       [8, 6, 5] ])

Отсутствие аргумента оси автоматически выбирает последнее и самое внутреннее измерение, которым в этом примере являются строки. Использование None сглаживает массив и выполняет глобальную сортировку. В противном случае вы можете указать, какую ось хотите. В Out[5] каждый столбец массива по-прежнему содержит все его элементы, но они отсортированы по возрастанию внутри этого столбца..

Наконец, вот пример конкатенации. Хотя существует функция np.concatenate(), есть также ряд вспомогательных функций, которые иногда легче читать.

Вот некоторые примеры:

In [1]: import numpy as np

In [2]: a = np.array([
   ...:     [4, 8],
   ...:     [6, 1]
   ...: ])

In [3]: b = np.array([
   ...:     [3, 5],
   ...:     [7, 2],
   ...: ])

In [4]: np.hstack((a, b))
Out[4]:
array([ [4, 8, 3, 5],
       [6, 1, 7, 2] ])

In [5]: np.vstack((b, a))
Out[5]:
array([ [3, 5],
       [7, 2],
       [4, 8],
       [6, 1] ])

In [6]: np.concatenate((a, b))
Out[6]:
array([ [4, 8],
       [6, 1],
       [3, 5],
       [7, 2] ])

In [7]: np.concatenate((a, b), axis=None)
Out[7]: array([4, 8, 6, 1, 3, 5, 7, 2])

In [4] и In [5] показывают чуть более интуитивно понятные функции hstack() и vstack(). In [6] и In [7] показывают более общий метод concatenate(), сначала без аргумента оси, а затем с axis=None. Это поведение сглаживания похоже по форме на то, что вы только что видели с помощью sort(). Следует отметить один важный камень преткновения: все эти функции в качестве первого аргумента принимают кортеж массивов, а не переменное количество аргументов, как вы могли ожидать. Вы можете это сказать, потому что здесь есть лишняя пара круглых скобок.

Агрегирование

Ваша последняя остановка в этом туре по функциональности перед тем, как погрузиться в более сложные темы и примеры, - это агрегирование. Вы уже видели довольно много методов агрегирования, включая .sum(), .max (), .mean () и .std (). Вы можете ссылаться на более обширную библиотеку функций NumPy, чтобы увидеть больше. Многие математические, финансовые,а статистические функции используют агрегирование, чтобы помочь вам уменьшить количество измерений в ваших данных.

Практический пример 1. Реализация серии Маклорена

Пришло время увидеть реалистичный пример использования навыков, представленных в разделах выше: реализация уравнения.

Одна из самых сложных вещей при преобразовании математических уравнений в код без NumPy заключается в том, что многие визуальные сходства отсутствуют, из-за чего трудно определить, на какую часть уравнения вы смотрите, читая код. Суммирования преобразуются в более подробные циклы for, а оптимизация пределов в конечном итоге выглядит как циклы while.

Использование NumPy позволяет вам приблизиться к однозначному представлению от уравнения к коду.

В следующем примере, например, закодируйте серию Маклорена для e^x. Ряды Маклорена - это способ приближения более сложных функций с помощью бесконечного ряда суммированных членов с центром около нуля.

Например, ряд Маклорена представляет собой следующее суммирование:

e^x = \sum_{n=0}^{\infty}{\dfrac{x^n}{n!}} = 1 + x + \dfrac{x^2}{2} + \dfrac{x^3}{3} \dots

Вы складываете члены, начиная с нуля и теоретически до бесконечности. Каждый n-й член будет возведен в x до n и разделен на n!, что является обозначением факториальной операции.

Теперь пришло время поместить это в код NumPy. Создайте файл с именем maclaurin.py:

from math import e, factorial

import numpy as np

fac = np.vectorize(factorial)

def e_x(x, terms=10):
    """Approximates e^x using a given number of terms of
    the Maclaurin series
    """
    n = np.arange(terms)
    return np.sum((x ** n) / fac(n))

if __name__ == "__main__":
    print("Значение:", e ** 3)  # Using e from the standard library

    print("N (ряд)\t\tПриближение\tПогрешность")

    for n in range(1, 14):
        maclaurin = e_x(3, terms=n)
        print(f"{n}\t\t{maclaurin:.03f}\t\t{e**3 - maclaurin:.03f}")

Когда вы запустите скрипт, то должны увидеть следующий результат:

Значение: 20.085536923187664
N (ряд)		Приближение	Погрешность
1		1.000		19.086
2		4.000		16.086
3		8.500		11.586
4		13.000		7.086
5		16.375		3.711
6		18.400		1.686
7		19.412		0.673
8		19.846		0.239
9		20.009		0.076
10		20.063		0.022
11		20.080		0.006
12		20.084		0.001
13		20.085		0.000

По мере увеличения количества членов ряда ваше значение Маклорена становится все ближе и ближе к фактическому значению, а ошибка становится все меньше и меньше.

Вычисление каждого члена включает в себя взятие x в степени n и деление на n!, или факториал числа n. Сложение, суммирование и возведение в степень - это все операции, которые NumPy может векторизовать автоматически и быстро, но не для factorial().

Чтобы использовать factorial() в векторизованных вычислениях, вы должны использовать np.vectorize() для создания векторизованной версии. Документация для np.vectorize() утверждает, что это не более чем тонкая оболочка, которая применяет цикл for к заданной функции. Использование этого кода вместо обычного кода Python не дает реальных преимуществ в производительности и потенциально может привести к некоторым штрафам за накладные расходы. Однако, как вы сейчас увидите, удобство чтения огромно. Как только ваш векторизованный факториал готов, фактический код для вычисления всего ряда Маклорена становится шокирующе коротким. Это также читабельно. Что наиболее важно, это почти точно соответствует тому, как выглядит математическое уравнение:

n = np.arange(terms)
return np.sum((x ** n) / fac(n))

Это настолько важная идея, что ее следует повторить. За исключением дополнительной строки для инициализации n, код читается почти так же, как исходное математическое уравнение. Нет циклов for, временных переменных i, j, k. Просто математика.

Точно так же вы используете NumPy для математического программирования! Для дополнительной практики попробуйте выбрать одну из других серий Maclaurin и реализовать ее аналогичным образом.

Оптимизация хранилища: типы данных

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

Однако в NumPy есть еще кое-что, что нужно осветить. NumPy использует код C под капотом для оптимизации производительности, и он не может этого сделать, если все элементы в массиве не относятся к одному типу. Это не означает только тот же тип Python. Они должны быть одного и того же базового типа C, с одинаковой формой и размером в битах!

Числовые типы: int, bool, float и complex

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

В таблице ниже приведены подробные сведения об этих типах:

Название Кол-во бит Тип Python Тип NumPy
Integer 64 int np.int_
Booleans 8 bool np.bool_
Float 64 float np.float_
Complex 128 complex np.complex_

Это просто типы, которые соответствуют существующим типам Python. В NumPy также есть типы для версий каждого меньшего размера, такие как 8-, 16- и 32-битные целые числа, 32-битные числа с плавающей запятой одинарной точности и 64-битные комплексные числа одинарной точности. В документации они перечислены полностью.

Чтобы указать тип при создании массива, можно указать аргумент dtype:

In [1]: import numpy as np

In [2]: a = np.array([1, 3, 5.5, 7.7, 9.2], dtype=np.single)

In [3]: a
Out[3]: array([1. , 3. , 5.5, 7.7, 9.2], dtype=float32)

In [4]: b = np.array([1, 3, 5.5, 7.7, 9.2], dtype=np.uint8)

In [5]: b
Out[5]: array([1, 3, 5, 7, 9], dtype=uint8)

NumPy автоматически преобразует ваш независимый от платформы тип np.single в любой тип фиксированного размера, поддерживаемый вашей платформой для этого размера. В этом случае он использует np.float32. Если предоставленные вами значения не соответствуют форме указанного вами dtype, NumPy либо исправит это за вас, либо выдаст ошибку.

Типы строк: Unicode с измененным размером

Строки ведут себя немного странно в коде NumPy, потому что NumPy нужно знать, сколько байтов следует ожидать, что обычно не является фактором программирования на Python. К счастью, NumPy неплохо справляется с менее сложными случаями:

>>> import numpy as np
>>> names = np.array(["bob", "amy", "han"], dtype=str)

>>> names
array(['bob', 'amy', 'han'], dtype='<U')

>>> names.itemsize
12

>>> names = np.array( ["bob", "amy", "han"] )

>>> names
array(['bob', 'amy', 'han'], dtype='<U')

>>> more_names = np.array( ["bobo", "jehosephat"] )
>>> np.concatenate((names, more_names))
array(['bob', 'amy', 'han', 'bobo', 'jehosephat'], dtype='< U10')

В строке 2 вы предоставляете dtype встроенного в Python типа str, но в Out[3] он был преобразован в строку Unicode с прямым порядком байтов размером 3. Когда вы проверяете размер данного элемента в In [4], то видите что каждый из них 12 байтов: три 4-байтовых символа Unicode.

Примечание. Имея дело с типами данных NumPy, вы должны думать о таких вещах, как порядок байтов ваших значений. В этом случае dtype '<U' означает, что каждое значение имеет размер трех символов Unicode, причем младший байт хранится первым в памяти, а старший значащий байт сохраняется последним. Тип dtype '>U3' будет означать обратное.

Например, NumPy представляет символ Unicode "🐍" с байтами 0xF4 0x01 0x00 с dtype ">U1" и 0x00 0x01 0xF4 с dtype ">U1". Попробуйте это, создав массив, полный эмодзи, установив для dtype одно или другое значение, а затем вызвав .tobytes() в своем массиве!

Если хотите узнать, как Python обрабатывает единицы и нули ваших обычных типов данных Python, то официальная документация для библиотеки struct, которая является стандартным библиотечным модулем, работающим с необработанными байтами, являясь еще одним хорошим ресурсом, для вас.

Когда вы объединяете это с массивом, который имеет больший элемент, чтобы создать новый массив в In [8], NumPy помогает выяснить, насколько большими должны быть элементы нового массива, и увеличивает их все до размера <U10.

Но вот что происходит, когда вы пытаетесь изменить один из слотов со значением, превышающим емкость dtype:

>>>names[2] = "jamima"

>>> names
array(['bob', 'amy', 'jam'], dtype='<U3')

Он не работает должным образом и вместо этого усекает ваше значение. Если у вас уже есть массив, автоматическое определение размера NumPy вам не подойдет. У вас есть три персонажа и все. Остальные теряются в пустоте.

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

Структурированные массивы

Первоначально вы узнали, что все элементы массива должны иметь один и тот же тип данных, но это было не совсем правильно. NumPy имеет особый вид массива, называемый массивом записей или структурированным массивом, с помощью которого вы можете указать тип и, при желании, имя для каждого столбца. Это делает сортировку и фильтрацию еще более мощной, что может быть похоже на работу с данными в Excel, CSV или реляционных базах данных.

Вот небольшой пример, чтобы немного их продемонстрировать:

>>> import numpy as np

>>> data = np.array([
   ...:     ("joe", 32, 6),
   ...:     ("mary", 15, 20),
   ...:     ("felipe", 80, 100),
   ...:     ("beyonce", 38, 9001),
   ...: ], dtype=[("name", str, 10), ("age", int), ("power", int)])

>>> data[0]
('joe', 32, 6)

>>> data["name"]
array(['joe', 'mary', 'felipe', 'beyonce'], dtype='<U10')

>>> data[data["power"] > 9000]["name"]
array(['beyonce'], dtype='<U10')

В In [2] вы создаете массив, за исключением того, что каждый элемент представляет собой кортеж с именем, возрастом и уровнем мощности. Для dtype вы фактически предоставляете список кортежей с информацией о каждом поле: name - это 10-символьное поле Unicode, а age и power - стандартные 4-байтовые или 8-байтовые целые числа.

В In [3] вы можете видеть, что строки, известные как записи, по-прежнему доступны с помощью index.

В In [4] вы видите новый синтаксис для доступа ко всему столбцу или полю.

Наконец, В In [5] вы видите сверхмощную комбинацию фильтрации на основе маски на основе поля и выбора на основе поля. Обратите внимание на то, что чтение следующего запроса SQL не сильно отличается:

SELECT name FROM data
WHERE power > 9000;

В обоих случаях результатом является список имен с уровнем мощности более 9000.

Вы даже можете добавить функциональность ORDER BY, используя np.sort():

In [6]: np.sort(data[data["age"] > 20], order="power")["name"]
Out[6]: array(['joe', 'felipe', 'beyonce'], dtype='<U10')

Перед извлечением данные сортируются по значению, и таким образом мы завершаем разговор о выборе инструментов NumPy для выбора, фильтрации и сортировки элементов, в том числе и с помощью SQL!

Подробнее о типах данных

Этот раздел руководства был разработан, чтобы дать вам достаточно знаний, чтобы продуктивно работать с типами данных NumPy, немного понять, как все работает под капотом, и распознать некоторые распространенные ошибки. Это, конечно, не исчерпывающее руководство. В документации NumPy на ndarrays гораздо больше ресурсов. Также есть намного больше информации об объектах dtype, в том числе о различных способах их создания, настройки и оптимизации, а также о том, как сделать их более надежными для всех ваших потребностей в обработке данных. Если у вас возникнут проблемы и ваши данные загружаются в массивы не так, как вы ожидали, то это хорошее место для начала.

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

Взгляд в будущее: более мощные библиотеки

В следующем разделе вы перейдете к мощным инструментам, созданным на основе фундаментальных строительных блоков, которые вы видели выше. Вот несколько библиотек, на которые вы захотите взглянуть в качестве следующих шагов на пути к полному овладению наукой о данных Python.

pandas

pandas - это библиотека, которая берет концепцию структурированных массивов и создает ее с помощью множества удобных методов, улучшений для разработчиков и улучшенной автоматизации. Если вам нужно импортировать данные практически из любого места, очистить их, изменить их форму, отполировать, а затем экспортировать практически в любой формат, тогда pandas - это библиотека для вас. Вполне вероятно, что в какой-то момент вы импортируете pandas как pd одновременно с импортом numpy как np.

В документации pandas есть краткое руководство с конкретными примерами под названием 10 Minutes to pandas. Это отличный ресурс, который можно использовать для быстрой практической практики.

scikit-learn

Если ваши цели больше связаны с машинным обучением, то следующим шагом станет scikit-learn. Имея достаточно данных, вы можете выполнить классификацию, регрессию, кластеризацию и многое другое всего в нескольких строках.

Если вы уже знакомы с математикой, то в документации scikit-learn есть отличный список руководств, которые помогут вам начать работу с Python.

Для вас важно понимать хотя бы основы математики, лежащей в основе алгоритмов, а не просто импортировать их и работать с ними. Предвзятость в моделях машинного обучения - огромная этическая, социальная и политическая проблема.

Использование данных в моделях, не задумываясь о том, как устранить предвзятость, - отличный способ попасть в беду и негативно повлиять на жизнь людей. Проведение некоторых исследований и изучение того, как предсказать, где может возникнуть предвзятость, - хорошее начало в правильном направлении.

Matplotlib

Независимо от того, что вы делаете со своими данными, в какой-то момент вам нужно будет показать свои результаты другим людям, и Matplotlib является одной из основных библиотек для этого. В следующем разделе вы получите практическую практику работы с Matplotlib, но будем использовать его для обработки изображений, а не для построения графиков.

Практический пример 2: манипулирование изображениями с помощью Matplotlib

Всегда удобно, когда вы работаете с библиотекой Python, и она дает вам что-то, что оказывается базовым массивом NumPy. В этом примере вы испытаете это во всей красе.

Вы собираетесь загрузить изображение с помощью Matplotlib, поймете, что изображения RGB на самом деле представляют собой массивы целых чисел int8 шириной × высотой × 3, манипулируйте этими байтами, и снова используйте Matplotlib, чтобы сохранить измененное изображение, когда закончите.

Загрузите эту картинку для своих упражнений:





Заключение

Независимо от того, в скольких измерениях находятся ваши данные, NumPy дает вам инструменты для работы с ними. Вы можете хранить его, изменять его форму, комбинировать, фильтровать и сортировать, и ваш код будет выглядеть так, как будто вы работаете только с одним числом за раз, а не с сотнями или тысячами.

В этом уроке вы узнали:

  • Основные концепции науки о данных, которые стали возможными благодаря NumPy
  • Как создавать массивы NumPy разными методами
  • Как манипулировать массивами NumPy для выполнения полезных вычислений
  • Как применить эти новые навыки к реальным проблемам

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

Теперь вы готовы к следующим шагам на пути к науке о данных. Независимо от того, очищаете ли вы данные, обучаете нейронные сети, общаетесь с помощью мощных графиков или собираете данные из Интернета вещей, все эти действия начинаются с одного места: скромного массива NumPy.


NumPy Tutorial: Your First Steps Into Data Science in Python
Out[8]: array(['bob', 'amy', 'han', 'bobo', 'jehosephat'], dtype='<U10')

В In [2] вы предоставляете dtype встроенного в Python типа str, но в Out[3] он был преобразован в строку Unicode с прямым порядком байтов размером 3. Когда вы проверяете размер данного элемента в In [4], то видите что каждый из них 12 байтов: три 4-байтовых символа Unicode.

Примечание. Имея дело с типами данных NumPy, вы должны думать о таких вещах, как порядок байтов ваших значений. В этом случае dtype '<U' означает, что каждое значение имеет размер трех символов Unicode, причем младший байт хранится первым в памяти, а старший значащий байт сохраняется последним. Тип dtype '>U3' будет означать обратное.

Например, NumPy представляет символ Unicode «🐍» с байтами 0xF4 0x01 0x00 с dtype «<U1» и 0x00 0x01 0xF4 с dtype «>U1». Попробуйте это, создав массив, полный эмодзи, установив для dtype одно или другое значение, а затем вызвав .tobytes() в своем массиве!

Если хотите узнать, как Python обрабатывает единицы и нули ваших обычных типов данных Python, то официальная документация для библиотеки struct, которая является стандартным библиотечным модулем, работающим с необработанными байтами, являясь еще одним хорошим ресурсом, для вас.

Когда вы объединяете это с массивом, который имеет больший элемент, чтобы создать новый массив в In [8], NumPy помогает выяснить, насколько большими должны быть элементы нового массива, и увеличивает их все до размера <U10.

Но вот что происходит, когда вы пытаетесь изменить один из слотов со значением, превышающим емкость dtype:

In [9]: names[2] = "jamima"

In [10]: names
Out[10]: array(['bob', 'amy', 'jam'], dtype='<U')

Он не работает должным образом и вместо этого усекает ваше значение. Если у вас уже есть массив, автоматическое определение размера NumPy вам не подойдет. У вас есть три персонажа и все. Остальные теряются в пустоте.

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

Структурированные массивы

Первоначально вы узнали, что все элементы массива должны иметь один и тот же тип данных, но это было не совсем правильно. NumPy имеет особый вид массива, называемый массивом записей или структурированным массивом, с помощью которого вы можете указать тип и, при желании, имя для каждого столбца. Это делает сортировку и фильтрацию еще более мощной, что может быть похоже на работу с данными в Excel, CSV или реляционных базах данных.

Вот небольшой пример, чтобы немного их продемонстрировать:

In [1]: import numpy as np

In [2]: data = np.array([
   ...:     ("joe", 32, 6),
   ...:     ("mary", 15, 20),
   ...:     ("felipe", 80, 100),
   ...:     ("beyonce", 38, 9001),
   ...: ], dtype=[("name", str, 10), ("age", int), ("power", int)])

In [3]: data[0]
Out[3]: ('joe', 32, 6)

In [4]: data["name"]
Out[4]: array(['joe', 'mary', 'felipe', 'beyonce'], dtype='<U10')

In [5]: data[data["power"] > 9000]["name"]
Out[5]: array(['beyonce'], dtype='<U10')

В In [2] вы создаете массив, за исключением того, что каждый элемент представляет собой кортеж с именем, возрастом и уровнем мощности. Для dtype вы фактически предоставляете список кортежей с информацией о каждом поле: name - это 10-символьное поле Unicode, а age и power - стандартные 4-байтовые или 8-байтовые целые числа.

В In [3] вы можете видеть, что строки, известные как записи, по-прежнему доступны с помощью index.

В In [4] вы видите новый синтаксис для доступа ко всему столбцу или полю.

Наконец, В In [5] вы видите сверхмощную комбинацию фильтрации на основе маски на основе поля и выбора на основе поля. Обратите внимание на то, что чтение следующего запроса SQL не сильно отличается:

SELECT name FROM data
WHERE power > 9000;

В обоих случаях результатом является список имен с уровнем мощности более 9000.

Вы даже можете добавить функциональность ORDER BY, используя np.sort():

In [6]: np.sort(data[data["age"] > 20], order="power")["name"]
Out[6]: array(['joe', 'felipe', 'beyonce'], dtype='<U10')

Перед извлечением данные сортируются по значению, и таким образом мы завершаем разговор о выборе инструментов NumPy для выбора, фильтрации и сортировки элементов, в том числе и с помощью SQL!

Подробнее о типах данных

Этот раздел руководства был разработан, чтобы дать вам достаточно знаний, чтобы продуктивно работать с типами данных NumPy, немного понять, как все работает под капотом, и распознать некоторые распространенные ошибки. Это, конечно, не исчерпывающее руководство. В документации NumPy на ndarrays гораздо больше ресурсов. Также есть намного больше информации об объектах dtype, в том числе о различных способах их создания, настройки и оптимизации, а также о том, как сделать их более надежными для всех ваших потребностей в обработке данных. Если у вас возникнут проблемы и ваши данные загружаются в массивы не так, как вы ожидали, то это хорошее место для начала.

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

Взгляд в будущее: более мощные библиотеки

В следующем разделе вы перейдете к мощным инструментам, созданным на основе фундаментальных строительных блоков, которые вы видели выше. Вот несколько библиотек, на которые вы захотите взглянуть в качестве следующих шагов на пути к полному овладению наукой о данных Python.

pandas

pandas - это библиотека, которая берет концепцию структурированных массивов и создает ее с помощью множества удобных методов, улучшений для разработчиков и улучшенной автоматизации. Если вам нужно импортировать данные практически из любого места, очистить их, изменить их форму, отполировать, а затем экспортировать практически в любой формат, тогда pandas - это библиотека для вас. Вполне вероятно, что в какой-то момент вы импортируете pandas как pd одновременно с импортом numpy как np.

В документации pandas есть краткое руководство с конкретными примерами под названием 10 Minutes to pandas. Это отличный ресурс, который можно использовать для быстрой практической практики.

scikit-learn

Если ваши цели больше связаны с машинным обучением, то следующим шагом станет scikit-learn. Имея достаточно данных, вы можете выполнить классификацию, регрессию, кластеризацию и многое другое всего в нескольких строках.

Если вы уже знакомы с математикой, то в документации scikit-learn есть отличный список руководств, которые помогут вам начать работу с Python.

Для вас важно понимать хотя бы основы математики, лежащей в основе алгоритмов, а не просто импортировать их и работать с ними. Предвзятость в моделях машинного обучения - огромная этическая, социальная и политическая проблема.

Использование данных в моделях, не задумываясь о том, как устранить предвзятость, - отличный способ попасть в беду и негативно повлиять на жизнь людей. Проведение некоторых исследований и изучение того, как предсказать, где может возникнуть предвзятость, - хорошее начало в правильном направлении.

Matplotlib

Независимо от того, что вы делаете со своими данными, в какой-то момент вам нужно будет показать свои результаты другим людям, и Matplotlib является одной из основных библиотек для этого. В следующем разделе вы получите практическую практику работы с Matplotlib, но будем использовать его для обработки изображений, а не для построения графиков.

Практический пример 2: манипулирование изображениями с помощью Matplotlib

Всегда удобно, когда вы работаете с библиотекой Python, и она дает вам что-то, что оказывается базовым массивом NumPy. В этом примере вы испытаете это во всей красе.

Вы собираетесь загрузить изображение с помощью Matplotlib, поймете, что изображения RGB на самом деле представляют собой массивы целых чисел int8 шириной × высотой × 3, манипулируйте этими байтами, и снова используйте Matplotlib, чтобы сохранить измененное изображение, когда закончите.

Загрузите эту картинку для своих упражнений:

Это изображение очаровательного котенка размером 1920 на 1299 пикселей. Вы собираетесь изменить цвета этих пикселей.

Создайте файл Python с именем image_mod.py, затем настройте импорт и загрузите изображение:

import numpy as np
import matplotlib.image as mpimg

img = mpimg.imread("kitty.jpg")
print(type(img))
print(img.shape)

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

Если вы запустите этот код, то ваш друг массив NumPy появится на выходе:

$ python3 image_mod.py

(1299, 1920, 3)

Это изображение высотой 1299 пикселей, шириной 1920 пикселей и тремя каналами: по одному для уровней красного, зеленого и синего цветов (RGB).

Хотите увидеть, что произойдет, если вы отключите каналы R и G? Добавьте это в свой скрипт:

output = img.copy()  # Исходное изображение только для чтения!
output[:, :, :2] = 0
mpimg.imsave("blue.jpg", output)

Запустите его еще раз и проверьте папку. Должен появиться новый образ:

Ваш мозг еще не взорван? Вы чувствуете силу? Изображения - это просто причудливые массивы! Пиксели - это просто числа!

Но теперь пора заняться чем-нибудь более полезным. Вы собираетесь преобразовать это изображение в оттенки серого. Однако преобразование в оттенки серого сложнее. Усредняя R, G и B, и если их объединить, получится изображение в оттенках серого. Но человеческий мозг странный, и это преобразование, похоже, не совсем правильно обрабатывает яркость цветов.

На самом деле, лучше увидеть это самому. Вы можете использовать тот факт, что если вы выводите массив только с одним каналом вместо трех, затем вы можете указать цветовую карту, известную как cmap в мире Matplotlib. Если вы укажете cmap, Matplotlib выполнит вычисления линейного градиента за вас.

Избавьтесь от последних трех строк в вашем скрипте и замените их следующим:

averages = img.mean(axis=2)  # Take the average of each R, G, and B
mpimg.imsave("bad-gray.jpg", averages, cmap="gray")

Эти новые строки создают новый массив, называемый средними значениями, который является копией массива img, который вы выровняли по оси 2, взяв среднее значение по всем трем каналам. Вы усреднили все три канала и вывели что-то со значениями R, G и B, равными этому среднему. Когда R, G и B одинаковы, результирующий цвет находится в градациях серого. То, что в итоге дает, не страшно:

Но вы можете добиться большего, используя метод яркости. Этот метод вычисляет средневзвешенное значение трех каналов, исходя из того, что зеленый цвет определяет, насколько ярким будет изображение, а синий цвет может сделать его темнее. Вы будете использовать оператор @, который является оператором NumPy для вычисления традиционного двумерного массива точечного произведения. Снова замените эти две последние строки в своем скрипте:

weights = np.array([0.3, 0.59, 0.11])
grayscale = img @ weights
mpimg.imsave("good-gray.jpg", grayscale, cmap="gray")

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

Вот результат:

Первое изображение немного темнее, а края и тени более жирные. Второе изображение светлее и ярче, а темные линии не такие жирные. Вот и все - вы использовали массивы Matplotlib и NumPy для управления изображением!

Заключение

Независимо от того, в скольких измерениях находятся ваши данные, NumPy дает вам инструменты для работы с ними. Вы можете хранить его, изменять его форму, комбинировать, фильтровать и сортировать, и ваш код будет выглядеть так, как будто вы работаете только с одним числом за раз, а не с сотнями или тысячами.

В этом уроке вы узнали:

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

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

Теперь вы готовы к следующим шагам на пути к науке о данных. Независимо от того, очищаете ли вы данные, обучаете нейронные сети, общаетесь с помощью мощных графиков или собираете данные из Интернета вещей, все эти действия начинаются с одного места: скромного массива NumPy.

По мотивам NumPy Tutorial: Your First Steps Into Data Science in Python

Опубликовано Вадим В. Костерин

ст. преп. кафедры ЦЭиИТ. Автор более 130 научных и учебно-методических работ. Лауреат ВДНХ (серебряная медаль).

Оставьте комментарий

Ваш адрес email не будет опубликован.