Нашли ошибку?
Скачать чистый
урок 1 из 5≈ 20 минупражнения: 0 из 4

Векторизация вместо циклов

Математические функции NumPy работают с целым массивом сразу — np.sqrt, np.abs, np.log10, np.exp; поэлементные np.maximum и np.minimum; как переписать цикл с условием в одну строку.

Python запустится при первом выполнении

Весь курс мы заменяли циклы операциями над массивами: prices * 0.9 вместо цикла по ценам, маска вместо if в цикле. Это называют векторизацией (vectorization): операция записывается для всего массива, а цикл по элементам выполняет NumPy — внутри, в десятки и сотни раз быстрее цикла на Python. В этом уроке — последние недостающие кусочки: математические функции и приёмы, которыми переписывают любой цикл.

Математика поэлементно

Функции модуля math из обычного Python работают с одним числом. У NumPy есть такие же, но для массивов — применяются к каждому элементу:

  • np.sqrt — квадратный корень;
  • np.abs — модуль числа (без знака);
  • np.log10 — десятичный логарифм: в какую степень нужно возвести 10, чтобы получить число (log10(1000) = 3);
  • np.log — натуральный логарифм, np.exp — обратная ему экспонента.
[ ]Корень и модуль
import numpy as np

print(np.sqrt(np.array([4, 9, 2])))
print(np.abs(np.array([-3, 0, 2.5, -7])))
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[2.         3.         1.41421356]
[3.  0.  2.5 7. ]
[ ]Упражнение 1Расстояние по прямой решено

Курьер проехал от склада dx километров на восток и dy на север (пять заказов). Сохраните в dist расстояние от склада до каждой точки по прямой — по теореме Пифагора: корень из dx² + dy².

dx = np.array([3.0, 5.0, 1.5, 8.0, 6.0])
dy = np.array([4.0, 12.0, 2.0, 6.0, 2.5])
dist = ...

[ ]Упражнение 2Ошибка прогноза решено

В actual — сколько кофе продали за неделю по дням, в forecast — сколько предсказывал прогноз. Сохраните в mae среднюю ошибку прогноза без учёта знака: насколько в среднем прогноз промахивался — хоть вверх, хоть вниз.

actual = np.array([120, 135, 150, 128, 142, 160, 155])
forecast = np.array([118, 140, 145, 130, 150, 152, 158])
mae = ...

Логарифм и экспонента

Логарифм сжимает огромные разбросы: у числа из k цифр десятичный логарифм — между k - 1 и k, так что его целая часть плюс один — число цифр. Он удобен, когда рядом стоят деревня и мегаполис:

[ ]Порядок величины
population = np.array([1200, 45000, 610000, 1100000, 12600000])
print(np.log10(population))
print(np.log10(population).astype(int) + 1)     # число цифр
print(np.log(np.array([1, 10, 100])))     # натуральный логарифм
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[3.07918125 4.65321251 5.78532984 6.04139269 7.10037055]
[4 5 6 7 8]
[0.         2.30258509 4.60517019]

np.exp в анализе данных часто встречается внутри формул. Например, сигмоида 1 / (1 + np.exp(-x)) превращает любое число в долю от 0 до 1 — так модели машинного обучения выдают вероятность:

[ ]Сигмоида
x = np.array([-4, -1, 0, 1, 4])
print(np.round(1 / (1 + np.exp(-x)), 3))
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[0.018 0.269 0.5   0.731 0.982]

Поэлементный максимум: np.maximum

np.maximum(a, b) сравнивает два массива поэлементно и берёт большее в каждой позиции. Вместо массива может быть число — тогда оно работает как нижняя граница. np.minimum — то же с меньшим:

[ ]np.maximum и np.minimum
a = np.array([3, -2, 7, -5])
b = np.array([1, 4, 6, 0])
print(np.maximum(a, b))
print(np.maximum(a, 0))      # отрицательные → 0
print(np.minimum(a, 5))      # больше 5 → 5
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[3 4 7 0]
[3 0 7 0]
[ 3 -2  5 -5]
Вопрос

Что выведет print(np.max(np.array([-2, 5, -1]), 0))?

[ ]Упражнение 3Переработки решено

В hours — сколько часов в неделю отработал каждый сотрудник. Всё, что больше 40, — переработка. Сохраните в overtime часы переработки каждого (у кого нет — 0), а в total_overtime — сумму по всем.

hours = np.array([38, 45, 40, 52, 36, 41])
overtime = ...
total_overtime = ...

Переписать цикл

Любой цикл «для каждого элемента посчитать» переписывается по одной схеме: вычисление — арифметикой над массивом, if — маской, np.where, np.maximum или np.minimum. Вот цикл, который начисляет скидку 10 % на дорогие товары, и та же логика одной строкой:

[ ]Цикл и одна строка
prices = np.array([80, 150, 95, 240])
result = []
for p in prices:
    if p > 100:
        result.append(p * 0.9)
    else:
        result.append(p)
print(np.array(result))
print(np.where(prices > 100, prices * 0.9, prices))
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[ 80. 135.  95. 216.]
[ 80. 135.  95. 216.]

Теперь ваша очередь. Этот цикл начисляет бонусные баллы: за каждый балл сверх 50 — два бонусных, но не больше 60:

[ ]Бонусы циклом
scores = np.array([35, 50, 62, 71, 88, 95, 49, 100])
result = []
for s in scores:
    if s >= 50:
        b = (s - 50) * 2
    else:
        b = 0
    if b > 60:
        b = 60
    result.append(b)
print(np.array(result))
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[ 0  0 24 42 60 60  0 60]
[ ]Упражнение 4Бонусные баллы решено

Перепишите цикл из ячейки выше без цикла: сохраните в bonus массив бонусов для scores — те же числа, что считает цикл.

bonus = ...

Итоги

  • Векторизация — операция для всего массива сразу, цикл по элементам выполняет NumPy.
  • np.sqrt, np.abs, np.log10, np.log, np.exp применяются к каждому элементу.
  • np.maximum(a, b) и np.minimum(a, b) — поэлементно; с числом — граница снизу или сверху.
  • np.max(a, 0) — не поэлементный максимум, а axis=0.
  • Цикл с if переписывается маской, np.where, np.maximum, np.minimum, np.clip.

Подробнее в справочнике

Нашли ошибку?