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

Анотация

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

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

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

Списки хороши, но над ними как мы помним нельзя выполнять нужные нами арфиметических операций. В частности, списки нельзя складывать. Точнее эта операция будет иметь иное толкование:

In [1]:
import numpy as np
In [2]:
[1.1, 2.2, -4.4] + [2.5, 1.9, 5.1] # Они совместяться в один список (контакенация)
Out[2]:
[1.1, 2.2, -4.4, 2.5, 1.9, 5.1]

Для содания списоков чисел, которые можно складывать поэлементно, нужны именно вектора/массивы.

In [3]:
# Они из списка создаются так:
np.array( [1.1, 2.2, -4.4])
Out[3]:
array([ 1.1,  2.2, -4.4])
In [4]:
# Такие объекты, массивы, можно складывть по элементно.
np.array( [1.1, 2.2, -4.4]) + np.array( [2.5, -1.9, 5.1])
Out[4]:
array([3.6, 0.3, 0.7])
In [5]:
# Размер массивов должен совпадать. Иначе система выдаст соответствующую ошибку.
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-5-a4f199efa0ef> 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,) 

При выводе ошибки система указала, что один массив имеет размер 3, а другой -- 4. Даже была указана их одномерность.

Упр. По массиву значений построй массив отношения текущего элемента к последующему (результирующий массив будет на один элемент меньше).

Размер массива всегда можно запросить у самого объекта.

In [6]:
l = np.array( [1.1, 2.2, -4.4]) # Сохраняем массив в переменную l.
l.shape # Запрашиваем у переменной её размер. Он хранится в переменной shape.
Out[6]:
(3,)
In [9]:
bb = [55, 22, 33] # Создаем список.
bb.shape # Размер имеют объекты array. Другие не обязаны давать значение на это поле.
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-9-2f84abcef0cf> in <module>
      1 bb = [55, 22, 33] # Создаем список.
----> 2 bb.shape # Размер имеют объекты array. Другие не обязаны давать значение на это поле.

AttributeError: 'list' object has no attribute 'shape'

Замечу, что тип размер/размерность не указывает. Последнее связано с тем, что все элементы массива должны иметь одтн и тот же тип.

In [10]:
type( l ) # Запросим и тип.
Out[10]:
numpy.ndarray
In [11]:
a0 = np.array([-1, 2])
a0
Out[11]:
array([-1,  2])
In [12]:
a1 = np.array([3, 5])
a1
Out[12]:
array([3, 5])
In [13]:
a0 * a1 # Поэлементное умножение массивов, а не скалярное.
Out[13]:
array([-3, 10])
In [14]:
# Ну или в явном виде, т.е. без создания дополнительных промежуточных переменных.
np.array([-2, 7]) * np.array([ -2 , 5]) 
Out[14]:
array([ 4, 35])
In [15]:
# Расширение данной идеи на матрицы.
a0 = np.array([[-1], [2]])
a0 # Отмечу, если раньше a0 был массивом, то теперь стал матрицей, т.е. поменялась размерность.
Out[15]:
array([[-1],
       [ 2]])
In [16]:
# Поэлементное умножение матриц (элемент массива является одноэлементным массивом). 
a0 * np.array([ [3] , [5]]) # Не путать с матричным умножением!
Out[16]:
array([[-3],
       [10]])
In [17]:
# Размеры у матрицы a0 были:
a0.shape # т.е. две строки, в каждой из которых по одному элементу.
Out[17]:
(2, 1)
In [18]:
a0 = np.array([[-1, 2]]) # Можно сделать матрицу наоборот (транспонирование). 
a0
Out[18]:
array([[-1,  2]])
In [19]:
a0.shape # Одна строка, содержащая два элемента.
Out[19]:
(1, 2)
In [20]:
# Такие матрицы тоже можно умножать по-элементно.
np.array([[-1, 2]]) * np.array([ [3 , 5]])
Out[20]:
array([[-3, 10]])
In [19]:
# Матрицы можно формировать из других матриц.
a0 = np.array( [ 3, -6 ] )
a1 = np.array( [ -1, 2 ] )
a = np.array( [ a0, a1] )
a
Out[19]:
array([[ 3, -6],
       [-1,  2]])
In [23]:
# Размеры должны конечно должны соответствовать.
a0 = np.array( [ 3, -6, 5 ] )
a1 = np.array( [ -1, 2 ] )
a = np.array( [ a0, a1] )
a # Иначе это бует массив объектов. Аккуратно сравни и увидь разницу!
Out[23]:
array([array([ 3, -6,  5]), array([-1,  2])], dtype=object)
In [24]:
l[4] # Как и со списками нельзя обарщатся к эелементу, которого нет в массиве.
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-24-5ada7b02373a> in <module>
----> 1 l[4] # Как и со списками нельзя обарщатся к эелементу, которого нет в массиве.

IndexError: index 4 is out of bounds for axis 0 with size 3

Как было показано выше, массивы можно создавать по спискам, т.е. сначала делается список, а потом массив по нему.

In [25]:
l = [i for i in range(3, 10)] # Создаем список с 3 до 10 (не включительно) с шагом 1.
np.array( l ) # Создаем по списку массив.
Out[25]:
array([3, 4, 5, 6, 7, 8, 9])

Их также можно генерировать готовой функцией.

In [26]:
np.arange(2, 20, 3) # В данному слачае, это последовательность
# с первого числа (2) по последнее (18), но не включая его, с шагом (3).
Out[26]:
array([ 2,  5,  8, 11, 14, 17])

Существуют и другие вспомогательные функции для формирования массивов. Например функция равномерного разбиения отрезка.

In [27]:
np.linspace(4, 25, 5) # А эта функция создает числа с первого (4) по последнее (включительно) (25) в количестве (5).
Out[27]:
array([ 4.  ,  9.25, 14.5 , 19.75, 25.  ])
In [28]:
# В отличии от списка, все элементы массива должны иметь один и тот же тип. Если объявить так:
q = np.array( [1.0, "aa"] )
q
Out[28]:
array(['1.0', 'aa'], dtype='<U32')
In [29]:
type(q[0]), type(q[1]) # то число тоже станет строчкой.
Out[29]:
(numpy.str_, numpy.str_)
In [30]:
np.array( [1.0, "aa"] ) + np.array( [2.0, "bb"] ) # Сложить их как раньше не получится.
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-30-635087e26002> 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')

В библиотеке есть набор функций для обработки массивов. Так, помимо поэлеметных функий наподобии np.sin

In [31]:
np.sin( l )
Out[31]:
array([ 0.14112001, -0.7568025 , -0.95892427, -0.2794155 ,  0.6569866 ,
        0.98935825,  0.41211849])

имеются и редуцирующие к числу. Например,

In [32]:
np.sum( l ) # Сумма элементов
Out[32]:
42
In [35]:
np.mean( l ), np.std( l ) # Срежнее значение и среднеквадратичное откланение.
Out[35]:
(6.0, 2.0)
In [36]:
np.median( l ), np.max( l ), np.min( l ) # Медиана, максимальное и мимимальное значение.
Out[36]:
(6.0, 9, 3)

Упр. По массиву посчитай скользящее среднее, т.е. среднее окна из например 10 элементов. Окно скользит по массиву.

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

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

In [37]:
x = np.linspace(-5, 10, 20) # Сгенерируем именно равномерный на набор точек.
y = x*x - 2*x +3 # Пишем выражение, которое ествественно будет вычислено в каждой точке, т.е. по-элементно.

Точки и значения вы сформировали. Теперь будем строить график. Для этого нужна ещё одна вспомогательная библиотека.

Библиотека matplotlib применяется для вывода графиков. Точнее её подмодуль (подбиблиотека) pyplot.

In [38]:
import matplotlib.pyplot as plt # Обозначи plt длинное название к отмеченной подбиблиотеке.
# Следуюшща строка позволяет выводить данные прям на данной странице в блоках. Иначе будет "всплывать" отдельное окно.
%matplotlib inline 

Отмечу, что последняя строчка относится к система jupyter, а не самому языку python.

Отрисуем нужную функцию. Используем для этого 100 точек. После загрузки библитеки в систему, график по сформированным ранее данным стротися так:

In [39]:
plt.plot( y ) # Значений достаточно.
Out[39]:
[<matplotlib.lines.Line2D at 0x7fdb2e76fba8>]

Но можно конечно добавить отсчеты по оси x и разукрасить график.

In [40]:
plt.plot( x, y, 'g.-') # Строит график по-точечно. g -- green (зеленая), . и - задают стиль точки и прямой.
Out[40]:
[<matplotlib.lines.Line2D at 0x7fdb2e6905f8>]
In [41]:
plt.plot( x, y, 'b*--') # b - синий, * -- звездочка в каждой точке. -- соединение пунктиром.
Out[41]:
[<matplotlib.lines.Line2D at 0x7fdb2e5fa470>]
In [42]:
# Цвета есть такие:
# '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 [43]:
# Можно рисовать несколько графиков на одном чертеже:
y2 =  10*np.sin( x ) + 25
plt.plot( x, y, 'g.-', x, y2, 'b*--')
Out[43]:
[<matplotlib.lines.Line2D at 0x7fdb2e5e50b8>,
 <matplotlib.lines.Line2D at 0x7fdb2e5e5240>]
In [45]:
# Ествественно, что значения можно считать "налету":
plt.plot( x, np.sin(x), 'g.-', x, x*x/100, 'b*--')
plt.legend( ['sin', 'x^2'] ) # Добавим и легенду.
Out[45]:
<matplotlib.legend.Legend at 0x7fdb3374bac8>

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

In [46]:
plt.plot( x, y, 'g.-')
plt.xlabel( "Ось абсцисс" ) # Задаем название для оси абсцисс.
plt.ylabel( "Ось ординат") # Задаем название для оси ординат.
Out[46]:
Text(0,0.5,'Ось ординат')
In [47]:
plt.plot( x, y, 'g.-') 
plt.xticks( [-2, 0.5, 2, 5] ) # Задаем отсчеты вдоль оси абсцисс.
plt.xlim(-4, 8) # Зданаем границы дипазона для оси абсцисс.
Out[47]:
(-4, 8)
In [48]:
plt.plot( x, np.exp(x), 'g.-')
Out[48]:
[<matplotlib.lines.Line2D at 0x7fdb2de562e8>]
In [49]:
plt.plot( x, np.exp(x), 'g.-')
plt.yscale( 'log' ) # Меняем шкалу на логарифмическую.

Упр. Нарисуй параболу (зеленым цветом) и отметь корни соответствуюшего уравнения точками (красного цвета).

Считать файл csv

Берем откуда-то простой csv (comma seperated values) файл. Отмечу, что расширение у некго не обязано быть csv, оно может быть и txt.

Например с https://www.finam.ru/profile/moex-akcii/mechel/export/ Параметры можно менять: сменить эмитента (например, выбрать Газпром, Сбербанк и тому подобное), период (например, выбрать день). Промежуток времени пока лучше выбрать поменьше. Внимание, пока скачиваем файл без заголовка (убрать соответствующий флажок)!

Для считывания данных файлов потребуется библиотека csv.

In [50]:
import csv # Для считывания csv файлов.
In [51]:
data = []
with open('MTLR_190101_190110.txt') as f:
    data_rows = csv.reader( f )
    for row in data_rows:
        data.append( row )
data
Out[51]:
[['MTLR;D;20190103;000000;73.8900000;74.4700000;73.0600000;73.1200000;391375'],
 ['MTLR;D;20190104;000000;73.3100000;74.8900000;73.2100000;73.6800000;462658'],
 ['MTLR;D;20190108;000000;74.4700000;74.8900000;73.5000000;73.7400000;406304'],
 ['MTLR;D;20190109;000000;74.0200000;74.6200000;73.8200000;73.9300000;651998'],
 ['MTLR;D;20190110;000000;73.9600000;74.7800000;73.9000000;74.4000000;358269']]

Видно, что строчки из файла считались целиком, как текстовые строчки (обрати внимание на одинарные кавычки). Хотелось бы чтобы каждый столбец отделился от других столбцов. Для этого можно указать дополнительный параметр функции обработки csv файлов. Параметр delimiter, он указывает какой разделитель используется для отделения стобцов.

In [52]:
data = []
with open('MTLR_190101_190110.txt') as f:
    data_rows = csv.reader( f, delimiter=';' )
    for row in data_rows:
        data.append( row )
data
Out[52]:
[['MTLR',
  'D',
  '20190103',
  '000000',
  '73.8900000',
  '74.4700000',
  '73.0600000',
  '73.1200000',
  '391375'],
 ['MTLR',
  'D',
  '20190104',
  '000000',
  '73.3100000',
  '74.8900000',
  '73.2100000',
  '73.6800000',
  '462658'],
 ['MTLR',
  'D',
  '20190108',
  '000000',
  '74.4700000',
  '74.8900000',
  '73.5000000',
  '73.7400000',
  '406304'],
 ['MTLR',
  'D',
  '20190109',
  '000000',
  '74.0200000',
  '74.6200000',
  '73.8200000',
  '73.9300000',
  '651998'],
 ['MTLR',
  'D',
  '20190110',
  '000000',
  '73.9600000',
  '74.7800000',
  '73.9000000',
  '74.4000000',
  '358269']]

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

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

In [53]:
int( 5 < 7 )
Out[53]:
1

По аналогии с этим:

In [54]:
int('34') # Преобразуем текст в число.
Out[54]:
34

Более дотошно:

In [55]:
a = '55'
b = int( a )
type( a ), type( b )
Out[55]:
(str, int)

Нам должна быть известна структура входного файла. Точнее должно быть известно назначение столбцов. Для ранее скаченного файла она такая: TICKER, PER, DATE, TIME, OPEN, HIGH, LOW, CLOSE, VOL. Предпоследний столбец соответствует цене закрытия.

In [56]:
close = [ float(row[-2]) for row in data ]
close
Out[56]:
[73.12, 73.68, 73.74, 73.93, 74.4]

Теперь все сработало и был считан столбец значений.

Теперь возьмем файл побольше и построим график.

In [57]:
data = []
with open('MTLR_180101_190110.txt') as f:
    data_rows = csv.reader( f, delimiter=';' )
    for row in data_rows:
        data.append( row )

close = [ float(row[-2]) for row in data ]
In [58]:
plt.plot( close )
plt.ylabel( 'Цена рубли')
plt.xlabel( 'День начиная с 2018 г')
Out[58]:
Text(0.5,0,'День начиная с 2018 г')

Рассмотрим какой-либо элемент (строчку) из файла.

In [59]:
data[3]
Out[59]:
['MTLR',
 'D',
 '20180109',
 '000000',
 '154.5000000',
 '155.8500000',
 '151.3000000',
 '155.6000000',
 '1156387']

Тогда можно при считывании файла преобразовать все стобцы кроме первых двух в число

In [60]:
data = []
with open('MTLR_180101_190110.txt') as f:
    data_rows = csv.reader( f, delimiter=';' )
    for row in data_rows:
        for i in range(2, len(row)):
            row[i] = float( row[i] )
        data.append( row )
In [61]:
data[3] # Теперь все числовые даные являются числом, а не текстом.
Out[61]:
['MTLR', 'D', 20180109.0, 0.0, 154.5, 155.85, 151.3, 155.6, 1156387.0]
In [62]:
close = [ day[-2] for day in data ] # -2 это цена закрытия.
In [63]:
plt.plot( close )
plt.ylabel( 'Цена рубли')
plt.xlabel( 'День начиная с 2018 г')
Out[63]:
Text(0.5,0,'День начиная с 2018 г')

Упр. Нарисуй график цены и его какое-то скользящее среднее.

Упр. Нарисуй графики процентного изменения цен двух эмитентов с начала периуда. С легендой.

Упр. Нарисуй графики процентного поэлементого изменения цен двух эмитентов. С легендой.

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

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

Существуют специальные (библиотечные) функции для создания случайных чисел. Библиотека Numpy уже загружена и переименована как np. Поэтому возможен код:

In [65]:
# В данном случае формируем числа согласно нормальному распределению.
np.random.randn( 4 ) # В скобках указывается размер массива.
Out[65]:
array([ 0.39795345, -0.75373803,  0.1234057 ,  1.14189184])
In [66]:
a = np.random.randn( 4, 3) # Можно размеры и по другим осям. В данном случае, будет матрица 4x3.
a
Out[66]:
array([[ 0.27695243, -0.31936474, -0.18054845],
       [-0.65154751, -2.08876949,  0.89716984],
       [ 1.26570975,  2.20511795,  1.41508189],
       [ 1.06400036,  0.76737742,  0.38388357]])
In [67]:
a.shape
Out[67]:
(4, 3)

np.random является модулем, т.е. он содержит набор функций со схожим смыслом. Равномерное распределение задается просто функцией rand из модуля np.random

In [68]:
np.random.rand(4) # Равномерное распределение
Out[68]:
array([0.06453281, 0.84670654, 0.99594404, 0.37036851])
In [69]:
# Раз уж if был упомянут...

If конструкция

Обеспечение ветвления является тоже крайне важным элементом в программировании. В питое это достигается конструкцией

if условие:

elif условие:

else:

In [70]:
data = np.random.randint(-10, 10, 10) # Равномерное целочисленное распределение между -10 и 10 невключительно.
data
Out[70]:
array([  6,   5, -10,   4,   3,   9,   2,   7,  -9,  -9])
In [71]:
cnt_n = 0 # Количество отрицательных
cnt_p = 0 # Количество положительных
cnt_e = 0 # Количество равных нулю

data = np.random.randint(-10, 10, 100)

for d in data:
    if d > 0:
        cnt_p += 1 # cnt_p = cnt_p + 1
    elif d < 0:
        cnt_n += 1 # cnt_n = cnt_n + 1
    else:
        cnt_e += 1 # cnt_e = cnt_e + 1

print( ">0 : ", cnt_p, "   <0 : ", cnt_n, " ==0 : ", cnt_e)
>0 :  40    <0 :  59  ==0 :  1
In [72]:
100/20 # Приблизительно равно количеству равных 0.
Out[72]:
5.0

Упр. Реши квадратное уравнение. Если корней нет, то напиши об этом.

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

Формирование случайной величины

In [73]:
gen_data = np.random.randn( 10000 ) # Вывода нет потому что мы присвоили результат переменой.
gen_data.shape
Out[73]:
(10000,)
In [74]:
# Строим гистограмму по заданному массиву чисел.
hist = plt.hist( gen_data, 50); # В переменную hist сохраняются сами значения гистограммы.
In [75]:
# Изучим переменую hist, которая соответствует гистограмме.
type( hist )
# Она является парой (tuple)...
Out[75]:
tuple
In [76]:
type( hist[0] ), type( hist[1] )
# состоящей из двух массивов (numpy.ndarray).
Out[76]:
(numpy.ndarray, numpy.ndarray)
In [77]:
hist # Смотрим содержимое переменной hist.
Out[77]:
(array([  1.,   1.,   3.,   1.,   4.,   5.,  11.,  22.,  26.,  40.,  42.,
         76.,  94., 120., 152., 188., 294., 286., 379., 401., 452., 493.,
        559., 602., 565., 547., 583., 528., 529., 500., 425., 405., 340.,
        306., 228., 172., 160., 130., 102.,  71.,  52.,  34.,  22.,  21.,
         11.,   6.,   3.,   6.,   0.,   2.]),
 array([-3.71437644, -3.56796772, -3.421559  , -3.27515028, -3.12874156,
        -2.98233284, -2.83592412, -2.6895154 , -2.54310669, -2.39669797,
        -2.25028925, -2.10388053, -1.95747181, -1.81106309, -1.66465437,
        -1.51824565, -1.37183693, -1.22542821, -1.07901949, -0.93261077,
        -0.78620206, -0.63979334, -0.49338462, -0.3469759 , -0.20056718,
        -0.05415846,  0.09225026,  0.23865898,  0.3850677 ,  0.53147642,
         0.67788514,  0.82429386,  0.97070257,  1.11711129,  1.26352001,
         1.40992873,  1.55633745,  1.70274617,  1.84915489,  1.99556361,
         2.14197233,  2.28838105,  2.43478977,  2.58119849,  2.7276072 ,
         2.87401592,  3.02042464,  3.16683336,  3.31324208,  3.4596508 ,
         3.60605952]),
 <a list of 50 Patch objects>)
In [78]:
h = np.histogram( gen_data, 50 ) # Вызовем функцию, которая вычисляет только гистограммы, она её не рисует.
h
Out[78]:
(array([  1,   1,   3,   1,   4,   5,  11,  22,  26,  40,  42,  76,  94,
        120, 152, 188, 294, 286, 379, 401, 452, 493, 559, 602, 565, 547,
        583, 528, 529, 500, 425, 405, 340, 306, 228, 172, 160, 130, 102,
         71,  52,  34,  22,  21,  11,   6,   3,   6,   0,   2]),
 array([-3.71437644, -3.56796772, -3.421559  , -3.27515028, -3.12874156,
        -2.98233284, -2.83592412, -2.6895154 , -2.54310669, -2.39669797,
        -2.25028925, -2.10388053, -1.95747181, -1.81106309, -1.66465437,
        -1.51824565, -1.37183693, -1.22542821, -1.07901949, -0.93261077,
        -0.78620206, -0.63979334, -0.49338462, -0.3469759 , -0.20056718,
        -0.05415846,  0.09225026,  0.23865898,  0.3850677 ,  0.53147642,
         0.67788514,  0.82429386,  0.97070257,  1.11711129,  1.26352001,
         1.40992873,  1.55633745,  1.70274617,  1.84915489,  1.99556361,
         2.14197233,  2.28838105,  2.43478977,  2.58119849,  2.7276072 ,
         2.87401592,  3.02042464,  3.16683336,  3.31324208,  3.4596508 ,
         3.60605952]))

Видим, что результат совпадает. Скоре всего код функции plt.hist вызывает функцию np.histogram.

Выражение из случайных величин

In [79]:
# Изучим Хи-квадрат численно.
gen_data = np.random.randn( 1000, 5) # 1000 раз по 5 нормальных распределений.
In [80]:
# Убедимся в правильности размера массива (матрицы).
gen_data.shape # В скобках перечислены размер массива вдоль каждого из измерений.
Out[80]:
(1000, 5)
In [81]:
gen_data[0].shape, gen_data[999].shape
Out[81]:
((5,), (5,))
In [82]:
a = np.array( [[2, 3], [-3, 4]])
a * a # Исходя из материала выше таким способом можно возвести в квадрат, по-элементно.
Out[82]:
array([[ 4,  9],
       [ 9, 16]])
In [83]:
# В Numpy есть ряд функций, которые выполняют операцию reduce.
np.sum( a ) # Например, вычисляет сумму массива a: 3 + -6 + -1 + 2.
Out[83]:
6

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

In [84]:
a = np.array( [[[2, 3], [-3, 4]], [[-5, 7], [-9, 11]] ])
In [85]:
a.shape
Out[85]:
(2, 2, 2)
In [86]:
a[:, 1, 0]
Out[86]:
array([-3, -9])
In [87]:
np.sum( a, axis = 0) # Вдоль нулевой делаем и сохраняем результат в другие размерности ( матрица 2 на 2).
# Например, -3 + -9 равно 12 сохранено в элемент матрицы 1, 0.
Out[87]:
array([[ -3,  10],
       [-12,  15]])
In [88]:
a[0, :, 1]
Out[88]:
array([3, 4])
In [89]:
np.sum( a, axis = 1) # Суммируем вдоль первой и сохранаяем результат в оставшуюся размерность.
# Например, 3 + 4 равно 7 сохранено в элемент матрицы 0, 1.
Out[89]:
array([[ -1,   7],
       [-14,  18]])
In [90]:
# Теперь продолжим наши изыскания...
sq_data = gen_data * gen_data # Массив состоящий их квадратов нормального распределения.
sq_data.shape
Out[90]:
(1000, 5)
In [91]:
gen_data2 = np.sum( sq_data, axis = 1 ) # сумируем вдоль оси с индекосм 1 (нумирация начинается с 0).
gen_data2.shape # получаем массив состоящий из суммы 5 квадратов номального распределения
# т.е. выборка из Хи-квадрат с параметром 5.
Out[91]:
(1000,)

Теперь забывае как мы его создали. Считаем, что данные откуда-то взялись.

In [92]:
hist = plt.hist( gen_data2, 50); # Сторим гистограмму. Она должна напоминать гистограмму Хи-квадрат.
In [93]:
11 < 5 # Помимо арифметических операций существуют и логические. В частности, операции сравнения.
Out[93]:
False
In [94]:
type( 11 < 5 )
Out[94]:
bool
In [95]:
# Лож (False) с точки зрения чисел равна 0,
# а истина (True) 1.
int( 12 < 15 ) # Явное преобразование типа.
Out[95]:
1
In [96]:
# Тогда можно строить такие гибридные выражения, где суммируются логические выражения.
( 13 < 12 ) + ( 13 < 14 ) + ( 13 < 16 ) # В данном случае мы посчитали скольких чисел строго больше 13.
Out[96]:
2
In [97]:
cnt = sum( x < 7 for x in gen_data2 ) # Данная строчка посчитает сколько числе в выборке меньше 7.
cnt
Out[97]:
789
In [98]:
cnt / gen_data2.shape[0] # Поделив на общее количество чисел, получаем значение распределения,
# т.е. вероятность того, что значение Хи-квадрат меньше 7.
Out[98]:
0.789
In [99]:
import scipy.stats as models
In [100]:
#?models.chi2
# Можно воспользоватся явной функцией (chi2), которая использует формулу.
models.chi2.cdf( 7, 5 ) # Первое число указывает точку (7), воторое значение параметра (5) распределения.
# Замечаем, что значение близко к рассчетному.
Out[100]:
0.7793596920632894
In [101]:
models.chi2.cdf( 7, 5, 0, 1 ) # Более полный набор параметров включает смещение (0) и масштаб (1).
Out[101]:
0.7793596920632894
In [102]:
# models.chi2.cdf.__doc__ # Так можно вызвать документацию по немонятной функции.
In [103]:
params = models.chi2.fit( gen_data2 ) # Можем найти параметры, которые наилучшим образом соответсвуют выборочным данным.
params 
Out[103]:
(5.014078817508173, -0.011442412320291764, 0.973155337515282)
In [104]:
models.chi2.cdf( 7, params[0], params[1] ) # Вычисляет вероятность по оценинным параметрам рапределения.
Out[104]:
0.7788930017071592
In [105]:
it = models.chi2.interval( 0.95, 5 ) # Строим доверительный интервал.
it
Out[105]:
(0.831211613486663, 12.832501994030027)
In [106]:
models.chi2.cdf( it[0], 5 ), 1.0 - models.chi2.cdf( it[1], 5 ) # Проверяем доверительный интервал.
Out[106]:
(0.02500000000000003, 0.025000000000000022)
In [107]:
models.chi2.cdf( it[1], 5 ) - models.chi2.cdf( it[0], 5 ) # Проверяем его иначе.
Out[107]:
0.95
In [108]:
sum( it[0] <= x and x <= it[1] for x in gen_data2 ) / gen_data2.shape[0] # Проверяем численно.
Out[108]:
0.952
In [109]:
models.chi2.mean( 5 ) # Вычисляем матожидание распределения по формуле.
Out[109]:
5.0
In [110]:
np.mean( gen_data2 ) # Вычисляем выборочное среднее, т.е. по данным.
Out[110]:
4.86805008555363

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

In [111]:
#data_fb.shape
In [112]:
#per = data_fb[1:]/data_fb[:-1] -1 # Процентное изменение из о дня в день.
#per
In [113]:
#per.shape
In [114]:
#plt.hist( per, 10); # Строим распределение процентного изменения изо дня в день.

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

In [115]:
#models.chi2.fit( per )
In [116]:
#models.norm.fit( per )

Pearson

In [117]:
models.pearsonr([1, 2, 3, 4, 5], [4, 7, 10, 13, 16])
Out[117]:
(1.0, 0.0)
In [118]:
plt.plot( [1, 2, 3, 4, 5] )
plt.plot( [4, 7, 10, 13, 16] )
Out[118]:
[<matplotlib.lines.Line2D at 0x7fdb258d1940>]
In [119]:
models.pearsonr([1, 2, 3, 4, 5], [-12, -14, -16, -18, -20])
Out[119]:
(-1.0, 0.0)
In [120]:
models.pearsonr([1, 2, 1, 3, 1], [10, 12, 10, 14, 10]) # Необязательно монотонный данные.
Out[120]:
(0.9999999999999999, 1.4042654220543602e-24)
In [121]:
plt.plot( [1, 2, 1, 3, 1] )
plt.plot( [10, 12, 10, 14, 10] )
Out[121]:
[<matplotlib.lines.Line2D at 0x7fdb258beb00>]

Spearman

In [122]:
models.spearmanr([1,2,3,4,5], [50,51,60,100,200])
Out[122]:
SpearmanrResult(correlation=0.9999999999999999, pvalue=1.4042654220543672e-24)
In [123]:
plt.plot( [1,2,3,4,5] )
plt.plot( [50,51,60,100,200] )
Out[123]:
[<matplotlib.lines.Line2D at 0x7fdb25828668>]
In [124]:
models.spearmanr([1,2,6,4,5], [50,51,300,100,200])
Out[124]:
SpearmanrResult(correlation=0.9999999999999999, pvalue=1.4042654220543672e-24)
In [125]:
plt.plot( [1,2,6,4,5] )
plt.plot( [50,51,300,100,200] )
Out[125]:
[<matplotlib.lines.Line2D at 0x7fdb2578eeb8>]
In [126]:
models.spearmanr([1,5,4,3,2], [5,6,7,8,7])
Out[126]:
SpearmanrResult(correlation=0.20519567041703082, pvalue=0.7405819415910722)

Проверка того, что распределение Нормально

Статистика Шапиро-Уилка

