Модуль 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 имеет собственную реализацию большинства функций ufuncs. Унарные и бинарные функции, имеющие область определения (такие как 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)
Массивы с маской также поддерживают стандартные ufuncs 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.18/reference/maskedarray.generic.html