wake-up-neo.net

Numpy - der beste Weg, das letzte Element aus einem eindimensionalen Array zu entfernen?

Was ist der effizienteste Weg, um das letzte Element aus einem numpy 1 dimensionalen Array zu entfernen? (wie Pop für Liste) 

11
Meni

NumPy-Arrays haben eine feste Größe, sodass Sie ein Element nicht direkt entfernen können. Zum Beispiel funktioniert die Verwendung von del nicht:

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

Beachten Sie, dass der Index -1 Das letzte Element darstellt. Das liegt daran, dass negative Indizes in Python (und NumPy)) vom Ende an gezählt werden, also ist -1 Der letzte, -2 Der vorletzte und -len Ist eigentlich das erste Element, das nur zu Ihrer Information dient, falls Sie es nicht wussten.

Python-Listen haben eine variable Größe, sodass das Hinzufügen oder Entfernen von Elementen einfach ist.

Wenn Sie also ein Element entfernen möchten, müssen Sie ein neues Array oder eine neue Ansicht erstellen.

Neue Ansicht erstellen

Sie können eine neue Ansicht mit allen Elementen außer dem letzten erstellen, indem Sie die Slice-Notation verwenden:

>>> 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])

In einer Ansicht werden die Daten jedoch mit dem ursprünglichen Array geteilt. Wenn also eines geändert wird, wird das andere geändert:

>>> 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])

Ein neues Array erstellen

1. Kopieren Sie die Ansicht

Wenn Sie diese Speicherfreigabe nicht mögen, müssen Sie ein neues Array erstellen. In diesem Fall ist es wahrscheinlich am einfachsten, eine Ansicht zu erstellen und dann zu kopieren (z. B. mit der Methode copy() von Arrays) es:

>>> 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])

2. Verwenden der Ganzzahl-Array-Indizierung [ docs ]

Sie können jedoch auch die Ganzzahl-Array-Indizierung verwenden, um das letzte Element zu entfernen und ein neues Array abzurufen. Diese Ganzzahl-Array-Indizierung erstellt immer (da nicht 100% sicher) eine Kopie und keine Ansicht:

>>> 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])

Diese Ganzzahl-Array-Indizierung kann nützlich sein, um beliebige Elemente aus einem Array zu entfernen (was schwierig oder unmöglich sein kann, wenn Sie eine Ansicht wünschen):

>>> 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])

Wenn Sie eine verallgemeinerte Funktion möchten, die das letzte Element mithilfe der Ganzzahl-Array-Indizierung entfernt:

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

3. Verwenden der booleschen Array-Indizierung [ docs ]

Es gibt auch eine boolesche Indizierung, die beispielsweise verwendet werden kann:

>>> 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])

Dies erstellt auch eine Kopie! Und ein verallgemeinerter Ansatz könnte so aussehen:

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]

Wenn Sie weitere Informationen zur Indizierung von NumPys wünschen, ist Dokumentation zu "Indizierung" ziemlich gut und deckt viele Fälle ab.

4. Verwenden Sie np.delete()

Normalerweise würde ich die NumPy-Funktionen nicht empfehlen, die "scheinen", als würden sie das Array direkt modifizieren (wie np.append Und np.insert), Aber Kopien zurückgeben, da diese im Allgemeinen unnötig langsam und irreführend sind . Sie sollten sie nach Möglichkeit meiden, deshalb ist es der letzte Punkt in meiner Antwort. In diesem Fall ist es jedoch eine perfekte Passform, also muss ich es erwähnen:

>>> 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])

5.) Mit np.resize()

NumPy hat eine andere Methode, die wie eine In-Place-Operation klingt, aber wirklich ein neues Array zurückgibt:

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

Um das letzte Element zu entfernen, habe ich einfach eine neue Form bereitgestellt, die 1 kleiner ist als zuvor, wodurch das letzte Element effektiv entfernt wird.

Ändern Sie das Array an Ort und Stelle

Ja, ich habe bereits geschrieben, dass Sie ein Array nicht direkt ändern können. Aber ich sagte das, weil es in den meisten Fällen nicht möglich ist oder nur durch Deaktivieren einiger (völlig nützlicher) Sicherheitsüberprüfungen. Bei den Interna bin ich mir nicht sicher, aber abhängig von der alten und der neuen Größe kann es sein, dass dies einen (nur internen) Kopiervorgang beinhaltet, so dass es könnte langsamer ist als das Erstellen eines Aussicht.

Mit np.ndarray.resize()

Wenn das Array seinen Speicher nicht mit einem anderen Array teilt, kann die Größe des Arrays geändert werden:

>>> arr = np.arange(5, 10)
>>> arr.resize(4)
>>> arr
array([5, 6, 7, 8])

Dies wirft jedoch ValueErrors, falls es tatsächlich auch von einem anderen Array referenziert wird:

>>> 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

Sie können diese Sicherheitsüberprüfung deaktivieren, indem Sie refcheck=False Einstellen. Dies sollte jedoch nicht leichtfertig erfolgen, da Sie sich für Segmentierungsfehler und Speicherbeschädigungen anfällig machen, falls die andere Referenz versucht, auf die entfernten Elemente zuzugreifen! Dieses refcheck Argument sollte als reine Expertenoption behandelt werden!

Zusammenfassung

Das Erstellen einer Ansicht ist sehr schnell und nimmt nicht viel zusätzlichen Speicher in Anspruch. Versuchen Sie daher nach Möglichkeit, so viel wie möglich mit Ansichten zu arbeiten. Abhängig von den Anwendungsfällen ist es jedoch nicht so einfach, beliebige Elemente mithilfe von Basic Slicing zu entfernen. Während es einfach ist, die ersten n Elemente und/oder letzten n Elemente zu entfernen oder jedes x Element (das Schrittargument für das Schneiden) zu entfernen, ist dies alles, was Sie damit tun können.

Wenn Sie jedoch das letzte Element eines eindimensionalen Arrays entfernen möchten, würde ich Folgendes empfehlen:

arr[:-1]          # if you want a view
arr[:-1].copy()   # if you want a new array

weil diese die Absicht am klarsten ausdrücken und jeder mit Python/NumPy-Erfahrung dies erkennen wird.

Timings

Basierend auf dem zeitlichen Rahmen von diesem Antwort :

# 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()

Ich erhalte die folgenden Timings als Log-Log-Plot, um alle Details zu erfassen. Niedrigere Zeit bedeutet immer noch schneller, aber der Bereich zwischen zwei Teilstrichen entspricht einer Größenordnung anstelle einer festgelegten Menge. Falls Sie an den spezifischen Werten interessiert sind, habe ich sie in dieses Gist kopiert:

enter image description here

Entsprechend diesen Zeitpunkten sind diese beiden Ansätze auch die schnellsten. (Python 3.6 und NumPy 1.14.0)

28
MSeifert

Um das letzte Element aus einem 1-dimensionalen NumPy-Array zu löschen, verwenden Sie die Methode numpy.delete wie folgt:

import numpy as np

# Create a 1-dimensional NumPy array that holds 5 values
values = np.array([1, 2, 3, 4, 5])

# Remove the last element of the array using the numpy.delete method
values = np.delete(values, -1)
print(values)

Ausgabe: [1 2 3 4]

Der letzte Wert des NumPy-Arrays (5) wurde jetzt entfernt.

0