In [127]:
np.random.seed( 2018 ) # Инициализация генератора случайных чисел.
In [128]:
dat = models.norm.rvs( 2, 3, size=10 ) # 2 и 3 это параметры нормального распределения.
plt.hist( dat );
t = models.shapiro( dat ) # Шапиро-Уилка тест для нормальности. Предполагается, что данных не очень много ( < 5000).
t
Out[128]:
(0.964828372001648, 0.8391991853713989)
In [129]:
# t[0] -- Это значение самой статистики.
# t[1] -- Это значение p-value
print( "Статистика -- ", t[0], ", p-значение ", t[1])
Статистика --  0.964828372001648 , p-значение  0.8391991853713989

Смотрим на p-значение. Если оно больше, например, 5%, то говорим, что с такой значимостью не можем отвергнуть гипотезу о нормальности даных.

In [130]:
plt.hist( dat * dat ); # Строим гистограмму для квадратов нормального распределения. Для данного распределения
models.shapiro( dat * dat ) # p-значение очень маленькое. Поэтому гипотезна о нормальности отвергнута. 
Out[130]:
(0.6501786112785339, 0.00022055456065572798)
In [131]:
plt.hist( 1/dat ); # Строим гистограмму для обратной величины от нормального распределения.
models.shapiro( 1/dat ) # Она тем более не являются нормальными. p-значение ну очень маленькое.
Out[131]:
(0.42994236946105957, 5.541986638490926e-07)
In [132]:
dat = models.norm.rvs( 2, 3, 10 ) # Возьмем новый набор данных.
In [133]:
plt.hist( dat * dat );
models.shapiro( dat * dat ) # p-значение чуть повыше, но все-равно меньше традиционной значимости 0.05.
Out[133]:
(0.8227037787437439, 0.027322569862008095)
In [134]:
plt.hist( 1/dat ); # При молой выборке конечно возможны ложные результаты
models.shapiro( 1/dat ) # Действительно смахивает на нормальное распределение. p-значение значительно выше 5%.
Out[134]:
(0.9199906587600708, 0.3568774163722992)
In [135]:
dat = models.norm.rvs( 2, 3, 100 ) # Если взять больше данных, то вероятность такого мала.
In [136]:
plt.hist( dat  ); # С увеличением данных, статестический тест может давать странные результаты.
models.shapiro( dat ) # В данном случае хоть тест и пройден, все-таки 8% не сильно далеко от 5%.
Out[136]:
(0.977222204208374, 0.08058711141347885)
In [137]:
plt.hist( dat * dat ); # Для произведения нормальных распределений.
models.shapiro( dat * dat ) # p-значение совсем маленькое. Поэтому, тест не пройден.
Out[137]:
(0.7918012142181396, 1.3974837953512065e-10)
In [138]:
plt.hist( 1/dat ); # При большой выборке вероятность ложного результата для сильных отклонений от нормального мала.
models.shapiro( 1/dat ) # p-значение совсем маленькое. Поэтому, тест не пройден.
Out[138]:
(0.16183680295944214, 2.745975081282877e-21)
In [139]:
dat = models.uniform.rvs( size = 10 ) # Возьмем проcто-напросто другой тип раcпределения, а именно равномерное.
plt.hist( dat  );
models.shapiro( dat ) # Тем не менее тест ломается на равномерном распределении, так как p-значение пройдено.
Out[139]:
(0.8418232798576355, 0.046399232000112534)
In [140]:
dat = models.uniform.rvs( size = 100 ) # Теперь увеличим объем выборки для равномерного распределения.
plt.hist( dat  ); 
models.shapiro( dat ) # p-значение не пройдено, поэтому гипотезу отвергаем.
Out[140]:
(0.95497065782547, 0.001789760310202837)
In [141]:
dat = models.f.rvs( 5, 7, size = 100 ) # f распределение с параметрами 5 и 7.
plt.hist( dat  );
models.shapiro( dat ) # f распределении отвергаем, так как p-значение существенно меньше.
Out[141]:
(0.6480447053909302, 3.8774498477452124e-14)
In [142]:
dat = models.f.rvs( 20, 35, size = 100 ) # f распределение с параметрами 20 и 35.
plt.hist( dat  );
models.shapiro( dat ) # Для данных параметров оно тоже отвергнуто.
Out[142]:
(0.8836591243743896, 2.600836523924954e-07)
In [131]:
dat = models.norm.rvs( 2, 3, 1000 ) 
plt.hist( dat  ); 
models.shapiro( dat ) # Все хорошо с p-значением.
Out[131]:
(0.9983240962028503, 0.4454226791858673)
In [ ]: