Датчик на метеостанции иногда не передаёт показание, в анкете не ответили на вопрос, в таблице пустая клетка. Такие места называют пропусками (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: он не равен даже самому себе. «Неизвестное равно неизвестному?» — ответ «нет»:
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
В readings — температура за неделю, два дня датчик молчал. Сохраните в gaps число пропусков, в known —
массив только известных значений, а в week_avg — среднюю температуру по известным дням.
np.isnan(readings); число True в ней — sum.readings[~np.isnan(readings)].Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
readings = np.array([-3.1, np.nan, -2.4, -4.0, np.nan, -1.8, -2.2])
gaps = ...
known = ...
week_avg = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
readings = np.array([-3.1, np.nan, -2.4, -4.0, np.nan, -1.8, -2.2])
gaps = np.isnan(readings).sum()
known = readings[~np.isnan(readings)]
week_avg = known.mean()Что выведет print(np.nan == np.nan)?
False. Поэтому пропуски ищут np.isnan, а не ==.Итоги без пропусков: nan-функции
Отфильтровывать пропуски перед каждым расчётом утомительно. У NumPy есть готовые функции, которые их
пропускают: np.nanmean, np.nansum, np.nanmax, np.nanmin — те же mean, sum, max, min, только
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.]
В week — температура трёх станций (строки) за семь дней (столбцы) с пропусками. Сохраните в station_avg
среднюю температуру каждой станции, а в day_max — самую высокую температуру каждого дня среди станций.
axis=1.np.nanmean(week, axis=1) и np.nanmax(week, axis=0).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
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 = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
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 = np.nanmean(week, axis=1)
day_max = np.nanmax(week, axis=0)Заполнить пропуски
Иногда пропуски нужно не пропустить, а заполнить — например, чтобы построить график без дыр. Простой способ —
подставить среднее по известным значениям. Здесь пригодится np.where из урока «Условная замена» (модуль 4): где пропуск —
среднее, где нет — само значение.
Сохраните в filled копию readings, где пропуски заменены средней температурой по известным дням. Сам
readings не меняйте.
np.nanmean(readings).np.where(np.isnan(readings), среднее, readings).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
filled = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
filled = np.where(np.isnan(readings), np.nanmean(readings), readings)Заполнение средним сохраняет среднюю, но делает данные «глаже», чем они были. Это решение аналитика, а не правило: иногда лучше взять соседний день или значение другой станции — так мы сделаем в мини-проекте модуля.
NaN и целые числа
NaN — дробное число, поэтому в массив целых его записать нельзя:
counts = np.array([3, 5, 2])
counts[1] = np.nanValueError: 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).