NaN в числовых массивах: обнаружение и nan-устойчивые агрегаты
Содержание курса
Почему стандартные агрегаты возвращают NaN
В арифметике с плавающей точкой NaN обычно распространяется через вычисления: когда NaN участвует в арифметической операции, результат, как правило, тоже NaN. Именно так устроены стандартные агрегирующие функции NumPy — np.sum, np.mean, np.min, np.max. При редукции массива они обрабатывают элементы последовательно, и как только в цепочке встречается NaN, итоговый результат становится NaN:
import numpy as np
arr = np.array([2.0, np.nan, 5.0, 8.0])
print(np.sum(arr)) # nan
print(np.mean(arr)) # nan
print(np.min(arr)) # nan
print(np.max(arr)) # nan
Все четыре вызова возвращают nan, хотя в массиве три вполне конкретных числа — 2.0, 5.0, 8.0.
Почему так? NumPy не знает, чем заменить «отсутствующее» значение. Если просто проигнорировать NaN и посчитать сумму оставшихся чисел, результат будет неверным: 2.0 + 5.0 + 8.0 = 15.0, но это сумма трёх элементов, а не четырёх. Вернуть 15.0 без явного указания пользователя было бы молчаливым искажением данных. Поэтому стандартные агрегаты выбирают честное поведение: «в данных есть неизвестное число — весь результат неизвестен».
Это не баг и не недосмотр. Это осознанное решение, которое заставляет явно думать о пропусках, а не получать «правдоподобные» числа из «грязных» данных незаметно для себя.
На практике это означает: если np.any(np.isnan(arr)) вернул True, то вызовы np.sum, np.mean, np.min, np.max дадут nan. Чтобы агрегировать только известные значения, нужны специальные функции, которые явно объявляют своё намерение игнорировать пропуски — о них в следующем разделе.
