note_1
Заметка 1. Введение в питон и анализ данных.
курса Введение в машинное обечение.
Шокуров Антон В.
shokurov.anton.v@yandex.ru
http://машинноезрение.рф
Версия 0.13

Анотация

Вводятся базовые элементы питона (Python версии 3.xx) на базе ключевых библиотек относящихся к анализу данных. Конкретно речь идет о численных выражениях, переменных, циклах, построение графиков/гистограмм и статистике. Последнее, в частности, используется для ввода ключевых понятий из анализа данных: выборка, плотность распределения и подгонка модели под данные.

Это предварительная версия! Любые замечания приветсвуются.

Вычисления как на калькуляторе

In [1]:
# Замечания, пояснения в коде начинаются с символа решетка и идут до конца строки. Как, например, эта строка.
In [2]:
# Код может быть разбит на блоки. Это например новый блок кода. При выполнеии они нумеруются.
In [3]:
# Можно выолнять вычисления как на калькуляторе. Доступны стандартные операции: +, -, * и /.
# Для этого выражение записывается в блоке
2.0 + 3.5 * ( 5 - 3.)
# и наживается клавиша воспроизведения.
# Отмечу, что последнее значение блока выводится в качестве результата блока.
Out[3]:
9.0
In [4]:
5/2 # Обычное деление.
Out[4]:
2.5
In [5]:
# Также доступны вычислеия над целыми числами:
# целочисленное деление
5//2
Out[5]:
2
In [6]:
(-5)//2 # Действует правило округление "вниз" -- берется нижняя граница. При деление было бы -2.5. Внизу -3.
Out[6]:
-3
In [7]:
# Можно также вычислить остаток от деления:
5%2
Out[7]:
1
In [8]:
(-5) % 2 # Остаток всегда положительный.
Out[8]:
1
In [9]:
(-5) % (-3) # Ну только, если сам делитель неотрицательный. 
Out[9]:
-2
In [10]:
5 % (-3) # Иначе остаток будет как раз отрицательный.
Out[10]:
-1
In [11]:
# Возведение в степень задается двойной звездочкой, т.е. 3 в степени 2 записывается как:
3 ** 2
Out[11]:
9
In [12]:
# Степень конечно может быть и дробной:
32 ** (1/5) # т.е. корень пятой степени из 32.
Out[12]:
2.0
In [13]:
# Для расширения возможностей ествественно необходимо применять сторонний код, а именно библиотеки.
# В следующем блоке показано как это делается.
In [14]:
# Одна из самых известных библиотек numpy -- это библиотека для работы с векторами, матрицами.
# Она же применяется для вычисления стандартных функций: тригонометрических, эекспонента, логарифм, степень и др.
import numpy as np # Название numpy длинное. Данная строчка загружает библиотеку numpy и дает ей сокращенное название np.
In [15]:
2.2 * np.sin( 1.1 ) # Применили функцию синус (np.sin).
# Следует отметить то как функция вызывается: не сама по себе, а через префикс "np.". Последнее связано с тем, что функция
# sin из библиотеки numpy, но она была загружена в систему с сокращенным названием np. Поэтому такое обозначение.
Out[15]:
1.9606561921351582
In [16]:
# Ествественно в качестве аргумента функции можно исользовать не только число, но и целое выражение
np.sqrt( 4**2 + 3**2 )
Out[16]:
5.0
In [17]:
# На всякий случай упомину и случай функция от фукнции:
np.arcsin( np.sin(0.5) )
Out[17]:
0.5

Переменные

In [18]:
# Главная особенность понятия кодинга/программирования это наличие переменных.
# сущность позволяющая сохрнаить предыдущий резльтат, более того, его переприсвоить (сохранить новле значение).
In [19]:
a = 2.1 - 5.8 * np.cos( 3.5 ) # Вычисляем очередное выражение. Но главное в другом. Значение сохранается в переменную.
# Переменая как коробка с именем. В неё можно положить что-то и оно там останется пока не будет положено что-то новое.
# Результат блока поэтому не выводится -- он сохранен в переменную.
In [20]:
a # Можем вывести значение переменной, т.е. фактически значение которое туда было ранее помещено.
Out[20]:
7.5314487862866191
In [21]:
# С переменой также связан её тип : целое, вещественное число и тому подобное. Можно узнать какой тип у переменной:
type( a ) #  Отмечу, что в других языках сначала объявляется переменная нужного типа, а потом присваивается значение.
# В питоне иначе. Переменная создается (если нужно) в момент присвоения ей значения.
# Последнее имеет как свои преимущества, так и недостатки. Преимущество в том, что не нужно отдельно создавать переменную,
# недостаток в том, что система не будет следить за правильностью присвоения значения переменной, т.е.
# переменной, которая ранее хранила матрицу, можно присовить просто число, а то и вообще строку некого текста.
Out[21]:
numpy.float64
In [22]:
# Итого, в переменую сохранаяется как значение, так и её тип.
In [23]:
a * 2 - 10 # Главное, что можно выполнять вычисления с переменными. Вданом случае используется переменная a.
# Вместо переменной в выражение будет подставлено её значение и выражение вычислиться как обычно.
Out[23]:
5.0628975725732381
In [24]:
a * 3 + 5; # Точка с запятой блокирует вывод вычисления. В данном случае получается, что данной строчка бессмысленна.
In [25]:
# Если попытатся использовать не существующую переменную, то компилятор будет ругаться:
5 + n # Он укажет, что такая переменая не задана.
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-25-bcadda51f186> in <module>()
      1 # Если попытатся использовать не существующую переменную, то компилятор будет ругаться:
