import numpy as np

"""
Explanation: NumPy: векторы и операции над ними

В этом ноутбуке нам понадобятся библиотека NumPy. Для удобства импортируем ее под более коротким именем:
End of explanation
"""


a = np.array([1, 2, 3, 4])
print 'Вектор:\n', a

b = np.array([1, 2, 3, 4, 5], dtype=float)
print 'Вещественный вектор:\n', b

c = np.array([True, False, True], dtype=bool)
print 'Булевский вектор:\n', c

"""
Explanation: 1. Создание векторов
Самый простой способ создать вектор в NumPy — задать его явно с помощью numpy.array(list, dtype=None, ...).
Параметр list задает итерируемый объект, из которого можно создать вектор. Например, в качестве этого параметра можно задать список чисел. Параметр dtype задает тип значений вектора, например, float — для вещественных значений и int — для целочисленных. Если этот параметр не задан, то тип данных будет определен из типа элементов первого аргумента.
End of explanation
"""


print 'Тип булевского вектора:\n', c.dtype

"""
Explanation: Тип значений вектора можно узнать с помощью numpy.ndarray.dtype:
End of explanation
"""


d = np.arange(start=10, stop=20, step=2) # последнее значение не включается!
print 'Вектор чисел от 10 до 20 с шагом 2:\n', d

f = np.arange(start=0, stop=1, step=0.3, dtype=float)
print 'Вещественный вектор чисел от 0 до 1 с шагом 0.3:\n', f

"""
Explanation: Другим способом задания вектора является функция numpy.arange(([start, ]stop, [step, ]...), которая задает последовательность чисел заданного типа из промежутка [start, stop) через шаг step:
End of explanation
"""


print c.ndim # количество размерностей

print c.shape # shape фактически задает длину вектора 

"""
Explanation: По сути вектор в NumPy является одномерным массивом, что соответствует интуитивному определению вектора:
End of explanation
"""


a = np.array([1, 2, 3])
b = np.array([6, 5, 4])
k = 2

print 'Вектор a:', a
print 'Вектор b:', b
print 'Число k:', k

print 'Сумма a и b:\n', a + b

print 'Разность a и b:\n', a - b

print 'Покоординатное умножение a и b:\n', a * b 

print 'Умножение вектора на число (осуществляется покоординатно):\n', k * a 

"""
Explanation: Обратите внимание: вектор _и одномерный массив тождественные понятия в NumPy. Помимо этого, также существуют понятия _вектор-столбец и вектор-строка, которые, несмотря на то что математически задают один и тот же объект, являются двумерными массивами и имеют другое значение поля shape (в этом случае поле состоит из двух чисел, одно из которых равно единице). Эти тонкости будут рассмотрены в следующем уроке.
Более подробно о том, как создавать векторы в NumPy, 
см. документацию.
2. Операции над векторами
Векторы в NumPy можно складывать, вычитать, умножать на число и умножать на другой вектор (покоординатно):
End of explanation
"""


from numpy.linalg import norm

"""
Explanation: 3. Нормы векторов
Вспомним некоторые нормы, которые можно ввести в пространстве $\mathbb{R}^{n}$, и рассмотрим, с помощью каких библиотек и функций их можно вычислять в NumPy.
p-норма
p-норма (норма Гёльдера) для вектора $x = (x_{1}, \dots, x_{n}) \in \mathbb{R}^{n}$ вычисляется по формуле:
$$
\left\Vert x \right\Vert_{p} = \left( \sum_{i=1}^n \left| x_{i} \right|^{p} \right)^{1 / p},~p \geq 1.
$$
В частных случаях при:
* $p = 1$ получаем $\ell_{1}$ норму
* $p = 2$ получаем $\ell_{2}$ норму
Далее нам понабится модуль numpy.linalg, реализующий некоторые приложения линейной алгебры. Для вычисления различных норм мы используем функцию numpy.linalg.norm(x, ord=None, ...), где x — исходный вектор, ord — параметр, определяющий норму (мы рассмотрим два варианта его значений — 1 и 2). Импортируем эту функцию:
End of explanation
"""


a = np.array([1, 2, -3])
print 'Вектор a:', a

print 'L1 норма вектора a:\n', norm(a, ord=1)

"""
Explanation: $\ell_{1}$ норма
$\ell_{1}$ норма 
(также известная как манхэттенское расстояние)
для вектора $x = (x_{1}, \dots, x_{n}) \in \mathbb{R}^{n}$ вычисляется по формуле:
$$
 \left\Vert x \right\Vert_{1} = \sum_{i=1}^n \left| x_{i} \right|.
$$
Ей в функции numpy.linalg.norm(x, ord=None, ...) соответствует параметр ord=1.
End of explanation
"""


a = np.array([1, 2, -3])
print 'Вектор a:', a

print 'L2 норма вектора a:\n', norm(a, ord=2)

"""
Explanation: $\ell_{2}$ норма
$\ell_{2}$ норма (также известная как евклидова норма)
для вектора $x = (x_{1}, \dots, x_{n}) \in \mathbb{R}^{n}$ вычисляется по формуле:
$$
 \left\Vert x \right\Vert_{2} = \sqrt{\sum_{i=1}^n \left( x_{i} \right)^2}.
$$
Ей в функции numpy.linalg.norm(x, ord=None, ...) соответствует параметр ord=2.
End of explanation
"""


a = np.array([1, 2, -3])
b = np.array([-4, 3, 8])
print 'Вектор a:', a
print 'Вектор b:', b

print 'L1 расстояние между векторами a и b:\n', norm(a - b, ord=1)

print 'L2 расстояние между векторами a и b:\n', norm(a - b, ord=2)

"""
Explanation: Более подробно о том, какие еще нормы (в том числе матричные) можно вычислить, см. документацию. 
4. Расстояния между векторами
Для двух векторов $x = (x_{1}, \dots, x_{n}) \in \mathbb{R}^{n}$ и $y = (y_{1}, \dots, y_{n}) \in \mathbb{R}^{n}$ $\ell_{1}$ и $\ell_{2}$ раccтояния вычисляются по следующим формулам соответственно:
$$
 \rho_{1}\left( x, y \right) = \left\Vert x - y \right\Vert_{1} = \sum_{i=1}^n \left| x_{i} - y_{i} \right|
$$
$$
 \rho_{2}\left( x, y \right) = \left\Vert x - y \right\Vert_{2} = 
 \sqrt{\sum_{i=1}^n \left( x_{i} - y_{i} \right)^2}.
$$
End of explanation
"""


from scipy.spatial.distance import cdist

"""
Explanation: Также расстояние между векторами можно посчитать с помощью функции scipy.spatial.distance.cdist(XA, XB, metric='euclidean', p=2, ...) из модуля SciPy, предназначенного для выполнения научных и инженерных расчётов.
End of explanation
"""


a = np.array([6, 3, -5])
b = np.array([-1, 0, 7])
print 'Вектор a:', a
print 'Его размерность:', a.shape
print 'Вектор b:', b
print 'Его размерность:', b.shape

a = a.reshape((1, 3))
b = b.reshape((1, 3))
print 'После применения метода reshape:\n'
print 'Вектор-строка a:', a
print 'Его размерность:', a.shape
print 'Вектор-строка b:', b
print 'Его размерность:', b.shape

print 'Манхэттенское расстояние между a и b (через cdist):', cdist(a, b, metric='cityblock')

"""
Explanation: scipy.spatial.distance.cdist(...) требует, чтобы размерность XA и XB была как минимум двумерная. По этой причине для использования этой функции необходимо преобразовать векторы, которые мы рассматриваем в этом ноутбуке, к вектор-строкам с помощью способов, которые мы рассмотрим ниже. 
Параметры XA, XB — исходные вектор-строки, а metric и p задают метрику расстояния
(более подробно о том, какие метрики можно использовать, см. документацию).
Первый способ из вектора сделать вектор-строку (вектор-столбец) — это использовать метод array.reshape(shape), где параметр shape задает размерность вектора (кортеж чисел).
End of explanation
"""


d = np.array([3, 0, 8, 9, -10])
print 'Вектор d:', d
print 'Его размерность:', d.shape

print 'Вектор d с newaxis --> вектор-строка:\n', d[np.newaxis, :]
print 'Полученная размерность:', d[np.newaxis, :].shape

print 'Вектор d с newaxis --> вектор-столбец:\n', d[:, np.newaxis]
print 'Полученная размерность:', d[:, np.newaxis].shape

"""
Explanation: Заметим, что после применения этого метода размерность полученных вектор-строк будет равна shape. Следующий метод позволяет сделать такое же преобразование, но не изменяет размерность исходного вектора.  
В NumPy к размерностям объектов можно добавлять фиктивные оси с помощью np.newaxis. Для того, чтобы понять, как это сделать, рассмотрим пример:
End of explanation
"""


a = np.array([6, 3, -5])
b = np.array([-1, 0, 7])
print 'Евклидово расстояние между a и b (через cdist):', cdist(a[np.newaxis, :], 
                                                               b[np.newaxis, :], 
                                                               metric='euclidean')

"""
Explanation: Важно, что np.newaxis добавляет к размерности ось, длина которой равна 1 (это и логично, так как количество элементов должно сохраняться). Таким образом, надо вставлять новую ось там, где нужна единица в размерности. 
Теперь посчитаем расстояния с помощью scipy.spatial.distance.cdist(...), используя np.newaxis для преобразования векторов:
End of explanation
"""


a = np.array([0, 5, -1])
b = np.array([-4, 9, 3])
print 'Вектор a:', a
print 'Вектор b:', b

"""
Explanation: Эта функция также позволяет вычислять попарные расстояния между множествами векторов. Например, пусть у нас имеется матрица размера $m_{A} \times n$. Мы можем рассматривать ее как описание некоторых $m_{A}$ наблюдений в $n$-мерном пространстве. Пусть также имеется еще одна аналогичная матрица размера $m_{B} \times n$, где  $m_{B}$ векторов в том же $n$-мерном пространстве. Часто необходимо посчитать попарные расстояния между векторами первого и второго множеств. В этом случае можно пользоваться функцией scipy.spatial.distance.cdist(XA, XB, metric='euclidean', p=2, ...), где в качестве XA, XB необходимо передать две описанные матрицы. Функция возвращает матрицу попарных расстояний размера $m_{A} \times m_{B}$, где элемент матрицы на $[i, j]$-ой позиции равен расстоянию между $i$-тым вектором первого множества и $j$-ым вектором второго множества. 
В данном случае эта функция предподчительнее numpy.linalg.norm(...), так как она вычисляет попарные расстояния быстрее и эффективнее. 
5. Скалярное произведение и угол между векторами
End of explanation
"""


print 'Скалярное произведение a и b (через функцию):', np.dot(a, b)

print 'Скалярное произведение a и b (через метод):', a.dot(b)

"""
Explanation: Скалярное произведение в пространстве $\mathbb{R}^{n}$ для двух векторов $x = (x_{1}, \dots, x_{n})$ и $y = (y_{1}, \dots, y_{n})$ определяется как:
$$
\langle x, y \rangle = \sum_{i=1}^n x_{i} y_{i}.
$$
Скалярное произведение двух векторов можно вычислять с помощью функции numpy.dot(a, b, ...) или метода vec1.dot(vec2), где vec1 и vec2 — исходные векторы. Также эти функции подходят для матричного умножения, о котором речь пойдет в следующем уроке.
End of explanation
"""


cos_angle = np.dot(a, b) / norm(a) / norm(b)
print 'Косинус угла между a и b:', cos_angle
print 'Сам угол:', np.arccos(cos_angle)

"""
Explanation: Длиной вектора $x = (x_{1}, \dots, x_{n}) \in \mathbb{R}^{n}$ называется квадратный корень из скалярного произведения, то есть длина равна евклидовой норме вектора:
$$
\left| x \right| = \sqrt{\langle x, x \rangle} = \sqrt{\sum_{i=1}^n x_{i}^2} =  \left\Vert x \right\Vert_{2}.
$$
Теперь, когда мы знаем расстояние между двумя ненулевыми векторами и их длины, мы можем вычислить угол между ними через скалярное произведение:
$$
\langle x, y \rangle = \left| x \right| | y | \cos(\alpha)
\implies \cos(\alpha) = \frac{\langle x, y \rangle}{\left| x \right| | y |},
$$
где $\alpha \in [0, \pi]$ — угол между векторами $x$ и $y$.
End of explanation
"""


