Numpy - 从1维数组中删除最后一个元素的最佳方法?

Men*_*eni 15 python arrays numpy python-2.7

从numpy 1维数组中删除最后一个元素的最有效方法是什么?(比如pop for list)

MSe*_*ert 29

NumPy数组具有固定大小,因此您无法就地删除元素.例如,使用del不起作用:

>>> import numpy as np
>>> arr = np.arange(5)
>>> del arr[-1]
ValueError: cannot delete array elements
Run Code Online (Sandbox Code Playgroud)

请注意,索引-1表示最后一个元素.那是因为Python(和NumPy)中的负指数是从末尾计算的,所以-1是最后一个,-2是前一个,-len实际上是第一个元素.这只是为了您的信息,以防您不知道.

Python列表的大小可变,因此可以轻松添加或删除元素.

因此,如果要删除元素,则需要创建新的数组或视图.

创建新视图

您可以使用切片表示法创建包含除最后一个元素之外的所有元素的新视图:

>>> arr = np.arange(5)
>>> arr
array([0, 1, 2, 3, 4])

>>> arr[:-1]  # all but the last element
array([0, 1, 2, 3])
>>> arr[:-2]  # all but the last two elements
array([0, 1, 2])
>>> arr[1:]   # all but the first element
array([1, 2, 3, 4])
>>> arr[1:-1] # all but the first and last element
array([1, 2, 3])
Run Code Online (Sandbox Code Playgroud)

但是一个视图与原始数组共享数据,所以如果一个被修改,那么另一个:

>>> sub = arr[:-1]
>>> sub
array([0, 1, 2, 3])
>>> sub[0] = 100
>>> sub
array([100,   1,   2,   3])
>>> arr
array([100,   1,   2,   3,   4])
Run Code Online (Sandbox Code Playgroud)

创建一个新数组

1.复制视图

如果您不喜欢这种内存共享,则必须创建一个新数组,在这种情况下,最简单的方法是创建一个视图然后复制(例如使用copy()数组方法):

>>> arr = np.arange(5)
>>> arr
array([0, 1, 2, 3, 4])
>>> sub_arr = arr[:-1].copy()
>>> sub_arr
array([0, 1, 2, 3])
>>> sub_arr[0] = 100
>>> sub_arr
array([100,   1,   2,   3])
>>> arr
array([0, 1, 2, 3, 4])
Run Code Online (Sandbox Code Playgroud)

2.使用整数数组索引[ docs ]

但是,您也可以使用整数数组索引来删除最后一个元素并获取新数组.此整数数组索引将始终(不是100%确定)创建副本而不是视图:

>>> arr = np.arange(5)
>>> arr
array([0, 1, 2, 3, 4])
>>> indices_to_keep = [0, 1, 2, 3]
>>> sub_arr = arr[indices_to_keep]
>>> sub_arr
array([0, 1, 2, 3])
>>> sub_arr[0] = 100
>>> sub_arr
array([100,   1,   2,   3])
>>> arr
array([0, 1, 2, 3, 4])
Run Code Online (Sandbox Code Playgroud)

此整数数组索引可用于从数组中删除任意元素(当您需要视图时,这可能很棘手或不可能):

>>> arr = np.arange(5, 10)
>>> arr
array([5, 6, 7, 8, 9])
>>> arr[[0, 1, 3, 4]]  # keep first, second, fourth and fifth element
array([5, 6, 8, 9])
Run Code Online (Sandbox Code Playgroud)

如果你想要一个使用整数数组索引删除最后一个元素的通用函数:

def remove_last_element(arr):
    return arr[np.arange(arr.size - 1)]
Run Code Online (Sandbox Code Playgroud)

3.使用布尔数组索引[ docs ]

还可以使用布尔索引,例如:

>>> arr = np.arange(5, 10)
>>> arr
array([5, 6, 7, 8, 9])
>>> keep = [True, True, True, True, False]
>>> arr[keep]
array([5, 6, 7, 8])
Run Code Online (Sandbox Code Playgroud)

这也创造了一个副本!一般化的方法可能如下所示:

def remove_last_element(arr):
    if not arr.size:
        raise IndexError('cannot remove last element of empty array')
    keep = np.ones(arr.shape, dtype=bool)
    keep[-1] = False
    return arr[keep]
Run Code Online (Sandbox Code Playgroud)

如果您想了解有关NumPys索引的更多信息,请参阅"索引"文档非常好并涵盖了很多案例.

4.使用 np.delete()

通常情况下,我不会推荐NumbPy函数"似乎"就像他们就地修改数组(比如np.appendnp.insert),但确实返回副本,因为这些函数通常是不必要的缓慢和误导.你应该尽可能地避免它们,这就是为什么它是我答案中的最后一点.然而在这种情况下,它实际上是一个完美的契合,所以我必须提到它:

>>> arr = np.arange(10, 20)
>>> arr
array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
>>> np.delete(arr, -1)
array([10, 11, 12, 13, 14, 15, 16, 17, 18])
Run Code Online (Sandbox Code Playgroud)

5.)使用 np.resize()

NumPy有另一种方法听起来就像它进行就地操作但它确实返回一个新数组:

>>> arr = np.arange(5)
>>> arr
array([0, 1, 2, 3, 4])
>>> np.resize(arr, arr.size - 1)
array([0, 1, 2, 3])
Run Code Online (Sandbox Code Playgroud)

为了删除最后一个元素,我只提供了一个比之前小1的新形状,这有效地删除了最后一个元素.

在原地修改数组

是的,我之前写过你不能修改阵列.但我这样说是因为在大多数情况下,这是不可能的,或者只是通过禁用一些(完全有用的)安全检查.我不确定内部是什么,但取决于旧的大小和新的大小,它可能包括(仅内部)复制操作,因此它可能比创建视图慢.

运用 np.ndarray.resize()

如果数组没有与任何其他数组共享其内存,则可以在适当的位置调整数组的大小:

>>> arr = np.arange(5, 10)
>>> arr.resize(4)
>>> arr
array([5, 6, 7, 8])
Run Code Online (Sandbox Code Playgroud)

但是,ValueError如果它实际上被另一个数组引用,它将抛出s:

>>> arr = np.arange(5)
>>> view = arr[1:]
>>> arr.resize(4)
ValueError: cannot resize an array that references or is referenced by another array in this way.  Use the resize function
Run Code Online (Sandbox Code Playgroud)

您可以通过设置禁用该安全检查,refcheck=False但这不应该轻易完成,因为如果其他引用尝试访问已删除的元素,您自己容易受到分段错误和内存损坏的影响!这个refcheck论点应该被视为专家选择!

摘要

创建一个视图非常快,并且不需要额外的内存,所以只要有可能,你应该尝试尽可能多地使用视图.但是,根据用例,使用基本切片删除任意元素并不容易.虽然很容易删除前n个元素和/或最后n个元素或删除每个x元素(切片的步骤参数),但这就是你可以用它做的全部.

但是在你删除一维数组的最后一个元素的情况下,我建议:

arr[:-1]          # if you want a view
arr[:-1].copy()   # if you want a new array
Run Code Online (Sandbox Code Playgroud)

因为这些最清楚地表达了意图,每个拥有Python/NumPy经验的人都会认识到这一点.

计时

根据这个答案的时间框架:

# Setup
import numpy as np

def view(arr):
    return arr[:-1]

def array_copy_view(arr):
    return arr[:-1].copy()

def array_int_index(arr):
    return arr[np.arange(arr.size - 1)]

def array_bool_index(arr):
    if not arr.size:
        raise IndexError('cannot remove last element of empty array')
    keep = np.ones(arr.shape, dtype=bool)
    keep[-1] = False
    return arr[keep]

def array_delete(arr):
    return np.delete(arr, -1)

def array_resize(arr):
    return np.resize(arr, arr.size - 1)

# Timing setup
timings = {view: [], 
           array_copy_view: [], array_int_index: [], array_bool_index: [], 
           array_delete: [], array_resize: []}
sizes = [2**i for i in range(1, 20, 2)]

# Timing
for size in sizes:
    print(size)
    func_input = np.random.random(size=size)
    for func in timings:
        print(func.__name__.ljust(20), ' ', end='')
        res = %timeit -o func(func_input)   # if you use IPython, otherwise use the "timeit" module
        timings[func].append(res)

# Plotting
%matplotlib notebook

import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure(1)
ax = plt.subplot(111)

for func in timings:
    ax.plot(sizes, 
            [time.best for time in timings[func]], 
            label=func.__name__)
ax.set_xscale('log')
ax.set_yscale('log')
ax.set_xlabel('size')
ax.set_ylabel('time [seconds]')
ax.grid(which='both')
ax.legend()
plt.tight_layout()
Run Code Online (Sandbox Code Playgroud)

我得到以下时间作为log-log图以覆盖所有细节,较低时间仍然意味着更快,但两个滴答之间的范围代表一个数量级而不是固定量.如果您对特定值感兴趣,我会将它们复制到这个要点中:

在此输入图像描述

根据这些时间,这两种方法也是最快的.(Python 3.6和NumPy 1.14.0)

  • 使用`refcheck = False`的[`resize`](http://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.resize.html)方法,但使用它是危险的除非您非常确定旧数据没有其他视图. (4认同)