最终结论如下(如下结论同时适用于 Linux 环境和 Windows 环境):

方法一:使用 np.delete 函数删除
def func1(arr):
    arr2 = np.delete(arr, DELETE, axis=1)
    return arr2
  • 时间复杂度:O(M×N)
  • 空间复杂度:O(M×N)
  • 保持删除前原数组顺序;
  • 返回参数二维数组的浅拷贝,不是原地操作。
方法二:先使用切片器将删除列后的所有列前移,再移除最后一列
def func2(arr):
    arr[:, DELETE:SHAPE[1] - 1] = arr[:, DELETE + 1:]
    arr2 = arr[:, :SHAPE[1] - 1]
    return arr2
  • 时间复杂度:O((M-D)×N)
  • 空间复杂度:O((M-D)×N)
  • 保持删除前原数组顺序;
  • 返回参数二维数组的视图,是原地操作。
方法三:先将删除列与最后一列交换,再移除最后一列
def func3(arr):
    arr[:, DELETE] = arr[:, SHAPE[1] - 1]
    arr2 = arr[:, :SHAPE[1] - 1]
    return arr2
  • 时间复杂度:O(N)
  • 空间复杂度:O(1)
  • 不保持删除前原数据顺序;
  • 返回参数二维数组的视图,是原地操作。

下面为结论得出的过程。

首先,我们通过下面的用例,分析 4 个 numpy 操作的时间、空间消耗。在用例中使用了 resources_monitor 装饰器用于监视函数的时间、空间消耗,其源码详见 监视函数运行资源的装饰器

import numpy as np

from utils.eff import resources_monitor


@resources_monitor()
def func1(arr):
    arr[:, DELETE:SHAPE[1] - 1] = arr[:, DELETE + 1:]
    return arr


@resources_monitor()
def func2(arr):
    arr2 = np.delete(arr, DELETE, axis=1)
    return arr2


@resources_monitor()
def func3(arr):
    arr[:, DELETE] = arr[:, SHAPE[1] - 1]
    return arr


@resources_monitor()
def func4(arr):
    arr2 = arr[:, :SHAPE[1] - 1]
    return arr2


if __name__ == "__main__":
    SHAPE = (3000, 4000)  # 数组尺寸
    DELETE = 1  # 需要删除的列
    array = np.random.randint(0, 10, SHAPE, np.int64)
    func1(array)
    func2(array)
    func3(array)
    func4(array)

具体地,我们构造一个尺寸为 SHAPE 的 numpy 二维数组,并检验以下 4 个 numpy 操作在编辑这个二维数组时的时间、空间消耗:

  • func1:使用切片器将二维数组的第 DELETE 列到第 SHAPE[1]-2 列替换为第 DELETE+1 列到第 SHAPE[1]-1 列;
  • func2:使用 np.delete 函数移除二维数组的第 DELETE 列。
  • func3:将二维数组的第 DELETE 列替换为第 SHAPE[1]-1 列;
  • func4:使用切片器切除二维数组的第 SHAPE[1]-1 列。

需要注意的是,因为 numpy 数组中元素的数据类型为 int64,所以每个元素需要 8byte 的存储空间。

运行结果1(SHAPE = (3000, 4000)DELETE = 1000):

func1 - 运行时间=0.0439 结束内存=56 内存峰值=71976404 # 8*3000*3000=72000000
func2 - 运行时间=0.047858 结束内存=95976200 内存峰值=95976680 # 8*4000*3000=96000000
func3 - 运行时间=0.000997 结束内存=0 内存峰值=176
func4 - 运行时间=0.0 结束内存=144 内存峰值=172

运行结果2(SHAPE = (3000, 4000)DELETE = 3000):

func1 - 运行时间=0.017952 结束内存=56 内存峰值=23976404 # 8*1000*3000=24000000
func2 - 运行时间=0.045916 结束内存=95976200 内存峰值=95976680 # 8*4000*3000=96000000
func3 - 运行时间=0.0 结束内存=0 内存峰值=176
func4 - 运行时间=0.0 结束内存=144 内存峰值=172

运行结果3(SHAPE = (30000, 4000)DELETE = 3000):

func1 - 运行时间=0.153571 结束内存=56 内存峰值=239760404 # 8*1000*30000=240000000
func2 - 运行时间=0.505604 结束内存=959760200 内存峰值=959760680 # 8*4000*30000=960000000
func3 - 运行时间=0.0 结束内存=0 内存峰值=176
func4 - 运行时间=0.0 结束内存=144 内存峰值=172

运行结果4(SHAPE = (300000, 400)DELETE = 300):

func1 - 运行时间=0.188529 结束内存=56 内存峰值=237600404 # 8*100*300000=240000000
func2 - 运行时间=0.497637 结束内存=957600200 内存峰值=957600680 # 8*400*300000=960000000
func3 - 运行时间=0.008997 结束内存=0 内存峰值=176
func4 - 运行时间=0.0 结束内存=144 内存峰值=172

运行结果5(SHAPE = (3000000, 40)DELETE = 30):

func1 - 运行时间=0.292217 结束内存=56 内存峰值=216000376  # 8*10*3000000=240000000
func2 - 运行时间=0.563493 结束内存=936000200 内存峰值=936000596  # 8*40*3000000=960000000
func3 - 运行时间=0.091753 结束内存=0 内存峰值=176
func4 - 运行时间=0.0 结束内存=144 内存峰值=144

根据以上运行结果,我们可以猜测这些操作的时间复杂度和空间复杂度如下(不妨用 M 表示 SHAPE[0]N 表示 SHAPE[1]D 表示 DELETE):

  • func1:时间复杂度 O((M-D)×N);空间复杂度 O((M-D)×N),原地修改操作;
  • func2:时间复杂度 O(M×N);空间复杂度 O(M×N),返回参数二维数组的浅拷贝,不是原地操作;
  • func3:时间复杂度 O(N);空间复杂度 O(1),原地修改操作;
  • func4:时间复杂度 O(1);空间复杂度 O(1),返回参数二维数组的视图,是原地操作。

其中,func1func2 的实际内存占用略小于这个空间复杂度,可能是因为 func1 中重复的两列和 func2 中被删除的列没有占用空间。

因为已知以上 numpy 操作的时间复杂度和空间复杂度,我们可以算出以下 3 种删除某一列(行)的方法的时间复杂度和空间复杂度,测试运行结果也可以佐证我们的结论。

import numpy as np

from utils.eff import resources_monitor


@resources_monitor()
def func1(arr):
    """
    时间复杂度:O(M×N)
    空间复杂度:O(M×N) - 返回arr的浅拷贝
    """
    arr2 = np.delete(arr, DELETE, axis=1)
    return arr2


@resources_monitor()
def func2(arr):
    """
    时间复杂度:O((M-D)×N)
    空间复杂度:O((M-D)×N) - 原地操作
    """
    arr[:, DELETE:SHAPE[1] - 1] = arr[:, DELETE + 1:]
    arr2 = arr[:, :SHAPE[1] - 1]
    return arr2


@resources_monitor()
def func3(arr):
    """
    时间复杂度:O(N)
    空间复杂度:O(1) - 原地操作
    """
    arr[:, DELETE] = arr[:, SHAPE[1] - 1]
    arr2 = arr[:, :SHAPE[1] - 1]
    return arr2


if __name__ == "__main__":
    SHAPE = (3000, 4000)  # 数组尺寸
    DELETE = 3000  # 需要删除的列
    array = np.random.randint(0, 10, SHAPE, np.int64)
    func1(array)
    func2(array)
    func3(array)

运行结果:

func1 - 运行时间=0.0479 结束内存=95976200 内存峰值=95976680
func2 - 运行时间=0.016914 结束内存=144 内存峰值=23976404
func3 - 运行时间=0.0 结束内存=144 内存峰值=176