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

Пропуски: NaN

Как NumPy хранит «нет данных» — значение NaN; почему с ним ломаются суммы и сравнения, как найти пропуски np.isnan и посчитать итоги без них nan-функциями.

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

Датчик на метеостанции иногда не передаёт показание, в анкете не ответили на вопрос, в таблице пустая клетка. Такие места называют пропусками (missing values). До сих пор наши данные были полными — в этом модуле научимся работать с неполными.

Значение «не число»

В массиве нельзя оставить пустую клетку: у каждого элемента есть значение. Для пропуска NumPy использует особое дробное число NaN — Not a Number, «не число». Записывается оно как np.nan:

[ ]Массив с пропуском
import numpy as np

a = np.array([4, np.nan, 7])
print(a)
print(a.dtype)
print(a + 1)
print(a.sum(), a.mean())
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[ 4. nan  7.]
float64
[ 5. nan  8.]
nan nan

Три наблюдения:

  • NaN — дробное число, поэтому массив с ним получает тип float64, даже если остальные числа целые.
  • Любая арифметика с NaN даёт NaN: неизвестное плюс единица — тоже неизвестное.
  • Поэтому sum и mean по массиву с пропуском возвращают nan. NumPy не выбрасывает пропуски молча — он показывает, что результат не определён.

Найти пропуски: np.isnan

Самое странное свойство NaN: он не равен даже самому себе. «Неизвестное равно неизвестному?» — ответ «нет»:

[ ]NaN не равен ничему
print(np.nan == np.nan)
print(a == np.nan)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
False
[False False False]

np.isnan(a) возвращает маску: True там, где пропуск. С ней работает всё, что вы знаете о масках: sum считает пропуски, ~ переворачивает маску, а a[~маска] оставляет только известные значения:

[ ]Маска пропусков
mask = np.isnan(a)
print(mask)
print(mask.sum())       # сколько пропусков
print(a[~mask])         # только известные
print(a[~mask].mean())
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[False  True False]
1
[4. 7.]
5.5
[ ]Упражнение 1Неделя с пропусками решено

В readings — температура за неделю, два дня датчик молчал. Сохраните в gaps число пропусков, в known — массив только известных значений, а в week_avg — среднюю температуру по известным дням.

readings = np.array([-3.1, np.nan, -2.4, -4.0, np.nan, -1.8, -2.2])
gaps = ...
known = ...
week_avg = ...

Вопрос

Что выведет print(np.nan == np.nan)?

Итоги без пропусков: nan-функции

Отфильтровывать пропуски перед каждым расчётом утомительно. У NumPy есть готовые функции, которые их пропускают: np.nanmean, np.nansum, np.nanmax, np.nanmin — те же mean, sum, max, min, только NaN они не замечают:

[ ]nan-функции
print(np.nanmean(a), np.nansum(a))
print(np.nanmax(a), np.nanmin(a))
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
5.5 11.0
7.0 4.0

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

[ ]Средняя по каждой станции
table = np.array([
    [1.0, np.nan, 3.0],
    [np.nan, 5.0, 7.0],
])
print(table.mean(axis=1))
print(np.nanmean(table, axis=1))
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[nan nan]
[2. 6.]
[ ]Упражнение 2Три станции за неделю решено

В week — температура трёх станций (строки) за семь дней (столбцы) с пропусками. Сохраните в station_avg среднюю температуру каждой станции, а в day_max — самую высокую температуру каждого дня среди станций.

week = np.array([
    [-2.1, -3.4, np.nan, -1.0, 0.5, 1.2, -0.3],
    [-3.5, np.nan, np.nan, -2.2, -0.8, 0.1, -1.9],
    [-4.0, -5.1, -4.4, -3.0, np.nan, -1.1, -2.6],
])
station_avg = ...
day_max = ...

Заполнить пропуски

Иногда пропуски нужно не пропустить, а заполнить — например, чтобы построить график без дыр. Простой способ — подставить среднее по известным значениям. Здесь пригодится np.where из урока «Условная замена» (модуль 4): где пропуск — среднее, где нет — само значение.

[ ]Упражнение 3Заполнить средним решено

Сохраните в filled копию readings, где пропуски заменены средней температурой по известным дням. Сам readings не меняйте.

filled = ...

Заполнение средним сохраняет среднюю, но делает данные «глаже», чем они были. Это решение аналитика, а не правило: иногда лучше взять соседний день или значение другой станции — так мы сделаем в мини-проекте модуля.

NaN и целые числа

NaN — дробное число, поэтому в массив целых его записать нельзя:

[ ]NaN в массиве целыхошибка: ValueError
counts = np.array([3, 5, 2])
counts[1] = np.nan
Ошибкасохранённый · нажмите «Выполнить», чтобы получить свой
ValueError: cannot convert float NaN to integer

Итоги

  • Пропуск в NumPy — дробное значение np.nan; массив с ним дробный.
  • Арифметика и обычные агрегаты с NaN дают NaN.
  • np.nan == np.nan — False; пропуски ищут маской np.isnan(a).
  • np.nanmean, np.nansum, np.nanmax, np.nanmin считают без пропусков и принимают axis=.
  • Заполнить пропуски — np.where(np.isnan(a), чем_заполнить, a).

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

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