NumPy для аналитика: массивы, маски и векторные расчёты
Что нужно знать аналитику о NumPy: массивы, типы, boolean-маски, np.where и векторизация, которая лежит под многими операциями pandas.
Содержание статьи
Pandas часто скрывает NumPy под привычными операциями над колонками. Но понимание массивов помогает объяснить типы, broadcasting, boolean-маски и причины, по которым векторный расчёт работает быстрее цикла по строкам. Аналитику не нужен весь NumPy: достаточно нескольких идей, которые встречаются каждый день.
Коротко
NumPy хранит однородные числовые массивы и выполняет операции над ними без явного цикла Python. ndarray, маски, np.where, np.select и базовые агрегаты помогают писать расчёты над колонками pandas проще и предсказуемее.
- Массив обычно содержит значения одного dtype.
- Операция над массивом применяется к элементам поэлементно.
- Boolean-маска выбирает нужные значения без цикла.
- Broadcasting позволяет применять одно число или вектор к массиву совместимой формы.
Массив и dtype
В списке Python числа и строки могут сосуществовать свободно. NumPy старается хранить массив компактно и однородно, поэтому тип влияет на память и результат операции. В аналитике это особенно заметно на больших числовых колонках и при смешении чисел с пропусками.
import numpy as np
revenue = np.array([1200, 800, 450, 1500], dtype='int32')
net_revenue = revenue * 0.9
print(net_revenue)
print(revenue.dtype, revenue.shape)Маски вместо циклов
Boolean-маска — массив True/False той же длины, что и исходные значения. Она превращает правило фильтрации в объект, который можно проверить и передать дальше. В pandas тот же подход работает для Series и DataFrame.
revenue = np.array([1200, 800, 450, 1500])
large_mask = revenue >= 1000
large_orders = revenue[large_mask]
print(large_mask) # [ True False False True ]
print(large_orders) # [1200 1500]np.where и np.select для правил
np.where подходит для простого выбора из двух вариантов. Для нескольких условий используй np.select, чтобы порядок правил был виден в коде. В обоих случаях обязательно задай значение для случая, который не попал в условия.
orders['is_large'] = np.where(
orders['revenue'].ge(1000), True, False
)
conditions = [orders['revenue'].ge(5000), orders['revenue'].ge(1500)]
orders['segment'] = np.select(
conditions, ['high', 'medium'], default='low'
)Агрегаты и broadcasting
Агрегаты NumPy считают сумму, среднее и квантили по массиву или выбранной оси. Broadcasting позволяет вычесть среднее из каждого значения без ручного цикла. В реальной метрике всё равно проверь пропуски и знаменатель: NumPy не знает, что означает отсутствие наблюдения.
revenue = np.array([1200, 800, 450, 1500], dtype='float64')
average = revenue.mean()
deviation = revenue - average
print('mean:', average)
print('p90:', np.quantile(revenue, .9))
print('deviation:', deviation)Векторная операция отвечает за вычисление. Определение периода, пользователя, активного действия и знаменателя остаётся аналитической задачей.
Когда использовать NumPy, а когда оставить pandas
NumPy удобен, когда работаешь с плотным числовым массивом: считаешь преобразование, маску или статистику и точно знаешь форму данных. Pandas добавляет индексы, имена колонок, nullable-типы и операции по группам. Поэтому в продуктовой аналитике обычно читают и описывают данные через pandas, а NumPy используют внутри правила, где он делает код короче или быстрее.
Не превращай DataFrame в values без причины. После этого теряются имена колонок и связь с индексом, а случайное изменение порядка может привести к неверному присваиванию. Если массив нужен для вычисления, возвращай результат обратно в Series с тем же индексом или используй to_numpy только в коротком, явно проверенном участке.
Выбор инструмента должен следовать форме вопроса. groupby, merge, даты и категориальные поля обычно читаются лучше в pandas. Матричные операции, маски над несколькими числовыми рядами и broadcasting естественнее выражаются в NumPy. Граница между ними — часть читаемости pipeline.
| Задача | Инструмент первого выбора | Почему |
|---|---|---|
| прочитать CSV и проверить схему | pandas | есть имена колонок и типы таблицы |
| выбрать строки по условию | pandas или NumPy mask | оба варианта сохраняют правило явно |
| groupby по каналу | pandas | агрегация привязана к измерению |
| применить формулу к числовому массиву | NumPy | операция векторная и компактная |
| сложить таблицы по ключу | pandas | нужно сохранить семантику JOIN |
dtype, пропуски и неожиданное приведение
Однородность массива — сила NumPy и источник некоторых сюрпризов. Если в числовой массив добавить строку, NumPy может привести все значения к строковому типу. Если добавить np.nan, целочисленный массив часто станет float. На небольшом примере это незаметно, но на больших данных меняет память и поведение сравнений.
Перед арифметикой проверь dtype, наличие конечных значений и диапазон. np.mean вернёт nan, если в массиве есть пропуск; np.nanmean игнорирует его, но это уже методологическое решение. Игнорировать пропуски можно не всегда: для конверсии отсутствие события может быть нулём, а отсутствие revenue — неизвестным значением.
Для денежных и процентных расчётов отдельно реши, где округлять. Округление каждой строки до сложения и округление итоговой суммы дают разные ответы. NumPy честно выполнит обе операции, но не скажет, какая соответствует договорённости с бизнесом.
values = np.array([1200.0, np.nan, 800.0])
print(values.dtype)
print('mean with NA:', np.mean(values))
print('mean ignoring NA:', np.nanmean(values))
finite = np.isfinite(values)
print('invalid values:', (~finite).sum())Составные маски нужно читать как условие
Одна маска проста: revenue >= 1000. С несколькими условиями легко ошибиться в приоритетах операторов. Для NumPy используй &, | и ~, а каждое сравнение заключай в скобки. Операторы and и or предназначены для одиночных boolean-значений и не работают как поэлементный выбор массива.
Полезно сохранять маски с именами: is_paid, is_recent, has_valid_user. Тогда итоговое условие можно обсудить с коллегой и проверить долю каждой части. Если итоговая выборка неожиданно пустая, посмотри пересечение условий вместо того, чтобы менять порог наугад.
В продуктовой аналитике маска часто является определением аудитории. Например, «активный пользователь» может означать событие в периоде, исключённое QA-устройство и подтверждённую учётную запись. Унеси это правило из временного массива в документацию метрики, иначе следующий расчёт создаст другую аудиторию.
is_recent = orders['created_at'].ge('2026-08-01')
is_valid = orders['user_id'].notna()
is_paid = orders['revenue'].gt(0)
audience = is_recent & is_valid & is_paid
print({
'recent': is_recent.mean(),
'valid': is_valid.mean(),
'paid': is_paid.mean(),
'audience': audience.mean(),
})Broadcasting: форма массива важнее магии
Broadcasting позволяет применить массив формы (3,) к матрице (2, 3) или одно число ко всем элементам. NumPy сравнивает размеры справа налево: измерения должны совпадать или одно из них должно быть равно единице. Ошибка формы лучше явного цикла, но сообщение может быть непривычным, если размерность не выписана в коде.
Классический сценарий аналитика — нормализовать значения по колонкам или вычесть среднее каждой группы. Для этого часто нужен keepdims=True, чтобы результат сохранил ось и корректно применился обратно. Перед расчётом выведи shape каждого объекта. Это занимает секунду и экономит час отладки.
Не используй broadcasting для скрытой бизнес-логики. Если вектор коэффициентов соответствует каналам, сохрани порядок и названия отдельно. Без индекса NumPy не знает, что третий коэффициент относится к email, а не к referral.
matrix = np.array([[10, 20, 30], [4, 8, 12]], dtype='float64')
row_mean = matrix.mean(axis=1, keepdims=True)
centered = matrix - row_mean
print(matrix.shape, row_mean.shape, centered.shape)
assert centered.shape == matrix.shapeОт массива к продуктовой метрике
NumPy ускоряет вычислительную часть, но сама метрика начинается раньше: с определения пользователя, события и периода. Перед тем как вызвать .mean() или np.quantile, ответь, что является наблюдением. Среднее время ответа по событиям не равно среднему времени ответа по пользователям. Массив может быть идеально посчитан и всё равно отвечать не на тот вопрос.
Полезная практика — сначала собрать DataFrame с именованными колонками, сделать проверки и только затем передать числовой массив в вычислительную функцию. После расчёта верни результат в таблицу с ключами сегмента и периода. Так промежуточная оптимизация не разрывает связь между числом и его смыслом.
Для следующего шага возьми одну свою метрику, запиши её зерно, создай маску аудитории, проверь пропуски и сравни векторную формулу с понятной реализацией на маленьком наборе. Если ответы совпали, NumPy стал частью воспроизводимого расчёта, а не отдельным фокусом с массивами.
- Проверить форму и dtype до операции.
- Явно решить, что делать с пропусками.
- Скобками закрепить составные маски.
- Проверить shape при broadcasting.
- Вернуть вычисление в таблицу с ключами и понятными названиями.
Материалы по теме

Pandas apply и векторизация: как писать быстрее и понятнее
Когда использовать apply, почему векторные операции быстрее и как переписать медленный построчный расчёт в pandas.

Визуализация в Python: как строить понятные графики в matplotlib и seaborn
Как выбрать тип графика и собрать его в Python: линии, столбцы, распределения и сравнение сегментов без декоративного шума.

Python для аналитика: рабочий стек из Jupyter, pandas и графиков
Какие инструменты нужны аналитику в Python: Jupyter, pandas, NumPy и matplotlib, как они связаны и с какого рабочего сценария начать.