Коротко
- Модуль
np.ma: массив данных плюс булева маска;Trueв маске — «значения нет». - Агрегации (
mean,sum,count…) и арифметика пропускают маскированные элементы, маска переносится в результат. - Создание:
masked_array(data, mask),masked_where(cond, data),masked_equal(data, value),masked_invalid(NaN и inf). - Обратно в обычный массив:
filled(значение)илиcompressed()— только известные. - Плюс перед
NaN: работает с целыми и не требует смены типа. Минус: медленнее и поддерживается не всеми библиотеками.
Синтаксис
m = np.ma.masked_array(data, mask=cond)
m = np.ma.masked_where(cond, data)
m = np.ma.masked_equal(data, -1)
m = np.ma.masked_invalid(data) # NaN и inf
m.mean(), m.count()
m.filled(0), m.compressed()Примеры
# количество заказов по дням; -1 — магазин закрыт, данных нет
orders = np.array([12, 15, -1, 9, -1, 20])m = np.ma.masked_array(orders, mask=orders < 0)
mВывод
masked_array(data=[12, 15, --, 9, --, 20],
mask=[False, False, True, False, True, False],
fill_value=999999)m = np.ma.masked_equal(orders, -1)
print(m.mean(), m.sum(), m.count()) # маскированные элементы не учитываются
orders.mean() # обычный массив учитывает −1Вывод
14.0 56 4 np.float64(9.0)
temps = np.array([12.5, np.nan, 14.0, np.inf])
np.ma.masked_invalid(temps).mean() # маскирует NaN и infВывод
np.float64(13.25)
m = np.ma.masked_equal(orders, -1)
m * 10 # маска сохраняетсяВывод
masked_array(data=[120, 150, --, 90, --, 200],
mask=[False, False, True, False, True, False],
fill_value=-1)m = np.ma.masked_equal(orders, -1)
print(m.filled(0)) # обычный массив, пропуски — 0
m.compressed() # только известные значенияВывод
[12 15 0 9 0 20] array([12, 15, 9, 20])
Подводные камни
Функции вне np.ma не знают о маске
Многие функции NumPy и других библиотек работают с данными под маской как с обычными значениями. Используйте функции из np.ma (np.ma.median, np.ma.concatenate…) или сначала filled / compressed.
m = np.ma.masked_equal(orders, -1)
print(np.concatenate([m, m]).mean()) # маска потеряна: −1 участвуют в среднем
np.ma.concatenate([m, m]).mean()Вывод
9.0 np.float64(14.0)
Когда нужна маска, а не NaN
Замена пропусков на NaN превращает целые в float. Маска сохраняет исходный тип.
m = np.ma.masked_equal(orders, -1)
m.dtype # тип остался целым — в отличие от замены на NaNВывод
dtype('int64')