Модуль 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])Маскирование массива, где значения равны заданному.
...
...
Доступ к данным
К данным базового массива можно получить доступ несколькими способами:
- через атрибут
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–2022 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.21/reference/maskedarray.generic.html