pyth_06_list_process
Заметка 6. Обработка списков и построение графиков.
курса Математический практикум по Питону.
Шокуров Антон В.
shokurov.anton.v@yandex.ru
http://машинноезрение.рф
Версия 0.12

Аннотация

Вводятся базовые элементы питона (Python версии 3.xx): списки и наборы (картежи). Вводятся базовые элементы питона (Python версии 3.xx) на базе ключевых библиотек относящихся к анализу данных: построение графиков/гистограмм и численой статистике. Последнее, в частности, используется для ввода ключевых понятий из анализа данных: выборка, плотность распределения и подгонка модели под данные. Ключевые слова: map, filter, reduce, lambda, matplotlib, pyplot, xlim, xticks, csv и plot.

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

Отрисовка цены эмитента

Обработка данных из списка

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

  • map -- выполнение определенного действия для всех элементов списка.
  • reduce -- интегральное значение для списка.
  • filter -- сокращение количества элементов в списке.

map

In [1]:
l = [4, 25, 9, 16]
In [2]:
import numpy as np
In [3]:
# Применим функцию np.sqrt для всех элиментов списка l.
map( np.sqrt, l) # Первый аргумент функция, второй -- список.
Out[3]:
<map at 0x7f0b6c1028d0>
In [4]:
q = map( np.sqrt, l)
q # Посмотрим содержимое.
Out[4]:
<map at 0x7f0b617839b0>
In [5]:
# Проверяем тип.
type( q ) # Map есть map.
Out[5]:
map

Map это объект действие (генератор). Нужно чтобы числа им были сгенерированы.

In [6]:
# Для этого создадим объект список с его помощью.
list( q )
Out[6]:
[2.0, 5.0, 3.0, 4.0]
In [7]:
l
Out[7]:
[4, 25, 9, 16]
In [8]:
q = map( np.sqrt, l) # Создали генератор по списку l.
l.append(81) # Добавили в список l элемент.
list(q) # Он был учтен,
# т.е. генератор актуальный список.
Out[8]:
[2.0, 5.0, 3.0, 4.0, 9.0]
In [9]:
# Приведем другой пример.
ll = [np.pi/2, np.pi, np.pi/4]
list( map( np.sin, ll ) ) # Взяли синус от каждого элемента
Out[9]:
[1.0, 1.2246467991473532e-16, 0.7071067811865475]

Преобразование

Как задать собственное преобразование. Для простых преобразований в питоне есть лямбда функции.

Данная конструкция задается следующим образом:

lambdaсписок аргументов/параметров : действие/значение.

  • список аргементов это список, заданный через запятую, значений переменных, например: x или x, y.
  • действие это выражение которое вычисляется для значений аргумента.
In [10]:
lambda x: x*2 # Умножаем число на 2.
lambda x, y: x**y # Возводим первое чило в степень второго.
# Тип имеет функции.
Out[10]:
<function __main__.<lambda>(x, y)>
In [11]:
# Для их применения нужно сохранить в переменну.
dbl = lambda x: x*2
mypow = lambda x, y: x**y
dbl, mypow
Out[11]:
(<function __main__.<lambda>(x)>, <function __main__.<lambda>(x, y)>)
In [12]:
type( dbl ), type( mypow )
Out[12]:
(function, function)
In [13]:
# Применим преоьразование к числам.
dbl( 2), dbl( 2.5 ), mypow( 2, 3 )
Out[13]:
(4, 5.0, 8)
In [14]:
l # Вспомним содержимое списка.
Out[14]:
[4, 25, 9, 16, 81]
In [15]:
# Применим к каждому элемента списка преобразование,
q = map( dbl, l) # умножение на два.
q 
Out[15]:
<map at 0x7f0b6179d438>
In [16]:
# Построим список по q.
list( q )
Out[16]:
[8, 50, 18, 32, 162]
In [17]:
# Можно в явном виде.
q = map( lambda x: x*2, l) 
q 
Out[17]:
<map at 0x7f0b6179d5c0>
In [18]:
list( q )
Out[18]:
[8, 50, 18, 32, 162]
In [19]:
# Можно так. Выводятся элементы.
list( map( print, l) )
# Список портится.
4
25
9
16
81
Out[19]:
[None, None, None, None, None]

Частично заданые аргументы

In [20]:
# Все аргументы нужно указывать.
list( map( mypow(,0.5), l) )
# Будет ошибка.
  File "<ipython-input-20-284c3d4ddcd9>", line 2
    list( map( mypow(,0.5), l) )
                     ^
SyntaxError: invalid syntax
In [21]:
# Нужно так: labmda от labmda.
list( map( lambda x: mypow(x, 0.5), l) )
Out[21]:
[2.0, 5.0, 3.0, 4.0, 9.0]

