通过移位索引在2d数组中移位数据

时间:2022-04-10 21:40:52

I need to shift a 2D array field, i.e. I have a "previous_data" array which I access through shifted indices to create my "new_data" array.

我需要移动一个2D数组字段,即我有一个“previous_data”数组,我通过移位索引访问该数组以创建我的“new_data”数组。

I can do this in a nonpythonic (and slow) loop, but would very much appreciate some help in finding a pythonic (and faster) solution!

我可以在非pythonic(和慢)循环中执行此操作,但非常感谢在寻找pythonic(和更快)解决方案时的一些帮助!

Any help and hints are very much appreciated!

任何帮助和提示非常感谢!

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import mpl

def nonpythonic():
    #this works, but is slow (for large arrays)        
    new_data = np.zeros((ny,nx))
    for j in xrange(ny):
        for i in xrange(nx):
            #go through each item, check if it is within the bounds
            #and assign the data to the new_data array
            i_new = ix[j,i]
            j_new = iy[j,i]
            if ((i_new>=0) and (i_new<nx) and (j_new>=0) and (j_new<ny)):
                new_data[j,i]=previous_data[j_new,i_new] 

    ef, axar = plt.subplots(1,2)
    im = axar[0].pcolor(previous_data, vmin=0,vmax=2)
    ef.colorbar(im, ax=axar[0], shrink=0.9)
    im = axar[1].pcolor(new_data, vmin=0,vmax=2)
    ef.colorbar(im, ax=axar[1], shrink=0.9)

    plt.show()

def pythonic():
    #tried a few things here, but none are working
    #-tried assigning NaNs to indices (ix,iy) which are out of bounds, but NaN's don't work for indices
    #-tried masked arrays, but they also don't work as indices
    #-tried boolean arrays, but ended in shape mismatches
    #just as in the nonworking code below
    ind_y_good = np.where(iy>=0) and np.where(iy<ny)
    ind_x_good = np.where(ix>=0) and np.where(ix<nx)

    new_data = np.zeros((ny,nx))

    new_data[ind_y_good,ind_x_good] = previous_data[iy[ind_y_good],ix[ind_x_good]]

#some 2D array:
nx = 20
ny = 30    
#array indices:
iy, ix = np.indices((ny,nx))
#modify indices (shift):
iy = iy + 1
ix = ix - 4
#create some out of range indices (which might happen in my real scenario)
iy[0,2:7] = -9999
ix[0:3,-1] = 6666

#some previous data which is the basis for the new_data:
previous_data = np.ones((ny,nx))
previous_data[2:8,10:20] = 2
nonpythonic()
pythonic()

This is the result of the working (nonpythonic) code above: 通过移位索引在2d数组中移位数据

这是上面的工作(nonpythonic)代码的结果:

1 个解决方案

#1


2  

I implemented a version of pythonic that replicates nonpythonic with some masking and index fiddling - see below. By the way I think the "new" indices should be the ones corresponding to the new array, rather than the old ones, but I've left it as in your existing function.

我实现了pythonic的一个版本,它复制了nonpythonic,带有一些掩盖和索引摆弄 - 见下文。顺便说一下,我认为“新”索引应该是与新数组相对应的索引,而不是旧数组,但我已将其保留为现有函数中的索引。

The main thing to realise is that in your attempt in the question, your conditions

要意识到的主要问题是,在您尝试问题时,您的条件

ind_y_good = np.where(iy>=0) and np.where(iy<ny)
ind_x_good = np.where(ix>=0) and np.where(ix<nx)

must be combined, since we must always have pairs of x and y indices. i.e. if the x index is invalid, then so is the y.

必须合并,因为我们必须总是有成对的x和y索引。即如果x索引无效,那么y也是如此。

Finally, if the indices are really all shifted by a constant factor, you can make this even simpler by using NumPy's roll function and taking a slice of the indices corresponding to the valid area.

最后,如果索引实际上都是按常数因子移动的,那么你可以通过使用NumPy的roll函数并获取与有效区域对应的索引切片来使这更简单。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import mpl


def nonpythonic(previous_data, ix, iy, nx, ny):
    #this works, but is slow (for large arrays)        
    new_data = np.zeros((ny,nx))
    for j in xrange(ny):
        for i in xrange(nx):
            #go through each item, check if it is within the bounds
            #and assign the data to the new_data array
            i_new = ix[j,i]
            j_new = iy[j,i]
            if ((i_new>=0) and (i_new<nx) and (j_new>=0) and (j_new<ny)):
                new_data[j,i]=previous_data[j_new,i_new] 

    return new_data

def pythonic(previous_data, ix, iy):

    ny, nx = previous_data.shape
    iy_old, ix_old = np.indices(previous_data.shape)

    # note you must apply the same condition to both
    # index arrays
    valid = (iy >= 0) & (iy < ny) & (ix >= 0) & (ix < nx)

    new_data = np.zeros((ny,nx))

    new_data[iy_old[valid], ix_old[valid]] = previous_data[iy[valid], ix[valid]]
    return new_data


def main():
    #some 2D array:
    nx = 20
    ny = 30    
    #array indices:
    iy, ix = np.indices((ny,nx))
    #modify indices (shift):
    iy = iy + 1
    ix = ix - 4
    #create some out of range indices (which might happen in my real scenario)
    iy[0,2:7] = -9999
    ix[0:3,-1] = 6666

    #some previous data which is the basis for the new_data:
    previous_data = np.ones((ny,nx))
    previous_data[2:8,10:20] = 2
    data_nonpythonic = nonpythonic(previous_data, ix, iy, nx, ny)
    data_pythonic = pythonic(previous_data, ix, iy)

    new_data = data_nonpythonic
    ef, axar = plt.subplots(1,2)
    im = axar[0].pcolor(previous_data, vmin=0,vmax=2)
    ef.colorbar(im, ax=axar[0], shrink=0.9)
    im = axar[1].pcolor(new_data, vmin=0,vmax=2)
    ef.colorbar(im, ax=axar[1], shrink=0.9)
    plt.show()
    print(np.allclose(data_nonpythonic, data_pythonic))

if __name__ == "__main__":
    main()

#1


2  

I implemented a version of pythonic that replicates nonpythonic with some masking and index fiddling - see below. By the way I think the "new" indices should be the ones corresponding to the new array, rather than the old ones, but I've left it as in your existing function.

我实现了pythonic的一个版本,它复制了nonpythonic,带有一些掩盖和索引摆弄 - 见下文。顺便说一下,我认为“新”索引应该是与新数组相对应的索引,而不是旧数组,但我已将其保留为现有函数中的索引。

The main thing to realise is that in your attempt in the question, your conditions

要意识到的主要问题是,在您尝试问题时,您的条件

ind_y_good = np.where(iy>=0) and np.where(iy<ny)
ind_x_good = np.where(ix>=0) and np.where(ix<nx)

must be combined, since we must always have pairs of x and y indices. i.e. if the x index is invalid, then so is the y.

必须合并,因为我们必须总是有成对的x和y索引。即如果x索引无效,那么y也是如此。

Finally, if the indices are really all shifted by a constant factor, you can make this even simpler by using NumPy's roll function and taking a slice of the indices corresponding to the valid area.

最后,如果索引实际上都是按常数因子移动的,那么你可以通过使用NumPy的roll函数并获取与有效区域对应的索引切片来使这更简单。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import mpl


def nonpythonic(previous_data, ix, iy, nx, ny):
    #this works, but is slow (for large arrays)        
    new_data = np.zeros((ny,nx))
    for j in xrange(ny):
        for i in xrange(nx):
            #go through each item, check if it is within the bounds
            #and assign the data to the new_data array
            i_new = ix[j,i]
            j_new = iy[j,i]
            if ((i_new>=0) and (i_new<nx) and (j_new>=0) and (j_new<ny)):
                new_data[j,i]=previous_data[j_new,i_new] 

    return new_data

def pythonic(previous_data, ix, iy):

    ny, nx = previous_data.shape
    iy_old, ix_old = np.indices(previous_data.shape)

    # note you must apply the same condition to both
    # index arrays
    valid = (iy >= 0) & (iy < ny) & (ix >= 0) & (ix < nx)

    new_data = np.zeros((ny,nx))

    new_data[iy_old[valid], ix_old[valid]] = previous_data[iy[valid], ix[valid]]
    return new_data


def main():
    #some 2D array:
    nx = 20
    ny = 30    
    #array indices:
    iy, ix = np.indices((ny,nx))
    #modify indices (shift):
    iy = iy + 1
    ix = ix - 4
    #create some out of range indices (which might happen in my real scenario)
    iy[0,2:7] = -9999
    ix[0:3,-1] = 6666

    #some previous data which is the basis for the new_data:
    previous_data = np.ones((ny,nx))
    previous_data[2:8,10:20] = 2
    data_nonpythonic = nonpythonic(previous_data, ix, iy, nx, ny)
    data_pythonic = pythonic(previous_data, ix, iy)

    new_data = data_nonpythonic
    ef, axar = plt.subplots(1,2)
    im = axar[0].pcolor(previous_data, vmin=0,vmax=2)
    ef.colorbar(im, ax=axar[0], shrink=0.9)
    im = axar[1].pcolor(new_data, vmin=0,vmax=2)
    ef.colorbar(im, ax=axar[1], shrink=0.9)
    plt.show()
    print(np.allclose(data_nonpythonic, data_pythonic))

if __name__ == "__main__":
    main()