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

Анотация

В дополнение к первой заметке рассматривются такие понятия как собственные функции, классы и иные библиотек относящихся к анализу данных.

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

In [1]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt

import scipy.stats as models
In [2]:
import pandas as pd
# import seaborn
%config InlineBackend.figure_format = 'svg'
In [3]:
flat_df = pd.read_csv('berkeley_case.csv', sep = ';') # Считываем данные из файла.
In [4]:
flat_df # Выводим считанную таблицу. Она выглядит достаточно опрятно и эффектно.
Out[4]:
faculty gender param number
0 A men applied 825
1 A men accepted 512
2 A women applied 108
3 A women accepted 89
4 B men applied 560
5 B men accepted 353
6 B women applied 25
7 B women accepted 17
8 C men applied 325
9 C men accepted 120
10 C women applied 593
11 C women accepted 202
12 D men applied 417
13 D men accepted 138
14 D women applied 375
15 D women accepted 131
16 E men applied 191
17 E men accepted 53
18 E women applied 393
19 E women accepted 94
20 F men applied 272
21 F men accepted 16
22 F women applied 341
23 F women accepted 24
In [5]:
flat_df.columns # Выводим название колонок.
Out[5]:
Index(['faculty', 'gender', 'param', 'number'], dtype='object')
In [6]:
# Переименовываем на русский лад.
flat_df.columns = ['Факультет', 'Пол', 'Параметр', 'Количество']
In [7]:
flat_df
Out[7]:
Факультет Пол Параметр Количество
0 A men applied 825
1 A men accepted 512
2 A women applied 108
3 A women accepted 89
4 B men applied 560
5 B men accepted 353
6 B women applied 25
7 B women accepted 17
8 C men applied 325
9 C men accepted 120
10 C women applied 593
11 C women accepted 202
12 D men applied 417
13 D men accepted 138
14 D women applied 375
15 D women accepted 131
16 E men applied 191
17 E men accepted 53
18 E women applied 393
19 E women accepted 94
20 F men applied 272
21 F men accepted 16
22 F women applied 341
23 F women accepted 24
In [8]:
# Выполним быструю агрегацию по полу.
total_stats = pd.pivot_table(flat_df, aggfunc = sum, index = 'Пол', columns = 'Параметр', values = 'Количество')
total_stats
Out[8]:
Параметр accepted applied
Пол
men 1192 2590
women 557 1835
In [9]:
total_stats.accepted/total_stats.applied # В процентах.
Out[9]:
Пол
men      0.460232
women    0.303542
dtype: float64
In [10]:
total_stats['Процент поступ'] = total_stats.accepted/total_stats.applied
total_stats
Out[10]:
Параметр accepted applied Процент поступ
Пол
men 1192 2590 0.460232
women 557 1835 0.303542
In [11]:
#total_stats['perc_admitted'] = [round_2digits( it ) for it in 100*total_stats.accepted/total_stats.applied ]
total_stats['Процент поступ'] = [ round( it, 2) for it in 100*total_stats.accepted/total_stats.applied ]
total_stats
Out[11]:
Параметр accepted applied Процент поступ
Пол
men 1192 2590 46.02
women 557 1835 30.35
In [12]:
df = pd.pivot_table(flat_df, index = 'Факультет', values = 'Количество', columns = ['Пол', 'Параметр'])
df
Out[12]:
Пол men women
Параметр accepted applied accepted applied
Факультет
A 512 825 89 108
B 353 560 17 25
C 120 325 202 593
D 138 417 131 375
E 53 191 94 393
F 16 272 24 341
In [13]:
df['women']
Out[13]:
Параметр accepted applied
Факультет
A 89 108
B 17 25
C 202 593
D 131 375
E 94 393
F 24 341
In [14]:
df['women', 'applied']
Out[14]:
Факультет
A    108
B     25
C    593
D    375
E    393
F    341
Name: (women, applied), dtype: int64
In [15]:
df['accepted'] # так нельзя
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance)
   2441             try:
-> 2442                 return self._engine.get_loc(key)
   2443             except KeyError:

pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: 'accepted'

During handling of the above exception, another exception occurred:

KeyError                                  Traceback (most recent call last)
<ipython-input-15-80043267d4ab> in <module>()
----> 1 df['accepted'] # так нельзя

/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/pandas/core/frame.py in __getitem__(self, key)
   1960             return self._getitem_frame(key)
   1961         elif is_mi_columns:
-> 1962             return self._getitem_multilevel(key)
   1963         else:
   1964             return self._getitem_column(key)

/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/pandas/core/frame.py in _getitem_multilevel(self, key)
   2004 
   2005     def _getitem_multilevel(self, key):
-> 2006         loc = self.columns.get_loc(key)
   2007         if isinstance(loc, (slice, Series, np.ndarray, Index)):
   2008             new_columns = self.columns[loc]

/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/pandas/core/indexes/multi.py in get_loc(self, key, method)
   1978 
   1979         if not isinstance(key, tuple):
-> 1980             loc = self._get_level_indexer(key, level=0)
   1981             return _maybe_to_slice(loc)
   1982 

/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/pandas/core/indexes/multi.py in _get_level_indexer(self, key, level, indexer)
   2241         else:
   2242 
-> 2243             loc = level_index.get_loc(key)
   2244             if isinstance(loc, slice):
   2245                 return loc

/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance)
   2442                 return self._engine.get_loc(key)
   2443             except KeyError:
-> 2444                 return self._engine.get_loc(self._maybe_cast_indexer(key))
   2445 
   2446         indexer = self.get_indexer([key], method=method, tolerance=tolerance)

pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: 'accepted'
In [ ]:
idx = pd.IndexSlice
idx[:, 'accepted']
In [16]:
idx[:]
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-16-4e4c9f6f3c5e> in <module>()
----> 1 idx[:]

NameError: name 'idx' is not defined
In [17]:
df.loc[idx[:]] # Никаких изменений.
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-17-01888c9ee652> in <module>()
----> 1 df.loc[idx[:]] # Никаких изменений.

NameError: name 'idx' is not defined
In [18]:
df.loc[idx[:], idx[:, 'accepted']]
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-18-f2545b3a21e2> in <module>()
----> 1 df.loc[idx[:], idx[:, 'accepted']]

NameError: name 'idx' is not defined
In [19]:
df_total = df['men'] + df['women']
df_total
Out[19]:
Параметр accepted applied
Факультет
A 601 933
B 370 585
C 322 918
D 269 792
E 147 584
F 40 613
In [20]:
df_totalT = df_total.T # Транпонируем таблицу.
df_totalT
Out[20]:
Факультет A B C D E F
Параметр
accepted 601 370 322 269 147 40
applied 933 585 918 792 584 613
In [21]:
df_totalT['пол'] = 'всего'
df_totalT
Out[21]:
Факультет A B C D E F пол
Параметр
accepted 601 370 322 269 147 40 всего
applied 933 585 918 792 584 613 всего
In [22]:
df_totalT.set_index('пол', append = True, inplace = True)
df_totalT
Out[22]:
Факультет A B C D E F
Параметр пол
accepted всего 601 370 322 269 147 40
applied всего 933 585 918 792 584 613
In [23]:
df_totalT = df_totalT.reorder_levels(['пол', 'Параметр'])
df_totalT
Out[23]:
Факультет A B C D E F
пол Параметр
всего accepted 601 370 322 269 147 40
applied 933 585 918 792 584 613
In [24]:
df_total = df_totalT.T
df_total
Out[24]:
пол всего
Параметр accepted applied
Факультет
A 601 933
B 370 585
C 322 918
D 269 792
E 147 584
F 40 613
In [25]:
df = pd.concat([df, df_total], axis = 1)
df
Out[25]:
Пол men women всего
Параметр accepted applied accepted applied accepted applied
Факультет
A 512 825 89 108 601 933
B 353 560 17 25 370 585
C 120 325 202 593 322 918
D 138 417 131 375 269 792
E 53 191 94 393 147 584
F 16 272 24 341 40 613
In [26]:
df_inv = df.reorder_levels(['Параметр', 'Пол'] )
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-26-5f828a97edf0> in <module>()
----> 1 df_inv = df.reorder_levels(['Параметр', 'Пол'] )

/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/pandas/core/frame.py in reorder_levels(self, order, axis)
   3428         if not isinstance(self._get_axis(axis),
   3429                           MultiIndex):  # pragma: no cover
-> 3430             raise TypeError('Can only reorder levels on a hierarchical axis.')
   3431 
   3432         result = self.copy()

TypeError: Can only reorder levels on a hierarchical axis.
In [27]:
df_inv = df.reorder_levels(['Параметр', 'Пол'], axis = 1)
df_inv
Out[27]:
Параметр accepted applied accepted applied accepted applied
Пол men men women women всего всего
Факультет
A 512 825 89 108 601 933
B 353 560 17 25 370 585
C 120 325 202 593 322 918
D 138 417 131 375 269 792
E 53 191 94 393 147 584
F 16 272 24 341 40 613
In [28]:
df_inv = df_inv.sort_index(level = 0, axis = 1)
df_inv
Out[28]:
Параметр accepted applied
Пол men women всего men women всего
Факультет
A 512 89 601 825 108 933
B 353 17 370 560 25 585
C 120 202 322 325 593 918
D 138 131 269 417 375 792
E 53 94 147 191 393 584
F 16 24 40 272 341 613
In [29]:
100*df_inv.accepted/df_inv.applied # Вычисляем итоговые проценты на факультетах в зависимости от пола.
# Итоговые проценту получили, но их нужно округлить. Как раньше не получится. Объект сложный. Итерация не годится.
Out[29]:
Пол men women всего
Факультет
A 62.060606 82.407407 64.415863
B 63.035714 68.000000 63.247863
C 36.923077 34.064081 35.076253
D 33.093525 34.933333 33.964646
E 27.748691 23.918575 25.171233
F 5.882353 7.038123 6.525285
In [30]:
# Можно ввести округление.
# Для этого понядобятся функции
def round_2digits( x ): # Данная функция от одного аргумента. Как синус, квадратный корень и тому подобное.
    return round(x, 2) # Вызываем функцию из самого Python, число 2 указывает на то что округляем две цифры после запятой.
In [31]:
round_2digits( 2.123456 )
Out[31]:
2.12
In [32]:
round_2digits( 2.1 )
Out[32]:
2.1
In [33]:
round_2digits( 2.12567 ) # Это именно округление, а не отрезание чисел после второго разряда после запятой.
Out[33]:
2.13
In [34]:
admitted_perc = 100*df_inv.accepted/df_inv.applied
admitted_perc = round_2digits( admitted_perc )
admitted_perc
Out[34]:
Пол men women всего
Факультет
A 62.06 82.41 64.42
B 63.04 68.00 63.25
C 36.92 34.06 35.08
D 33.09 34.93 33.96
E 27.75 23.92 25.17
F 5.88 7.04 6.53
In [35]:
admitted_perc[['всего', 'men', 'women']].plot(kind = 'bar', title = 'Процент посупивших в Беркли')
Out[35]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f6fc8fc6fd0>
In [36]:
df_applied = flat_df[ flat_df.Параметр == 'applied' ]
df_applied
Out[36]:
Факультет Пол Параметр Количество
0 A men applied 825
2 A women applied 108
4 B men applied 560
6 B women applied 25
8 C men applied 325
10 C women applied 593
12 D men applied 417
14 D women applied 375
16 E men applied 191
18 E women applied 393
20 F men applied 272
22 F women applied 341
In [37]:
gndr_fclt_appl = pd.pivot_table( df_applied, index = 'Факультет', values = 'Количество', columns = 'Пол')
gndr_fclt_appl
Out[37]:
Пол men women
Факультет
A 825 108
B 560 25
C 325 593
D 417 375
E 191 393
F 272 341
In [38]:
gndr_fclt_appl.sum()
Out[38]:
Пол
men      2590
women    1835
dtype: int64
In [39]:
gndr_fclt_appl = 100 * gndr_fclt_appl / gndr_fclt_appl.sum()
gndr_fclt_appl
Out[39]:
Пол men women
Факультет
A 31.853282 5.885559
B 21.621622 1.362398
C 12.548263 32.316076
D 16.100386 20.435967
E 7.374517 21.416894
F 10.501931 18.583106
In [40]:
round_2digits( gndr_fclt_appl )
Out[40]:
Пол men women
Факультет
A 31.85 5.89
B 21.62 1.36
C 12.55 32.32
D 16.10 20.44
E 7.37 21.42
F 10.50 18.58
In [41]:
faculty_stats = admitted_perc[['всего']].join( gndr_fclt_appl )
faculty_stats
Out[41]:
Пол всего men women
Факультет
A 64.42 31.853282 5.885559
B 63.25 21.621622 1.362398
C 35.08 12.548263 32.316076
D 33.96 16.100386 20.435967
E 25.17 7.374517 21.416894
F 6.53 10.501931 18.583106
In [42]:
faculty_stats.columns = ['Процент посупивших', 'доля мужчин', 'доля женщин']
faculty_stats.plot(kind = 'bar', title = 'Статистика факультетов Беркли')
Out[42]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f6fc54fd630>

Функции

In [43]:
def mysum( a, b): # Теперь у функции два аргумента
    return a + b * 2; # Возвращаем значение выражения.
In [44]:
mysum( 3, 2) # 3 + 2 * 2
Out[44]:
7
In [45]:
mysum( 3, a = 2) # При таком вызове система запутается. Точнее будет повторное присвоение первому аргументу.
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-45-0b18aef4102d> in <module>()
----> 1 mysum( 3, a = 2) # При таком вызове система запутается. Точнее будет повторное присвоение первому аргументу.

TypeError: mysum() got multiple values for argument 'a'
In [46]:
mysum( b = 3, 2) # А так нельзя потомучто после присвоения переменной значения по имени позиции не учитываются.
  File "<ipython-input-46-acfda9f6c440>", line 1
    mysum( b = 3, 2) # А так нельзя потомучто после присвоения переменной значения по имени позиции не учитываются.
                 ^
SyntaxError: positional argument follows keyword argument
In [47]:
mysum( b = 3, a = 2) # 2 + 3 * 2
Out[47]:
8
In [48]:
# Можно сделать значения по умолчанию
def mysum2( a, b = 5):
    return a + b * 2;
In [49]:
mysum2( 3 ) # 3 + 5 * 2
Out[49]:
13
In [50]:
mysum2( 3, 2) # 3 + 2 * 2
Out[50]:
7
In [51]:
# После переменных с значением по умолчанию обычные переменные не могут следовать
def mysum3( a, b = 5, c):
    return a + b + c
  File "<ipython-input-51-cd39ff2bab97>", line 2
    def mysum3( a, b = 5, c):
               ^
SyntaxError: non-default argument follows default argument
In [52]:
# Тело функции ествественно может быть сложным
def doPow( a, c ): # Вычисляем возведение в степень. a возводится в степен c.
    d = 1
    for i in range(c):
        d = d * a
    return d
In [53]:
doPow( 2, 3)
Out[53]:
8
In [54]:
doPow( 3, 4)
Out[54]:
81
In [55]:
# Функция может иметь несколько точек воврата
def myAbs( a ):
    if a < 0:
        return -a # else опустили для краткости.
    return a
In [56]:
myAbs( -11 )
Out[56]:
11
In [57]:
doPow( 3, -5) # Для отрицательной степени не сработали.
Out[57]:
1
In [58]:
# Можем вызвать сами себя:
def doPow( a, c ):
    if c < 0: #Учитываем отрицательость степени
        return doPow( 1/a, -c ) 
    d = 1
    for i in range(c):
        d = d * a
    return d
In [59]:
doPow( 3, -5)
Out[59]:
0.004115226337448559
In [60]:
(1/doPow( 3, -5))/3/3/3/3/3
Out[60]:
1.0000000000000002
In [61]:
# Можем и более сложные алгортмы делать. Например вычисление факториала.
def fact( n ):
    if n <= 1:
        return 1
    return n * fact( n - 1 )
In [62]:
fact( 3 )
Out[62]:
6
In [63]:
# Раз уж if был упомянут...
In [64]:
data = np.random.randint(-10, 10, 10) # Равномерное целочисленное распределение между -10 и 10 невключительно.
data
Out[64]:
array([ 4, -4, -6, -5,  9,  2,  5, -4,  0,  8])
In [65]:
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 :  45    <0 :  51  ==0 :  4
In [66]:
100/20 # Приблизительно равно количеству равных 0.
Out[66]:
5.0

Собственное распределение

In [67]:
x = models.norm.rvs(2, 3, size = 100)
In [68]:
models.norm.fit( x )
Out[68]:
(1.6326093985235286, 3.2575250243461613)
In [69]:
np.mean( x )
Out[69]:
1.6326093985235286
In [70]:
np.std( x ) # Вот и все, т.е. по формулам.
Out[70]:
3.2575250243461613
In [71]:
pi = 0.3
lmbd = 3
In [72]:
def zip_pmf( x, pi = pi, lmbd = lmbd ):
    if pi < 0 or pi > 1 or lmbd <= 0:
        return np.zeros_like( x )
    else:
        return (x == 0) * pi + (1 - pi) * models.poisson.pmf( x, lmbd)
In [73]:
zip_pmf( 0 )
Out[73]:
0.33485094785750474
In [74]:
zip_pmf( 1 )
Out[74]:
0.10455284357251429
In [75]:
models.poisson.pmf( 0, lmbd)
Out[75]:
0.049787068367863944
In [76]:
fig, ax = plt.subplots(figsize=(8, 6))
xs = np.arange(0, 10)

palette = ['g','b']

ax.bar( 2.5*xs, models.poisson.pmf( xs, lmbd), width = 0.9, color = palette[0], label='Poisson')
ax.bar( 2.5*xs + 1, zip_pmf( xs ), width = 0.9, color = palette[1], label='Zero - Poisson')

ax.set_xticks( 2.5 * xs + 1 )
ax.set_xticklabels( xs )
ax.set_xlabel('$x$')

ax.set_ylabel('$P(X=x)$')
ax.legend()
Out[76]:
<matplotlib.legend.Legend at 0x7f6fc521a240>
In [77]:
N = 1000
iz = models.bernoulli.rvs( pi, size = N)
x = (1 - iz) * models.poisson.rvs( lmbd, size = N)
print(x[::50])
x.shape
[3 0 2 0 6 0 0 0 1 2 1 0 4 0 0 3 2 0 3 1]
Out[77]:
(1000,)
In [78]:
fig, ax = plt.subplots(figsize=(8, 6))
ax.hist( x, width=0.8,  bins = np.arange( x.max() + 1 ), normed = True)

ax.set_xticks( np.arange( x.max() + 1 ) + 0.4 )
ax.set_xticklabels( np.arange( x.max() + 1 ) )
ax.set_xlabel('$x$')

ax.set_ylabel('Частота')
Out[78]:
<matplotlib.text.Text at 0x7f6fc52e8630>
In [79]:
from statsmodels.base.model import GenericLikelihoodModel
class ZeroInflated(GenericLikelihoodModel):
    def __init__(self, endog, exog = None, **kwds):
        if exog == None:
            exog = np.zeros_like( endog )
        super( ZeroInflated, self).__init__( endog, exog, **kwds)
    
    def nloglikeobs( self, params):
        pi = params[0]
        lmbd = params[1]
        
        return -np.log( zip_pmf( self.endog, pi = pi, lmbd = lmbd) )
    
    def fit(self, start_params = None, maxiter = 10000, maxfun = 5000, **kwds):
        if start_params is None:
            lmbd_start = self.endog.mean()
            extra_zeros = ( self.endog == 0 ).mean() - models.poisson.pmf(0, lmbd_start )
            start_params = np.array([extra_zeros, lmbd_start])
            
        return super( ZeroInflated, self ).fit(start_params = start_params, maxiter = maxiter, maxfun = maxfun, **kwds)
     
In [80]:
model = ZeroInflated( x )
In [81]:
res = model.fit()
Optimization terminated successfully.
         Current function value: 1.832573
         Iterations: 34
         Function evaluations: 68
In [82]:
res.params # Profit!
Out[82]:
array([ 0.31378477,  3.02823459])

Измерение соотношения количеств

Используем распределение Бернули

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

In [83]:
models.bernoulli.rvs( 0.7, 10) # Так нельзя, так как 10 будет использоватся как сдвиг.
Out[83]:
10

В питоне функции могут использовать два типа аргументов или иначе параметров, которые ей передаются. Первый тип позиционый, который является стандартным для большинства языков программиования. Суть в том, что параметры присваиваются в порядке и задания при вызове. А бывают аргументы именные, которые можно присвоить вне очереди.

Рассмотреть документацию по данной функции https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.bernoulli.html

Конкретно изучим описание функции rvs: rvs(p, loc=0, size=1, random_state=None)

Последнее к сожалению позволит понять какие значения были присвоены аргументам по умолчанию. часть аргументов просто перечислена (а данном случае только p), а часть через знак '='.

In [84]:
# Вообще у любого объекта, функции по соглашению может быть поле __doc__ которое дает краткую документацию по нему.
# Так как это поле является строчкой, то для аккуратного вывода лучше применить функцию print
# Подробнее о ней будет позже. Сейчас она используется для вывода самоописания функции.
print( models.bernoulli.rvs.__doc__ )
        Random variates of given type.

        Parameters
        ----------
        arg1, arg2, arg3,... : array_like
            The shape parameter(s) for the distribution (see docstring of the
            instance object for more information).
        loc : array_like, optional
            Location parameter (default=0).
        size : int or tuple of ints, optional
            Defining number of random variates (Default is 1).  Note that `size`
            has to be given as keyword, not as positional argument.
        random_state : None or int or ``np.random.RandomState`` instance, optional
            If int or RandomState, use it for drawing the random variates.
            If None, rely on ``self.random_state``.
            Default is None.

        Returns
        -------
        rvs : ndarray or scalar
            Random variates of given `size`.

        

Из данного описания следует, что сначала идут позиционные параметры (arg1, arg2, ...), а потом именные (loc, size, random_state). Тем не менее, если они идут подряд, то присвоение идет подряд.

In [85]:
# В данном случае:
models.bernoulli.rvs( 0.7, 10) # 0.7 это параметр распределения, а 10 присвоилось аргументу loc.
Out[85]:
11
In [86]:
models.bernoulli.rvs( 0.7, 10, 5) # Вот теперь очередь дошла до размера, которому было присвоено значение 5.
Out[86]:
array([11, 11, 11, 11, 11])
In [87]:
# Но можно было бы и явно присвоить переменной отвечащей за размер размер:
models.bernoulli.rvs( 0.7, size = 10 ) # Аргументу size присвоено значение вне очереди.
Out[87]:
array([1, 1, 1, 0, 1, 1, 1, 1, 0, 1])
In [88]:
# Нельзя позиционный аргумент подавать после именных. Последнее выдаст соответствующую ошибку:
models.bernoulli.rvs( size = 10, 0.7 )
  File "<ipython-input-88-a2b1035edb92>", line 2
    models.bernoulli.rvs( size = 10, 0.7 )
                                    ^
SyntaxError: positional argument follows keyword argument
In [89]:
# Но если аргументы заданы по имени, то их можно подавать функции в произвольном порядке.
models.bernoulli.rvs( size = 10, p = 0.7 )
Out[89]:
array([1, 0, 0, 0, 1, 1, 1, 1, 1, 1])
In [90]:
models.bernoulli.rvs( size = 10, p = 0.7, loc = 5 ) # В данном случае было задано и значение параметра loc, положения.
Out[90]:
array([6, 6, 5, 6, 6, 6, 6, 6, 5, 5])

Закон больших чисел

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

In [91]:
# Имитация закона больших чисел:
data = [] # Содали пустой список
for i in range( 1000 ): # Мы явно указали размер нешей выборки
    lap = models.bernoulli.rvs( 0.7, size=100 ) # а также количество элементов в устреднение.
    data.append( np.sum( lap )/100 )
In [92]:
plt.hist( data );
In [93]:
# Хотим цикл ещё раз запустить с другим количеством эллементов в сумме.

Продолжаем с Бернули

In [94]:
# Вводим понятие функции, т.е. сами пишем.
def buildBernSum( p, cycles = 1000, elems = 100 ): # Два аргумента. Оба необязательны. Имеют значение по умолчанию.
    data = [] # Содали пустой список
    for i in range( cycles ):
        lap = models.bernoulli.rvs( p, size=elems ) # 0.7
        data.append( np.sum( lap )/elems )
    return data # Вернули результат.
In [95]:
bern = buildBernSum( 0.7 )
plt.hist( bern );
In [96]:
bern = buildBernSum( 0.7, elems = 10000 )
plt.hist( bern );
In [97]:
bern = buildBernSum( elems = 10000, p = 0.7)
plt.hist( bern );
In [98]:
bern = buildBernSum( 0.7, 100000, 10000 ) # Позиционное присвоение параметров
plt.hist( bern );
In [99]:
bern = buildBernSum( 0.7, 4000, 3000 )
plt.hist( bern );

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

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

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

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

In [103]:
plt.hist( dat * dat ); # Строим гистограмму для квадратов нормального распределения. Для данного распределения
models.shapiro( dat * dat ) # p-значение очень маленькое. Поэтому гипотезна о нормальности отвергнута. 
Out[103]:
(0.6501786112785339, 0.00022055456065572798)
In [104]:
plt.hist( 1/dat ); # Строим гистограмму для обратной величины от нормального распределения.
models.shapiro( 1/dat ) # Она тем более не являются нормальными. p-значение ну очень маленькое.
Out[104]:
(0.42994236946105957, 5.541986638490926e-07)
In [105]:
dat = models.norm.rvs( 2, 3, 10 ) # Возьмем новый набор данных.
In [106]:
plt.hist( dat * dat );
models.shapiro( dat * dat ) # p-значение чуть повыше, но все-равно меньше традиционной значимости 0.05.
Out[106]:
(0.8227037787437439, 0.027322569862008095)
In [107]:
plt.hist( 1/dat ); # При молой выборке конечно возможны ложные результаты
models.shapiro( 1/dat ) # Действительно смахивает на нормальное распределение. p-значение значительно выше 5%.
Out[107]:
(0.9199906587600708, 0.3568774163722992)
In [108]:
dat = models.norm.rvs( 2, 3, 100 ) # Если взять больше данных, то вероятность такого мала.
In [109]:
plt.hist( dat  ); # С увеличением данных, статестический тест может давать странные результаты.
models.shapiro( dat ) # В данном случае хоть тест и пройден, все-таки 8% не сильно далеко от 5%.
Out[109]:
(0.977222204208374, 0.08058711141347885)
In [110]:
plt.hist( dat * dat ); # Для произведения нормальных распределений.
models.shapiro( dat * dat ) # p-значение совсем маленькое. Поэтому, тест не пройден.
Out[110]:
(0.7918012142181396, 1.3974837953512065e-10)
In [111]:
plt.hist( 1/dat ); # При большой выборке вероятность ложного результата для сильных отклонений от нормального мала.
models.shapiro( 1/dat ) # p-значение совсем маленькое. Поэтому, тест не пройден.
Out[111]:
(0.16183680295944214, 2.745975081282877e-21)
In [112]:
dat = models.uniform.rvs( size = 10 ) # Возьмем проcто-напросто другой тип раcпределения, а именно равномерное.
plt.hist( dat  );
models.shapiro( dat ) # Тем не менее тест ломается на равномерном распределении, так как p-значение пройдено.
Out[112]:
(0.8418232798576355, 0.046399232000112534)
In [113]:
dat = models.uniform.rvs( size = 100 ) # Теперь увеличим объем выборки для равномерного распределения.
plt.hist( dat  ); 
models.shapiro( dat ) # p-значение не пройдено, поэтому гипотезу отвергаем.
Out[113]:
(0.95497065782547, 0.001789760310202837)
In [114]:
dat = models.f.rvs( 5, 7, size = 100 ) # f распределение с параметрами 5 и 7.
plt.hist( dat  );
models.shapiro( dat ) # f распределении отвергаем, так как p-значение существенно меньше.
Out[114]:
(0.6480447053909302, 3.8774498477452124e-14)
In [115]:
dat = models.f.rvs( 20, 35, size = 100 ) # f распределение с параметрами 20 и 35.
plt.hist( dat  );
models.shapiro( dat ) # Для данных параметров оно тоже отвергнуто.
Out[115]:
(0.8836591243743896, 2.600836523924954e-07)
In [116]:
dat = models.norm.rvs( 2, 3, 1000 ) 
plt.hist( dat  ); 
models.shapiro( dat ) # Все хорошо с p-значением.
Out[116]:
(0.9983240962028503, 0.4454226791858673)
In [117]:
#С увеличением параметров распределение будет стремится к нормальному

Anderson-Darling

Предыдущая статистика работает только для нормального распределения. Для данного же теста можно задать используемое распределение: norm, expon, logistic, gumbel, gumbel_l, gumbel_r, extreme1. Пока будем оставатся в рамках нормального.

В Шапиро-Уилка граница была единственной. Той что обычно принято пользоваться. В методе же Андерсона таких границ считается несколько для различных уровней значимости.

In [118]:
dat = models.uniform.rvs( size = 100 ) # Пвторно проверим на равномером распределении.
plt.hist( dat  );
models.anderson( dat, dist='norm' ) # Тест отвергает данные как принадлежащие равномерному распределению.
# Статистику нужно возвести в квадрат и сравнить с критерием. Если больше, то гипотеза отвергается.
Out[118]:
AndersonResult(statistic=1.7489833322056967, critical_values=array([ 0.555,  0.632,  0.759,  0.885,  1.053]), significance_level=array([ 15. ,  10. ,   5. ,   2.5,   1. ]))

Проверка того что распределение то что нужно.

In [119]:
dat = models.norm.rvs( 2, 3, 100 ) # Для начала прверим для нормального распределения.
res = models.anderson( dat )
In [120]:
res
Out[120]:
AndersonResult(statistic=0.52028506183778234, critical_values=array([ 0.555,  0.632,  0.759,  0.885,  1.053]), significance_level=array([ 15. ,  10. ,   5. ,   2.5,   1. ]))
In [121]:
res[1] # Значение значимости критических уровней.
Out[121]:
array([ 0.555,  0.632,  0.759,  0.885,  1.053])
In [122]:
res[2] # Сами уровни.
Out[122]:
array([ 15. ,  10. ,   5. ,   2.5,   1. ])
In [123]:
# Можно представить их матрицей.
table = np.array([res[1], res[2]])
table
Out[123]:
array([[  0.555,   0.632,   0.759,   0.885,   1.053],
       [ 15.   ,  10.   ,   5.   ,   2.5  ,   1.   ]])
In [124]:
# Хотим красивое представление результата/критерия в виде таблицы
import pandas as pd # Очередная библиотека полезная для представления данных в таблицах, и их считывания.
In [125]:
pd.DataFrame( table, columns=['Уровни', 'Значимость'] ) # Система ругнется так как матрица не правильного размера.
# Данная функция ожидала матрицу состоящую из двух столбцов. 
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/pandas/core/internals.py in create_block_manager_from_blocks(blocks, axes)
   4293                 blocks = [make_block(values=blocks[0],
-> 4294                                      placement=slice(0, len(axes[0])))]
   4295 

/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/pandas/core/internals.py in make_block(values, placement, klass, ndim, dtype, fastpath)
   2718 
-> 2719     return klass(values, ndim=ndim, fastpath=fastpath, placement=placement)
   2720 

/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/pandas/core/internals.py in __init__(self, values, placement, ndim, fastpath)
    114                              'implies %d' % (len(self.values),
--> 115                                              len(self.mgr_locs)))
    116 

ValueError: Wrong number of items passed 5, placement implies 2

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
<ipython-input-125-d3ed74bd43b3> in <module>()
----> 1 pd.DataFrame( table, columns=['Уровни', 'Значимость'] ) # Система ругнется так как матрица не правильного размера.
      2 # Данная функция ожидала матрицу состоящую из двух столбцов.

/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/pandas/core/frame.py in __init__(self, data, index, columns, dtype, copy)
    304             else:
    305                 mgr = self._init_ndarray(data, index, columns, dtype=dtype,
--> 306                                          copy=copy)
    307         elif isinstance(data, (list, types.GeneratorType)):
    308             if isinstance(data, types.GeneratorType):

/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/pandas/core/frame.py in _init_ndarray(self, values, index, columns, dtype, copy)
    481             values = maybe_infer_to_datetimelike(values)
    482 
--> 483         return create_block_manager_from_blocks([values], [columns, index])
    484 
    485     @property

/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/pandas/core/internals.py in create_block_manager_from_blocks(blocks, axes)
   4301         blocks = [getattr(b, 'values', b) for b in blocks]
   4302         tot_items = sum(b.shape[0] for b in blocks)
-> 4303         construction_error(tot_items, blocks[0].shape[1:], axes, e)
   4304 
   4305 

/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/pandas/core/internals.py in construction_error(tot_items, block_shape, axes, e)
   4278         raise ValueError("Empty data passed with indices specified.")
   4279     raise ValueError("Shape of passed values is {0}, indices imply {1}".format(
-> 4280         passed, implied))
   4281 
   4282 

ValueError: Shape of passed values is (5, 2), indices imply (2, 2)
In [126]:
# В Numpy естественно есть функция для транспонирования матрицы.
np.transpose( table )
Out[126]:
array([[  0.555,  15.   ],
       [  0.632,  10.   ],
       [  0.759,   5.   ],
       [  0.885,   2.5  ],
       [  1.053,   1.   ]])
In [127]:
# Транспонироваие дает результат и в общем случае: она переставляет оси.
x = np.ones((1, 2, 3)) # ones вспомогательная функция составляющая многомерную матрицу из единиц. В скобках указан размер.
x
Out[127]:
array([[[ 1.,  1.,  1.],
        [ 1.,  1.,  1.]]])
In [128]:
xT = np.transpose(x, (1, 2, 0)) # В скобках указана перестановка исходных осей, т.е. оси будут идти в порядке 1, 2, 0.
xT
Out[128]:
array([[[ 1.],
        [ 1.],
        [ 1.]],

       [[ 1.],
        [ 1.],
        [ 1.]]])
In [129]:
x.shape, xT.shape # Если оси переставлены, то мы это увидем на соответствующем образом изменившимся порядке их размере.
# Вначале должна идти первая компонента, потом вторая, а уже потом нулевая векто рараеров (1, 2, 3).
# Тогда получим 2, 3, 1
Out[129]:
((1, 2, 3), (2, 3, 1))
In [130]:
# Тогда:
pd.DataFrame( np.transpose( table ), columns=['Значение статистики', 'Уровень значимости'] )
# Получаем красивую таблицу.
Out[130]:
Значение статистики Уровень значимости
0 0.555 15.0
1 0.632 10.0
2 0.759 5.0
3 0.885 2.5
4 1.053 1.0
In [131]:
# Но можно иначе.
table = np.dstack( (res[1], res[2]) )
table, table.shape # Но появится лишняя размерность.
Out[131]:
(array([[[  0.555,  15.   ],
         [  0.632,  10.   ],
         [  0.759,   5.   ],
         [  0.885,   2.5  ],
         [  1.053,   1.   ]]]), (1, 5, 2))
In [132]:
# Её можно убрать взяв первый элемент:
table[0], table[0].shape
Out[132]:
(array([[  0.555,  15.   ],
        [  0.632,  10.   ],
        [  0.759,   5.   ],
        [  0.885,   2.5  ],
        [  1.053,   1.   ]]), (5, 2))
In [133]:
# тогда опять можем получить таблицу.
pd.DataFrame( table[0], columns=['Значение статистики', 'Уровень значимости'] )
Out[133]:
Значение статистики Уровень значимости
0 0.555 15.0
1 0.632 10.0
2 0.759 5.0
3 0.885 2.5
4 1.053 1.0
In [134]:
# Скорее всего ошибка в коде. Значимости нужны в обратном порядке.
table = np.dstack( (res[1], res[2][::-1] ) )[0]
pd.DataFrame( table, columns=['Значение статистики', 'Уровень значимости'] )
Out[134]:
Значение статистики Уровень значимости
0 0.555 1.0
1 0.632 2.5
2 0.759 5.0
3 0.885 10.0
4 1.053 15.0
In [135]:
# В res[0] у нас значение статистики.
res[0] # Для проверки гипотезы нужно сравнить это число с критическими значениями.
Out[135]:
0.52028506183778234

Бернули

In [136]:
bern = buildBernSum( 0.7, 4000, 3000 )
res = models.anderson( bern ) # Второй параметр указывает на распределение. Поумолчанию проверка на нормальность.
res # Дествительно стремимся к нормальному распределению.
Out[136]:
AndersonResult(statistic=0.57464054091587968, critical_values=array([ 0.575,  0.655,  0.786,  0.917,  1.091]), significance_level=array([ 15. ,  10. ,   5. ,   2.5,   1. ]))
In [137]:
params = models.norm.fit( bern )
params
Out[137]:
(0.70028191666666673, 0.0084027241411970457)
In [138]:
models.norm.interval( 0.95, params[0], params[1] )
Out[138]:
(0.6838128799778953, 0.71675095335543815)
In [139]:
models.bernoulli.interval( 0.7, 0.7 ) # Системе плохо.
Out[139]:
(0.0, 1.0)
In [140]:
models.norm.interval( 0.95 )
Out[140]:
(-1.959963984540054, 1.959963984540054)
In [141]:
pp = np.mean( bern )
pp
Out[141]:
0.70028191666666673
In [142]:
dd = np.sqrt(pp*(1-pp)/3000)
dd
Out[142]:
0.0083643520142791371
In [143]:
lim = models.norm.interval( 0.95, 0, 1 )
lim
Out[143]:
(-1.959963984540054, 1.959963984540054)
In [144]:
pp + dd*lim[0], pp + dd*lim[1]
Out[144]:
(0.6838880879646646, 0.71667574536866885)

Pearson

In [145]:
models.pearsonr([1, 2, 3, 4, 5], [4, 7, 10, 13, 16])
Out[145]:
(1.0, 0.0)
In [146]:
plt.plot( [1, 2, 3, 4, 5] )
plt.plot( [4, 7, 10, 13, 16] )
Out[146]:
[<matplotlib.lines.Line2D at 0x7f6fc4b4df28>]
In [147]:
models.pearsonr([1, 2, 3, 4, 5], [-12, -14, -16, -18, -20])
Out[147]:
(-1.0, 0.0)
In [148]:
models.pearsonr([1, 2, 1, 3, 1], [10, 12, 10, 14, 10]) # Необязательно монотонный данные.
Out[148]:
(0.99999999999999989, 1.4042654220543602e-24)
In [149]:
plt.plot( [1, 2, 1, 3, 1] )
plt.plot( [10, 12, 10, 14, 10] )
Out[149]:
[<matplotlib.lines.Line2D at 0x7f6fc47f0390>]

Spearman

In [150]:
models.spearmanr([1,2,3,4,5], [50,51,60,100,200])
Out[150]:
SpearmanrResult(correlation=0.99999999999999989, pvalue=1.4042654220543672e-24)
In [151]:
plt.plot( [1,2,3,4,5] )
plt.plot( [50,51,60,100,200] )
Out[151]:
[<matplotlib.lines.Line2D at 0x7f6fc4af7f60>]
In [152]:
models.spearmanr([1,2,6,4,5], [50,51,300,100,200])
Out[152]:
SpearmanrResult(correlation=0.99999999999999989, pvalue=1.4042654220543672e-24)
In [153]:
plt.plot( [1,2,6,4,5] )
plt.plot( [50,51,300,100,200] )
Out[153]:
[<matplotlib.lines.Line2D at 0x7f6fc4a57f98>]
In [154]:
models.spearmanr([1,5,4,3,2], [5,6,7,8,7])
Out[154]:
SpearmanrResult(correlation=0.20519567041703082, pvalue=0.74058194159107216)
In [155]:
models.laplace.rvs()
Out[155]:
0.078798686274605831

A/B Тест

In [ ]: