基于最小/最大值的NumPy动态数组切片

时间:2019-10-28 15:57:22

标签: python arrays numpy

我有一个hape (365, x, y的3维数组,其中36对应于每日数据。在某些情况下,沿时间轴axis=0的所有元素都是np.nan

axis=0中每个点的时间序列如下所示:

Image

我需要找到出现最大值(峰值数据)的索引,然后找到峰两边的两个最小值。

import numpy as np

a = np.random.random(365, 3, 3) * 10
a[:, 0, 0] = np.nan

peak_mask = np.ma.masked_array(a, np.isnan(a))
peak_indexes = np.nanargmax(peak_mask, axis=0)

我可以使用以下方法找到峰值之前的最小值:

early_minimum_indexes = np.full_like(peak_indexes, fill_value=0)

for i in range(peak_indexes.shape[0]):
    for j in range(peak_indexes.shape[1]):
        if peak_indexes[i, j] == 0:
            early_minimum_indexes[i, j] = 0
        else:
            early_mask = np.ma.masked_array(a, np.isnan(a))
            early_loc = np.nanargmin(early_mask[:peak_indexes[i, j], i, j], axis=0)   
            early_minimum_indexes[i, j] = early_loc

结果峰和谷的绘制如下:

Image

对于大型数组(1m +个元素),这种方法在时间上非常不合理。有没有更好的方法可以使用numpy做到这一点?

2 个答案:

答案 0 :(得分:0)

这里是一种方法

  1. 复制数据
  2. 保存所有nan位置并将所有nan替换为全局min-1
  3. 找到按行的argmax
  4. 从整行中减去其值
    • 请注意,每行现在仅具有非正值,最大值现在为零
  5. 将所有nan位置归零
  6. 将所有值的正负向右翻转
    • 这是主要思想;它在右手最小值之前的位置创建一个新的行全局最大值;同时确保左侧的min现在是行全局的
  7. 检索行式argmin和argmax,它们是原始数组中左右mins的位置
  8. 查找全南行,并使用INVALINT覆盖这些位置的最大和最小索引

代码:

INVALINT = -9999
t,x,y = a.shape
t,x,y = np.ogrid[:t,:x,:y]
inval = np.isnan(a)
b = np.where(inval,np.nanmin(a)-1,a)
pk = b.argmax(axis=0)
pkval = b[pk,x,y]
b -= pkval
b[inval] = 0
b[t>pk[None]] *= -1
ltr = b.argmin(axis=0)
rtr = b.argmax(axis=0)
del b
inval = inval.all(axis=0)
pk[inval] = INVALINT
ltr[inval] = INVALINT
rtr[inval] = INVALINT

# result is now in ltr ("left trough"), pk ("peak") and rtr

答案 1 :(得分:0)

虽然在这种情况下使用遮罩数组可能不是最有效的解决方案,但它可以让您在特定的轴上执行遮罩操作,而几乎保留形状,这非常方便。请记住,在许多情况下,被屏蔽的功能仍将最终复制被屏蔽的数据。

在当前代码中,您基本上有正确的主意,但错过了一些技巧,例如能够否定和组合蒙版。另外,将掩码作为布尔值预先分配的事实更加有效,并且像np.full(..., 0) -> np.zeros(..., dtype=bool)这样的小挑剔。

让我们从此倒退。假设您有一个行为良好的一维数组,其峰值为a1。您可以使用遮罩轻松找到最大值和最小值(或索引),如下所示:

peak_index = np.nanargmax(a1)
mask = np.zeros(a1.size, dtype=np.bool)
mask[peak:] = True
trough_plus = np.nanargmin(np.ma.array(a1, mask=~mask))
trough_minus = np.nanargmin(np.ma.array(a1, mask=mask))

这尊重这样一个事实,即相对于普通的numpy布尔索引,被掩码的数组会颠倒掩码的含义。最大值也可以出现在trough_plus的计算中,因为可以保证它不会是最小值(除非您遇到全纳情况)。

现在,如果a1已经是一个蒙版数组(但仍然是1D),则可以执行相同的操作,但是可以暂时合并蒙版。例如:

a1 = np.ma.array(a1, mask=np.isnan(a1))
peak_index = a1.argmax()
mask = np.zeros(a1.size, dtype=np.bool)
mask[peak:] = True
trough_plus = np.ma.masked_array(a1, mask=a.mask | ~mask).argmin()
trough_minus  (np.ma.masked_array(a1, mask=a.mask | mask).argmin()

同样,由于掩码数组具有反向掩码,因此像使用普通的numpy布尔掩码一样,使用|而不是&组合掩码非常重要。在这种情况下,无需调用argmaxargmin的nan版本,因为所有nan都已被屏蔽。

希望,鉴于numpy函数中普遍使用axis关键字,从这里可以清楚地实现对多维的概括:

a = np.ma.array(a, mask=np.isnan(a))
peak_indices = a.argmax(axis=0).reshape(1, *a.shape[1:])
mask = np.arange(a.shape[0]).reshape(-1, *(1,) * (a.ndim - 1)) >= peak_indices

trough_plus = np.ma.masked_array(a, mask=~mask | a.mask).argmin(axis=0)
trough_minus = np.ma.masked_array(a, mask=mask | a.mask).argmin(axis=0)

N维掩蔽技术来自Fill mask efficiently based on start indices,正是出于这一目的而被要求的。