The 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
The numpy.ma модуль#
Основная особенность numpy.ma модуль является MaskedArray
класс, который является подклассом numpy.ndarray. Класс, его атрибуты и методы описаны более подробно в
класс MaskedArray раздел.
The 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, ...])Класс массива с возможными замаскированными значениями.
псевдоним
MaskedArrayТретий вариант — взять представление существующего массива. В этом случае маска представления устанавливается в
nomaskесли массив не имеет именованных полей, или массив логических значений с той же структурой, что и исходный массив.
>>> import numpy as np
>>> 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, 1e+20),
dtype=[('a', '
Ещё одна возможность - использовать любую из следующих функций:
asarray(a[, dtype, order])Преобразовать входные данные в маскированный массив заданного типа данных.
asanyarray(a[, dtype, order])Преобразовать входные данные в маскированный массив, сохраняя подклассы.
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])Маскировать массив, где не равно заданному значению.
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 запись в маске указывает на
invalid data.
Другая возможность - использовать getmask и getmaskarray
функций. getmask(x) выводит маску x if x является маскированным массивом, и специальное значение nomask в противном случае. getmaskarray(x)
выводит маску x if x является маскированным массивом. Если x не содержит недопустимых записей или не является маскированным массивом, функция выводит логический массив
False с таким же количеством элементов, как x.
Доступ только к действительным записям#
Чтобы получить только действительные записи, мы можем использовать инверсию маски в качестве индекса. Инверсию маски можно вычислить с помощью
numpy.logical_not функция или просто с ~ оператор:
>>> import numpy as np
>>> 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 к маске:
>>> import numpy.ma as ma
>>> 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)
Снятие маски с записи#
Чтобы снять маску с одной или нескольких конкретных записей, мы можем просто присвоить им одно или несколько новых допустимых значений:
>>> import numpy.ma as ma
>>> 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)
Примечание
Снятие маски с записи путем прямого присваивания молча завершится неудачей, если маскированный массив имеет hard маска, как показано hardmask
атрибут. Эта функция была введена для предотвращения перезаписи маски.
Чтобы принудительно снять маску с элемента, где массив имеет жесткую маску,
сначала необходимо смягчить маску, используя soften_mask метод
перед выделением. Его можно повторно усилить с помощью harden_mask как
следует:
>>> import numpy.ma as ma
>>> 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 к маске:
>>> import numpy.ma as ma
>>> 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):
>>> import numpy.ma as ma
>>> 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
с тем же dtype, что и исходный массив, если хотя бы одно из полей
замаскировано.
>>> import numpy.ma as ma
>>> 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 (если в исходном массиве не было недопустимых записей) или представление соответствующего среза исходной маски. Представление требуется для обеспечения распространения любых изменений маски на исходную.
>>> import numpy.ma as ma
>>> 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 записи
должен должен быть одинаковым до и после операции.
Предупреждение
Необходимо подчеркнуть, что такое поведение может быть несистематическим, что замаскированные данные могут быть затронуты операцией в некоторых случаях, и поэтому пользователям не следует полагаться на неизменность этих данных.
The numpy.ma модуль поставляется с конкретной реализацией большинства
универсальных функций. Унарные и бинарные функции, имеющие область допустимых значений (такие как
log или divide) возвращают masked
константа, когда входные данные маскированы или выходят за пределы допустимой области:
>>> import numpy.ma as ma
>>> 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, его аргументы и домен), контекст обрабатывается, и записи выходного маскированного массива маскируются везде, где соответствующие входные данные выходят за пределы допустимого домена:
>>> import numpy.ma as ma
>>> 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]
Заполнение пропущенных данных#
Предположим теперь, что мы хотим вывести те же данные, но с заменой пропущенных значений на среднее значение.
>>> import numpy.ma as ma
>>> mx = ma.masked_values (x, -9999.)
>>> 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]:
>>> import numpy as np
>>> import numpy.ma as ma
>>> d = np.linspace(0, 1, 20)
>>> print(d.mean() - ma.masked_outside(d, 0.2, 0.9).mean())
-0.05263157894736836