----> 2 5 + n # Он укажет, что такая переменая не задана.

NameError: name 'n' is not defined
In [26]:
a = 3 + 1 # Не менее важно и, что переменной можно, ествественно, присвоить новое значение выражения.
# Если переменная ранее не существовала, то она создается.
# Если она уже существовала, то она забывает свое прошлое значение.
a #Это строчка чтобы вывод все-ткаи увидеть значение переменной a.
Out[26]:
4
In [27]:
bb = a * 2 # Переменных ествественно может быть много. Их имена могут быть длинными, но с некими огрничениями.
bb # Но не будем об этом.
Out[27]:
8
In [28]:
a = a + 1 # Одну и туже переменную конечно можно использовать и справа и слево от рвенста. Важно понимать:
# Сначала вычисляется правая часть. Потом присваивается левой, где подразумевается переменная.
a
Out[28]:
5
In [29]:
# , т.е. в левой части должна быть перемена
a + 1 = a + 2 # В левой части не должно быть выражения. Ну разве что какого-то хитрого...
# Иначе будет ошибка.
  File "<ipython-input-29-210061f5f364>", line 2
    a + 1 = a + 2 # В левой части не должно быть выражения. Ну разве что какого-то хитрого...
                                                                                             ^
SyntaxError: can't assign to operator
In [30]:
# Блок можно выполнять нескоко раз, для этого его нужно щелкнуть и заново нажать кнопку проигрывателя.
# Попробуй это сделать с блоком содержащем строчку "a = a + 1"
# При каждом новом выполнении значение блока будет менятся.
In [31]:
# Отмечу, что при присвоении блок забывает не только свое значение, но и свой тип.
# Например, сейчас тип у переменной a целочисленый.
a
Out[31]:
5
In [32]:
# Это определяется отсутвием точки (запятой). Но можно это проверить и явно спросив это у системы:
type( a )
Out[32]:
int
In [33]:
# Но если перейти к вычисления с действительными числами, то тип сразу изменится.
a = 25 ** 0.5 # хотя в даном случае значение будет тем же. Но это чистое совпадение.
a
Out[33]:
5.0
In [34]:
# Повторим запрос типа у системы.
type( a )
Out[34]:
float
In [35]:
# Итого, в первом случае тип был целочисленым (int), а во втором вещественный (плавающая точка, float)
In [36]:
# Важно понимать, что система как в таких простых случаях, так и в более сложных ругатся не будет.
# Она такое присвоение со сменой типа разрешит (во многих других языках так нельзя).
# Это важно, когдя появятся более сложные выражения.
In [37]:
# Подчеркну ещё раз, что в переменную записывается значение, а не выражение, которое это значение выдало.
# Покажу это на характерном прмере.
a = 5 # Присвоеим переменным значения.
b = a * 2
In [38]:
a, b # Выведем значения переменных.
Out[38]:
(5, 10)
In [39]:
a = -3 # Зададим новое значение для переменной a.
b # Изменится ли значение перменной b? Конечно же нет!
Out[39]:
10
In [40]:
# Прежде чем переходить к самому главному объекту языка Python покажу как можно присваивать значения в паралель.
a, b = 5 + bb, 6 - np.sin ( 1)
In [41]:
# По аналогии чтобы вывести значения переменных пишем:
b, a # Специально в другом порядке.
# Убедись, что ты понимаешь результат.
Out[41]:
(5.1585290151921033, 13)

Списки -- основной объект питона!

In [42]:
# Вычисления над отдельными числами это хорошо, но нам понадобиться список чисел. Хотябы чтобы хранить наши данные.
# Такой тип объекта подддерживает добавление новых элементов в его конец, а также извлечение и удаление подсписков.
In [43]:
[4, 8, 15, 16, 23, 42] # Создаем список из 4 элементах.
Out[43]:
[4, 8, 15, 16, 23, 42]
In [44]:
[ 2 + 5, 7 - 9]
Out[44]:
[7, -2]
In [45]:
# Чтобы были понятны принципы работы с ним, нужно его все-ткаи присвоить переменной.
In [46]:
l = [4, 8, 15, 16, 23, 42] # Присвоили переменной l списк
In [47]:
type( l ) # Выведем его тип. Видно что он отличается от ранее используемых числовых типов.
Out[47]:
list
In [48]:
l[3] # Число в квадратных скобках указывает на индекс списка. Внимание, элементы списка нумеруются с 0.
Out[48]:
16
In [49]:
type( l[3] ) # Но, тип самого элемента списка ествественно свой, числовой.
Out[49]:
int
In [50]:
len( l ) # Можно узнать размер списка.
Out[50]:
6
In [51]:
l[7] = 2 # Индекс должен соответствовать существующему элементу списка. Иначе будет ошибка.
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-51-dbd124f9abea> in <module>()
----> 1 l[7] = 2 # Индекс должен соответствовать существующему элементу списка. Иначе будет ошибка.

IndexError: list assignment index out of range
In [52]:
l[3:5] # Можно указывать диапазон индексов: от и до. Причем, "до" невключительно, т.е. в данном случае индексы: 3 и 4.
Out[52]:
[16, 23]
In [53]:
l[3:100] # При задании интервала индесов диапазон может выходить за пределы списка.
# В таком случае диапазон будет укорочен. Ошибкой это считаться не будет.
Out[53]:
[16, 23, 42]
In [54]:
l[2:] # Если после двоеточия числа нет, то значит нужно идти до конца списка.
Out[54]:
[15, 16, 23, 42]
In [55]:
l[:4] # Если числа нет до двоеточия, то значит новый список начинется с начала данного.
Out[55]:
[4, 8, 15, 16]
In [56]:
# Итого, если не указывать явно одну из границ диапазона индексов, то берутся по масимому. 
l[:5], l[3:] # т.е. илибо с начала либо до конца.
Out[56]:
([4, 8, 15, 16, 23], [16, 23, 42])
In [57]:
l[1:100:2] # В расширенном варианте можно указать шаг увеличения индекса. В данном случае он равен 2.
Out[57]:
[8, 16, 42]
In [58]:
l[1::2] # Ествественно в расширенном варинте тоже можно опускать числа. Смысл будет прежний.
Out[58]:
[8, 16, 42]
In [59]:
# Отмечу, что элементы списка можно нумировать и с конца. Для этого используются отрицательные индексы.
l[-1] # -1 соответствует первому элементу с конца, т.е. последнему.
Out[59]:
42
In [60]:
l[-2] # Второй элемент с конца.
Out[60]:
23
In [61]:
# Такие элементы также можно использовать при задании диапазона индексов.
l[:-2] # В данном случае, извлекутся все элементу кроме двух посдних.
Out[61]:
[4, 8, 15, 16]
In [62]:
l[::-1] # Шаг тоже может быть отрицательный. В даном случае получится список в обратном порядке.
Out[62]:
[42, 23, 16, 15, 8, 4]
In [63]:
l[2] = 1 # Можно присловить другое значение элементу списка.
l # Выводим значение переменной l, т.е. списк чисел
Out[63]:
[4, 8, 1, 16, 23, 42]
In [64]:
# Присвоить можно и диапазону значений сразу:
l[1:3] = 3, 7 # Список числе должен соответствовать по количеству.
l
Out[64]:
[4, 3, 7, 16, 23, 42]
In [65]:
l.append(3) # Список на то и списк, что к нему можно добавить элемент в его конец.
l
Out[65]:
[4, 3, 7, 16, 23, 42, 3]
In [66]:
len( l )
Out[66]:
7
In [67]:
l.remove(3) # Вырезаем (удаляем) первый элемент из списка равные данному.
l
Out[67]:
[4, 7, 16, 23, 42, 3]
In [68]:
del l[2:5] # Удаляет элемент с данным индексом (ами)
l
Out[68]:
[4, 7, 3]
In [69]:
del l[1], l[1] # Удаляем один или более разрозненных элементов. подумайте почему индекс указан ожин и тот же.
l
Out[69]:
[4]
In [70]:
l.append( -5 )
l.append( 7 )
l
Out[70]:
[4, -5, 7]
In [71]:
# Списки нужны для хранения данных. Если мы захотим все числа умножить на два, то возможно мы бы написали:
l * 2
Out[71]:
[4, -5, 7, 4, -5, 7]
In [72]:
# И увидели бы результат, который скоре всего не соответсвует нашим ожиданиям.
# Дело в том, что список является объектом сам по себе. Поэтому он имеет свои смысловые нагрузки на операции.
# Например, + объединяет списки (конкатенация), точнее она ставит один список (второй) в конец другому (первому).
[1, -5] + [-7, 5, 18]
Out[72]:
[1, -5, -7, 5, 18]
In [73]:
# Операции умножение соответствует многократной конкатенации (т.е. повтору) самого себя:
[2, 5] * 3
Out[73]:
[2, 5, 2, 5, 2, 5]

Списки: map, reduce, filter

In [74]:
# Возвращаясь к нашему желанию выполнить опредленное действо для всех элементов. Для это используются циклы: for.
# Сама конструкция является генератором списка, т.е. она по списку строит список (того же размера!).
[ x * 2 for x in l] # По умному это назвается map, т.е. применение отображения к каждому из элементов списка.
Out[74]:
[8, -10, 14]
In [75]:
 x * 2 for x in l # Так нельзя. Обрати внимание на квадратные скобки в примере в прошлом блоке.
  File "<ipython-input-75-040357c58b76>", line 1
    x * 2 for x in l # Так нельзя. Обрати внимание на квадратные скобки в примере в прошлом блоке.
            ^
SyntaxError: invalid syntax
In [76]:
l
Out[76]:
[4, -5, 7]
In [77]:
[ x for x in l if x%2 == 1 ]
Out[77]:
[-5, 7]
In [78]:
# Но прежде чем углубляться в эту компактную конструкцию рассмотрим дла начала её более полный вариант.
summa = 0 # Обявили переменную summa для хранения суммы всех элементов списка.
# Конструкция for позволяет взять каждый элемент списка поочередно и совершить некое действие над ним.
# В данном случае элементы берутся их списка l и временно сохраняются в переменную x.
for x in l: #l -- мы ранее определяли. Двоеточее в конце строки обазательно.
    summa = summa + x # Каждый элемент добавляется к общей сумме. Операция делается постепенно. Отступ необходим.
summa # По умному это назвается reduce, т.е. редукция, когда список сводится к "числу".
Out[78]:
6
In [79]:
# Список, ествественно, можно задавать разными способами. Например явным. Временная переменная тоже необязательно x.
summa = 0
cnt = 0
# Цикл пробегает по элементам явно заданного списка.
for d in [2, -5, 9, 4]: # В даном примере действо для каждого элемента будет состоять из двух строчек.
    summa = summa + d # По аналогии считается сумма чисел.
    cnt = cnt + 1 # Но и их общее количество. Это позволит вычислить среднее значение.
summa/cnt # Выводим среднее значение элементов списка.
Out[79]:
2.5
In [80]:
# Помимо map и reduce важной операцией над списками является операция filter, т.е. фильтрация объектов по некому условию.
[x for x in [1, -4, 7, 2, 9] if x%2 == 0] # Создает список состоящей их четных чисел.
Out[80]:
[-4, 2]
In [81]:
# Сущесвуют ряд встроеных объектов, позволяющих увпростить процесс создания списков.
[i for i in range(20)] # Списки можно создавать циклом. Используется range объект задающий диапазон.
# В данном случае с 0 до 20 (не включительно).
Out[81]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
In [82]:
# В заключении обсуждения списков отмечу, что их элементы не обязаны иметь один и тот же тип
ll = [2, "строка текста", 6]
ll
Out[82]:
[2, 'строка текста', 6]
In [83]:
# Элемент с индексом 0 и 2 содержит
ll[0], ll[2]
Out[83]:
(2, 6)
In [84]:
# А элемент с индексом 1 являтся строчкой:
ll[1]
Out[84]:
'строка текста'
In [85]:
type( ll[1] )
Out[85]:
str
In [86]:
'строка текста'[4] # Строка текста тоже на самом деле список элементов.
Out[86]:
'к'
In [87]:
ll[1][5] # Извлекаем отдельную букву в этом списке.
Out[87]:
'а'
In [88]:
type(ll[0]), type(ll[2])
Out[88]:
(int, int)
In [89]:
# Последнее показывает, что списки могут соедржать совершенно разнородные объекты. В этом смысле они полноценные.
In [90]:
# Поэтому, естественно, что списки могут содержать списки.
a = [ 2, 3, [4, 5], 8] # Список из трех элементов.
a
Out[90]:
[2, 3, [4, 5], 8]
In [91]:
# Нулевой, первый и третьий содержат числа:
a[0], a[1], a[3]
Out[91]:
(2, 3, 8)
In [92]:
# А вот второй элемент сам является списком:
a[2]
Out[92]:
[4, 5]
In [93]:
# Один из способов, как вычислить значение от каждого элемента списка, было показано выше.
# Показанный способ (отображение) был явный. Он был реализован на чистом питоне без вспомогательных средств.
# Но можно вычислить функцию от каждого элемента списка инче, использую вспомогательные (библиотечные) функции.
a = np.sin( [1.1, 2.2, -4.4] )
a
# Полученый объект уже не будет списоком (см. слово array). Он преврятится в массив, т.е. array.
Out[93]:
array([ 0.89120736,  0.8084964 ,  0.95160207])
In [94]:
# Тип array с точки зрения обычных языков фактически является массивом, т.е. объектом у которого тип элементов постоянен.
# По суте идексируемая переменная (имеет тот же тип).
# Такой тип соответсвует математическим понятиям вектора, матрице. Переменая с индексом.
# Перейдем к описанию массивов.

Вычисления над векторами, матрицами

In [95]:
# Списки хороши, но над ними как мы помним нельзя выполнять нужные нами арфиметических операций.
# В частности, списки нельзя складывать. Точнее эта операция будет иметь иное толкование:
[1.1, 2.2, -4.4] + [2.5, 1.9, 5.1]
# Они совместяться в один список (контакенация)
Out[95]:
[1.1, 2.2, -4.4, 2.5, 1.9, 5.1]
In [96]:
# Для содания списоков чисел, которые можно скоадывать поэлементно, нужны именно вектора/массивы.
# Они из списка создаются так:
np.array( [1.1, 2.2, -4.4])
Out[96]:
array([ 1.1,  2.2, -4.4])
In [97]:
# Такие объекты, массивы, можно складывть по элементно.
np.array( [1.1, 2.2, -4.4]) + np.array( [2.5, -1.9, 5.1])
Out[97]:
array([ 3.6,  0.3,  0.7])
In [98]:
# Размер массивов должен совпадать. Иначе система выдаст соответствующую ошибку.
np.array( [1.1, 2.2, -4.4]) + np.array( [2.5, -1.9, 5.1, 7.1])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-98-e8be634319ba> in <module>()
      1 # Размер массивов должен совпадать. Иначе система выдаст соответствующую ошибку.
----> 2 np.array( [1.1, 2.2, -4.4]) + np.array( [2.5, -1.9, 5.1, 7.1])

ValueError: operands could not be broadcast together with shapes (3,) (4,) 
In [99]:
# При выводе ошибки система указала, что один массив имеет размер 3, а другой -- 4. Даже была указана их одномерность.
In [100]:
# Размер массива всегда можно запросить у самого объекта.
l = np.array( [1.1, 2.2, -4.4]) # Сохраняем массив в переменную l
l.shape # Запрашиваем у переменной её размер. Он хранится в переменной shape.
Out[100]:
(3,)
In [101]:
bb.shape # Размер имеют объекты array. Другие не обязаны давать значение на это поле.
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-101-1aaacd00ae90> in <module>()
----> 1 bb.shape # Размер имеют объекты array. Другие не обязаны давать значение на это поле.

AttributeError: 'int' object has no attribute 'shape'
In [102]:
type( l ) # Запросим и тип. Замечу, что он размер не знает.
Out[102]:
numpy.ndarray
In [103]:
a0 = np.array([-1, 2])
a0
Out[103]:
array([-1,  2])
In [104]:
a1 = np.array([3, 5])
a1
Out[104]:
array([3, 5])
In [105]:
a0 * a1 # Поэлементное умножение массивов.
Out[105]:
array([-3, 10])
In [106]:
# Ну или в явном виде, т.е. без создания дополнительных промежуточных переменных.
np.array([-2, 7]) * np.array([ -2 , 5]) 
Out[106]:
array([ 4, 35])
In [107]:
# Расширение данной идеи на матрицы.
a0 = np.array([[-1], [2]])
a0 # Отмечу, если раньше a0 был массивом, то теперь стал матрицей, т.е. поменялась размерность.
Out[107]:
array([[-1],
       [ 2]])
