NumPy - Более быстрые операции над замаскированным массивом? - PullRequest
2 голосов
/ 14 марта 2020

У меня есть массив numpy:

import numpy as np
arr = np.random.rand(100)

Если я хочу найти его максимальное значение, я запускаю np.amax, который запускает 155,357 раз в секунду на моей машине.

Однако по некоторым причинам мне приходится маскировать некоторые из его значений. Например, давайте замаскируем только одну ячейку:

import numpy.ma as ma
arr = ma.masked_array(arr, mask=[0]*99 + [1])

Теперь нахождение максимума происходит намного медленнее, когда 26,574 раз в секунду.

Это только 17% скорости этой операции для немаскированного массива.

Другими операциями, например, являются subtract, add и multiply. Хотя в массиве с маской они работают на ВСЕХ ЗНАЧЕНИЙ , это всего лишь 4% скорости по сравнению с массивом без маски (15 433/497 663)

Я ищу более быстрый способ работы с замаскированными массивами, как этот, независимо от того, используется ли он numpy или нет.

(мне нужно запустить это на реальных данных, то есть массивах с несколькими измерениями, и миллионы клеток)

1 Ответ

4 голосов
/ 14 марта 2020

MaskedArray является подклассом базы numpy ndarray. У него нет собственного скомпилированного кода. Подробности смотрите в каталоге numpy/ma/ или в главном файле:

/usr/local/lib/python3.6/dist-packages/numpy/ma/core.py

Маскированный массив имеет ключевые атрибуты, data и mask, один из них - массив данных, который вы использовали для его создания. другой логический массив того же размера.

Таким образом, все операции должны учитывать эти два массива. Он не только вычисляет новый data, но также должен вычислять новый mask.

. Может потребоваться несколько подходов (в зависимости от операции):

  • использовать data как есть

  • использовать сжатый data - новый массив с удаленными маскированными значениями

  • использовать заполненный data, где замаскированные значения заменены fillvalue или некоторым безобидным значением (например, 0 при выполнении сложения, 1 при выполнении умножения).

Количество маскируемых значений, 0 или все, мало что делает, если есть, разница в скорости.

Так что разница в скорости, которую вы видите, не удивительна. Там много дополнительных вычислений происходит. В файле ma.core.py говорится, что этот пакет был впервые разработан за 10 * 10 дней и включен в numpy около 2005 года. Несмотря на то, что были внесены изменения, чтобы поддерживать его в актуальном состоянии, я не думаю, что он был существенно переработан .

Вот код для np.ma.max метода:

def max(self, axis=None, out=None, fill_value=None, keepdims=np._NoValue):

    kwargs = {} if keepdims is np._NoValue else {'keepdims': keepdims}

    _mask = self._mask
    newmask = _check_mask_axis(_mask, axis, **kwargs)
    if fill_value is None:
        fill_value = maximum_fill_value(self)
    # No explicit output
    if out is None:
        result = self.filled(fill_value).max(
            axis=axis, out=out, **kwargs).view(type(self))
        if result.ndim:
            # Set the mask
            result.__setmask__(newmask)
            # Get rid of Infs
            if newmask.ndim:
                np.copyto(result, result.fill_value, where=newmask)
        elif newmask:
            result = masked
        return result
    # Explicit output
    ....

Ключевые шаги:

fill_value = maximum_fill_value(self)  # depends on dtype
self.filled(fill_value).max(
            axis=axis, out=out, **kwargs).view(type(self))

Вы можете поэкспериментировать с filled, чтобы увидеть, что происходит с ваш массив.

In [40]: arr = np.arange(10.)                                                                                        
In [41]: arr                                                                                                         
Out[41]: array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
In [42]: Marr = np.ma.masked_array(arr, mask=[0]*9 + [1])                                                            
In [43]: Marr                                                                                                        
Out[43]: 
masked_array(data=[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, --],
             mask=[False, False, False, False, False, False, False, False,
                   False,  True],
       fill_value=1e+20)
In [44]: np.ma.maximum_fill_value(Marr)                                                                              
Out[44]: -inf
In [45]: Marr.filled()                                                                                               
Out[45]: 
array([0.e+00, 1.e+00, 2.e+00, 3.e+00, 4.e+00, 5.e+00, 6.e+00, 7.e+00,
       8.e+00, 1.e+20])
In [46]: Marr.filled(_44)                                                                                            
Out[46]: array([  0.,   1.,   2.,   3.,   4.,   5.,   6.,   7.,   8., -inf])
In [47]: arr.max()                                                                                                   
Out[47]: 9.0
In [48]: Marr.max()                                                                                                  
Out[48]: 8.0
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...