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

Аннотация

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

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

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

import scipy.stats as models

Напомню:

In [2]:
import csv
In [3]:
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 )
close = [ day[-2] for day in data ] # -2 это цена закрытия.
In [4]:
plt.plot( close )
plt.ylabel( 'Цена рубли')
plt.xlabel( 'День начиная с 2018 г')
Out[4]:
Text(0.5,0,'День начиная с 2018 г')

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

Функции

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

TypeError: mysum() got multiple values for argument 'a'
In [9]:
mysum( b = 3, 2) # А так нельзя потомучто после присвоения переменной значения по имени позиции не учитываются.
  File "<ipython-input-9-872b55c45048>", line 1
    mysum( b = 3, 2) # А так нельзя потомучто после присвоения переменной значения по имени позиции не учитываются.
                 ^
SyntaxError: positional argument follows keyword argument
In [10]:
mysum( 5 )
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-10-be885016f2a6> in <module>
----> 1 mysum( 5 )

TypeError: mysum() missing 1 required positional argument: 'b'

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

In [11]:
# Можно сделать значения по умолчанию
def mysum2( a, b = 5):
    return a + b * 2;
In [12]:
mysum2( 3 ) # 3 + 5 * 2
Out[12]:
13
In [13]:
mysum2( 3, 2) # 3 + 2 * 2
Out[13]:
7
In [14]:
# После переменных с значением по умолчанию обычные переменные не могут следовать
def mysum3( a, b = 5, c):
    return a + b + c
  File "<ipython-input-14-093715687c9e>", line 2
    def mysum3( a, b = 5, c):
               ^
SyntaxError: non-default argument follows default argument

Упр. Напиши функцию вычисляющую логарифм. По умолчанию основание пусть будет натуральным. Иначе, оно должно быть указано.

In [15]:
# Тело функции ествественно может быть сложным
def doPow( a, c ): # Вычисляем возведение в степень. a возводится в степен c.
    d = 1
    for i in range(c):
        d = d * a
    return d
In [16]:
doPow( 2, 3)
Out[16]:
8
In [17]:
doPow( 3, 4)
Out[17]:
81

Упр. Напиши функцию вычисляющую среднее и среднеквадратичное отклонение массива. Написать нужно через цикл самостоятельно, а не черз фкнции из numpy.

In [18]:
# Функция может иметь несколько точек воврата
def myAbs( a ):
    if a < 0:
        return -a # else опустили для краткости.
    return a
In [19]:
myAbs( -11 )
Out[19]:
11
In [20]:
doPow( 3, -5) # Для отрицательной степени не сработали.
Out[20]:
1

Упр. Напиши функцию которая возвращает знак числа, т.е. -1 для отрицательных, 1 для положительных и 0 для 0.

In [21]:
# Можем вызвать сами себя:
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 [22]:
doPow( 3, -5)
Out[22]:
0.004115226337448559
In [23]:
(1/doPow( 3, -5))/3/3/3/3/3
Out[23]:
1.0000000000000002
In [24]:
# Можем и более сложные алгортмы делать. Например вычисление факториала.
def fact( n ):
    if n <= 1:
        return 1
    return n * fact( n - 1 )
In [25]:
fact( 3 )
Out[25]:
6

Упр. Напиши фукнцию вычисляющую наибольший общий делитель. Рекурсивным способом: через вычитания или остаток.

Обратно к эмитентам

In [26]:
def txt2data( name ): # Функция принимает на вход название файла.
    data = []
    with open( name ) 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 )
    return data
In [27]:
mechel = txt2data( 'MTLR_180101_190110.txt' )
mtl_cl = np.array( [ day[-2] for day in mechel ] ) # -2 это цена закрытия.
In [28]:
vtb = txt2data( 'VTBR_180101_190110.txt' )
vtb_cl = np.array( [ day[-2] for day in vtb ] )
In [29]:
plt.plot( mtl_cl )
plt.plot( vtb_cl )
plt.ylabel( 'Цена рубли')
plt.xlabel( 'День начиная с 2018 г')
Out[29]:
Text(0.5,0,'День начиная с 2018 г')
In [30]:
plt.plot( mtl_cl/mtl_cl[0] )
plt.plot( vtb_cl/vtb_cl[0] )
plt.ylabel( 'Процентое изменение')
plt.xlabel( 'День начиная с 2018 г')
plt.legend( ["Мечел", "ВТБ"] )
Out[30]:
<matplotlib.legend.Legend at 0x7ff74f1bb358>
In [31]:
gaz = txt2data( 'GAZP_180101_190110.txt' )
gaz_cl = np.array( [ day[-2] for day in gaz ] )

Как во всех этих элементах не запутаться? Есть такая вещь как словарь.

Словарь -- ещё одна сущность важня для питона

In [32]:
country = dict()
In [33]:
country['Россия'] = 'Москва'
country['Франция'] = 'Париж'
country['Италия'] = 'Рим'
In [34]:
country['Франция']
Out[34]:
'Париж'
In [35]:
country['Германия']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-35-006eed5c9558> in <module>
----> 1 country['Германия']

KeyError: 'Германия'

по аналогии с этим...

In [36]:
ticket = dict()
In [37]:
ticket['MTLR'] = mtl_cl
ticket['VTBR'] = vtb_cl
ticket['GAZP'] = gaz_cl
In [38]:
ticket.keys() # Можно узнать какие ключи есть.
Out[38]:
dict_keys(['MTLR', 'VTBR', 'GAZP'])
In [39]:
lab = []
for k, t in ticket.items():#Итерация tuple ами.
    plt.plot( t/t[0] )
    lab.append( k )
plt.ylabel( 'Процентое изменение')
plt.xlabel( 'День начиная с 2018 г')
plt.legend( lab )
Out[39]:
<matplotlib.legend.Legend at 0x7ff74f11c9b0>

Упражнение. Как сделать так чтобы подписи в легенде были не сокращенные названия, а полноценные имена (как в предыдущем графике).

Сложный ключ

Наборы могут выступать в качестве ключа/индекса у словаря.

In [40]:
works = dict()
In [ ]:
....
In [41]:
#plt.bar( [1,3,5],[10,5,8])

Библиотека Pandas

In [42]:
import pandas as pd
# import seaborn
%config InlineBackend.figure_format = 'svg'
In [43]:
flat_df = pd.read_csv('berkeley_case.csv', sep = ';') # Считываем данные из файла.
In [44]:
flat_df.values
Out[44]:
array([['A', 'men', 'applied', 825],
       ['A', 'men', 'accepted', 512],
       ['A', 'women', 'applied', 108],
       ['A', 'women', 'accepted', 89],
       ['B', 'men', 'applied', 560],
       ['B', 'men', 'accepted', 353],
       ['B', 'women', 'applied', 25],
       ['B', 'women', 'accepted', 17],
       ['C', 'men', 'applied', 325],
       ['C', 'men', 'accepted', 120],
       ['C', 'women', 'applied', 593],
       ['C', 'women', 'accepted', 202],
       ['D', 'men', 'applied', 417],
       ['D', 'men', 'accepted', 138],
       ['D', 'women', 'applied', 375],
       ['D', 'women', 'accepted', 131],
       ['E', 'men', 'applied', 191],
       ['E', 'men', 'accepted', 53],
       ['E', 'women', 'applied', 393],
       ['E', 'women', 'accepted', 94],
       ['F', 'men', 'applied', 272],
       ['F', 'men', 'accepted', 16],
       ['F', 'women', 'applied', 341],
       ['F', 'women', 'accepted', 24]], dtype=object)
In [45]:
flat_df # Выводим считанную таблицу. Она выглядит достаточно опрятно и эффектно.
Out[45]:
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 [46]:
flat_df.columns # Выводим название колонок.
Out[46]:
Index(['faculty', 'gender', 'param', 'number'], dtype='object')
In [47]:
# Переименовываем на русский лад.
flat_df.columns = ['Факультет', 'Пол', 'Параметр', 'Количество']
In [48]:
flat_df
Out[48]:
Факультет Пол Параметр Количество
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 [49]:
# Выполним быструю агрегацию по полу.
total_stats = pd.pivot_table(flat_df, aggfunc = sum, index = 'Пол', columns = 'Параметр', values = 'Количество')
total_stats
Out[49]:
Параметр accepted applied
Пол
men 1192 2590
women 557 1835
In [50]:
#total_stats[:]['men']
In [51]:
total_stats.accepted/total_stats.applied # В процентах.
Out[51]:
Пол
men      0.460232
women    0.303542
dtype: float64
In [52]:
total_stats['Процент поступ'] = total_stats.accepted/total_stats.applied
total_stats
Out[52]:
Параметр accepted applied Процент поступ
Пол
men 1192 2590 0.460232
women 557 1835 0.303542
In [53]:
100*total_stats.accepted/total_stats.applied
Out[53]:
Пол
men      46.023166
women    30.354223
dtype: float64
In [54]:
#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[54]:
Параметр accepted applied Процент поступ
Пол
men 1192 2590 46.02
women 557 1835 30.35
In [55]:
df = pd.pivot_table(flat_df, index = 'Факультет', values = 'Количество', columns = ['Пол', 'Параметр'])
df
Out[55]:
Пол 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 [56]:
#df['A']
In [57]:
df['women']
Out[57]:
Параметр accepted applied
Факультет
A 89 108
B 17 25
C 202 593
D 131 375
E 94 393
F 24 341
In [58]:
#df['applied']
In [59]:
df['women', 'applied']
Out[59]:
Факультет
A    108
B     25
C    593
D    375
E    393
F    341
Name: (women, applied), dtype: int64
In [60]:
df[:,'accepted'] # так нельзя
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-60-fb2a81ebb99d> in <module>
----> 1 df[:,'accepted'] # так нельзя

/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/pandas/core/frame.py in __getitem__(self, key)
   2913         if is_single_key:
   2914             if self.columns.nlevels > 1:
-> 2915                 return self._getitem_multilevel(key)
   2916             indexer = self.columns.get_loc(key)
   2917             if is_integer(indexer):

/data/conda/anaconda3/envs/data_analysis/lib/python3.6/site-packages/pandas/core/frame.py in _getitem_multilevel(self, key)
   2959 
   2960     def _getitem_multilevel(self, key):
-> 2961         loc = self.columns.get_loc(key)
   2962         if isinstance(loc, (slice, Series, np.ndarray, Index)):
   2963             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)
   2404 
   2405         if keylen == self.nlevels and self.is_unique:
-> 2406             return self._engine.get_loc(key)
   2407 
   2408         # -- partial selection or non-unique index

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

TypeError: '(slice(None, None, None), 'accepted')' is an invalid key
In [61]:
idx = pd.IndexSlice
idx[:, 'accepted']
Out[61]:
(slice(None, None, None), 'accepted')
In [62]:
#df[idx[:, 'accepted']]
In [63]:
idx[:]
Out[63]:
slice(None, None, None)
In [64]:
df.loc[idx[:]] # Никаких изменений.
Out[64]:
Пол 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 [65]:
#df
In [66]:
#df_AA=df.reorder_levels(['Пол', 'Параметр'])
#df_AA
In [67]:
df_A=df.loc[idx['A']]
df_A
Out[67]:
Пол    Параметр
men    accepted    512
       applied     825
women  accepted     89
       applied     108
Name: A, dtype: int64
In [68]:
df_A[ idx['women'] ]
Out[68]:
Параметр
accepted     89
applied     108
Name: A, dtype: int64
In [69]:
df_A[ idx[:, 'accepted'] ]
Out[69]:
Пол
men      512
women     89
Name: A, dtype: int64
In [70]:
#df.loc[idx[:], idx[:, 'accepted']]
df.loc[idx['A'], idx[:, 'accepted']]
Out[70]:
Пол    Параметр
men    accepted    512
women  accepted     89
Name: A, dtype: int64
In [71]:
df['men']
Out[71]:
Параметр accepted applied
Факультет
A 512 825
B 353 560
C 120 325
D 138 417
E 53 191
F 16 272
In [72]:
df_total = df['men'] + df['women']
df_total
Out[72]:
Параметр accepted applied
Факультет
A 601 933
B 370 585
C 322 918
D 269 792
E 147 584
F 40 613
In [73]:
df_totalT = df_total.T # Транпонируем таблицу.
df_totalT
Out[73]:
Факультет A B C D E F
Параметр
accepted 601 370 322 269 147 40
applied 933 585 918 792 584 613
In [74]:
df_totalT['пол'] = 'всего'
df_totalT
Out[74]:
Факультет A B C D E F пол
Параметр
accepted 601 370 322 269 147 40 всего
applied 933 585 918 792 584 613 всего
In [75]:
df_totalT.set_index('пол', append = True, inplace = True)
df_totalT
Out[75]:
Факультет A B C D E F
Параметр пол
accepted всего 601 370 322 269 147 40
applied всего 933 585 918 792 584 613
In [76]:
df_totalT = df_totalT.reorder_levels(['пол', 'Параметр'])
df_totalT
Out[76]:
Факультет A B C D E F
пол Параметр
всего accepted 601 370 322 269 147 40
applied 933 585 918 792 584 613
In [77]:
df_total = df_totalT.T
df_total
Out[77]:
пол всего
Параметр accepted applied
Факультет
A 601 933
B 370 585
C 322 918
D 269 792
E 147 584
F 40 613
In [78]:
df = pd.concat([df, df_total], axis = 1)
df
Out[78]:
Пол 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 [79]:
df_inv = df.reorder_levels(['Параметр', 'Пол'] )
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-79-db1aa63e28dd> 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)
   5054         if not isinstance(self._get_axis(axis),
   5055                           MultiIndex):  # pragma: no cover
-> 5056             raise TypeError('Can only reorder levels on a hierarchical axis.')
   5057 
   5058         result = self.copy()

TypeError: Can only reorder levels on a hierarchical axis.
In [80]:
df_inv = df.reorder_levels(['Параметр', 'Пол'], axis = 1)
df_inv
Out[80]:
Параметр 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 [81]:
df_inv = df_inv.sort_index(level = 0, axis = 1)
df_inv
Out[81]:
Параметр 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 [82]:
100*df_inv.accepted/df_inv.applied # Вычисляем итоговые проценты на факультетах в зависимости от пола.
# Итоговые проценту получили, но их нужно округлить. Как раньше не получится. Объект сложный. Итерация не годится.
Out[82]:
Пол 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 [83]:
# Можно ввести округление.
# Для этого понядобятся функции
def round_2digits( x ): # Данная функция от одного аргумента. Как синус, квадратный корень и тому подобное.
    return round(x, 2) # Вызываем функцию из самого Python, число 2 указывает на то что округляем две цифры после запятой.
In [84]:
round_2digits( 2.123456 )
Out[84]:
2.12
In [85]:
round_2digits( 2.1 )
Out[85]:
2.1
In [86]:
round_2digits( 2.12567 ) # Это именно округление, а не отрезание чисел после второго разряда после запятой.
Out[86]:
2.13
In [87]:
admitted_perc = 100*df_inv.accepted/df_inv.applied
admitted_perc = round( admitted_perc, 2)#round_2digits( admitted_perc )
admitted_perc
Out[87]:
Пол 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 [88]:
admitted_perc[['всего', 'men', 'women']].plot(kind = 'bar', title = 'Процент посупивших в Беркли')
Out[88]:
<matplotlib.axes._subplots.AxesSubplot at 0x7ff74556f400>
In [89]:
df_applied = flat_df[ flat_df.Параметр == 'applied' ]
df_applied
Out[89]:
Факультет Пол Параметр Количество
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 [90]:
gndr_fclt_appl = pd.pivot_table( df_applied, index = 'Факультет', values = 'Количество', columns = 'Пол')
gndr_fclt_appl
Out[90]:
Пол men women
Факультет
A 825 108
B 560 25
C 325 593
D 417 375
E 191 393
F 272 341
In [91]:
gndr_fclt_appl.sum()
Out[91]:
Пол
men      2590
women    1835
dtype: int64
In [92]:
gndr_fclt_appl = 100 * gndr_fclt_appl / gndr_fclt_appl.sum()
gndr_fclt_appl
Out[92]:
Пол 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 [93]:
gndr_fclt_appl = round_2digits( gndr_fclt_appl )
gndr_fclt_appl
Out[93]:
Пол 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 [94]:
faculty_stats = admitted_perc[['всего']].join( gndr_fclt_appl )
faculty_stats
Out[94]:
Пол всего men women
Факультет
A 64.42 31.85 5.89
B 63.25 21.62 1.36
C 35.08 12.55 32.32
D 33.96 16.10 20.44
E 25.17 7.37 21.42
F 6.53 10.50 18.58
In [95]:
faculty_stats.columns = ['Процент посупивших', 'доля мужчин', 'доля женщин']
faculty_stats.plot(kind = 'bar', title = 'Статистика факультетов Беркли')
Out[95]:
<matplotlib.axes._subplots.AxesSubplot at 0x7ff745516dd8>
In [ ]:
 
In [ ]: