有没有办法重塑不保持原始大小的数组(或方便的解决方法)?

Is there a way to reshape an array that does not maintain the original size (or a convenient work-around)?

作为一个简化示例,假设我有一个由 40 个排序值组成的数据集。此示例的值均为整数,但实际数据集不一定如此。

import numpy as np
data = np.linspace(1,40,40)

我试图在数据集中找到某些 window 大小的最大值。计算 window 大小的公式产生了一种最适合使用数组执行的模式(在我看来)。为了简单起见,假设表示 window 大小的索引是一个列表 [1,2,3,4,5];这对应于 [2,4,8,16,32] 的 window 大小(模式为 2**index)。

## this code looks long because I've provided docstrings
## just in case the explanation was unclear

def shapeshifter(num_col, my_array=data):
    """
    This function reshapes an array to have 'num_col' columns, where 
    'num_col' corresponds to index.
    """
    return my_array.reshape(-1, num_col)

def looper(num_col, my_array=data):
    """
    This function calls 'shapeshifter' and returns a list of the 
    MAXimum values of each row in 'my_array' for 'num_col' columns. 
    The length of each row (or the number of columns per row if you 
    prefer) denotes the size of each window.
    EX:
        num_col = 2
        ==> window_size = 2
        ==> check max( data[1], data[2] ),
                  max( data[3], data[4] ),
                  max( data[5], data[6] ), 
                               .
                               .
                               .
                  max( data[39], data[40] )
            for k rows, where k = len(my_array)//num_col
    """
    my_array = shapeshifter(num_col=num_col, my_array=data)
    rows = [my_array[index] for index in range(len(my_array))]
    res = []
    for index in range(len(rows)):
        res.append( max(rows[index]) )
    return res

到目前为止,代码没有问题。我用以下内容检查了它:

check1 = looper(2)
check2 = looper(4)
print(check1)
>> [2.0, 4.0, ..., 38.0, 40.0] 
print(len(check1))
>> 20
print(check2)
>> [4.0, 8.0, ..., 36.0, 40.0] 
print(len(check2))
>> 10

到目前为止一切顺利。现在我的问题来了。

def metalooper(col_ls, my_array=data):
    """
    This function calls 'looper' - which calls
    'shapeshifter' - for every 'col' in 'col_ls'.

    EX:
        j_list = [1,2,3,4,5]
        ==> col_ls = [2,4,8,16,32]
        ==> looper(2), looper(4),
            looper(8), ..., looper(32)
        ==> shapeshifter(2), shapeshifter(4),
            shapeshifter(8), ..., shapeshifter(32)
                such that looper(2^j) ==> shapeshifter(2^j)
                for j in j_list
    """
    res = []
    for col in col_ls:
        res.append(looper(num_col=col))
    return res

j_list = [2,4,8,16,32]
check3 = metalooper(j_list)

运行 上面的代码提供了这个错误:

ValueError: total size of new array must be unchanged

使用40 data points,数组可以重塑为20 rows2 columns,或10 rows4 columns,或10 rows8 columns 5 rows,但在 16 columns,自 40/16 ≠ integer 以来,如果不裁剪数据,则无法重塑数组。我相信这是我的代码的问题,但我不知道如何解决。

我希望有一种方法可以截断 each 行中不适合每个 window 的 last 值].如果这不可能,我希望我可以附加零来填充保持原始数组大小的条目,以便我可以删除之后的零。或者甚至可能是一些复杂的 if - try - break 块。有什么方法可以解决这个问题?

以下是使用截断重塑的通用方法:

def reshape_and_truncate(arr, shape):
    desired_size_factor = np.prod([n for n in shape if n != -1])
    if -1 in shape:  # implicit array size
        desired_size = arr.size // desired_size_factor * desired_size_factor
    else:
        desired_size = desired_size_factor
    return arr.flat[:desired_size].reshape(shape)

您的 shapeshifter 可以用来代替 reshape

我认为这将一步步为您提供您想要的东西:

def windowFunc(a, window, f = np.max):
    return np.array([f(i) for i in np.split(a, range(window, a.size, window))])

使用默认 f,这将为您提供一组最大值 windows。

通常,使用 np.splitrange,这会让您拆分成一个(可能参差不齐的)数组列表:

def shapeshifter(num_col, my_array=data):    
    return np.split(my_array, range(num_col, my_array.size, num_col))

您需要一个数组列表,因为二维数组不能参差不齐(每行需要相同的列数)

如果你真的想要用零填充,你可以使用np.lib.pad:

def shapeshifter(num_col, my_array=data):
    return np.lib.pad(my_array, (0, num_col - my.array.size % num_col), 'constant',  constant_values = 0).reshape(-1, num_col)

警告:

在技术上也可以使用,例如,a.resize(32,2) 将创建一个用零填充的 ndArray(根据您的要求)。 但是有一些重要的警告:

  1. 您需要计算第二个轴,因为 -1 技巧不适用于 resize
  2. 如果原始数组 a 被任何其他引用,a.resize 将失败并出现以下错误:

    ValueError: cannot resize an array that references or is referenced
    by another array in this way.  Use the resize function
    
  3. resize 函数(即 np.resize(a))不等同于 a.resize,因为它不会用零填充,而是循环回到开头。

由于您似乎想通过多个 windows 来引用 a,因此 a.resize 不是很有用。但这是一个容易掉进去的兔子洞。

编辑:

遍历列表很慢。如果您的输入很长而 windows 很小,上面的 windowFunc 将陷入 for 循环。这应该更有效率:

def windowFunc2(a, window, f = np.max):
    tail = - (a.size % window)
    if tail == 0:
        return f(a.reshape(-1, window), axis = -1)
    else:
        body = a[:tail].reshape(-1, window)
        return np.r_[f(body, axis = -1), f(a[tail:])]