# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3 (ipykernel)
#     language: python
#     name: python3
# ---

# # Семинар по numpy
#
# В этом семинаре мы познакомимся с первой библиотекой, которая понадобится нам в курсе - библиотекой numpy. Это библиотека для работы с матрицами, и она может быть также полезна для матричных вычислений в других дисциплинах. Но сначала познакомимся с интерфейсом jupyter notebook. Это инструмент, позволяющий писать код в интерактивном режиме, рисовать графики и оформлять материалы.
#
# ## Очень краткий обзор интерфейса jupyter notebook
# Jupyter notebook состоит из ячеек.
# В ячейку можно записывать код, выполнять, а потом использовать созданные переменные / определенные функции и т. д. в других ячейках:

x = [1, 2, 3]

print(x)

x = 9

y = 3

# Выполнить ячейку: shift + enter, показать аргументы функции: shift + tab. Есть много других горячих клавиш, см. Help -> Keyboard Shortcuts (вверху интерфейса). Там же есть Help -> User Interface Tool!
#
# Обратите внимание на кнопки + (добавить ячейку), ножницы (удалить ячейку), стрелки, меняющие ячейки местами, кнопку остановить выполнение.
#
# Ноутбук сохраняется автоматически. Чтобы скачать: File -> Download as -> ipynb.
#
# Этот текст написан в ячейке типа Markdown, она позволяет красиво оформлять код. Переключение типа ячейки справа от кнопки стоп (черный квадрат). Ячейки с кодом имеют тип Code.

# ## Создание массивов в numpy
#
# Numpy - библиотека для работы с матрицами. Импортируем библиотеку:

import numpy as np

# Предположим, что мы провели опрос, в котором было четыре вопроса, на каждый можно ответить Да, Нет, Воздержался. В итоге получим таблицу размера 4 на 3. Создадим такую таблицу в numpy:

l = [[30, 2, 0], [3, 27, 1], [28, 1, 1], [6, 17, 5]]
ar = np.array(l)

ar # выводится созданное в последней строке, если в ней нет присваивания (знак =)

# Можно создавать массивы из нулей (np.zeros) или из единиц (np.ones):

A = np.ones((7, 9))
A

# Также часто используется создание векторов из натуральных чисел:

vec = np.arange(15)
vec

# И случайно сгенерированные массивы:

r = np.random.rand(3, 5)
r

# ### Размерности массивов

# Размерности:

ar.shape[0]

A.shape

vec.shape

# В numpy нулевая размерность отвечает за строки, первая - за столбцы. В нашей матрице ar 4 строки и 3 столбца. Вообще в numpy можно создавать массивы любых размерностей точно таким же образом, как мы сделали выше - numpy не различает векторы, матрицы и тензоры, все они называются массивами. Например, vec имеет длину 15 только по одной размерности, поэтому его shape равен (15,). Shape - это обычный кортеж языка python.

# Можно вытянуть все ответы в одну строку:

ar.ravel()

# Можно наоборот: преобразовать вектор в матрицу:

vec.reshape(3, 5)

# Обратите внимание, что числа записываются по строкам.

vec.reshape(3, 5).shape

# По одной из осей можно указывать -1, тогда библиотека сама посчитает число элементов по этой размерности:

vec.reshape(3, -1)

# Аналогичным образом можно дублироать функционал функции ravel:

vec.reshape(-1)

# ### Операции с массивами

# Можно выделить три группы операций с массивами в numpy:
# * поэлементные
# * матричные
# * агрегирующие

# Поэлементные выполняются между массивами одной формы (shape), хотя ниже мы обсудим некое обощение правила одной формы.

vec + vec + vec

A * 10

x = np.array([1, 2, 3])
y = np.array([-1, 1, -1])
x * y

# Обратите внимание, что * - это поэлементное умножение, а не матричное!

np.exp(x)

# Матричные операции - операции из линейной алгебры. Например, матричное произведение:

A = np.random.rand(7, 8)
B = np.random.rand(8, 3)
A.dot(B)

# Можно писать и так:

np.dot(A, B)

# И так:

A @ B

# Проверим форму:

A.dot(B).shape

# Обращение матрицы:

np.linalg.inv(np.random.rand(3, 3))

# Модуль np.linalg содержит много полезных матричных функций, их можно посмотреть в документации модуля.

#  Агрегирующие операции агрерируют информацию в троках, столбцах, во всем массиве и т. д. Самые популярные такие операции - суммирование np.sum, усреднение np.mean, медиана np.median, максимум np.max и минимум np.min.

# Число полученных ответов на вопросы (всего): 

np.sum(ar)

# Пробуем выяснить число респондентов. Для этого просуммируем матрицу по строкам (это делается с помощью указания axis=1):

np.sum(ar, axis = 1) 

np.sum(ar, axis = 1).shape

# По столбцам: axis=0, по строкам: axis=1.
#
# В результате суммирования получился вектор (размерность на 1 меньше, чем у исходной матрицы). Можно указать keepdims=True, чтобы сохранть размерности:

np.sum(ar, axis = 1, keepdims=True).shape

# Задание для студентов: посчитать сумму по строкам, используя матричное произведение.

np.sum(A.dot(B), axis=1)

# Считаем число ответов "да", "нет", "воздержался" двумя способами:

np.sum(ar, axis=0)

ones = np.ones(4)

np.dot(ones, ar)

# ### Индексация
# Для индексации ставим [ ] и через запятую перечисляем действия с осями. В матрице 0 - по вертикали, 1 - по горизонтали

ar[1, 1] # выбрать 1 элемент

ar # вывели для проверки

ar[:, 2] # выделить столбец

ar[:, -1] # выделить последний столбец

ar[0] # выделить строку

ar[:, ::2] # выделить все столбы с четными номерами

#  Можно делать логическую индексацию, чтобы выбирались только те элементы, для которых стоит True. Выберем ответы на вопросы с номерами, кратными 2: 

 ar[np.arange(ar.shape[0])%2==0]

# ### Добавление оси
#
# Для удобного перехода между размерностями используют добавление оси. Вектор можно сделать матрицей с размером 1 по одной из размерностей.

ones[:, np.newaxis]

ones[:, np.newaxis].shape 
# вместо вектора с формой (4,) стала матрциа с формой (4, 1)

ones[np.newaxis, :]
# вместо вектора с формой (4,) стала матрциа с формой (1, 4)

ones[np.newaxis, :].shape

# ### Добавление оси в поэлементных операциях

# В поэлементных операциях можно использовать не только массивы в одинаковым в точности размером. В общем виде условие такое: по каждой размерности либо размер совпадает, либо в одном из массивов размер 1. Например, матрица размера (4, 3) и вектор размера (4, 1). В этом случае при выполнении операции столбец будет как бы "дублироваться" для каждого столбца в первой матрице. Воспользуемся этим, чтобы найти долю каждого ответа на все вопросы:

sums = ar.sum(axis=1) # всего ответов на каждый вопрос
sums.shape

sums[:, np.newaxis].shape # добавили ось

ar / sums[:, np.newaxis] # поделили число каждого варианта на общее число ответов на вопрос

# ### Объединение массивов

# Добавляем новый вопрос в таблицу:

row = np.array([5, 12, 15])
row = row[np.newaxis, :] 
# конкретно тут можно без увеличения размерности
# но в других случаях может быть ошибка, лучше добавлять
ar = np.vstack((ar, row))

# Добавляем новый столбец в таблицу - максимальное число ответов:

mx = np.max(ar, 1)
mx

mx.shape

mx = mx[:, np.newaxis]

ar = np.hstack ((ar, mx))
ar

# Удаление строки (аналогично можно удалять столбец):

np.delete(ar, np.arange(3, 4), axis=1)

# ### Задания для студентов

# Выделите строки, у которых ответов "нет" больше, чем ответов "да":

ar[ar[:, 0]<ar[:, 1]] 

# Вывести квадраты первых десяти натуральных чисел:

np.arange(10)**2

# Перемешать числа натуральные числа от 1 до 10 (воспользуйтесь np.random.permutation):

np.random.permutation(10)

# student's code here


# Составить таблицу умножения от 1 до 10:

p = np.arange(1, 10)
p * p[:, None]







StarcoderdataJupyterScriptsDedupFiltered