ЛЕКЦИЯ_2 (22.09.2017)

Jupyter и magic functions

In [ ]:
lsmagic
In [2]:
str='Vasya'
%timeit 'Hello, {}'.format(str)
499 ns ± 9.97 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [3]:
%timeit 'Hello, %s' % str
314 ns ± 8.21 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [ ]:
%timeit?

Массивы и linalg в библиотеке Numpy

https://docs.scipy.org/doc/numpy/reference/ - официальная документация

Справка по модулям

Функция dir() позволяет получить список возможных команд. Функция __doc__ позволяет получить справку по функции.
In [9]:
import numpy
#print(dir(numpy))
#print (numpy.linalg.__doc__)
In [6]:
# список всех модулей
#help("modules")
In [7]:
# справка по объекту
#help(numpy.ndarray)

Немного о списках в Python

In [4]:
numbers = [4, 8, 9, 2, 6]
In [5]:
strings = ["Hello", "World", "Test"]
In [6]:
mixed_list = ["Hello", 6, 7.8]

Метод append добавляет элемент в список. Можно набрать numbers., нажать табуляцию (после точки), и получить список доступных методов. Можно набрать help(list) или help(numbers) и получить краткое описание этих методов.

In [7]:
numbers.append(45)
print(numbers)
[4, 8, 9, 2, 6, 45]
In [8]:
help(numbers.extend)
Help on built-in function extend:

extend(...) method of builtins.list instance
    L.extend(iterable) -> None -- extend list by appending elements from the iterable

In [9]:
numbers.extend([4,6,7])
print(numbers)
[4, 8, 9, 2, 6, 45, 4, 6, 7]
In [10]:
#cравним = и extend
#%timeit num_1 = numbers + mixed_list
#%timeit num_2 = numbers.extend(mixed_list)

Присвоение и копирование списков

In [11]:
#numbers.clear()
In [12]:
numbers
Out[12]:
[4, 8, 9, 2, 6, 45, 4, 6, 7]

Cоздадим копию списка numbers и изменим первый эелемент списка

In [13]:
copy = numbers
copy[0] = 45
copy
Out[13]:
[45, 8, 9, 2, 6, 45, 4, 6, 7]
In [14]:
numbers
Out[14]:
[45, 8, 9, 2, 6, 45, 4, 6, 7]

Что произошло?

  1. С помощью операции «квадратные скобки» был создан список.
  2. С помощью операции «равно» была создана ссылка на numbers c именем copy

Изменение элементов copy приведет к изменению numbers, и наоборот.

Для визуализации исполнения кода будем использовать сервис Python Tutor (http://www.pythontutor.com/)

In [15]:
%load_ext tutormagic
# Это магия, позволяющая вставить визуализацию с pythontutor прямо в этот notebook. 
# Чтобы его использовать, необходимо установить пакет tutormagic
# pip install tutormagic
In [16]:
%%tutor --lang python3
first_list = [5, 8, 9, 'Hello']
second_list = first_list
second_list[0] = 777

В левой части наш код, зелёная стрелка — это команда, которая только что была выполнена, красная — это команда, которую сейчас предстоит выполнить; в правой части — мир имён (Frames) и мир платоновских идеальных объектов (Objects). Возможно, вам придётся воспользоваться горизонатльной прокруткой, чтобы увидеть платоновский мир. Нажимая на кнопку Forward, вы можете проследить, что происходит с вашим кодом.

Если мы хотим создать действительно новый список, то есть скопировать существующий, нужно использовать метод copy().

In [17]:
first_list = [6, 9, 2, 5]
third_list = first_list.copy()
print(third_list)
third_list[0] = 100
print(third_list)
print(first_list)
[6, 9, 2, 5]
[100, 9, 2, 5]
[6, 9, 2, 5]

Как видите, теперь first_list и third_list ведут себя независимо. Этот код тоже можно визуализировать.

In [18]:
%%tutor --lang python3
first_list = [6, 9, 2, 5]
third_list = first_list.copy()
print(third_list)
third_list[0] = 100
print(third_list)
print(first_list)

Вы также можете встретиться с таким синтаксисом для копирования списков. [:] — это срез, начало которого совпадает с началом исходного списка, а конец — с концом. Такой код вы часто встречается в программах, написанных на Python 2, потому что там не было метода copy()

In [19]:
first_list = [6, 9, 2, 5]
other_list = first_list[:]
other_list
Out[19]:
[6, 9, 2, 5]

Массивы в Numpy

Информация про массивы https://docs.scipy.org/doc/numpy/reference/arrays.html

Атрибуты объекта ndarray

В Python атрибуты отдельного объекта обозначаются как name_of_object.attribute

ndarray.ndim — число осей (измерений) массива. Как уже было сказано, в мире Python число измерений часто называют рангом.

ndarray.shape — размеры массива, его форма. Это кортеж натуральных чисел, показывающий длину массива по каждой оси. Для матрицы из n строк и m столбов, shape будет (n,m). Число элементов кортежа shape равно рангу массива, то есть ndim.

ndarray.size — число всех элементов массива. Равно произведению всех элементов атрибута shape.

ndarray.dtype — объект, описывающий тип элементов массива. Можно определить dtype, используя стандартные типы данных Python. NumPy здесь предоставляет целый букет возможностей, например: bool, character, int, int8, int16, int32, int64, float, float8, float16, float32, float64, complex, complex64, object_.

ndarray.itemsize — размер каждого элемента массива в байтах. Например, для массива из элементов типа float64 значение itemsize равно 8 (=64/8), а для complex32 этот атрибут равен 4 (=32/8).

ndarray.data — буфер, содержащий фактические элементы массива. Обычно нам не будет нужно использовать этот атрибут, потому как мы будем обращаться к элементам массива с помощью индексов.

In [20]:
import numpy as np

Объект массив в Numpy - это np.array (на самом деле он называется np.ndarray).

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

Создание массивов

Набор функций Numpy для создания массивов https://docs.scipy.org/doc/numpy/reference/routines.array-creation.html

1. С помощью функции numpy.array()

In [21]:
# help(numpy.array)
#numbers = [4, 8, 9, 2, 6]
print(numbers) 
np_numbers = np.array(numbers)
[45, 8, 9, 2, 6, 45, 4, 6, 7]
In [22]:
numbers
Out[22]:
[45, 8, 9, 2, 6, 45, 4, 6, 7]
In [23]:
np_numbers
Out[23]:
array([45,  8,  9,  2,  6, 45,  4,  6,  7])

2. Переопределение типа при создании массива с помощью array()

In [24]:
print(numbers)
np_bool = np.array(numbers, dtype=bool)
np_bool
[45, 8, 9, 2, 6, 45, 4, 6, 7]
Out[24]:
array([ True,  True,  True,  True,  True,  True,  True,  True,  True], dtype=bool)

??? Добавьте ячейку с выводом атрибутов массива nb_bool

3. Функция numpy.zeros() создает массив из нулей.

Функция принимает кортеж с размерами, и аргумент dtype

In [25]:
np.zeros((3, 5))
Out[25]:
array([[ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.]])

4. Функция numpy.ones() создает массив из единиц. Функция принимает кортеж с размерами, и аргумент dtype

In [26]:
np.ones((2, 2, 2))
Out[26]:
array([[[ 1.,  1.],
        [ 1.,  1.]],

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

5. Функция eye() создаёт единичную матрицу (двумерный массив)

In [27]:
np.eye(5)
Out[27]:
array([[ 1.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.,  0.],
       [ 0.,  0.,  1.,  0.,  0.],
       [ 0.,  0.,  0.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  1.]])

6. Функция empty() создает массив без его заполнения. Исходное содержимое случайно и зависит от состояния памяти на момент создания массива

In [28]:
np.empty((3, 3))
Out[28]:
array([[  6.90403615e-310,   6.90403615e-310,   6.90402887e-310],
       [  6.90402887e-310,   6.90402887e-310,   6.90402887e-310],
       [  6.90402887e-310,   6.90402887e-310,   6.90402887e-310]])

7. Для создания последовательностей чисел, в NumPy имеется функция arange(), аналогичная встроенной в Python range(), только вместо списков она возвращает массивы, и принимает не только целые значения:

In [29]:
np.arange(10, 30, 5)
#np.arange(0, 1, 0.1)
Out[29]:
array([10, 15, 20, 25])

8. Функция linspace() вместо шага в качестве одного из аргументов принимает число, равное количеству нужных элементов

In [30]:
# 9 чисел от 0 до 2 включительно
np.linspace(0, 2, 9)  
Out[30]:
array([ 0.  ,  0.25,  0.5 ,  0.75,  1.  ,  1.25,  1.5 ,  1.75,  2.  ])

9. Функция fromfunction() применяет заданную функцию ко всем комбинациям индексов

In [31]:
def f1(i, j):
    return 3 * i + j
np.fromfunction(f1, (3, 4))
Out[31]:
array([[ 0.,  1.,  2.,  3.],
       [ 3.,  4.,  5.,  6.],
       [ 6.,  7.,  8.,  9.]])
In [32]:
np.fromfunction(f1, (3, 3))
Out[32]:
array([[ 0.,  1.,  2.],
       [ 3.,  4.,  5.],
       [ 6.,  7.,  8.]])

10. Массив, заполненный случайными числами, равномерно распределенными на отрезке [0,1)

In [33]:
#help(np.random.rand)
np.random.rand(3)
#np.random.rand(2,3)
Out[33]:
array([ 0.63747576,  0.02298088,  0.40581177])

Вывод массивов

При выводебольших массивов центральная часть скрывается автоматически. Для настройки выводы используется функция np.set_printoptions

In [34]:
# np.set_printoptions(threshold=1000) по умолчанию
print(np.arange(0, 3000, 1))
[   0    1    2 ..., 2997 2998 2999]
In [35]:
# np.set_printoptions(threshold=np.nan)
print(np.arange(0, 3000, 1))
[   0    1    2 ..., 2997 2998 2999]

Сходства и различия списков и массивов

Практически все операции выполняются поэлементно.

Список операций https://docs.scipy.org/doc/numpy/reference/arrays.ndarray.html#arithmetic-matrix-multiplication-and-comparison-operations

In [36]:
# Например, возведение в квадрат каждого элемента
np_numbers**2
Out[36]:
array([2025,   64,   81,    4,   36, 2025,   16,   36,   49])

Сравним умножение для списков и массивов

In [37]:
np_numbers*2
Out[37]:
array([90, 16, 18,  4, 12, 90,  8, 12, 14])
In [38]:
numbers*2
Out[38]:
[45, 8, 9, 2, 6, 45, 4, 6, 7, 45, 8, 9, 2, 6, 45, 4, 6, 7]
In [39]:
# упростим, чтобы не писать каждый раз np. 
# Команда from ... import позволяет импортировать не весь модуль целиком, а только определенное его содержимое
from numpy import array 
a = array([1,2,3,4]) 
In [40]:
# обращение к элементу массива (??? a[5])
a[3] 
Out[40]:
4
In [41]:
# изменение элемента массива
a[0] = 12
a
Out[41]:
array([12,  2,  3,  4])
In [42]:
# итерация элементов 
for x in a:
    print(x)
12
2
3
4

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

In [43]:
# сложение (!!! массивы одинаковой длины)
b = array([2, 3, 6, 10])
a+b
Out[43]:
array([14,  5,  9, 14])
In [44]:
# и конкатенация
np.concatenate( [a, b])
Out[44]:
array([12,  2,  3,  4,  2,  3,  6, 10])

Математические операции из библиотеки numpy https://docs.scipy.org/doc/numpy/reference/routines.math.html

In [45]:
# экспонента для каждого элемента массива a
np.exp(a)
Out[45]:
array([  1.62754791e+05,   7.38905610e+00,   2.00855369e+01,
         5.45981500e+01])
In [46]:
np.sqrt(a)
Out[46]:
array([ 3.46410162,  1.41421356,  1.73205081,  2.        ])

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

In [47]:
mixed_array = np.array(mixed_list)
mixed_array
Out[47]:
array(['Hello', '6', '7.8'],
      dtype='<U5')
In [48]:
mixed_array[0]='HelloHello'
mixed_array
Out[48]:
array(['Hello', '6', '7.8'],
      dtype='<U5')

???? Почему mixed_array[0] не изменил свое значение ????

In [49]:
# np.array([1,2,3]) целые
array([1,2,3])
Out[49]:
array([1, 2, 3])
In [50]:
# вещественные
array([1,2.,3])
Out[50]:
array([ 1.,  2.,  3.])

Срезы

In [51]:
q = a[1:3]
q
Out[51]:
array([2, 3])
In [52]:
# изменим значение элемента в срезе
q[0] = 25
q
Out[52]:
array([25,  3])
In [53]:
a
Out[53]:
array([12, 25,  3,  4])

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

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

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

In [54]:
c = a.copy()
In [55]:
c[0]=5
c
Out[55]:
array([ 5, 25,  3,  4])
In [56]:
a
Out[56]:
array([12, 25,  3,  4])

Выбор из массива в Numpy

In [57]:
# выбор элемента с нужным номером
print(a)
d = a[ [1, 3] ]
d
[12 25  3  4]
Out[57]:
array([25,  4])
In [58]:
# использование одинаковых номеров
print(a)
d = a[ [1, 1, 1] ]
d
[12 25  3  4]
Out[58]:
array([25, 25, 25])

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

In [59]:
# выбор по условию
print(a)
d = a[ a < 12 ]
d
[12 25  3  4]
Out[59]:
array([3, 4])
In [60]:
# операция сравнения применяется ко всему массиву a
a<12
Out[60]:
array([False, False,  True,  True], dtype=bool)
In [61]:
# затем массив подставляется в качестве индексов 
a[[False, False,  True,  True]]
Out[61]:
array([3, 4])
In [62]:
# несколько условий сразу | (ИЛИ), ~(НЕ)
a[ (a < 12) & (a > 3) ]
Out[62]:
array([4])
In [63]:
# замена элементов по условию
a[ (a < 12) & (a > 3) ] = 0
a
Out[63]:
array([12, 25,  3,  0])
In [64]:
# сравнение массивов
np.array([1, 2, 3]) == np.array([1, 2, 3])
Out[64]:
array([ True,  True,  True], dtype=bool)
In [65]:
# метод all(), возвращающий истину только если все элементы являются истиными
(np.array([1, 2, 3]) == np.array([1, 2, 3])).all()
Out[65]:
True
In [66]:
# есть специализированная функция для проверки на равенство
np.array_equal(np.array([1, 2, 3]), np.array([1, 2, 3]))
Out[66]:
True