Numpy - получить среднее значение по оси, но с другим подмножеством этой оси в каждой ячейке - PullRequest
1 голос
/ 09 июля 2020

Мне нужно среднее значение по оси времени массива (1).

Уловка: это будет не среднее значение всех значений вдоль этой оси, а скорее подмножество, которое начинается с индекса, который дается в массиве (2).

Массивы, с которыми я работаю:

 (array1) 3 axes: time, x, y
array([[[ 820,  820,  720,  720],
        [ 860,  860,  500,  500],
        [ 860,  860,  500,  500],
        [ 860,  860,  500,  500]],
       [[5980, 5980, 4760, 4760],
        [7500, 7500, 7940, 7940],
        [7500, 7500, 7940, 7940],
        [7500, 7500, 7940, 7940]],
       [[ 740,  740,  440,  440],
        [1240, 1240, 1140, 1140],
        [1240, 1240, 1140, 1140],
        [1240, 1240, 1140, 1140]],
       [[3200, 3200, 7600, 7600],
        [ 900,  900,  400,  400],
        [ 900,  900,  400,  400],
        [ 900,  900,  400,  400]]])
 (array2) 2 axes: x, y 
array([[  1,   2,   1,   1],
       [  1,   0,   3,   3],
       [  4,   0,   2,   2],
       [  4,   0,   1,   2]])

Для дальнейшей иллюстрации примера:

Значения в array1 представляют количество осадков за день в местах х / у. Значения в array2 представляют, с какого дня необходимо рассчитать среднее значение для местоположения x / y.

Глядя на первую ячейку, мы бы исключили первый день из расчета, как array2 [0,0] = 1. Таким образом, наш результат будет np.mean (array1 [1 :, 0, 0]) = 3306.67.

То, что я не могу понять, - это то, как указать подмножество для каждой ячейки на основе array 2. Я знаю, что могу использовать np.mean по любой оси, но как я могу динамически исключать значения (срезать массив) из расчета?

Ответы [ 2 ]

1 голос
/ 10 июля 2020
arr1 = np.array(
    [[[ 820,  820,  720,  720],
      [ 860,  860,  500,  500],
      [ 860,  860,  500,  500],
      [ 860,  860,  500,  500]],
     
     [[5980, 5980, 4760, 4760],
      [7500, 7500, 7940, 7940],
      [7500, 7500, 7940, 7940],
      [7500, 7500, 7940, 7940]],
     
     [[ 740,  740,  440,  440],
      [1240, 1240, 1140, 1140],
      [1240, 1240, 1140, 1140],
      [1240, 1240, 1140, 1140]],
     
     [[3200, 3200, 7600, 7600],
      [ 900,  900,  400,  400],
      [ 900,  900,  400,  400],
      [ 900,  900,  400,  400]]]
)
arr2 = np.array(
    [[  1,   2,   1,   1],
     [  1,   0,   3,   3],
     [  3,   0,   2,   2],
     [  3,   0,   1,   2]]
)

то, что мы пытаемся сделать, это разрезать временную ось arr1, используя индексы, хранящиеся в arr2, теперь python позволяет разрезать только с использованием :, которое мы можем только передать при буквальном индексировании ie не использует другой массив для индексации. поэтому нам нужен обходной способ сделать это

, одним из способов может быть изменение всех значений в arr1, которые были бы проигнорированы при нарезке, на 0

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

no_days = arr1.shape[0]
arr3 = np.arange(no_days)
arr3.shape = [-1,1,1]
arr3

>>> [[[0]],

     [[1]],

     [[2]],

     [[3]]]
filter = arr3 < arr2
filter.shape

>>> (4, 4, 4)

arr3 - это массив индексов временной оси. мы сравнили его с arr2, и теперь у нас есть булевы индексы значений, которые нужно игнорировать в filter, и мы можем установить их на 0

arr1[filter] = 0
arr1

>>>   [[[   0,    0,    0,    0],
        [   0,  860,    0,    0],
        [   0,  860,    0,    0],
        [   0,  860,    0,    0]],

       [[5980,    0, 4760, 4760],
        [7500, 7500,    0,    0],
        [   0, 7500,    0,    0],
        [   0, 7500, 7940,    0]],

       [[ 740,  740,  440,  440],
        [1240, 1240,    0,    0],
        [   0, 1240, 1140, 1140],
        [   0, 1240, 1140, 1140]],

       [[3200, 3200, 7600, 7600],
        [ 900,  900,  400,  400],
        [ 900,  900,  400,  400],
        [ 900,  900,  400,  400]]]

, у нас может возникнуть соблазн использовать arr1.mean(axis= 0), но при этом также учитываются все допустимые записи 0, которые влияют на среднее значение, вместо того, чтобы игнорировать их

, поэтому вместо этого мы суммируем arr1 по оси времени и не делим его ни на какие элементы, которые были бы в срезах

arr1.sum(axis= 0) / (no_days - arr2)

>>>   [[3306.66666667, 1970.        , 4266.66666667, 4266.66666667],
       [3213.33333333, 2625.        ,  400.        ,  400.        ],
       [ 900.        , 2625.        ,  770.        ,  770.        ],
       [ 900.        , 2625.        , 3160.        ,  770.        ]]

если t < x*y, то следующее будет работать быстрее

arr1.sum(axis= 0) / (~filter).astype(int).sum(axis= 0)

1 голос
/ 10 июля 2020

Я нашел способ, используя xarray . Это не совсем красиво, но, по крайней мере, я считаю, что он векторизован.

Сначала преобразуйте массивы numpy в xarray DataArray и поместите оба в Dataset, используя xr.merge:

rainfall = xr.DataArray(rainfall, dims=("day", "x", "y"), name="rainfall")
start_idxs = xr.DataArray(start_idxs, dims=("x", "y"), name="start_idxs")

ds = xr.merge((rainfall, start_idxs))

Вот как выглядит Dataset:

>>> ds
<xarray.Dataset>
Dimensions:     (day: 4, x: 4, y: 4)
Dimensions without coordinates: day, x, y
Data variables:
    rainfall    (day, x, y) int64 820 820 720 720 860 ... 400 900 900 400 400
    start_idxs  (x, y) int64 1 2 1 1 1 0 3 3 4 0 2 2 4 0 1 2

Затем мы хотим произвести другие вычисления на основе переменной start_idxs, поэтому мы groupby это переменная данных:

>>> groups = ds.groupby("start_idxs")
>>> groups
DatasetGroupBy, grouped over 'start_idxs' 
5 groups with labels 0, 1, 2, 3, 4.

Вы видите, что есть 5 групп, как и ожидалось. Теперь мы хотим применить вычисление для каждой группы, поэтому мы будем использовать map.

>>> res = groups.map(mean_start_idxs, args=("day",))
>>> res["rainfall"]
<xarray.DataArray 'rainfall' (x: 4, y: 4)>
array([[3306.66666667, 1970.        , 4266.66666667, 4266.66666667],
       [3213.33333333, 2625.        ,  400.        ,  400.        ],
       [          nan, 2625.        ,  770.        ,  770.        ],
       [          nan, 2625.        , 3160.        ,  770.        ]])
Dimensions without coordinates: x, y

Это ожидаемый результат. Обратите внимание на nan значения, в которых было предложено начинать среднее с индекса 4, всего за 4 дня это невозможно.

Но для того, чтобы это работало, нам нужно определить функцию mean_start_idxs, и это сложная часть.

Это сложно, потому что получить «метку» групп изнутри функции, вызываемой map, непросто, но вот решение:

def mean_start_idxs(ds, dim):
    # Get the start indice
    #   groups were made from start_idxs, so we can
    #   take any value of ds["start_idxs"] as a start indice
    start = ds["start_idxs"][0].item()
    end = ds.sizes[dim]

    return ds.isel({dim: slice(start, end)}).mean(dim=dim)
...