#!/usr/bin/env python # coding: utf-8 # # Программирование на языке Python для сбора и анализа данных # # *Текст лекции: Щуров И.В., НИУ ВШЭ* # # Данный notebook является конспектом лекции по курсу «Программирование на языке Python для сбора и анализа данных» (НИУ ВШЭ, 2015-16). Он распространяется на условиях лицензии [Creative Commons Attribution-Share Alike 4.0](http://creativecommons.org/licenses/by-sa/4.0/). При использовании обязательно упоминание автора курса и аффилиации. При наличии технической возможности необходимо также указать активную гиперссылку на [страницу курса](http://math-info.hse.ru/s15/m). Фрагменты кода, включенные в этот notebook, публикуются как [общественное достояние](http://creativecommons.org/publicdomain/zero/1.0/). # # Другие материалы курса, включая конспекты и видеозаписи лекций, а также наборы задач, можно найти на [странице курса](http://math-info.hse.ru/s15/m). # ## Лекция №11: Библиотеки numpy и matplotlib # ### Библиотека numpy: эффективные массивы # Писать программы на Python легко и приятно. Гораздо легче и приятнее, чем на низкоуровневых языках программирования, таких как C или C++. Но, увы, чудес не бывает: за простоту написания кода мы платим скоростью его исполнения. # In[2]: numbers = [1.2] * 10000 numbers[:10] # Мы создали список из 10000 чисел. Для простоты они все одинаковые, но Python об этом не знает. Как быстро мы возведём каждое из них в квадрат? # # Для проверки, с какой скоростью выполняется некоторый фрагмент кода, полезно использовать магическое слово `%%timeit`. Оно говорит, что ячейку нужно выполнить несколько раз и засечь, сколько времени на это ушло. # In[5]: get_ipython().run_cell_magic('timeit', '', '\nsquares = [x**2 for x in numbers]\n') # Больше миллисекунды на один проход! (Кстати, `x*x` будет в два раза быстрее — попробуйте!) Не очень-то быстро, на самом деле. Для «тяжелой» математики, часто возникающей при обработке больших массивов данных, хочется использовать все возможности компьютера. # # Но не надо отчаиваться: для быстрой работы с числами есть специальные библиотеки, и главная из них — `numpy`. # In[6]: import numpy as np # Главный объект, с которым мы будем работать — это `np.array` (на самом деле он называется `np.ndarray`): # In[7]: np_numbers = np.array(numbers) # In[8]: np_numbers # `np.array` — это специальный тип данных, похожий на список, но содержащий данные только одного типа (в данном случае — только вещественные числа). # In[9]: np_numbers[3] # In[10]: len(np_numbers) # С математической точки зрения, `np.array` — это что-то, похожее на вектор. Но практически все операции выполняются поэлементно. Например, возведение в квадрат каждого элемента можно реализовать как `np_numbers**2`. # In[12]: np_squares = np_numbers**2 np_squares # Посмотрим, как быстро работает эта операция: # In[11]: get_ipython().run_cell_magic('timeit', '', '\nnp_squares = np_numbers**2\n') # Здесь 5 микросекунд, в 200 раз быстрее! Правда, нас предупреждают, что это может быть последствия кеширования — но в любом случае, работа с массивами чисел с помощью `numpy` происходит гораздо быстрее, чем с помощью обычных списков и циклов. # # Давайте посмотрим на `np.array` более подробно. # ### Массивы похожи на списки… # In[53]: from numpy import array # чтобы не писать каждый раз np # In[54]: q = array([4, 5, 8, 9]) # Я дальше буду называть `np.array` массивами (в отличие от списком, которые мы так в Python не называем). Итак, можно обращаться к элементам массива по индексам, как и к спискам. # In[33]: q[0] # И менять их тоже можно # In[34]: q[0] = 12 q # Можно итерировать элементы списка, хотя этого следует по возможности избегать — массивы numpy нужны как раз для того, чтобы не использовать циклы для выполнения массовых операций. Ниже будет понятно, как это можно делать. # In[185]: for x in q: print(x) # Можно делать срезы (но с ними тоже есть хитрости, об этом ниже). # In[186]: q[1:3] # ### …но не всегда похожи! # Давайте заведём ещё один массив. # In[187]: w = array([2, 3, 6, 10]) # Все арифметические операции над массивами выполняются поэлементно. Поэтому `+` означает сложение, а не конкатенацию. В отличие от обычных списков. # In[188]: q + w # Если вы хотели сделать конкатенацию, то нужно использовать не оператор сложения, а специальную функцию. # In[189]: np.concatenate( [q, w] ) # Аналогично сложению работают и другие операции. Например, умножение: # In[190]: q * w # Если у массивов будет разная длина, то ничего не получится: # In[191]: q = np.array([14, 8, 14, 9]) w = np.array([1, 3, 4]) q + w # Можно применять различные математические операции к массивам. # In[192]: x = array([1,2,3,4,5]) y = array([4, 5, 6, 2, 1]) # In[193]: np.exp(x) # Заметим, что мы должны были использовать функцию `exp` из `numpy`, а не из обычного `math`. Если бы мы взяли эту функцию из `math`, ничего бы не сработало. # In[194]: import math # In[195]: math.sqrt(x) # Вообще в `numpy` много математических функций. Вот, например, квадратный корень: # In[72]: np.sqrt(x) # ### Типы элементов в массивах # Вообще, в массивах могут храниться не только числа. # In[61]: mixed_array = np.array([1, 2, 3, "Hello"]) # Однако, все элементы, лежащие в одном массиве, должны быть одного типа. # In[62]: mixed_array # Здесь видно, что числа `1`, `2`, `3` превратились в строчки `'1'`, `'2'`, `'3'`. Параметр `dtype` содержит информацию о типе объектов, хранящихся в массиве. ` 2) ] # Скобочки очень важны, иначе ничего не заработает. Операция `&` соответствует логическому И и опять же выполняется поэлементно. # In[76]: (x < 50) & (x > 2) # Для логического ИЛИ мы бы исползовали `|`, а для отрицания `~`. # In[116]: (x < 50) | (x > 2) # In[117]: ~ (x < 50) # Результатом такого выбора снова является *вид*, и это очень удобно, потому что позволяет заменять одни элементы на другие в зависимости от условий и таким образом избавляться от операторов `if`. # In[118]: x # In[121]: x[ x>50 ] = 0 # заменить все элементы, большие 50, на 0 # In[122]: x # Кстати, чтобы узнать, правда ли, что два массива равны (в том числе, что состоят из одних и тех же элементов, находящихся в одном и том же порядке), теперь нельзя использовать `==` — ведь это тоже поэлементная операция! # In[142]: np.array([1, 2, 3]) == np.array([1, 2, 3]) # Чтобы понять, правда ли, что массивы равны, можно использовать такой синтаксис: # In[144]: (np.array([1, 2, 3]) == np.array([1,2,3])).all() # Здесь мы сначала сравниваем массивы поэлементно, а потом применяем к результату метод `all()`, возвращающий истину только если все элементы являются истиными. Этот подход часто используется, хотя имеет свои подводные камни (см. [на stackoverflow](http://stackoverflow.com/questions/10580676/comparing-two-numpy-arrays-for-equality-element-wise)). Есть и специализированная функция для проверки на равенство: # In[151]: np.array_equal(np.array([1, 2, 3]), np.array([1, 2, 3])) # ### Построение графиков в matplotlib # В Python существует много способов строить графики. Мы сейчас рассмотрим самый простой из них, а позже поговорим про более сложные. Для этого нам потребуется библиотека `matplotlib`, а точнее её часть под названием `pyplot`. Стандартный способ её импорта выглядит вот так: # In[123]: import matplotlib.pyplot as plt # Чтобы графики рисовались прямо в ноутбуке, нужно дать вот такую магическую команду: # In[124]: get_ipython().run_line_magic('matplotlib', 'inline') # Простейшее рисование — это функция `plot`, она принимает на вход список $x$-координат, список $y$-координат и рисует соответствующую картинку либо в виде ломаной: # In[84]: plt.plot([1, 2, 3, 4], [1, 4, 9, 16]) # либо в виде отдельный точек: # In[126]: plt.plot([1, 2, 3, 4], [1, 4, 9, 16], 'o') # Либо ещё кучей способов. # In[127]: plt.plot([1, 2, 3, 4], [1, 4, 9, 16], '-o') # Посмотрим, как `numpy` работает в связке с `matplotlib.pyplot`. Вообще это всё очень похое на *MATLAB*, и если вы знаете *MATLAB*, то для вас многое здесь будет знакомо — и наоборот, после `numpy` и `matplotlib.pyplot` будете чувствовать себя как дома в *MATLAB*. # # > Chewie, we're home. [Здесь должна была быть картинка из «Звёздных войн», но я не могу её включить по соображениям авторских прав.] # In[128]: x = np.linspace(-5, 5, 200) # это массив из 200 элементов, состоящий из равномерно разбросанных чисел от -5 до 5 # In[129]: len(x) # In[132]: x[:10] # Вот так можно нарисовать параболу: # In[136]: plt.plot(x, x**2) # Действительно, `x**2` — это массив, элементами которого являются квадраты чисел, лежащих в `x`. Значит, построив график, состоящий из точек, $x$-координаты которых записаны в `x`, а $y$-координаты с `x**2`, мы построим график функции $y=x^2$. # А вот, например, синусоида: # In[139]: plt.plot(x, np.sin(x)) # А вот что-то посложнее: # In[140]: plt.plot(x, np.sin(x**2)) # Вот так можно построить несколько графиков и сделать подписи. # In[157]: x = np.linspace(-1,1,201) plt.plot(x,x**2, label = '$y = x^2$') plt.plot(x,x**3, label = '$y = x^3$') plt.legend(loc='best') # Знак `$` в `label` используется для того, чтобы записывать формулы — это делается в [LaTeX](https://en.wikibooks.org/wiki/LaTeX/Mathematics)-нотации и долларами там обозначается начало и конец формулы. (Кстати, в IPython Notebook в ячейках типа `Markdown` тоже можно записывать формулы в LaTeX-нотации.) # Конечно, мы могли бы получить `x` и `y` не в результате вычисления значений какой-то функции, а откуда-то извне. Возьмём для примера случайные числа. # In[152]: x = np.random.random(100) # In[153]: y = np.random.random(100) plt.plot(x,y, 'o') # Есть и специализированная функция для создания *scatter plot*. # In[154]: plt.scatter(x,y) # Ещё можно построить [гистограмму](https://ru.wikipedia.org/wiki/%D0%93%D0%B8%D1%81%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B0#.D0.92_.D1.81.D1.82.D0.B0.D1.82.D0.B8.D1.81.D1.82.D0.B8.D0.BA.D0.B5). # In[155]: plt.hist(x) # Можно строить трёхмерные картинки, но тут уже нужна магия и я не буду вдаваться в детали. # In[175]: import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() # In[176]: get_ipython().run_line_magic('matplotlib', 'inline') # In[178]: x,y = np.mgrid[-1:1:0.01, -1:1:0.01] ax = fig.add_subplot(111, projection='3d') ax.plot_surface(x,y,x**2+y**2) fig # Наконец, можно строить интерактивные картинки! # In[180]: from ipywidgets import interact, interactive, fixed import ipywidgets as widgets import numpy as np import matplotlib.pyplot as plt # In[181]: get_ipython().run_line_magic('matplotlib', 'inline') # In[182]: def plot_pic(a, b): x = np.linspace(-3,3,200) plt.plot(x, np.sin(x*a+b)) # In[183]: interact(plot_pic, a=[0, 3, 0.1], b=[0, 3, 0.1]) # Функция `interact` создаёт несколько бегунков и позволяет с их помощью задавать параметры у функции (в данном случае `plot_pic`), которая строит график. # # > Эта картинка не будет интерактивной при просмотре IPython Notebook, но если вы скачаете его и запустите у себя на компьютере, то там будет. # Ещё `interact` можно вызывать так: # In[184]: @interact(a=[0, 3, 0.1], b=[0, 3, 0.1]) def plot_pic(a, b): x = np.linspace(-3,3,200) plt.plot(x, np.sin(x*a+b)) # На сегодня всё!