Кусочек нарезки с чеками - PullRequest
0 голосов
/ 11 февраля 2019

Предлагает ли numpy способ проверки границ при разрезании массива?Например, если я сделаю:

arr = np.ones([2,2]) sliced_arr = arr[0:5,:]

Этот фрагмент будет в порядке, и он просто вернет мне весь arr, даже если я запросил индексы, которые не существуют.Есть ли другой способ нарезать NumPy, который выдаст ошибку, если я попытаюсь срезать за пределы массива?

Ответы [ 2 ]

0 голосов
/ 11 февраля 2019

Это закончилось немного дольше, чем ожидалось, но вы можете написать свою собственную обертку, которая проверяет операции get, чтобы убедиться, что срезы не выходят за пределы (аргументы индексации, которые не являются срезами, уже проверяются NumPy).Я думаю, что я рассмотрел все случаи здесь (многоточие, np.newaxis, отрицательные шаги ...), хотя все еще может быть некоторый неудачный угловой случай.

import numpy as np

# Wrapping function
def bounds_checked_slice(arr):
    return SliceBoundsChecker(arr)

# Wrapper that checks that indexing slices are within bounds of the array
class SliceBoundsChecker:

    def __init__(self, arr):
        self._arr = np.asarray(arr)

    def __getitem__(self, args):
        # Slice bounds checking
        self._check_slice_bounds(args)
        return self._arr.__getitem__(args)

    def __setitem__(self, args, value):
        # Slice bounds checking
        self._check_slice_bounds(args)
        return self._arr.__setitem__(args, value)

    # Check slices in the arguments are within bounds
    def _check_slice_bounds(self, args):
        if not isinstance(args, tuple):
            args = (args,)
        # Iterate through indexing arguments
        arr_dim = 0
        i_arg = 0
        for i_arg, arg in enumerate(args):
            if isinstance(arg, slice):
                self._check_slice(arg, arr_dim)
                arr_dim += 1
            elif arg is Ellipsis:
                break
            elif arg is np.newaxis:
                pass
            else:
                arr_dim += 1
        # Go backwards from end after ellipsis if necessary
        arr_dim = -1
        for arg in args[:i_arg:-1]:
            if isinstance(arg, slice):
                self._check_slice(arg, arr_dim)
                arr_dim -= 1
            elif arg is Ellipsis:
                raise IndexError("an index can only have a single ellipsis ('...')")
            elif arg is np.newaxis:
                pass
            else:
                arr_dim -= 1

    # Check a single slice
    def _check_slice(self, slice, axis):
        size = self._arr.shape[axis]
        start = slice.start
        stop = slice.stop
        step = slice.step if slice.step is not None else 1
        if step == 0:
            raise ValueError("slice step cannot be zero")
        bad_slice = False
        if start is not None:
            start = start if start >= 0 else start + size
            bad_slice |= start < 0 or start >= size
        else:
            start = 0 if step > 0 else size - 1
        if stop is not None:
            stop = stop if stop >= 0 else stop + size
            bad_slice |= (stop < 0 or stop > size) if step > 0 else (stop < 0 or stop >= size)
        else:
            stop = size if step > 0 else -1
        if bad_slice:
            raise IndexError("slice {}:{}:{} is out of bounds for axis {} with size {}".format(
                slice.start if slice.start is not None else '',
                slice.stop if slice.stop is not None else '',
                slice.step if slice.step is not None else '',
                axis % self._arr.ndim, size))

Небольшая демонстрация:

import numpy as np

a = np.arange(24).reshape(4, 6)
print(bounds_checked_slice(a)[:2, 1:5])
# [[ 1  2  3  4]
#  [ 7  8  9 10]]
bounds_checked_slice(a)[:2, 4:10]
# IndexError: slice 4:10: is out of bounds for axis 1 with size 6

Если вы хотите, вы можете даже сделать это подклассом ndarray , так что вы получите такое поведение по умолчанию, вместо того, чтобы каждый раз оборачивать массив.

Также обратите вниманиечто могут быть некоторые вариации относительно того, что вы можете считать «за пределами».Приведенный выше код учитывает, что выход хотя бы одного индекса за пределы размера выходит за пределы, что означает, что вы не можете взять пустой фрагмент с чем-то вроде arr[len(arr):].В принципе, вы можете редактировать код, если думаете о немного другом поведении.

0 голосов
/ 11 февраля 2019

Если вы используете range вместо обычной записи срезов, вы можете получить ожидаемое поведение.Например, для правильной нарезки:

arr[range(2),:]

array([[1., 1.],
       [1., 1.]])

И если мы попытаемся нарезать, например, с помощью:

arr[range(5),:]

Это выдаст следующую ошибку:

IndexError: индекс 2 выходит за границы для размера 2

Я думаю, почему это приводит к ошибке, так как нарезка с использованием обычной записи среза является базовым свойством в массивах numpy, а также списках,и, таким образом, вместо того, чтобы выбросить индекс из-за ошибки диапазона, когда мы пытаемся срезать с неправильными индексами, он уже рассматривает это и обрезает до ближайших действительных индексов.Принимая во внимание, что это, по-видимому, не рассматривается при разрезании с range, который является неизменным объектом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...