Занятие 6

Лабораторное занятие 6

Для работы с большими объёмами данных удобным форматом является массив. Рассмотрим библиотеку для работы с массивами.

NumPy — это библиотека Python, которую применяют для математических вычислений: начиная с базовых функций и заканчивая линейной алгеброй. Полное название библиотеки — Numerical Python extensions, или «Числовые расширения Python».

Где используется NumPy?

Библиотека NumPy используется:

  • Научные вычисления

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

  • Создание новых массивных библиотек

На основе NumPy появляются новые типы массивов, возможности которых выходят за рамки того, что предлагает библиотека. Например, библиотеки Dask, CuPy или XND.

  • Data Science

В основе экосистемы для анализа данных лежит NumPy. Библиотека используется на всех этапах работы с данными: извлечение и преобразование, анализ, моделирование и оценка, репрезентация.

  • Machine Learning

Библиотеки для машинного обучения scikit-learn и SciPy тоже работают благодаря вычислительным мощностям NumPy.

  • Визуализация данных

По сравнению непосредственно с Python возможности NumPy позволяют исследователям визуализировать наборы данных, которые гораздо больше по размеру. Например, библиотека лежит в основе системы PyViz, которая включает в себя десятки программ для визуализации.

Основными объектами с которыми работает библиотека NumPy являются массивы.

Массив Numpy — это многомерный массив (ndarray, n-dimensional array) данных, над которыми можно быстро и эффективно выполнять множество математических, статистических, логических и других операций.

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

Массив можно создать через np.array() передав в качестве аргумента список из которого нужно создать массив. Если будет передан пустой список, то будет создан пустой массив.

Официальная документация: https://numpy.org/doc/stable/user/index.html

In [ ]:
import numpy as np
import matplotlib.pyplot as plt
In [ ]:
arr = np.array([])
In [ ]:
print(type(arr))
print(arr)
<class 'numpy.ndarray'>
[]

Массив может быть нулевой размерности (это число (скаляр) и квадратных скобок не имеет).

In [ ]:
arr_0D = np.array(42)
arr_0D
Out[ ]:
array(42)
In [ ]:
arr = np.array([i for i in range(10)])
print(type(arr))
print(arr)
<class 'numpy.ndarray'>
[0 1 2 3 4 5 6 7 8 9]
In [ ]:
arr = np.array((0, 1, 2, 3, 4, 5, 6, 7, 8, 9))
arr
Out[ ]:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

А вот создать массив по строке таким образом не получится.

In [ ]:
arr = np.array('18217492830924')
print(type(arr))
print(arr)
print(len(arr))
<class 'numpy.ndarray'>
18217492830924
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipython-input-143596956.py in <cell line: 0>()
      2 print(type(arr))
      3 print(arr)
----> 4 print(len(arr))

TypeError: len() of unsized object

Другой вариант создание массива заключается в использовании функции np.arange(10). Эта функция имеет три параметра, как и функция range(). Обязательным параметром является верхняя граница, которая не входит в последовательность.

In [ ]:
arr = np.arange(10)
arr

Различие range() и функции np.arange() состоит в том, что range() не допускает использования типа float.

Тип элементов массива можно задать явным образом:

In [ ]:
arr_i = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], int)
arr_i
In [ ]:
arr_f = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], float)
arr_f
In [ ]:
arr_s = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], str)
arr_s

Свойства (атрибуты) массива

Одним из свойств массива является количество его измерений - это атрибут ndim.

In [ ]:
arr = np.array([1, 2, 3, 4, 5])
print(arr)
print(arr.ndim)

С помощью атрибута shape можно вывести количество элементов в каждом измерении.

In [ ]:
arr.shape

Атрибут size выводит общее количество элементов во всех измерениях.

In [ ]:
arr.size

С помощью атрибута dtype можно вывести тип данных отдельного элемента.

In [ ]:
arr.dtype

Атрибут itemsize позволяет узнать размер в байтах (один байт состоит из 8 бит) одного элемента.

In [ ]:
arr.itemsize

Общий размер массива в байтах можно вывести через атрибут nbytes.

