Модуль numpy.ma
Обоснование
Маскированные массивы — это массивы, которые могут иметь пропущенные или недопустимые элементы. Модуль numpy.ma предоставляет практически идентичную замену модулю numpy, поддерживающую массивы данных с масками.
Что такое маскированный массив?
Во многих случаях наборы данных могут быть неполными или испорченными наличием недопустимых данных. Например, датчик может не записать данные или записать недопустимое значение. Модуль numpy.ma предоставляет удобный способ решения этой проблемы, введя маскированные массивы.
Маскированный массив — это комбинация стандартного numpy.ndarray и маски. Маска либо nomask, что означает, что ни одно значение связанного массива не является недопустимым, либо массив булевых значений, определяющий для каждого элемента связанного массива, является ли значение допустимым или нет. Когда элемент маски False, соответствующий элемент связанного массива является допустимым и считается не замаскированным. Когда элемент маски True, соответствующий элемент связанного массива считается замаскированным (недопустимым).
Пакет гарантирует, что замаскированные элементы не используются в вычислениях.
В качестве иллюстрации рассмотрим следующий набор данных:
>>> import numpy as np >>> import numpy.ma as ma >>> x = np.array([1, 2, 3, -1, 5])
Мы хотим пометить четвертый элемент как недопустимый. Самый простой способ — создать маскированный массив:
>>> mx = ma.masked_array(x, mask=[0, 0, 0, 1, 0])
Теперь мы можем вычислить среднее значение набора данных, не учитывая недопустимые данные:
>>> mx.mean() 2.75
Модуль numpy.ma
Основной особенностью модуля numpy.ma является класс MaskedArray, который является подклассом numpy.ndarray. Подробнее о классе, его атрибутах и методах см. в разделе Класс MaskedArray.
Модуль numpy.ma может использоваться как дополнение к numpy:
>>> import numpy as np >>> import numpy.ma as ma
Чтобы создать массив с недопустимым вторым элементом, мы должны сделать следующее:
>>> y = ma.array([1, 2, 3], mask = [0, 1, 0])
Чтобы создать маскированный массив, где все значения, близкие к 1.e20, считаются недопустимыми, мы должны сделать следующее:
>>> z = ma.masked_values([1.0, 1.e20, 3.0, 4.0], 1.e20)
Полное обсуждение методов создания маскированных массивов см. в разделе Создание маскированных массивов.
Использование numpy.ma
Создание маскированных массивов
Существует несколько способов создания маскированного массива.
- Первое — непосредственно вызвать класс
MaskedArray. -
Второе — использовать два конструктора маскированных массивов:
arrayиmasked_array.array(data[, dtype, copy, order, mask, …])Класс массива, возможно, с замаскированными значениями.
псевдоним
numpy.ma.core.MaskedArray -
Третий вариант — взять представление существующего массива. В этом случае маска представления устанавливается в
nomask, если массив не имеет именованных полей, или массив булевых значений с той же структурой, что и массив, в противном случае.>>> x = np.array([1, 2, 3]) >>> x.view(ma.MaskedArray) masked_array(data=[1, 2, 3], mask=False, fill_value=999999) >>> x = np.array([(1, 1.), (2, 2.)], dtype=[('a',int), ('b', float)]) >>> x.view(ma.MaskedArray) masked_array(data=[(1, 1.0), (2, 2.0)], mask=[(False, False), (False, False)], fill_value=(999999, 1.e+20), dtype=[('a', '<i8'), ('b', '<f8')]) -
Еще один вариант — использовать любую из следующих функций:
asarray(a[, dtype, order])Преобразовать вход в маскированный массив заданного типа данных.
asanyarray(a[, dtype])Преобразовать вход в маскированный массив, сохраняя подклассы.
fix_invalid(a[, mask, copy, fill_value])Возвращает входные данные с недопустимыми данными, замаскированными и замененными значением заполнения.
masked_equal(x, value[, copy])Маскировать массив, где равны данному значению.
masked_greater(x, value[, copy])Маскировать массив, где больше, чем данное значение.
masked_greater_equal(x, value[, copy])Маскировать массив, где больше или равно данному значению.
masked_inside(x, v1, v2[, copy])Маскировать массив внутри заданного интервала.
masked_invalid(a[, copy])Маскировать массив, где встречаются недопустимые значения (NaN или inf).
masked_less(x, value[, copy])Маскировать массив, где меньше, чем данное значение.
masked_less_equal(x, value[, copy])Маскировать массив, где меньше или равно данному значению.
masked_not_equal(x, value[, copy])Маскировать массив, где
notне равно данному значению.masked_object(x, value[, copy, shrink])Маскировать массив
xтам, где данные точно равны значению.masked_outside(x, v1, v2[, copy])Маскировать массив за пределами заданного интервала.
masked_values(x, value[, rtol, atol, copy, …])Маскирование с использованием равенства с плавающей точкой.
masked_where(condition, a[, copy])Маскировать массив, где выполняется условие.
Доступ к данным
К данным базового массива можно получить доступ несколькими способами:
- через атрибут
data. Результат — представление массива какnumpy.ndarrayили одного из его подклассов, в зависимости от типа базовых данных при создании маскированного массива. - через метод
__array__. Результатом будетnumpy.ndarray. - путем непосредственного взятия представления маскированного массива как
numpy.ndarrayили одного из его подклассов (на самом деле именно так и работает атрибутdata). - с помощью функции
getdata.
Ни один из этих методов не является полностью удовлетворительным, если некоторые записи были помечены как недопустимые. Как общее правило, если требуется представление массива без замаскированных записей, рекомендуется использовать метод filled для заполнения массива.
Доступ к маске
К маске маскированного массива можно получить доступ через атрибут mask. Следует помнить, что запись True в маске указывает на недопустимые данные.
Ещё один вариант — использовать функции getmask и getmaskarray. getmask(x) возвращает маску x, если x является массивом с пропусками, и специальное значение nomask в противном случае. getmaskarray(x) возвращает маску x, если x является массивом с пропусками. Если x не содержит недопустимых значений или не является массивом с пропусками, функция возвращает булевый массив False с количеством элементов, равным x.
Доступ только к допустимым значениям
Чтобы получить только допустимые значения, можно использовать инвертированную маску в качестве индекса. Инвертировать маску можно с помощью функции numpy.logical_not или просто оператора ~:
>>> x = ma.array([[1, 2], [3, 4]], mask=[[0, 1], [1, 0]])
>>> x[~x.mask]
masked_array(data=[1, 4],
mask=[False, False],
fill_value=999999)
Ещё один способ получить допустимые данные — использовать метод compressed, который возвращает одномерный ndarray (или один из его подклассов, в зависимости от значения атрибута baseclass):
>>> x.compressed() array([1, 4])
Обратите внимание, что результат работы compressed всегда одномерный.
Изменение маски
Маскирование элемента
Рекомендуемый способ отметить один или несколько конкретных элементов массива с пропусками как недопустимые — присвоить им специальное значение masked:
>>> x = ma.array([1, 2, 3])
>>> x[0] = ma.masked
>>> x
masked_array(data=[--, 2, 3],
mask=[ True, False, False],
fill_value=999999)
>>> y = ma.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> y[(0, 1, 2), (1, 2, 0)] = ma.masked
>>> y
masked_array(
data=[[1, --, 3],
[4, 5, --],
[--, 8, 9]],
mask=[[False, True, False],
[False, False, True],
[ True, False, False]],
fill_value=999999)
>>> z = ma.array([1, 2, 3, 4])
>>> z[:-2] = ma.masked
>>> z
masked_array(data=[--, --, 3, 4],
mask=[ True, True, False, False],
fill_value=999999)
Второй вариант — напрямую изменить mask, но от такого использования лучше отказаться.
Примечание
При создании нового массива с пропусками с простым, не структурированным типом данных, маска изначально устанавливается в специальное значение nomask, что примерно соответствует булевому False. Попытка установить элемент nomask завершится ошибкой TypeError, так как булевое значение не поддерживает присвоение элементов.
Все элементы массива можно сразу замаскировать, присвоив значение True маске:
>>> x = ma.array([1, 2, 3], mask=[0, 0, 1])
>>> x.mask = True
>>> x
masked_array(data=[--, --, --],
mask=[ True, True, True],
fill_value=999999,
dtype=int64)
Наконец, отдельные элементы можно замаскировать и/или размаскировать, присвоив маске последовательность булевых значений:
>>> x = ma.array([1, 2, 3])
>>> x.mask = [0, 1, 0]
>>> x
masked_array(data=[1, --, 3],
mask=[False, True, False],
fill_value=999999)
Размаскирование элемента
Чтобы размаскировать один или несколько элементов, достаточно присвоить им новые допустимые значения:
>>> x = ma.array([1, 2, 3], mask=[0, 0, 1])
>>> x
masked_array(data=[1, 2, --],
mask=[False, False, True],
fill_value=999999)
>>> x[-1] = 5
>>> x
masked_array(data=[1, 2, 5],
mask=[False, False, False],
fill_value=999999)
Примечание
Размаскирование элемента при помощи прямого присваивания завершится без ошибок, если массив с пропусками имеет жёсткую маску, как указано атрибутом hardmask. Эта особенность была введена для предотвращения перезаписи маски. Чтобы принудительно размаскировать элемент в массиве с жёсткой маской, маску нужно сначала размягчить с помощью метода soften_mask перед присваиванием. Можно снова сделать маску жёсткой с помощью harden_mask:
>>> x = ma.array([1, 2, 3], mask=[0, 0, 1], hard_mask=True)
>>> x
masked_array(data=[1, 2, --],
mask=[False, False, True],
fill_value=999999)
>>> x[-1] = 5
>>> x
masked_array(data=[1, 2, --],
mask=[False, False, True],
fill_value=999999)
>>> x.soften_mask()
masked_array(data=[1, 2, --],
mask=[False, False, True],
fill_value=999999)
>>> x[-1] = 5
>>> x
masked_array(data=[1, 2, 5],
mask=[False, False, False],
fill_value=999999)
>>> x.harden_mask()
masked_array(data=[1, 2, 5],
mask=[False, False, False],
fill_value=999999)
Чтобы размаскировать все замаскированные элементы массива с пропусками (при условии, что маска не является жёсткой), проще всего присвоить константу nomask маске:
>>> x = ma.array([1, 2, 3], mask=[0, 0, 1])
>>> x
masked_array(data=[1, 2, --],
mask=[False, False, True],
fill_value=999999)
>>> x.mask = ma.nomask
>>> x
masked_array(data=[1, 2, 3],
mask=[False, False, False],
fill_value=999999)
Индексирование и срезы
Поскольку MaskedArray является подклассом numpy.ndarray, он наследует механизмы индексирования и срезов.
При доступе к отдельному элементу массива с пропусками без именованных полей, результатом будет скаляр (если соответствующий элемент маски False) или специальное значение masked (если соответствующий элемент маски True):
>>> x = ma.array([1, 2, 3], mask=[0, 0, 1]) >>> x[0] 1 >>> x[-1] masked >>> x[-1] is ma.masked True
Если массив с пропусками имеет именованные поля, доступ к отдельному элементу возвращает объект numpy.void если ни одно из полей не замаскировано, или массив с пропусками размера 0 с тем же типом данных, что и исходный массив, если хотя бы одно из полей замаскировано.
>>> y = ma.masked_array([(1,2), (3, 4)],
... mask=[(0, 0), (0, 1)],
... dtype=[('a', int), ('b', int)])
>>> y[0]
(1, 2)
>>> y[-1]
(3, --)
При доступе к срезу результатом будет массив с пропусками, чьё атрибут data является представлением оригинальных данных, а маска — либо nomask (если в исходном массиве не было недопустимых значений), либо представление соответствующего среза исходной маски. Представление необходимо для обеспечения передачи любых изменений маски в исходный массив.
>>> x = ma.array([1, 2, 3, 4, 5], mask=[0, 1, 0, 0, 1])
>>> mx = x[:3]
>>> mx
masked_array(data=[1, --, 3],
mask=[False, True, False],
fill_value=999999)
>>> mx[1] = -1
>>> mx
masked_array(data=[1, -1, 3],
mask=[False, False, False],
fill_value=999999)
>>> x.mask
array([False, False, False, False, True])
>>> x.data
array([ 1, -1, 3, 4, 5])
Доступ к полю массива с пропусками со структурированным типом данных возвращает MaskedArray.
Операции над массивами с пропусками
Арифметические и сравнения операции поддерживаются массивами с пропусками. По возможности, недопустимые элементы массива с пропусками не обрабатываются, что означает, что соответствующие data элементы должны быть одинаковыми до и после операции.
Предупреждение
Стоит отметить, что это поведение может не быть систематичным, замаскированные данные могут быть изменены операцией в некоторых случаях, поэтому пользователи не должны полагаться на то, что эти данные останутся неизменными.
Модуль numpy.ma содержит особую реализацию большинства ufunc. Унарные и бинарные функции, которые имеют область определения (такие как log или divide) возвращают константу masked всякий раз, когда вход замаскирован или выходит за пределы области определения:
>>> ma.log([-1, 0, 1, 2])
masked_array(data=[--, --, 0.0, 0.6931471805599453],
mask=[ True, True, False, False],
fill_value=1e+20)
Массивы с пропусками также поддерживают стандартные ufunc numpy. Результат — массив с пропусками. Результат унарной ufunc замаскирован там, где вход замаскирован. Результат бинарной ufunc замаскирован там, где любой из входов замаскирован. Если ufunc также возвращает контекст выхода (кортеж из 3 элементов, содержащий имя ufunc, её аргументы и её область определения), контекст обрабатывается, и элементы выходного массива с пропусками замаскированы там, где соответствующие входные данные выходят за пределы области определения:
>>> x = ma.array([-1, 1, 0, 2, 3], mask=[0, 0, 0, 0, 1])
>>> np.log(x)
masked_array(data=[--, 0.0, --, 0.6931471805599453, --],
mask=[ True, False, True, False, True],
fill_value=1e+20)
Примеры
Данные с заданным значением, обозначающим пропущенные данные
Рассмотрим список элементов, x, где значения -9999. представляют пропущенные данные. Мы хотим вычислить среднее значение данных и вектор аномалий (отклонения от среднего):
>>> import numpy.ma as ma >>> x = [0.,1.,-9999.,3.,4.] >>> mx = ma.masked_values (x, -9999.) >>> print(mx.mean()) 2.0 >>> print(mx - mx.mean()) [-2.0 -1.0 -- 1.0 2.0] >>> print(mx.anom()) [-2.0 -1.0 -- 1.0 2.0]
Заполнение пропущенных данных
Предположим, что мы хотим вывести те же данные, но с заменой пропущенных значений на среднее значение.
>>> print(mx.filled(mx.mean())) [ 0. 1. 2. 3. 4.]
Числовые операции
Числовые операции можно легко выполнять, не беспокоясь о пропущенных значениях, делении на ноль, квадратных корнях из отрицательных чисел и т. д.:
>>> import numpy.ma as ma >>> x = ma.array([1., -1., 3., 4., 5., 6.], mask=[0,0,0,0,1,0]) >>> y = ma.array([1., 2., 0., 4., 5., 6.], mask=[0,0,0,0,0,1]) >>> print(ma.sqrt(x/y)) [1.0 -- -- 1.0 -- --]
Четыре значения результата недопустимы: первое получено из вычисления квадратного корня из отрицательного числа, второе — из деления на ноль, а два последних — когда входы были замаскированы.
Игнорирование экстремальных значений
Рассмотрим массив d чисел с плавающей точкой в диапазоне от 0 до 1. Мы хотим вычислить среднее значение значений d, игнорируя любые данные за пределами диапазона [0.2, 0.9]:
>>> d = np.linspace(0, 1, 20) >>> print(d.mean() - ma.masked_outside(d, 0.2, 0.9).mean()) -0.05263157894736836
© 2005–2020 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.19/reference/maskedarray.generic.html