最终结论如下(如下结论同时适用于 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)
,返回参数二维数组的视图,是原地操作。
其中,func1
和 func2
的实际内存占用略小于这个空间复杂度,可能是因为 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