In [ ]:
arr.nbytes

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

Нахождение нормы вектора:

In [ ]:
a = np.array([1.0, -1.0, 1.0])
print(np.linalg.norm(a), 3 ** 0.5)

Есть поэлементное и скалярное произведение массивов:

In [ ]:
a = np.array([1.0, 0.0, 2.0])
b = np.array([1.0, 15.0, 10.0])
print(a * b)
print(a @ b)

Операция @ определена для матриц. Напомним, что матрицы размерами $n*k_1$ и $k_2*m$ можно перемножить тогда и только тогда, когда $k_1=k_2$. Метод reshape меняет форму и размерность массива, к которому он применяется. В скобках указываются размеры нового массива, при этом, можно указывать в качестве одного из аргументов -1. В этом случае размер этой оси будет рассчитан автоматически:

In [ ]:
a = np.array([1.0, 0.0, 2.0]).reshape(-1, 1)
b = np.array([1.0, 15.0, 10.0]).reshape(-1, 1)
print(a)
print()
print(b)
In [ ]:
print(a @ b)

Получили ошибку, потому что пытались перемножить 2 матрицы $3*1$.

In [ ]:
a = np.array([1.0, 0.0, 2.0]).reshape(1, -1)
b = np.array([1.0, 15.0, 10.0]).reshape(-1, 1)
print(a)
print()
print(b)
In [ ]:
print(a @ b)

Получили двумерную матрицу размером $1*1$. Чтобы вытащить число, обратимся к нему по индексам:

In [ ]:
print((a @ b)[0][0])

Рассмотрим выполнение срезов в массивах

In [ ]:
a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

print(a[:2])
print(a[:2][0])
print(a[:2])
print(a[:2][0][-1])
[[1 2 3 4]
 [5 6 7 8]]
[1 2 3 4]
[[1 2 3 4]
 [5 6 7 8]]
4

Упражнение 1. Перемножить матрицы $A$ и $B$ двумя способами: с использованием numpy, затем без использования numpy.

In [ ]:
A = np.array([[1.0, 2.0, 3.0],
              [4.0, 5.0, 6.0]])
B = np.array([[1.0, 0.0],
              [0.0, 1.0],
              [1.0, 1.0]])
In [ ]:
 
In [ ]:
 

Упражнение 2. Найти косинус угла между векторами $a$ и $b$.

In [ ]:
a = np.array([1.0, 0.0, 2.0])
b = np.array([1.0, 15.0, 10.0])
In [ ]:
 

Чтобы провести через набор экспериментальных точек наилучшую прямую, используются следующие формулы:

$$ k = \dfrac{N \cdot \sum(x_i \cdot y_i) - (\sum x_i) \cdot (\sum y_i)}{N \cdot \sum x_i^2 - (\sum x_i)^2} $$ $$ b = \dfrac{\sum y_i - k \cdot \sum x_i}{N} $$

In [ ]:
x = np.linspace(0.0, 5.0, 10)
y = 2 * x - 5
print(x)
print(y)
plt.scatter(x, y);

Добавим к данным шум. np.random.randn(n) вовзвращает n сэмплов из стандартного нормального распределения.

In [ ]:
eps = np.random.randn(y.shape[0])
print(eps)
print(eps * 0.2)
In [ ]:
y = y + eps * 0.2
plt.scatter(x, y)

В numpy есть возможность генерировать случайные числа из заданного типа распределения. За этот функционал отвечает модуль random. Ниже приведены примеры наиболее часто встречающихся распределений:

  • beta(a, b[, size]) — бета-распределение;
  • binomial(n, p[, size]) — биномиальное распределение;
  • exponential([scale, size]) — экспоненциальное распределение;
  • exponential([scale, size]) — экспоненциальное распределение;
  • gamma(shape[, scale, size]) — гамма-распределение;
  • geometric(p[, size]) — геометрическое распределение;
  • normal([loc, scale, size]) — нормальное распределение;
  • poisson([lam, size]) — пуассоновское распределение;
  • uniform([low, high, size]) — равномерное распределение.
