Добавил:
Developer Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Лекции / 12. Библиотеки numpy и matplotlib Jupyter Notebook

.pdf
Скачиваний:
33
Добавлен:
15.12.2022
Размер:
809.37 Кб
Скачать

28.11.2022, 02:19

12 Библиотеки numpy и matplotlib - Jupyter Notebook

Лекция №12: Библиотеки numpy и matplotlib

Библиотека numpy: эффективные массивы

Писать программы на Python легко и приятно. Гораздо легче и приятнее, чем на низкоуровневых языках программирования, таких как C или C++. Но, увы, чудес не бывает: за простоту написания кода мы платим скоростью его исполнения.

In [2]:

numbers = [1.2] * 10000 numbers[:10]

Out[2]:

[1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2]

Мы создали список из 10000 чисел. Для простоты они все одинаковые, но Python об этом не знает. Как быстро мы возведём каждое из них в квадрат?

Для проверки, с какой скоростью выполняется некоторый фрагмент кода, полезно использовать магическое слово %%timeit . Оно говорит, что ячейку нужно выполнить несколько раз и засечь, сколько времени на это ушло.

In [5]:

%%timeit

squares = [x**2 for x in numbers]

1000 loops, best of 3: 1.39 ms per loop

Больше миллисекунды на один проход! (Кстати, x*x будет в два раза быстрее — попробуйте!) Не очень-то быстро, на самом деле. Для «тяжелой» математики, часто возникающей при обработке больших массивов данных, хочется использовать все возможности компьютера.

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

— numpy .

In [6]:

import numpy as np

Главный объект, с которым мы будем работать — это np.array (на самом деле он называется np.ndarray ):

In [7]:

np_numbers = np.array(numbers)

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/12 Библиотеки numpy и matplotlib.ipynb

1/22

28.11.2022, 02:19

12 Библиотеки numpy и matplotlib - Jupyter Notebook

In [8]:

np_numbers

Out[8]:

array([ 1.2, 1.2, 1.2, ..., 1.2, 1.2, 1.2])

np.array — это специальный тип данных, похожий на список, но содержащий данные только одного типа (в данном случае — только вещественные числа).

In [9]:

np_numbers[3]

Out[9]:

1.2

In [10]:

len(np_numbers)

Out[10]:

10000

С математической точки зрения, np.array — это что-то, похожее на вектор. Но практически все операции выполняются поэлементно. Например, возведение в квадрат каждого элемента можно реализовать как np_numbers**2 .

In [12]:

np_squares = np_numbers**2 np_squares

Out[12]:

array([ 1.44, 1.44, 1.44, ..., 1.44, 1.44, 1.44])

Посмотрим, как быстро работает эта операция:

In [11]:

%%timeit

np_squares = np_numbers**2

The slowest run took 17.01 times longer than the fastest. This could mean th at an intermediate result is being cached

100000 loops, best of 3: 5.04 µs per loop

Здесь 5 микросекунд, в 200 раз быстрее! Правда, нас предупреждают, что это может быть последствия кеширования — но в любом случае, работа с массивами чисел с помощью numpy происходит гораздо быстрее, чем с помощью обычных списков и циклов.

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/12 Библиотеки numpy и matplotlib.ipynb

2/22

28.11.2022, 02:19

12 Библиотеки numpy и matplotlib - Jupyter Notebook

Давайте посмотрим на np.array более подробно.

Массивы похожи на списки…

In [53]:

from numpy import array

# чтобы не писать каждый раз np

In [54]:

q = array([4, 5, 8, 9])

Я дальше буду называть np.array массивами (в отличие от списком, которые мы так в Python не называем). Итак, можно обращаться к элементам массива по индексам, как и к спискам.

In [33]:

q[0]

Out[33]:

4

И менять их тоже можно

In [34]:

q[0] = 12 q

Out[34]:

array([12, 5, 8, 9])

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

In [185]:

for x in q: print(x)

14

8

14

9

Можно делать срезы (но с ними тоже есть хитрости, об этом ниже).

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/12 Библиотеки numpy и matplotlib.ipynb

3/22

28.11.2022, 02:19

12 Библиотеки numpy и matplotlib - Jupyter Notebook

In [186]: q[1:3]

Out[186]:

array([ 8, 14])

…но не всегда похожи!

Давайте заведём ещё один массив.

In [187]:

w = array([2, 3, 6, 10])

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

In [188]: q + w

Out[188]:

array([16, 11, 20, 19])

Если вы хотели сделать конкатенацию, то нужно использовать не оператор сложения, а специальную функцию.

In [189]: np.concatenate( [q, w] )

Out[189]:

array([14, 8, 14, 9, 2, 3, 6, 10])

Аналогично сложению работают и другие операции. Например, умножение:

In [190]: q * w

Out[190]:

array([28, 24, 84, 90])

Если у массивов будет разная длина, то ничего не получится:

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/12 Библиотеки numpy и matplotlib.ipynb

4/22

28.11.2022, 02:19

12 Библиотеки numpy и matplotlib - Jupyter Notebook

In [191]:

 

q =

np.array([14, 8, 14, 9])

 

w

=

np.array([1, 3, 4])

 

q

+

w

 

---------------------------------------------------------------------------

ValueError

Traceback (most recent call last)

<ipython-input-191-a14671572ef0> in <module>()

1

q = np.array([14, 8, 14, 9])

 

2

w

= np.array([1, 3, 4])

 

----> 3

q

+ w

 

ValueError: operands could not be broadcast together with shapes (4,) (3,)

Можно применять различные математические операции к массивам.

In [192]:

x = array([1,2,3,4,5])

y = array([4, 5, 6, 2, 1])

In [193]:

 

np.exp(x)

 

Out[193]:

 

array([ 2.71828183,

7.3890561 , 20.08553692, 54.59815003,

148.4131591 ])

 

Заметим, что мы должны были использовать функцию exp из numpy , а не из обычного math . Если бы мы взяли эту функцию из math , ничего бы не сработало.

In [194]:

import math

In [195]:

math.sqrt(x)

---------------------------------------------------------------------------

TypeError Traceback (most recent call last) <ipython-input-195-655644d1b4c7> in <module>()

----> 1 math.sqrt(x)

TypeError: only length-1 arrays can be converted to Python scalars

Вообще в numpy много математических функций. Вот, например, квадратный корень:

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/12 Библиотеки numpy и matplotlib.ipynb

5/22

28.11.2022, 02:19

12 Библиотеки numpy и matplotlib - Jupyter Notebook

In [72]:

 

 

np.sqrt(x)

 

 

Out[72]:

 

 

array([ 1.

, 1.41421356, 1.73205081, 2.

, 2.23606798])

Типы элементов в массивах

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

In [61]:

mixed_array = np.array([1, 2, 3, "Hello"])

Однако, все элементы, лежащие в одном массиве, должны быть одного типа.

In [62]:

mixed_array

Out[62]:

array(['1', '2', '3', 'Hello'], dtype='<U21')

Здесь видно, что числа 1 , 2 , 3 превратились в строчки '1' , '2' , '3' . Параметр dtype содержит информацию о типе объектов, хранящихся в массиве. <U21 означает юникодную строку длиной максимум 21 байт. При попытке записать более длинную строку она будет обрезана.

In [63]:

mixed_array[0] = 'Hello, World, This is a Test' mixed_array[0]

Out[63]:

'Hello, World, This is'

Вообще numpy при создании массива старается не терять информацию и выбирает самый «вместительный» тип.

In [64]:

np.array([1,2,3])

Out[64]:

array([1, 2, 3])

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/12 Библиотеки numpy и matplotlib.ipynb

6/22

28.11.2022, 02:19

12 Библиотеки numpy и matplotlib - Jupyter Notebook

In [65]:

array([1,2,3, 5.])

Out[65]:

array([ 1., 2., 3., 5.])

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

Коварные срезы

Давайте посмотрим внимательно на срезы.

In [78]:

x = array([1.1, 2.2, 3.3, 4.4, 5.5])

In [79]:

s = x[1:3]

In [80]:

s

Out[80]:

array([ 2.2, 3.3])

Пока всё идёт как обычно: мы создали срез, начинающийся с элемента с индексом 1 (то есть второй элемент, нумерация с нуля) и заканчивающийся элементом с индексом 3 (последний элемент всегда не включается).

Теперь попробуем изменить значение элемента в срезе:

In [83]:

s[0] = 100

In [84]:

s

Out[84]:

array([ 100. ,

3.3])

Как вы думаете, что произойдёт с исходным массивом x ?

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/12 Библиотеки numpy и matplotlib.ipynb

7/22

28.11.2022, 02:19

12 Библиотеки numpy и matplotlib - Jupyter Notebook

In [85]:

x

Out[85]:

array([ 1.1, 100. ,

3.3,

4.4,

5.5])

Он тоже изменился! Раньше мы видели подобную штуку в ситуациях, когда один список имел несколько имён (то есть несколько переменных на него ссылались), но создание среза раньше приводило к копированию информации. Оказывается, в numpy создание среза ничего не копирует: срез — это не новый массив, содержащий те же элементы, что старый, а так называемый view (вид), то есть своего рода интерфейс к старому массиву. Грубо говоря, наш срез s просто помнит, что «его» элемент с индексом 0 — это на самом деле элемент с индексом 1 от исходного массива x , а его элемент с индексом 1 — это на самом деле элемент с индексом 2 от исходного массива, а других элементов у него нет. Можно думать про срез как про такие специальные очки, через которые мы смотрим на исходный массив.

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

Если вам всё-таки нужно сделать копию массива, нужно использовать метод copy() .

In [86]:

y = x.copy()

In [87]:

y

Out[87]:

array([ 1.1, 100. ,

3.3,

4.4,

5.5])

In [88]:

x

Out[88]:

array([ 1.1, 100. ,

3.3,

4.4,

5.5])

In [89]:

y[0] = 12

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/12 Библиотеки numpy и matplotlib.ipynb

8/22

28.11.2022, 02:19

12 Библиотеки numpy и matplotlib - Jupyter Notebook

In [90]:

 

 

 

 

 

 

x

 

 

 

 

 

 

Out[90]:

 

 

 

 

 

 

array([

1.1,

100.

,

3.3,

4.4,

5.5])

In [91]:

 

 

 

 

 

 

y

 

 

 

 

 

 

Out[91]:

 

 

 

 

 

 

array([

12. ,

100.

,

3.3,

4.4,

5.5])

In [92]:

 

 

 

 

 

 

x

 

 

 

 

 

 

Out[92]:

 

 

 

 

 

 

array([

1.1,

100.

,

3.3,

4.4,

5.5])

Продвинутая индексация

Помимо коварных срезов есть ещё некоторые возможности создания новых массивов из старых. Например, можно выбрать из массива элементы с нужными номерами вот так:

In [93]:

x

Out[93]:

array([ 1.1, 100. ,

3.3,

4.4,

5.5])

In [94]:

y = x[ [1, 3, 4] ]

In [95]:

y

Out[95]:

array([ 100. ,

4.4,

5.5])

Можно даже использовать одинаковые номера.

In [96]:

y = x[ [1, 1, 1] ]

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/12 Библиотеки numpy и matplotlib.ipynb

9/22

28.11.2022, 02:19

12 Библиотеки numpy и matplotlib - Jupyter Notebook

In [97]:

y

Out[97]:

array([ 100., 100., 100.])

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

In [98]:

y[0] = 123

In [99]:

y

Out[99]:

array([ 123., 100., 100.])

In [100]:

x

Out[100]:

array([ 1.1, 100. ,

3.3,

4.4,

5.5])

In [101]:

x

Out[101]:

array([ 1.1, 100. ,

3.3,

4.4,

5.5])

Есть ещё один хитрый способ выбора элементов из массива. Допустим, мы хотим выбрать только те элементы, которые обладают каким-то свойством — скажем, меньше 50. Можно было бы использовать цикл с условием или аналогичный ему list comprehension, но в numpy используют другой синтаксис.

In [102]:

y = x[ x < 50 ]

In [103]:

x

Out[103]:

array([ 1.1, 100. ,

3.3,

4.4,

5.5])

127.0.0.1:8888/notebooks/EXONTOOLS/2/Доп. занятия/12 Библиотеки numpy и matplotlib.ipynb

10/22