In [108]:
# Поэлементное умножение матриц (элемент массива является одноэлементным массивом). 
a0 * np.array([ [3] , [5]]) # Не путать с матричным умножением!
Out[108]:
array([[-3],
       [10]])
In [109]:
# Размеры у матрицы a0 были:
a0.shape # т.е. две строки, в каждой из которых по одному элементу.
Out[109]:
(2, 1)
In [110]:
a0 = np.array([[-1, 2]]) # Можно сделать матрицу наоборот. 
a0
Out[110]:
array([[-1,  2]])
In [111]:
a0.shape # Одна строка, содержащая два элемента.
Out[111]:
(1, 2)
In [112]:
# Такие матрицы тоже можно умножать по-элементно.
np.array([[-1, 2]]) * np.array([ [3 , 5]])
Out[112]:
array([[-3, 10]])
In [113]:
# Матрицы можно формировать из других матриц.
a0 = np.array( [ 3, -6 ] )
a1 = np.array( [ -1, 2 ] )
a = np.array( [ a0, a1] )
a
Out[113]:
array([[ 3, -6],
       [-1,  2]])
In [114]:
l[4] # Как и со списками нельзя обарщатся к эелементу, которого нет в массиве.
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-114-ae4131034fb0> in <module>()
----> 1 l[4] # Как и со списками нельзя обарщатся к эелементу, которого нет в массиве.

IndexError: index 4 is out of bounds for axis 0 with size 3
In [115]:
# Как было показано выше, массивы можно создавать по спискам, т.е. сначала делается список, а потом массив по нему.
l = [i for i in range(3, 10)] # Создаем список с 3 до 10 (не включительно) с шагом 1.
np.array( l ) # Создаем по списку массив.
Out[115]:
array([3, 4, 5, 6, 7, 8, 9])
In [116]:
np.arange(2, 20, 3) # Их также можно генерировать по готовой формуле. В данному слачае, это последовательность
# с первого числа (2) по последнее (18), но не включая его, с шагом (3).
Out[116]:
array([ 2,  5,  8, 11, 14, 17])
In [117]:
# Существуют и другие вспомогательные функции для формирования массивов. Например функция равномерного разбиения отрезка.
np.linspace(4, 25, 5) # А эта функция создает числа с первого (4) по последнее (включительно) (25) в количестве (5).
Out[117]:
array([  4.  ,   9.25,  14.5 ,  19.75,  25.  ])
In [118]:
# В отличии от списка, все элементы массива должны иметь один и тот же тип. Если объявимть так:
np.array( [1.0, "aa"] ) # то число тоже станет строчкой.
Out[118]:
array(['1.0', 'aa'],
      dtype='<U32')
In [119]:
np.array( [1.0, "aa"] ) + np.array( [2.0, "bb"] ) # Сложить их как раньше не получится.
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-119-ed0e44c118c1> in <module>()
----> 1 np.array( [1.0, "aa"] ) + np.array( [2.0, "bb"] ) # Сложить их как раньше не получится.

TypeError: ufunc 'add' did not contain a loop with signature matching types dtype('<U32') dtype('<U32') dtype('<U32')

Отрисовка графика функции

In [120]:
# Будем строить функцию по-точечно, т.е. построем значение в каждой из выбранных точек и соеденим соседнии прямой.
# Для начала сгенерируем точки в которых будет вычислена функция, а именно равномерный на набор точек.
x = np.linspace(-5, 10, 20) # Отрисуем нужную функцию. Используем для этого 100 точек.
y = x*x -2*x +3 # Пишем выражение, которое ествественно будет вычислено в каждой точке, т.е. по-элементно.
In [121]:
# Точки и значения вы сформировали. Теперь будем строить график. Для этого нужна ещё одна вспомогательная библиотека.
In [122]:
import matplotlib.pyplot as plt # Библиотека для вывода графиков.
# Следуюшща строка позволяет выводить данные прям на этой странице в блоках. Иначе будет "всплывать" отдельное окно.
%matplotlib inline
# Отмечу, что последняя строчка относится к система jupyter, а не самому языку python.
In [123]:
# После загрузки библитеки в систему график по сформированным данным стротися так:
plt.plot( y ) # Значений достаточно.
Out[123]:
[<matplotlib.lines.Line2D at 0x7efe2f3fd9e8>]
In [124]:
# Но можно конечно добавить отсчеты по оси x и разукрасить график.
plt.plot( x, y, 'g.-') # Строит график по-точечно. g -- green (зеленая), . и - задают стиль точки и прямой.
Out[124]:
[<matplotlib.lines.Line2D at 0x7efe2f2eb5f8>]
In [125]:
plt.plot( x, y, 'b*--') # b - синий, * -- звездочка в каждой точке. -- соединение пунктиром.
Out[125]:
[<matplotlib.lines.Line2D at 0x7efe2f2084e0>]
In [126]:
# Цвета есть такие:
# 'b' -- синий     | 'c' -- бирюзовый   | 'k' -- черный
# 'g' -- зеленый   | 'm' -- пурпурный   | 'w' -- белый
# 'r' -- красный   | 'y' -- желтый      |

# стили прямых такие:
# '-' -- сплошная     | '-.' -- точка-дефис
# '--' -- пунктирная  | ':' -- по-точечно

# А стили точек:
# Различные размеры:
# '.', ',' и 'o' -- точек
# 'v', '^', '<' и '>' -- треугольников
# '1', '2', '3' и '4' -- уголка.
# Специфика:
# 's' -- квадрат    | 'h' -- гексагон-1   | 'x' -- знак x            | '|' -- вертикальные
# 'p' -- пентагон   | 'H' -- гексагон-2   | 'D' -- ромб              | '_' -- горизонтальные
# '*' -- звезда     | '+' -- знак плюс    | 'd' -- ромб утонченный   |
In [127]:
# Можно рисовать несколько графиков на одном чертеже:
y2 =  10*np.sin( x ) + 25
plt.plot( x, y, 'g.-', x, y2, 'b*--')
Out[127]:
[<matplotlib.lines.Line2D at 0x7efe2f1a45c0>,
 <matplotlib.lines.Line2D at 0x7efe2f1a4748>]
In [128]:
# Ествественно, что значения можно считать "налету":
plt.plot( x, np.sin(x), 'g.-', x, x*x/100, 'b*--')
Out[128]:
[<matplotlib.lines.Line2D at 0x7efe2f0d40b8>,
 <matplotlib.lines.Line2D at 0x7efe2f0d4240>]
In [129]:
# Ввиду того, что количество точек по которым строилась кривая было небольшим, синусойда получилась угловатой.

Загрузка котировок акций

In [130]:
from pandas_datareader import data
import matplotlib.pyplot as plt
import pandas as pd
In [131]:
#tickers = ['AAPL', 'MSFT'] # Создаем список акций.

# Define which online source one should use
#data_source = 'google'

# We would like all available data from 01/01/2000 until 12/31/2016.
#start_date = '2016-01-01'
#end_date = '2017-12-31'

# User pandas_reader.data.DataReader to load the desired data. As simple as that.
#panel_data = data.DataReader(tickers, data_source, start_date, end_date)
In [132]:
# panel_data
In [133]:
#import datetime as dt
#from pandas_datareader import data, wb

#start_date = dt.datetime(1980, 1, 1)
#dat = data.DataReader('googl', 'yahoo', start_date, dt.datetime.today())
In [134]:
import quandl
import datetime as dt
quandl.ApiConfig.api_key = '8qNu5k13WLNT-6tRKj9M'
today=dt.date.today()

thirty_days = dt.timedelta( days=30 )
fifty_days = dt.timedelta( days=50 )
hundred_days = dt.timedelta( days=100 )

thirty_days_ago = today - thirty_days
fifty_days_ago = today - fifty_days
hundred_days_ago = today - hundred_days

#WIKI/AAPL
fb_df = quandl.get("WIKI/FB", start_date=str( thirty_days_ago ), end_date=str(today),column_index=4)
print ( fb_df ) # Facebook, цена закрытия.
             Close
Date              
2018-03-19  172.56
2018-03-20  168.15
2018-03-21  169.39
2018-03-22  164.89
2018-03-23  159.39
2018-03-26  160.06
2018-03-27  152.19
In [135]:
fb_df # fb_df достаточно мощный объект. Он (dataframe) сам может вывести красивую таблицу. Объект из Pandas.
Out[135]:
Close
Date
2018-03-19 172.56
2018-03-20 168.15
2018-03-21 169.39
2018-03-22 164.89
2018-03-23 159.39
2018-03-26 160.06
2018-03-27 152.19
In [136]:
fb_df.plot() # А также сам себя может отрисовать в виде графика.
Out[136]:
<matplotlib.axes._subplots.AxesSubplot at 0x7efe2786ecf8>
In [137]:
fb_df.iloc[:,0].values # Переводим данные в массив numpy
Out[137]:
array([ 172.56,  168.15,  169.39,  164.89,  159.39,  160.06,  152.19])
In [138]:
# Для дальнейшего возьмем более большой объем.
fb_df = quandl.get("WIKI/FB", start_date=str( hundred_days_ago ), end_date=str(today),column_index=4)
data_fb = fb_df.iloc[:,0].values
data_fb.shape
Out[138]:
(55,)
In [139]:
# Можно вызвать отрисовку графика ничего не указав кроме самих данных.
plt.plot( data_fb ) # Котировки Facebook.
Out[139]:
[<matplotlib.lines.Line2D at 0x7efe26e31390>]

Генерирование случайных чисел.

In [140]:
# Существуют специальные (библиотечные) функции для создания случайных чисел.
# Библиотека Numpy уже загружена и переименована как np. Поэтому возможен код:
# В данном случае формируем числа согласно нормальному распределению.
np.random.randn( 4 ) # В скобках указывается размер массива.
Out[140]:
array([ 0.13428197,  0.56377384, -0.11210783, -1.10367361])
In [141]:
a = np.random.randn( 4, 3) # Можно размеры и по другим осям. В данном случае, будет матрица 4x3.
a
Out[141]:
array([[ 0.6127028 ,  0.58256332, -0.72141269],
       [ 0.11645788, -1.25256511,  0.0534171 ],
       [-1.0455882 ,  0.97927537,  1.39222814],
       [-0.10908051,  0.91841817, -0.03822836]])
In [142]:
a.shape
Out[142]:
(4, 3)
In [143]:
# np.random является модулем, т.е. он содержит набор функций со схожим смыслом.
# Равномерное распределение задается просто функцией rand из модуля np.random
np.random.rand(4)
Out[143]:
array([ 0.3349154 ,  0.75341313,  0.77911795,  0.19104844])

Слуйчайные величины и использование компьютера по назначению

In [144]:
gen_data = np.random.randn( 10000 ) # Вывода нет потому что мы присвоили результат переменой.
gen_data.shape
Out[144]:
(10000,)
In [145]:
hist = plt.hist( gen_data, 50); # Строит гистограмму по заданному массиву.
In [146]:
# Изучим переменую hist, которая соответствует гистограмме.
type( hist )
# Она является парой (tuple)...
Out[146]:
tuple
In [147]:
type( hist[0] ), type( hist[1] )
# состоящей из двух массивов (numpy.ndarray).
Out[147]:
(numpy.ndarray, numpy.ndarray)
In [148]:
hist # Смотрим содержимое переменной hist.
Out[148]:
(array([   3.,    0.,    3.,    4.,    2.,    4.,   15.,   16.,   29.,
          36.,   37.,   68.,   87.,  123.,  179.,  196.,  255.,  284.,
         362.,  391.,  441.,  477.,  506.,  606.,  592.,  571.,  593.,
         559.,  542.,  521.,  433.,  405.,  340.,  270.,  247.,  181.,
         173.,  123.,  103.,   72.,   58.,   33.,   22.,   15.,   11.,
           5.,    4.,    0.,    2.,    1.]),
 array([-3.80966665, -3.66070052, -3.5117344 , -3.36276828, -3.21380216,
        -3.06483604, -2.91586992, -2.76690379, -2.61793767, -2.46897155,
        -2.32000543, -2.17103931, -2.02207318, -1.87310706, -1.72414094,
        -1.57517482, -1.4262087 , -1.27724258, -1.12827645, -0.97931033,
        -0.83034421, -0.68137809, -0.53241197, -0.38344584, -0.23447972,
        -0.0855136 ,  0.06345252,  0.21241864,  0.36138476,  0.51035089,
         0.65931701,  0.80828313,  0.95724925,  1.10621537,  1.2551815 ,
         1.40414762,  1.55311374,  1.70207986,  1.85104598,  2.0000121 ,
         2.14897823,  2.29794435,  2.44691047,  2.59587659,  2.74484271,
         2.89380884,  3.04277496,  3.19174108,  3.3407072 ,  3.48967332,
         3.63863944]),
 <a list of 50 Patch objects>)
In [149]:
h = np.histogram( gen_data, 50 ) # Вызовем функцию, которая вычисляет только гистограммы, она её не рисует.
h
Out[149]:
(array([  3,   0,   3,   4,   2,   4,  15,  16,  29,  36,  37,  68,  87,
        123, 179, 196, 255, 284, 362, 391, 441, 477, 506, 606, 592, 571,
        593, 559, 542, 521, 433, 405, 340, 270, 247, 181, 173, 123, 103,
         72,  58,  33,  22,  15,  11,   5,   4,   0,   2,   1]),
 array([-3.80966665, -3.66070052, -3.5117344 , -3.36276828, -3.21380216,
        -3.06483604, -2.91586992, -2.76690379, -2.61793767, -2.46897155,
        -2.32000543, -2.17103931, -2.02207318, -1.87310706, -1.72414094,
        -1.57517482, -1.4262087 , -1.27724258, -1.12827645, -0.97931033,
        -0.83034421, -0.68137809, -0.53241197, -0.38344584, -0.23447972,
        -0.0855136 ,  0.06345252,  0.21241864,  0.36138476,  0.51035089,
         0.65931701,  0.80828313,  0.95724925,  1.10621537,  1.2551815 ,
         1.40414762,  1.55311374,  1.70207986,  1.85104598,  2.0000121 ,
         2.14897823,  2.29794435,  2.44691047,  2.59587659,  2.74484271,
         2.89380884,  3.04277496,  3.19174108,  3.3407072 ,  3.48967332,
         3.63863944]))
In [150]:
# Видим, что результат совпадает. Скоре всего код функции plt.hist вызывает функцию np.histogram.

Численная статистика

In [151]:
# Изучим Хи-квадрат численно.
gen_data = np.random.randn( 1000, 5) # 1000 раз по 5 нормальных распределений.
In [152]:
# Убедимся в правильности размера массива (матрицы).
gen_data.shape # В скобках перечислены размер массива вдоль каждого из измерений.
Out[152]:
(1000, 5)
In [153]:
gen_data[0].shape, gen_data[999].shape
Out[153]:
((5,), (5,))
In [154]:
a = np.array( [[2, 3], [-3, 4]])
a * a # Исходя из материала выше таким способом можно возвести в квадрат, по-элементно.
Out[154]:
array([[ 4,  9],
       [ 9, 16]])
In [155]:
# В Numpy есть ряд функций, которые выполняют операцию reduce.
np.sum( a ) # Например, вычисляет сумму массива a: 3 + -6 + -1 + 2.
Out[155]:
6
In [156]:
a = np.array( [[[2, 3], [-3, 4]], [[-5, 7], [-9, 11]] ])
In [157]:
a[:, 1, 0]
Out[157]:
array([-3, -9])
In [158]:
# Такой вариант вызова выполняет операцию для всех элементов массива как для единого массива, т.е. без учета размерности.
# Но можно и указать вдолб какой именно размерности делать вычисления.
np.sum( a, axis = 0) # Вдоль нулевой делаем и сохраняем результат в другие размерности ( матрица 2 на 2).
# Например, -3 + -9 равно 12 сохранено в элемент матрицы 1, 0.
Out[158]:
array([[ -3,  10],
       [-12,  15]])
In [159]:
a[0, :, 1]
Out[159]:
array([3, 4])
In [160]:
np.sum( a, axis = 1) # Суммируем вдоль первой и сохранаяем результат в оставшуюся размерность.
# Например, 3 + 4 равно 7 сохранено в элемент матрицы 0, 1.
Out[160]:
array([[ -1,   7],
       [-14,  18]])
In [161]:
# Теперь продолжим наши изыскания...
sq_data = gen_data * gen_data # Массив состоящий их квадратов нормального распределения.
sq_data.shape
Out[161]:
(1000, 5)
In [162]:
gen_data2 = np.sum( sq_data, axis = 1 ) # сумируем вдоль оси с индекосм 1 (нумирация начинается с 0).
gen_data2.shape # получаем массив состоящий из суммы 5 квадратов номального распределения
# т.е. выборка из Хи-квадрат с параметром 5.
Out[162]:
(1000,)
In [163]:
hist = plt.hist( gen_data2, 50); # Сторим гистограмму. Она должна напоминать гистограмму Хи-квадрат.
In [164]:
11 < 5 # Помимо арифметических операций существуют и логические. В частности, операции сравнения.
Out[164]:
False
In [165]:
# Лож (False) с точки зрения чисел равна 0,
# а истина (True) 1.
int( 12 < 15 )
Out[165]:
1
In [166]:
# Тогда можно строить такие гибридные выражения, где суммируются логические выражения.
( 13 < 12 ) + ( 13 < 14 ) + ( 13 < 16 ) # В данном случае мы посчитали скольких чисел строго больше 13.
Out[166]:
2
In [167]:
cnt = sum( x < 7 for x in gen_data2 ) # Данная строчка посчитает сколько числе в выборке меньше 7.
cnt
Out[167]:
763
In [168]:
cnt / gen_data2.shape[0] # Поделив на общее количество чисел, получаем значение распределения.
Out[168]:
0.76300000000000001
In [169]:
import scipy.stats as models
In [170]:
#?models.chi2
# Можно воспользоватся явной функцией (chi2), которая использует формулу.
models.chi2.cdf( 7, 5 ) # Первое число указывает точку (7), воторое значение параметра (5) распределения.
# Замечаем, что значение близко к рассчетному.
Out[170]:
0.77935969206328937
In [171]:
models.chi2.cdf( 7, 5, 0, 1 ) # Более полный набор параметров включает смещение (0) и масштаб (1).
Out[171]:
0.77935969206328937
In [172]:
# models.chi2.cdf.__doc__ # Так можно вызвать документацию по немонятной функции.
In [173]:
models.chi2.fit( gen_data2 ) # Можем найти параметры, которые наилучшим образом соответсвуют выборочным данным.
Out[173]:
(4.4852291253046896, 0.065765153949090799, 1.0854804168861074)
In [174]:
it = models.chi2.interval( 0.95, 5 ) # Строим доверительный интервал.
it
Out[174]:
(0.8312116134866625, 12.832501994030025)
In [175]:
models.chi2.cdf( it[0], 5 ), 1.0 - models.chi2.cdf( it[1], 5 ) # Проверяем доверительный интервал.
Out[175]:
(0.025000000000000001, 0.025000000000000022)
In [176]:
models.chi2.cdf( it[1], 5 ) - models.chi2.cdf( it[0], 5 ) # Проверяем его иначе.
Out[176]:
0.94999999999999996
In [177]:
sum( it[0] <= x and x <= it[1] for x in gen_data2 ) / gen_data2.shape[0] # Проверяем численно.
Out[177]:
0.94699999999999995
In [178]:
models.chi2.mean( 5 ) # Вычисляем матожидание распределения по формуле.
Out[178]:
5.0
In [179]:
np.mean( gen_data2 ) # Вычисляем выборочное среднее, т.е. по данным.
Out[179]:
4.9343626757168897

ДЗ0) Выбрать ожну из статистик и выполнить сравнение численного анализа с известными формулами из учебника

In [180]:
data_fb.shape
Out[180]:
(55,)
In [181]:
per = data_fb[1:]/data_fb[:-1] -1 # Процентное изменение из о дня в день.
per
Out[181]:
array([-0.00217761, -0.00015968, -0.00037266, -0.04473558, -0.00546357,
       -0.0044285 ,  0.01238739,  0.00828699,  0.02250538,  0.02147057,
       -0.01478743,  0.00498526,  0.01344143, -0.02115789,  0.00612969,
       -0.00122916,  0.03317459, -0.0145528 , -0.0400988 ,  0.01456337,
       -0.02768334, -0.0478971 ,  0.02582397,  0.00244346, -0.01847968,
        0.03678891,  0.00245098, -0.01444766, -0.00761164,  0.01079484,
        0.00607049,  0.02402369,  0.00894757, -0.01876386, -0.01730409,
       -0.01334679,  0.00386495,  0.02140188, -0.00343681,  0.02186005,
       -0.00745741,  0.01584951, -0.00253739, -0.01558779,  0.01270068,
       -0.00179163,  0.00668987, -0.0676968 , -0.02555633,  0.00737437,
       -0.02656591, -0.03335557,  0.00420353, -0.04916906])
In [182]:
per.shape
Out[182]:
(54,)
In [183]:
plt.hist( per, 10); # Строим распределение процентного изменения изо дня в день.

ДЗ1) Выбрать эмитент, некое условие и построить распределение некого другого условия.

In [184]:
models.chi2.fit( per )
Out[184]:
(0.80589128711305191, -0.067696796153222785, 1.1273621606688855)
In [185]:
models.norm.fit( per )
Out[185]:
(-0.0036966498474948792, 0.021586991671529394)
In [ ]: