Математический практикум по Питону.
Вводятся базовые элементы питона (Python версии 3.xx) на базе ключевых библиотек относящихся к анализу данных: numpy и matplotlib.
Это предварительная версия! Любые замечания приветсвуются.
Списки хороши, но над ними как мы помним нельзя выполнять нужные нами арфиметических операций. В частности, списки нельзя складывать. Точнее эта операция будет иметь иное толкование:
import numpy as np
# Один из способов, как вычислить значение от каждого элемента списка, было показано выше.
# Показанный способ (отображение) был явный. Он был реализован на чистом питоне без вспомогательных средств.
# Но можно вычислить функцию от каждого элемента списка инче, использую вспомогательные (библиотечные) функции.
a = np.sin( [1.1, 2.2, -4.4] )
a
# Полученый объект уже не будет списоком (см. слово array). Он преврятится в массив, т.е. array.
Тип array с точки зрения обычных языков фактически является массивом, т.е. объектом у которого тип элементов постоянен. По суте идексируемая переменная (имеет тот же тип). Такой тип соответсвует математическим понятиям вектора, матрице. Переменая с индексом. В следующей заметке перейдем к описанию массивов.
[1.1, 2.2, -4.4] + [2.5, 1.9, 5.1] # Они совместяться в один список (контакенация)
Создание
Для содания списоков чисел, которые можно складывать поэлементно, нужны именно вектора/массивы.
# Они из списка создаются так:
np.array( [1.1, 2.2, -4.4])
Размер массива всегда можно запросить у самого объекта.
l = np.array( [1.1, 2.2, -4.4]) # Сохраняем массив в переменную l.
l.shape # Запрашиваем у переменной её размер. Он хранится в переменной shape.
bb = [55, 22, 33] # Создаем список.
bb.shape # Размер имеют объекты array. Другие не обязаны давать значение на это поле.
Замечу, что тип размер/размерность не указывает. Последнее связано с тем, что все элементы массива должны иметь одтн и тот же тип.
type( l ) # Запросим и тип.
l.dtype # Тип элементов массива.
Функцией
l = range(3, 10) # Создаем список с 3 до 10 (не включительно) с шагом 1.
np.array( l ) # Создаем по списку массив.
np.array(range(1,15.4,2))
Их также можно генерировать готовой функцией.
np.arange(2, 20, 3) # В данному слачае, это последовательность
# с первого числа (2) по последнее (20!!!), но не включая его, с шагом (3).
np.arange(2, 20.5, 1)
Существуют и другие вспомогательные функции для формирования массивов. Например функция равномерного разбиения отрезка.
l=np.linspace(4, 25, 5) # А эта функция создает числа с первого (4) по последнее (включительно) (25) в количестве (5).
Сложные индексы
l
l[2], type(l[2]) # Считываем значение и тип элемента массива.
l[400] # Как и со списками нельзя обарщатся к эелементу, которого нет в массиве.
l[-1], l[1:-1:2] # Такая же история и сложными индексами.
Базовые операции
Операции над массивами по-элементны.
# Такие объекты, массивы, можно складывть по элементно.
np.array( [1.1, 2.2, -4.4]) + np.array( [2.5, -1.9, 5.1])
# Размер массивов должен совпадать. Иначе система выдаст соответствующую ошибку.
np.array( [1.1, 2.2, -4.4]) + np.array( [2.5, -1.9, 5.1, 7.1])
При выводе ошибки система указала, что один массив имеет размер 3, а другой -- 4. Даже была указана их одномерность.
Упр. По массиву значений построй массив отношения текущего элемента к последующему (результирующий массив будет на один элемент меньше).
a0 = np.array([-1, 2, 3])
a0
a1 = np.array([3, 5, -2])
a1
a0 * a1 # Поэлементное умножение массивов, а не скалярное.
# Ну или в явном виде, т.е. без создания дополнительных промежуточных переменных.
np.array([-2, 7]) * np.array([ -2 , 5])
Тип элементов
# В отличии от списка, все элементы массива должны иметь один и тот же тип. Если объявить так:
q = np.array( [1.0, "aa"] )
q
type(q[0]), type(q[1]) # то число тоже станет строчкой.
np.array( [1.0, "aa"] ) + np.array( [2.0, "bb"] ) # Сложить их как раньше не получится.
Вычисления над массивами
В библиотеке есть набор функций для обработки массивов. Так, помимо поэлеметных функий наподобии np.sin
l = np.array(l)
l
3*l
np.sin( l )
имеются и редуцирующие к числу. Например,
np.sum( l ) # Сумма элементов
np.mean( l ), np.std( l ) # Срежнее значение и среднеквадратичное откланение.
np.median( l ), np.max( l ), np.min( l ) # Медиана, максимальное и мимимальное значение.
Упр. По массиву посчитай скользящее среднее, т.е. среднее окна из например 10 элементов. Окно скользит по массиву.
import matplotlib.pyplot as plt
# Сгенерируем именно равномерный на набор точек.
x = np.linspace(-5, 9.5, 29)
y = x*x - 2*x + 3
plt.plot( x, y, 'g.-')
plt.plot( x, np.sin(x), 'g.-')
plt.plot(x, x*x/100, 'b*--')
plt.xlabel( "Ось абсцисс" )
plt.ylabel( "Ось ординат")
#plt.axis('equal') # Чтобы оси были одного масштаба.
plt.legend( ['sin', 'x^2'] )
Произведение векторов
# Скаляроне произведение.
a = np.array([ 1, 2, 3])
b = np.array([ -1, 0, 2])
np.inner( a, b) # Скяларное произведение
a * b # Поэлементное произведение векторов.
np.sum( a * b ) # Но можно и так.
np.dot(a, b)
# Сопряженное скалярное произведение.
a = np.array([ 1 + 2j, 3 + 4j])
b = np.array([ 1 - 2j, 3 + 4j])
np.vdot(a, b)
c = a * b
c
np.dot( a.conjugate(), b )
Многомерные массивы
# Расширение данной идеи на двумерные массивы.
a0 = np.array([[-1], [2]]) # На вторую размерность указывают вложенные скобки.
a0 # Отмечу, если раньше a0 был массивом, то теперь стал матрицей, т.е. поменялась размерность.
b0 = np.array([ [3] , [5]])
b0
# Размеры у матрицы a0 были:
a0.shape # т.е. две строки, в каждой из которых по одному элементу.
a0 = np.array([[-1, 2]]) # Можно сделать матрицу наоборот (транспонирование).
a0
b0.shape, a0.shape
np.dot(a0,b0)
a0.shape # Одна строка, содержащая два элемента.
# Матрицы можно формировать из других матриц.
a0 = np.array( [ 3, -6 ] )
a1 = np.array( [ -1, 2 ] )
a = np.array( [ a0, a1] )
a
# Размеры должны конечно должны соответствовать.
a0 = np.array( [ 3, -6, 5 ] )
a1 = np.array( [ -1, 2 ] )
aa = np.array( [ a0, a1] )
aa # Иначе это бует массив объектов. Аккуратно сравни и увидь разницу!
np.zeros((4,6))
# Поэлементное умножение матриц (элемент массива является одноэлементным массивом).
a0 * b0 # Не путать с матричным умножением!
a0
np.array([[[1],[2]],[[3],[4]]])*a0
a0.shape,b0.shape
#b0=np.array([1,2,3])
#a0*b0
# Такие матрицы тоже можно умножать по-элементно.
np.array([[-1, 2]]) * np.array([ [3 , 5]])
Как было показано выше, массивы можно создавать по спискам, т.е. сначала делается список, а потом массив по нему.
v = np.array([-2,1])
v
a.dot(v) # Обычное произведение вектора на матрицу.
np.dot( a[0], v), np.dot( a[1], v)
Линейные уравнения
# Зададим матрицу в переменной a.
a = np.array( [ [2, -1], [1, 3] ] ) # 2x -y = 9
b = np.array( [ 9, 8] ) # 1x +3y = 8
a, b # Вектор значений в b.
# Решаем систему уравнений.
x = np.linalg.solve(a, b)
x
np.dot(a, x) # Проверяем правильность решения.
# Проверяем на равенство с учетом машинного эпсилон,
np.allclose(np.dot(a, x), b) # т.е. числа могуть чуть отличаться.
# Например, должны были получить 0,
5 - np.sqrt( 5 ) ** 2 # но жизнь иначе распорядилась.
a.shape, x.shape
np.dot(a, x.reshape(2,1) )
np.allclose( 5 , np.sqrt( 5 ) ** 2 )
# Проверим, что все-таки когда нет равенства,
np.allclose( 5 - 0.1 , np.sqrt( 5 ) ** 2 ) # будет лож.
np.allclose( 5 , (5**(0.5)) ** 2 )
# Можно вычислить и обратную матрицу в явном виде
# a = np.array( [ [2, -1], [1, 3] ] )
ainv = np.linalg.inv( a )
ainv
Решаем систему. Для этого используем произведение вектора на матрицу.
# Умножаем обратную матрицу на вектор значений.
ainv.dot( b ) # Получили тот же ответ, что и раньше.
# Можно умножить и на матрицу. Тогда произведение матрицы на её обратное должно дать единичную.
np.allclose( np.dot(a, ainv), np.eye(2) )
Линейная регрессия
x = np.linspace(-5, 9.5, 9)
y = 0.5*x - 3
plt.plot(x, y)
plt.axis('equal')
np.random.randn() # Гауссова случайная величина. Нормальное распределение.
# Добавим шума к данным.
yy = y + np.random.randn()
plt.plot(x, yy) # Нарисуем график.
plt.axis('equal') # Оси будут иметь один масштаб.
# Чего-то не видна шума.
yy[3]-y[3], yy[1]-y[1]
# Видно, что ко всем зачениям y была добавлена одна и та же случайная величина.
# Теперь создадим массив из случайных величин.
yy = y + np.random.randn( y.shape[0] ) # В скобках размер.
plt.plot(x, yy)
plt.axis('equal')
yy = y + np.random.randn( y.shape[0] )/3 # По-меньше шум.
plt.plot(x, yy, '-b')
plt.plot(x, yy, '*r')
plt.axis('equal')
# Создаем общую матрицу. Присваеваем её переменной A.
A = np.array( [np.ones_like(x), x ] )
A
A = A.T # Транспонирование.
A
# Матрица прямоугольная.
A.shape # Значит обратной не существует.
# Вычисляем псевдо обратную матрицу от A. Метод решения обычных систем не годится.
AA = np.linalg.pinv(A)
AA.shape
AA.dot(yy) # Домножаем на значения
# и получаем коэффициенты исходного уравнения.
Упр. Нарисуй график данной прямой. Лучше поверх исходных данных.