In [ ]:
x = np.random.poisson(lam=10.5, size=100)
In [ ]:
import matplotlib.pyplot as plt

plt.hist(x, bins=20);

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

In [ ]:
 

Библиотеку numpy удобно использовать для решения систем линейных уравнений. Если система уравнений $Ax=y$ имеет решение, и при этом только одно, то оно выражается как $x=A^{-1} y$. Для обращения матрицы используется функция np.linalg.inv. Рассмотрим систему уравнений: $$ \left\{ \begin{array}{c} y+3z=-1 \\ 2x+3y+5z=3 \\ 3x+5y+7z=6 \end{array} \right. $$

In [ ]:
A = np.array([[0.0, 1.0, 3.0],
              [2.0, 3.0, 5.0],
              [3.0, 5.0, 7.0]])
y = np.array([-1.0, 3.0, 6.0])
print(np.linalg.inv(A) @ y)

Упражнение 4. Решить систему уравнений: $$ \left\{ \begin{array}{c} 2x+3y+4z+5t=30 \\ 3x+3y+4z+5t=34 \\ 4x+4y+4z+5t=41 \\ x+y+z+t=10 \end{array} \right. $$

In [ ]:
 

Другие функции и особенности numpy

In [ ]:
a = np.array([1.0, 2.0, 3.0])
b = np.array([1.0, 1.0, 1.0])
print(a == b)
print((a == b).any())
print((a == b).all())

Отсюда возникает следующая ситуация: в условной конструкции if, а также в цикле while в условии нужно указывать any или all.

In [ ]:
a = np.ones((1, 2))
b = a
print(a)
if a == b:
    print(1)
In [ ]:
a = np.ones((1, 2))
b = a
if (a == b).all():
    print(1)

Функции zeros, ones используются для создания массивов заданного размера, заполненных нулями или единицами. Выше при рисовании графика для создания массива x была использована функция linspace(a, b, n), которая создает массив n чисел от a до b, упорядоченных по возрастанию с равными интервалами. Существуют также функции для агрегации (all и any тоже относятся к ним):

In [ ]:
a = np.array([1.0, 2.0, 3.0, 3.0, 3.0, 4.0, 5.0])
print(a.sum())
print(a.mean())
print(a.var())
print(a.std())
print(a.max())
print(a.min())

Можно делать срезы и свертку по осям:

In [ ]:
a = np.array([[1.0, 2.0, 3.0, 3.0, 3.0, 4.0, 5.0],
              [1.0, 2.0, 3.0, 3.0, 3.0, 4.0, 5.0],
              [1.0, 2.0, 3.0, 3.0, 3.0, 4.0, 5.0]])
print(a[:2,:2])
In [ ]:
print(a[:,:4])
In [ ]:
print(a[1:,:])
In [ ]:
a = np.array([[1.0, 2.0, 3.0],
              [4.0, 5.0, 6.0]])
print(a.sum(), np.sum(a))
print(np.sum(a, axis=0))
print(np.sum(a, axis=1))
print(a.max(), np.max(a))
print(np.max(a, axis=0))
print(np.max(a, axis=1))
In [ ]:
 

Упражнение 5.

  1. Создать матрицу случайных чисел $3*5$ из стандартного нормального распределения.
  2. Среди наибольших элементов в столбцах найти наименьший.
  3. Умножить эту матрицу на себя транспонированную.
  4. Найти стандартное отклонение по строкам, по столбцам и по всей матрице.
  5. Для разных размеров таких матриц посмотреть, как будет меняться std по всей матрице в зависимости от ее размеров. Можно рассмотреть квадратные матрицы $n*n$, где n будет равномерно расти от 10 до 1010. Использовать linspace для n, построить график.
In [ ]:
a = np.array([])
for n in [10, 20, 50, 100, 200, 500, 1000, 5000, 10000]:
    b = np.random.randn(n, n)
    a = np.append(a, b.std())
plt.scatter([10, 20, 50, 100, 200, 500, 1000, 5000, 10000], a)
plt.title('Стандартное отклонение для выборки из n случайных чисел')
plt.ylabel('Стандартное отклонение')
plt.xlabel('n - размер выборки');
In [ ]:
 

Векторизация

In [ ]:
def f(x):
      return x ** 2
a = np.array([1.0, 2.0, 3.0])
print(np.vectorize(f)(a))
In [ ]:
print(np.vectorize(f)(np.vectorize(f)(a)))
In [ ]:
f1 = np.vectorize(f)
print(f1(f1(a)))

Упражнение 6. На основе массивов a и b с помощью функции vectorize создайте массив, каждый элемент которого - минимальный по модулю из соответствующих элементов a и b.

In [ ]:
a = np.array([1.0, 2.0, -3.0])
b = np.array([-3.0, -1.5, 3.5])

Логические матрицы

In [ ]:
a = np.array([1.0, 2.0, 3.0])
print(a[a < 2])
print(a[a >= 2])
In [ ]:
a[a < 2] = 0
print(a)
In [ ]:
a = np.array([1.0, 2.0, 3.0])
b = np.array([1.5, 3.0, 3.0])
print(b[a <= 2])

Упражнение 7. Умножить матрицу A на себя транспонированную. В исходной матрице обнулить те элементы, которые больше, чем элементы в полученной матрице, стоящие на тех же местах.

In [ ]:
 
In [ ]:
A = np.array([[1.0, -0.5],
              [-0.5, 0.25]])
print(A @ A.T)

Часто используется также функция np.where. У нее 3 аргумента - условие, значение элемента, если условие выполняется и значение элемента, если условие не выполняется.

In [ ]:
a = np.array([-1.0, 2.0, 3.0, -4.0, 5.0])
print(np.where(a < 0, a, 0))
In [ ]:
a = np.array([-1.0, 2.0, 3.0, -4.0, 5.0])
print(np.where((a < 0) & (a % 2 == 0), a, 0))
In [ ]:
a = np.array([-1.0, 2.0, 3.0, -4.0, 5.0])
a[(a < 0) & (a % 2 == 0)]

Упражнение 8. С помощью linspace создать массив из натуральных чисел от 1 до 31. Из него удалить те элементы, которые не делятся на 3. Среди оставшихся обнулить те, которые не делятся на 9.

In [ ]:
 

Создание массива из функции индексов

In [ ]:
np.fromfunction(lambda i, j: i + j, (3, 3), dtype=int)
In [ ]:
np.fromfunction(lambda i, j: i, (3, 3), dtype=int)
In [ ]:
np.fromfunction(lambda i, j: j, (3, 3), dtype=int)

Упражнение 9. Создать с помощью fromfunction единичную матрицу.

In [ ]:
 

Упражнение 10. Дан массив (числа от -100 до 100), поменять знак у элементов, значения которых между 1 и 10.

In [ ]:
 

Упражнение 11. С клавиатуры введены элементы двух массивов. В одной строке вводится один массив. Проверить, одинаковы ли 2 numpy массива.

In [ ]:
 

Упражнение 12. Сделать массив неизменяемым (для этого потребуется посмотреть документацию).

In [ ]:
 

Упражнение 13. Определить, есть ли в 2D массиве нулевые столбцы.

In [ ]:
 

Упражнение 14. Найти наиболее частое значение в массиве.

In [ ]:
 

Упражнение 15. Найти n наибольших значений в массиве.

In [ ]:
 

Упражнение 16. Создать вектор размера 10 со значениями от 0 до 1, не включая ни то, ни другое.

In [ ]:
 

Упражнение 17. Создать структурированный массив, представляющий координату (x,y) и цвет (r,g,b).

In [ ]:
 

Упражнение 18. Создайте случайную матрицу. Вычтите среднее из каждой строки в матрице.

In [ ]:
 

Упражнение 19. Дан набор из 10 троек, описывающих 10 треугольников (с общими вершинами). Найдите множество уникальных отрезков, составляющих все треугольники.

In [ ]:
 

Упражнение 20. Дана 10x3 матрица (сгенерируйте как матрицу из случайных целых чисел от 0 до 100), найти строки из неравных значений (например [2,2,3]).

In [ ]: