Библиотека Numpy используется в модулях для работы с различными типами данных:
- Научными данными - SciPy
- Табличными данными — Pandas
- Визуализацией данных — Matplotlib, Plotly, Seaborn
- Алгоритмами машинного обучения — Sklearn
- Тензорами и глубокими нейронными сетями — TensorFlow
- Изображениями — OpenCV
Так произошло, потому что Numpy позволяет использовать единый интерфейс для работы с разными многомерными структурами данных и содержит широкий спектр операций для этого. В этом уроке мы познакомимся с математическими и статистическими методами библиотеки, решив ряд аналитических задач.
Рассмотрим недельные данные продаж в сети четырех магазинов:
# Продажи за неделю по 4 магазинам
orders = [
[7, 1, 7, 8],
[4, 2, 4, 5],
[3, 5, 2, 3],
[8, 12, 8, 7],
[15, 11, 13, 9],
[21, 86, 35, 16],
[70, 90, 124, 94]
]
orders = np.array(orders)
Одна из базовых задач аналитика — знакомство с данными. Типичный подход к этому вопросу — это оценка суммы заказов во всей сети и в каждом магазине по отдельности:
# Сложение всех элементов
print(orders.sum())
# => 670
# Сумма продаж по магазинам
print(orders.sum(axis = 0))
# => [128 207 193 142]
Кроме интегральных показателей, для оценки полезно понимать разброс значений от минимального до максимального. Мы можем обнаружить крайние значения и использовать эти данные, чтобы найти ошибки и расследовать инциденты в работе сети:
# Минимальное значение продаж
print(orders.min())
# => 1
# Максимальное значение продаж
print(orders.max())
# => 124
# Максимальное значение продаж в первом магазине
print(orders[:, 0].max())
# => 70
# Индекс (день недели) максимального элемента в первом магазине
print(orders[:, 0].argmax())
# => 6
По абсолютным цифрам не всегда получается проверить гипотезы о состоянии сети. Для этого удобно смотреть на показатели относительно общего числа продаж:
# Деление всех продаж на суммарное значение за всю неделю по сети
print(orders / orders.sum())
# => [[0.01044776 0.00149254 0.01044776 0.0119403 ]
# [0.00597015 0.00298507 0.00597015 0.00746269]
# [0.00447761 0.00746269 0.00298507 0.00447761]
# [0.0119403 0.01791045 0.0119403 0.01044776]
# [0.02238806 0.01641791 0.01940299 0.01343284]
# [0.03134328 0.12835821 0.05223881 0.0238806 ]
# [0.10447761 0.13432836 0.18507463 0.14029851]]
Раньше в аналитике использовались статические показатели, которые не показывают динамику изменений во времени. А теперь для оценки прироста заказов по дням можно использовать кумулятивные суммы и конечные разности.
В примере ниже показана сумма для первого магазина. Каждое следующее значение равно сумме предыдущего и количеству продаж в этот день:
# Кумулятивная сумма для первого магазина
print(orders[:, 0].cumsum())
# => [ 7 11 14 22 37 58 128]
# Кумулятивная сумма для последнего магазина
print(orders[:, -1].cumsum())
# => [ 8 13 16 23 32 48 142]
В случае разности для всех магазинов каждая строка представляет собой поэлементную разность показателей соседних дней:
# Конечная разность между продажами по дням
print(np.diff(orders, axis=0))
# => [[-3 1 -3 -3]
# [-1 3 -2 -2]
# [ 5 7 6 4]
# [ 7 -1 5 2]
# [ 6 75 22 7]
# [49 4 89 78]]
Фиксированная точность в расчетах требует выполнить один из двух вариантов:
- Округлить полученные показатели до целых значений сверху, снизу или к ближайшему
- Оставить определенное количество знаков после запятой
Все эти подходы реализованы в Numpy. Ниже в примере рассмотрим приемы округления для относительных показателей продаж в магазинах. В качестве относительной величины берем среднее по всей сети:
# Заказы относительно среднего значения
relative_orders = orders / orders.mean()
print(relative_orders)
# => [[0.29253731 0.04179104 0.29253731 0.33432836]
# [0.16716418 0.08358209 0.16716418 0.20895522]
# [0.12537313 0.20895522 0.08358209 0.12537313]
# [0.33432836 0.50149254 0.33432836 0.29253731]
# [0.62686567 0.45970149 0.54328358 0.3761194 ]
# [0.87761194 3.59402985 1.46268657 0.66865672]
# [2.92537313 3.76119403 5.18208955 3.92835821]]
# Округление до целых
print(relative_orders.round())
# => [[0. 0. 0. 0.]
# [0. 0. 0. 0.]
# [0. 0. 0. 0.]
# [0. 1. 0. 0.]
# [1. 0. 1. 0.]
# [1. 4. 1. 1.]
# [3. 4. 5. 4.]]
# Округление до заданного количества знаков после запятой
print(relative_orders.round(2))
# => [[0.29 0.04 0.29 0.33]
# [0.17 0.08 0.17 0.21]
# [0.13 0.21 0.08 0.13]
# [0.33 0.5 0.33 0.29]
# [0.63 0.46 0.54 0.38]
# [0.88 3.59 1.46 0.67]
# [2.93 3.76 5.18 3.93]]
# Округление до ближайшего сверху целого
print(np.ceil(relative_orders))
# => [[0. 0. 0. 0.]
# [0. 0. 0. 0.]
# [0. 0. 0. 0.]
# [0. 1. 0. 0.]
# [1. 0. 1. 0.]
# [1. 4. 1. 1.]
# [3. 4. 5. 4.]]
На продажи в магазинах могут влиять разные факторы:
- Выходные, предпраздничные и праздничные дни
- Акции
- Логистические задержки
- Погода
Все они по-разному отражаются на продажах. Если факторы плановые, то их последствия можно точно предугадать. А вот если они имеют вероятностную природу, то точность существенно падает.
В этих случаях используют статистические методы. Они позволяют оценить влияние факторов на продажи. Аналитики сперва находят статистические показатели:
- Среднее — какое число продаж можно ожидать, если взять случайный день недели и магазин
- Отклонение от среднего — насколько сильно реальные показатели могут отличаться от среднего
- Медиана — значение продаж, в сравнение с которым ровно половина продаж — меньше, а другая половина — больше
Посмотрим, как находить такие показатели:
# Среднее значение продаж
print(orders.mean())
# => 23.93
# Стандартное отклонение от среднего
print(orders.std())
# => 33.64
# Медианное значение
print(np.median(orders))
# => 8.0
Более детальный анализ продаж может потребовать использования персентилей. Персентиль N% показывает значение продаж, относительно которого N% продаж меньше. Например, персентиль 50% дает значение продаж, относительно которого в данных ровно 50% значений меньше его. Оно совпадает с медианным значением. Часто используют персентили 25%, 50% и 75%:
# Персентиль 25% — ниже этого показателя лежит 25% всех продаж
print(np.percentile(orders, 25))
# => 4.75
# Персентиль 50% — медианное значение
print(np.percentile(orders, 50))
# => 8.0
# Персентиль 75% — ниже этого показателя лежит 75% всех продаж
print(np.percentile(orders, 75))
# => 17.25
На разные магазины случайные факторы могут влиять по-разному. Мы можем находить магазины с похожими закономерностями, для этого мы прибегаем к корреляционному анализу. Если корреляция близка по модулю к 1, то факторы влияют одинаково. Если она близка к 0, то факторы влияют по-разному.
Ниже рассмотрим примеры корреляции для продаж магазинов. Используемая функция возвращает корреляции в виде матрицы — тем самым она помогает найти корреляции для нескольких массивов. В случае с двумя массивами нас интересует значение в первой строке и втором столбце:
# Корелляция заказов первого и второго магазинов
print(np.corrcoef(orders[:, 0], orders[:, 1]))
# => [[1. 0.80868227]
# [0.80868227 1. ]]
# Корелляция заказов первого и последнего магазинов
print(np.corrcoef(orders[:, 0], orders[:, 3]))
# [[1. 0.98744122]
# [0.98744122 1. ]]
Выводы
В этом уроке мы рассмотрели математические и статистические функции библиотеки Numpy, которые используют при решении аналитических задач. На практических примерах мы увидели правила и приемы их использования.
Эти знания упрощают работу с библиотеками, использующими Numpy. На это есть несколько причин:
- Интерфейсы многих функций используют принципы построения функций в Numpy
- В других библиотеках структуры данных — это Numpy-масcивы
- Методы структур данных можно дополнить методами массивов Numpy с небольшими доработками
Самостоятельная работа
Нажмите, чтобы увидеть тестовые данные
clicks_values = [
[319, 265, 319, 328],
[292, 274, 292, 301],
[283, 301, 274, 283],
[328, 364, 328, 12],
[391, 355, 373, 337],
[445, 418, 409, 445],
[481, 400, 481, 409],
[86, 267, 333, 344],
[300, 278, 300, 311],
[289, 311, 278, 289],
[344, 388, 344, 333],
[421, 377, 399, 355],
[487, 454, 443, 487],
[531, 432, 531, 443],
[312, 264, 312, 320],
[288, 14, 288, 296],
[280, 296, 272, 280],
[320, 352, 320, 312],
[376, 344, 360, 328],
[424, 400, 392, 424],
[456, 384, 456, 392],
[347, 269, 347, 360],
[308, 282, 308, 321],
[295, 321, 282, 423],
[360, 412, 360, 347],
[189, 399, 425, 373],
[529, 490, 477, 529],
[581, 464, 581, 477]
]
clicks_values = np.array(clicks_values)
clicks_values
Представим такую ситуацию: аналитику нужно провести первичный анализ данных. Данные представлены в виде двумерного массива Numpy и уже почищены от пропусков. Необходимо пройти несколько шагов для составления отчета.
Шаг 1. Напишите функцию get_stat_values(clicks_values: np.ndarray)
, которая возвращает кортеж статистических показателей (среднее, стандартное отклонение, медиану).
Нажмите, чтобы увидеть ответ
def test(stat_values):
expected_value = np.array((355, 93, 344))
# array_equal
assert np.array_equal(expected_value, np.array(stat_values, dtype=int))
def get_stat_values(clicks_values):
return clicks_values.mean(), clicks_values.std(), np.median(clicks_values)
stat_values = get_stat_values(clicks_values)
test(stat_values)
Шаг 2. Напишите функцию get_min_value_position(clicks_values: np.ndarray)
, которая возвращает позицию минимального элемента в массиве. В этом вам поможет функция unravel_index()
. Также стоит помнить, что функция np.argmin()
возвращает целое число. Оно обозначает, сколько элементов слева направо и сверху вниз надо пройти по массиву, чтобы найти минимальный элемент.
Нажмите, чтобы увидеть ответ
def test(min_value_position):
expected_value = np.array((3, 3))
# array_equal
assert np.array_equal(expected_value, np.array(min_value_position, dtype=int))
def get_min_value_position(clicks_values):
return np.unravel_index(np.argmin(clicks_values), clicks_values.shape)
min_value_position = get_min_value_position(clicks_values)
test(stat_values)
Шаг 3. Иногда нам нужно понять, в каком интервале сконцентрированы 50% всех центральных значений. Этот интервал называется интерквартильным размахом и строится следующим образом:
-
Левая граница — персентиль 25%
-
Правая граница — персентиль 75%
Напишите функцию get_iqr(clicks_values: np.ndarray)
, которая возвращает кортеж из левой и правой границ интерквартильного размаха.
Нажмите, чтобы увидеть ответ
def test(iqr):
expected_value = np.array((299.0, 413.5))
# array_equal
assert np.array_equal(expected_value, np.array(iqr))
def get_iqr(clicks_values):
return np.percentile(clicks_values, 25), np.percentile(clicks_values, 75)
iqr = get_iqr(clicks_values)
test(iqr)
Шаг 4. Напишите функцию get_corr(array1, array2)
, которая возвращает значение корелляции для двух заданных одномерных массивов.
Нажмите, чтобы увидеть ответ
def test(corr):
expected_value = 0.54
# array_equal
assert np.isclose(expected_value, corr, atol=1e-02)
def get_corr(array1, array2):
return np.corrcoef(array1, array2)[0,1]
corr = get_corr(clicks_values[:, 0], clicks_values[:, -1])
test(corr)
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.