filter

Используется для фильтрации (как сито) элементов списка. После обработки остаются не все элементы списка. Остаются только те для которые дают истину (для предиката).

In [22]:
# Возвращает истину тогда и только тогдв число нечетное.
odd = lambda x: x%2 == 1
In [23]:
odd(1), odd(4), odd(5.1)
Out[23]:
(True, False, False)
In [24]:
list(map(odd,  [1,4,6,5,8,9,11]))
Out[24]:
[True, False, False, True, False, True, True]
In [25]:
q = filter( odd, [1,4,6,5,8,9,11])
q # Тоже возвращает не сам список, а генератор.
Out[25]:
<filter at 0x7f0b6179db38>
In [26]:
# Формируем числа используя генератор.
list( q ) # От списка останется только те элементы
# н которых была истина: нечетные.
Out[26]:
[1, 5, 9, 11]
In [27]:
stud = []
In [28]:
stud.append( ['муж', 'Максим'] )
stud.append( ['жен', 'Лена'] )
stud.append( ['жен', 'Катя'] )
stud.append( ['муж', 'Дима'] )
stud.append( ['муж', 'Саша'] )
stud.append( ['жен', 'Яна'] )
In [29]:
stud
Out[29]:
[['муж', 'Максим'],
 ['жен', 'Лена'],
 ['жен', 'Катя'],
 ['муж', 'Дима'],
 ['муж', 'Саша'],
 ['жен', 'Яна']]
In [30]:
stud[2]
Out[30]:
['жен', 'Катя']
In [31]:
print( 'идекс 0:', stud[2][0], 'и индекс 1:', stud[2][1] )
идекс 0: жен и индекс 1: Катя
In [32]:
# Тогда можно проверить что на нудевом индексе значение 'муж'
male = lambda x: x[0] == 'муж'
In [33]:
print(stud[2], 'мужчина? Ответ: ', male( stud[2] ) )
print(stud[3], 'мужчина? Ответ: ', male( stud[3] ) )
['жен', 'Катя'] мужчина? Ответ:  False
['муж', 'Дима'] мужчина? Ответ:  True
In [34]:
men = list( filter(male, stud) )
men
Out[34]:
[['муж', 'Максим'], ['муж', 'Дима'], ['муж', 'Саша']]
In [35]:
men[1] # Такая же пока структура.
Out[35]:
['муж', 'Дима']
In [36]:
list( map( lambda x: x[1], men) ) # Извлекаем имя.
Out[36]:
['Максим', 'Дима', 'Саша']

reduce

Другим действием является редукция,т.е. сведение списка к некому значению.

In [37]:
from functools import reduce
In [38]:
mac = lambda acc, x: acc + 2*x # acc аккумулирует значение. Правило ассоциативно.
In [39]:
reduce( mac, [-1,2,3]) # -1 + 2*(2) + 2*(3) есть 9. Ясно что ассоциативно.
Out[39]:
9
In [40]:
mac2 = lambda acc, x: 2*acc + x # неассоциатовно
In [41]:
reduce( mac2, [ -2, 2, 3] ) # 2*(2*(-2) + 2) + 3  есть 3.
Out[41]:
-1
In [42]:
2*(2*(-2) + 2) + 3
Out[42]:
-1
In [43]:
sum( [-1, 2, 3] )
Out[43]:
4

Отрисовка графиков

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

In [44]:
# С range можно создавать списки чисел, где аргументы: начадо, конец и шаг.
x_10 = list( range(0, 10, 1) ) # От 0, до 10 с шагом 1.
x_10 # Разрешены только целые числа.
Out[44]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [45]:
len( x_10 )
Out[45]:
10
In [46]:
# от, до и шаг должны быть целыми.
x_10_5 = list( range(0, 10.5, 1) )
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-46-d26085321d3d> in <module>
      1 # от, до и шаг должны быть целыми.
----> 2 x_10_5 = list( range(0, 10.5, 1) )

TypeError: 'float' object cannot be interpreted as an integer
In [47]:
# Выполним преобразование списка чисел 0 до 9
list( map( lambda x: x/2 - 2, x_10 ) )
# Получим список чисел с шагом в 0.5 от -2 до 2.5.
Out[47]:
[-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5]
In [48]:
# Теперь возьмем список побольше
x_30 = list( range(0, 30, 1) )
len( x_30 )
Out[48]:
30
In [49]:
#Сгенерируем именно равномерный на набор точек.
x = list( map( lambda x: x/2 - 5, x_30 ) )
# список от -5 до 9.5 с шагом в 0.5.
In [50]:
# Пишем выражение, которое ествественно будет вычислено в каждой точке,
f = lambda x:x*x - 2*x +3 # т.е. по-элементно для списка x.
y = list( map( f, x) )
In [51]:
len(x), len(y) # Видим что длины совпадают.
Out[51]:
(30, 30)
In [52]:
# Значение в точке -2 равно 11.
x[6], y[6] # (-2)*(-2) - 2(-2) + 3 = 4 + 4 + 3 = 11
Out[52]:
(-2.0, 11.0)

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

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

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

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

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

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

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

In [56]:
# plot строит график по-точечно.
plt.plot( x, y, 'g.-') # g -- green (зеленая), . и - задают стиль точки и прямой.
Out[56]:
[<matplotlib.lines.Line2D at 0x7f0b60225d30>]
In [57]:
# b - синий, * -- звездочка в каждой точке. -- соединение пунктиром.
plt.plot( x, y, 'b*--')
Out[57]:
[<matplotlib.lines.Line2D at 0x7f0b6019b1d0>]
In [58]:
# Цвета есть такие:
# '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 [59]:
# Можно рисовать несколько графиков на одном чертеже:
y2 =  list( map( lambda x: 10*np.sin( x ) + 25, x) )
plt.plot( x, y, 'g.-', x, y2, 'b*--')
Out[59]:
[<matplotlib.lines.Line2D at 0x7f0b60103860>,
 <matplotlib.lines.Line2D at 0x7f0b601039b0>]
In [60]:
# Ествественно, что значения можно считать "налету":
plt.plot( x, list( map( lambda x: np.sin(x), x) ), 'g.-')
plt.plot(x, list( map( lambda x: x*x/100, x) ), 'b*--')
plt.legend( ['sin', 'x^2'] ) # Добавим и легенду.
Out[60]:
<matplotlib.legend.Legend at 0x7f0b600f5630>

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

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

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

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

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

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

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

In [65]:
import csv # Для считывания csv файлов.
In [66]:
data = []
with open('MTLR_190101_190110.txt') as f:
    data_rows = csv.reader( f )
    data = list( data_rows )
data
Out[66]:
[['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 [67]:
len(data)
Out[67]:
5
In [68]:
data[1]
Out[68]:
['MTLR;D;20190104;000000;73.3100000;74.8900000;73.2100000;73.6800000;462658']

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

In [69]:
data = []
with open('MTLR_190101_190110.txt') as f:
    data_rows = csv.reader( f, delimiter = ';' )
    data = list( data_rows )
data
Out[69]:
[['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 [70]:
int( 5 < 7 )
Out[70]:
1

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

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

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

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

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

In [73]:
# Извлечение цены закрытия
get_close = lambda x: x[-2]
In [74]:
data[2]
Out[74]:
['MTLR',
 'D',
 '20190108',
 '000000',
 '74.4700000',
 '74.8900000',
 '73.5000000',
 '73.7400000',
 '406304']
In [75]:
get_close(data[2])
# Будет сторокой.
Out[75]:
'73.7400000'
In [76]:
fromStr = lambda x: float(x)
fromStr( get_close(data[2]) )
Out[76]:
73.74
In [77]:
close_str = list( map( get_close, data) )
close_str
Out[77]:
['73.1200000', '73.6800000', '73.7400000', '73.9300000', '74.4000000']
In [78]:
close = list( map( fromStr, close_str) )
close
Out[78]:
[73.12, 73.68, 73.74, 73.93, 74.4]
In [79]:
# В два прохода.
close_str = list( map( lambda x: x[-2], data) )
close = list( map( lambda x: float(x), close_str) )
close
Out[79]:
[73.12, 73.68, 73.74, 73.93, 74.4]
In [80]:
# Или в один, сразу.
close = list( map( lambda x: float(x[-2]), data) )
close
Out[80]:
[73.12, 73.68, 73.74, 73.93, 74.4]

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

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

In [81]:
data = []
with open( 'MTLR_180101_190110.txt' ) as f:
    data_rows = csv.reader( f, delimiter = ';' )
    data = list( data_rows )
close = list( map( lambda x: float(x[-2]), data) )
#close Не будем его выводить. Там много данных.
In [82]:
len( close ) # А именно столько.
Out[82]:
259
In [83]:
close[10:20] # Посмотрим на кусочек данных.
Out[83]:
[149.9, 147.0, 153.0, 154.3, 152.95, 150.0, 149.2, 145.2, 145.0, 146.05]

Графики

In [84]:
from matplotlib import pyplot as plt
# %matplotlib inline # Раньше это требовалось. Если что попробуй.
In [85]:
plt.plot( close ) # Рисуем график.
plt.ylabel( 'Цена рубли') # Название для оси y (оси ординат).
plt.xlabel( 'День начиная с 2018 г') # Название для оси x (оси абсцисс).
Out[85]:
Text(0.5, 0, 'День начиная с 2018 г')

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

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

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

In [ ]: