Простой Python — страница 52 из 66

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

• Anaconda (https://store.continuum.io/cshop/anaconda/). Это бесплатный пакет, имеющий множество самых свежих возможностей. Он поддерживает Python 2 и 3 и не вредит установленной у вас версии Python.

• Python(x,y) (https://code.google.com/p/pythonxy/). Этот релиз подходит только для Windows.

• Pyzo (http://www.pyzo.org/). Этот пакет основан на некоторых инструментах пакета Anaconda, а также других.

• ALGORETE Loopy (http://algorete.org/). Этот пакет также основан на Anaconda и содержит дополнения.

Я рекомендую вам установить пакет Anaconda. Он большой, и все, что представлено в этом приложении, в нем содержится. В приложении Г рассматривается использование Python 3 и Anaconda. Примеры остальной части приложения подразумевают, что вы установили необходимые пакеты либо отдельно, либо как часть Anaconda.

NumPy

Это одна из основных причин популярности Python среди ученых (http://www.numpy.org/). Вы слышали, что динамические языки вроде Python зачастую медленнее компилирующих языков вроде С или даже других интерпретируемых языков вроде Java. NumPy был написан для предоставления доступа к быстрым многомерным массивам по аналогии с научными языками вроде FORTRAN. Вы получаете скорость С и дружественность к разработчикам Python.

Если вы загрузили один из научных дистрибутивов, у вас уже есть NumPy. Если же нет, следуйте инструкциям на странице загрузки NumPy (http://www.scipy.org/scipylib/download.html).

Для того чтобы начать работу с NumPy, вы должны понять устройство основной структуры данных, многомерного массива ndarray (от N-dimensional array — «N-мерный массив») или просто array. В отличие от списков и кортежей в Python, все элементы должны иметь одинаковый тип.

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


array в NumPy и array в Python — это не одно и то же. В дальнейшем в этом приложении я буду работать только с массивами NumPy.


Но зачем нам нужны массивы?

• Научные данные зачастую представляют собой большие последовательности.

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

• NumPy обрабатывает массивы гораздо быстрее, чем стандартные списки или кортежи Python.

Существует множество способов создать массив NumPy.

Создание массива с помощью функции array()

Вы можете создать массив из обычного списка или кортежа:

>>> b = np.array([2, 4, 6, 8])

>>> b

array([2, 4, 6, 8])

Атрибут ndim возвращает ранг массива:

>>> b.ndim

1

Общее число значений можно получить с помощью атрибута size:

>>> b.size

4

Количество значений каждого ранга возвращает атрибут shape:

>>> b.shape

(4,)

Создание массива с помощью функции arange()

Метод arange() похож на стандартный метод range(). Если вы вызовете метод arange(), передав ему один целочисленный аргумент num, он вернет ndarray от 0 до num-1:

>>> import numpy as np

>>> a = np.arange(10)

>>> a

array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

>>> a.ndim

1

>>> a.shape

(10,)

>>> a.size

10

С помощью двух значений он создаст массив от первого элемента до последнего минус один:

>>> a = np.arange(7, 11)

>>> a

array([7,··8,··9, 10])

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

>>> a = np.arange(7, 11, 2)

>>> a

array([7, 9])

До сих пор мы показывали примеры лишь с целыми числами, но метод arrange() работает и с числами с плавающей точкой:

>>> f = np.arange(2.0, 9.8, 0.3)

>>> f

array([2.,··2.3,··2.6,··2.9,··3.2,··3.5,··3.8,··4.1,··4.4,··4.7,··5.,

········5.3,··5.6,··5.9,··6.2,··6.5,··6.8,··7.1,··7.4,··7.7,··8.,··8.3,

········8.6,··8.9,··9.2,··9.5,··9.8])

И последний прием: аргумент dtype указывает функции arrange(), какого типа значения следует создать:

>>> g = np.arange(10, 4, -1.5, dtype=np.float)

>>> g

array([10.,···8.5,···7.,···5.5])

Создание массива с помощью функций zeros(), ones() и random()

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

>>> a = np.zeros((3,))

>>> a

array([0.,··0.,··0.])

>>> a.ndim

1

>>> a.shape

(3,)

>>> a.size

3

Этот массив имеет ранг 2:

>>> b = np.zeros((2, 4))

>>> b

array([[0.,··0.,··0.,··0.],

·······[0.,··0.,··0.,··0.]])

>>> b.ndim

2

>>> b.shape

(2, 4)

>>> b.size

8

Другой особой функцией, заполняющей массив одинаковыми значениями, является ones():

>>> import numpy as np

>>> k = np.ones((3, 5))

>>> k

array([[1.,··1.,··1.,··1.,··1.],

·······[1.,··1.,··1.,··1.,··1.],

·······[1.,··1.,··1.,··1.,··1.]])

Последняя функция создает массив и заполняет его случайными значениями из промежутка от 0,0 до 1,0:

>>> m = np.random.random((3, 5))

>>> m

array([[··1.92415699e-01,···4.43131404e-01,···7.99226773e-01,

··········1.14301942e-01,···2.85383430e-04],

·······[··6.53705749e-01,···7.48034559e-01,···4.49463241e-01,

··········4.87906915e-01,···9.34341118e-01],

·······[··9.47575562e-01,···2.21152583e-01,···2.49031209e-01,

··········3.46190961e-01,···8.94842676e-01]])

Изменяем форму массива с помощью метода reshape()

До этого момента массив не особо отличался от списка или кортежа. Одним из различий между ними является возможность изменять его форму с помощью функции reshape():

>>> a = np.arange(10)

>>> a

array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

>>> a = a.reshape(2, 5)

>>> a

array([[0, 1, 2, 3, 4],

·······[5, 6, 7, 8, 9]])

>>> a.ndim

2

>>> a.shape

(2, 5)

>>> a.size

10

Вы можете изменять форму массива разными способами:

>>> a = a.reshape(5, 2)

>>> a

array([[0, 1],

·······[2, 3],

·······[4, 5],

·······[6, 7],

·······[8, 9]])

>>> a.ndim

2

>>> a.shape

(5, 2)

>>> a.size

10

Присваиваем кортеж, указывающий параметры формы, атрибуту shape:

>>> a.shape = (2, 5)

>>> a

array([[0, 1, 2, 3, 4],

·······[5, 6, 7, 8, 9]])

Единственное ограничение — произведение рангов должно быть равным количеству значений (в нашем случае 10):

>>> a = a.reshape(3, 4)

Traceback (most recent call last):

··File "", line 1, in 

ValueError: total size of new array must be unchanged

Получаем элемент с помощью конструкции []

Одномерный массив работает как список:

>>> a = np.arange(10)

>>> a[7]

7

>>> a[-1]

9

Но если массив имеет другую форму, используйте индексы, разделенные запятыми:

>>> a.shape = (2, 5)

>>> a

array([[0, 1, 2, 3, 4],

·······[5, 6, 7, 8, 9]])

>>> a[1,2]

7

Это отличается от двухмерного списка:

>>> l = [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]

>>> l

[[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]

>>> l[1,2]

Traceback (most recent call last):

··File "", line 1, in 

TypeError: list indices must be integers, not tuple

>>> l[1][2]

7

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

>>> a = np.arange(10)

>>> a = a.reshape(2, 5)

>>> a

array([[0, 1, 2, 3, 4],

·······[5, 6, 7, 8, 9]])

Используйте разбиение, чтобы получить первый ряд — элементы начиная со смещения 2, до конца:

>>> a[0, 2:]

array([2, 3, 4])

Теперь получим последний ряд — все элементы вплоть до третьего с конца:

>>> a[-1,3]

array([5, 6, 7])

Вы также можете присвоить значение более чем одному элементу с помощью разбиения. Следующее выражение присваивает значение 1000 колонкам (смещениям) 2 и 3 каждого ряда:

>>> a[:, 2:4] = 1000

>>> a

array([[···0,····1, 1000, 1000,····4],

·······[···5,····6, 1000, 1000,····9]])

Математика массивов

Создание и изменение формы массивов так нас увлекли, что мы почти забыли сделать с ними что-то более полезное. Для начала используем переопределенный в NumPy оператор умножения (*), чтобы умножить все значения массива за раз:

>>> from numpy import *

>>> a = arange(4)

>>> a

array([0, 1, 2, 3])

>>> a *= 3

>>> a

array([0, 3, 6, 9])

Если вы пытались умножить каждый элемент обычного списка Python на число, вам бы понадобились цикл или включение:

>>> plain_list = list(range(4))

>>> plain_list

[0, 1, 2, 3]

>>> plain_list = [num * 3 for num in plain_list]

>>> plain_list

[0, 3, 6, 9]

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

>>> from numpy import *

>>> a = zeros((2, 5)) + 17.0

>>> a

array([[17.,··17.,··17.,··17.,··17.],

·······[17.,··17.,··17.,··17.,··17.]])

Линейная алгебра

NumPy содержит множество функций линейной алгебры. Например, определим такую систему линейных уравнений:

4x + 5y = 20

x + 2y = 13

Как мы можем найти х и у? Создадим два массива:

• коэффициенты (множители для х и у);

• зависимые переменные (правая часть уравнения):

>>> import numpy as np

>>> coefficients = np.array([[4, 5], [1, 2]])

>>> dependents = np.array([20, 13])

Теперь используем функцию solve() модуля linalg:

>>> answers = np.linalg.solve(coefficients, dependents)

>>> answers

array([-8.33333333,··10.66666667])

В результате получим, что x примерно равен –8.3, а у примерно равен 10.6. Являются ли эти числа решениями уравнения?

>>> 4 * answers[0] + 5 * answers[1]

20.0

>>> 1 * answers[0] + 2 * answers[1]

13.0

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

>>> product = np.dot(coefficients, answers)

>>> product

array([20.,··13.])

Если решение верно, значения массива product должны быть близки к значениям массива dependents. Вы можете использовать функцию allclose(), чтобы проверить, являются ли массивы хотя бы приблизительно равными (они могут быть не полностью равными из-за округления чисел с плавающей точкой):

>>> np.allclose(product, dependents)

True

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

Библиотека SciPy