web-dev-qa-db-fra.com

Numpy - le meilleur moyen de supprimer le dernier élément d'un tableau à une dimension?

Quel est le moyen le plus efficace de supprimer le dernier élément d'un tableau numpy à 1 dimension? (comme pop pour liste) 

11
Meni

Les tableaux NumPy ont une taille fixe, vous ne pouvez donc pas supprimer un élément sur place. Par exemple, utiliser del ne fonctionne pas:

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

Notez que l'index -1 représente le dernier élément. En effet, les indices négatifs en Python (et NumPy) sont comptés à partir de la fin, donc -1 est le dernier, -2 est celui avant dernier et -len est en fait le premier élément. C'est juste pour votre information au cas où vous ne le sauriez pas.

Les listes Python étant de tailles variables, il est facile d’ajouter ou de supprimer des éléments.

Donc, si vous souhaitez supprimer un élément, vous devez créer un nouveau tableau ou une nouvelle vue.

Créer une nouvelle vue

Vous pouvez créer une nouvelle vue contenant tous les éléments sauf le dernier en utilisant la notation slice:

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

Cependant, une vue partage les données avec le tableau d'origine, donc si l'une est modifiée, l'autre l'est:

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

Créer un nouveau tableau

1. Copier la vue

Si vous n'aimez pas ce partage de mémoire, vous devez créer un nouveau tableau. Dans ce cas, il est probablement plus simple de créer une vue, puis de la copier (par exemple, en utilisant la méthode copy() de tableaux)

>>> 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. Utilisation de l'indexation de tableaux entiers [ docs ]

Cependant, vous pouvez également utiliser l'indexation de tableaux entiers pour supprimer le dernier élément et obtenir un nouveau tableau. Cette indexation de tableaux entiers créera toujours (et non à 100%) une copie et non une vue:

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

Cette indexation de tableau de nombres entiers peut être utile pour supprimer des éléments arbitraires d'un tableau (ce qui peut être délicat ou impossible lorsque vous voulez une vue):

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

Si vous souhaitez une fonction généralisée qui supprime le dernier élément à l'aide de l'indexation de tableaux entiers:

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

3. Utilisation de l'indexation de tableaux booléens [ docs ]

Il existe également une indexation booléenne qui pourrait être utilisée, par exemple:

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

Cela crée également une copie! Et une approche généralisée pourrait ressembler à ceci:

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]

Si vous souhaitez plus d'informations sur NumPys, la documentation sur "l'indexation" est très utile et couvre de nombreux cas.

4. Utilisation de np.delete()

Normalement, je ne recommanderais pas les fonctions NumPy qui "semblent" comme si elles modifiaient le tableau sur place (comme np.append et np.insert), mais renvoient des copies car elles sont généralement inutilement lentes et trompeuses. Vous devriez les éviter autant que possible, c'est pourquoi c'est le dernier point de ma réponse. Cependant, dans ce cas, c'est un ajustement parfait, je dois donc le mentionner:

>>> 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.) Utilisation de np.resize()

NumPy a une autre méthode qui ressemble à une opération sur place, mais renvoie en réalité un nouveau tableau:

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

Pour supprimer le dernier élément, j'ai simplement fourni une nouvelle forme qui est 1 plus petite qu'avant, ce qui supprime effectivement le dernier élément.

Modification du tableau en place

Oui, j'ai déjà écrit que vous ne pouvez pas modifier un tableau à la place. Mais j'ai dit cela parce que dans la plupart des cas, ce n'est pas possible ou seulement en désactivant certaines vérifications de sécurité (tout à fait utiles). Je ne suis pas sûr des éléments internes, mais en fonction de l'ancienne taille et de la nouvelle taille, il est possible que cela inclue une opération de copie (interne uniquement), de sorte que pourrait être plus lent que la création d'une vue.

Utilisation de np.ndarray.resize()

Si le tableau ne partage pas sa mémoire avec un autre tableau, il est alors possible de redimensionner le tableau en place:

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

Cependant, cela lancera ValueErrors dans le cas où il est également référencé par un autre tableau:

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

Vous pouvez désactiver cette vérification de sécurité en définissant refcheck=False, mais cela ne devrait pas être fait à la légère, car vous vous rendez vulnérable aux erreurs de segmentation et à la corruption de mémoire au cas où l'autre référence tente d'accéder aux éléments supprimés! Cet argument refcheck doit être traité comme une option réservée aux experts!

Résumé

La création d'une vue est très rapide et ne nécessite pas beaucoup de mémoire supplémentaire. Par conséquent, essayez autant que possible de travailler autant que possible avec les vues. Cependant, selon les cas d'utilisation, il n'est pas si facile de supprimer des éléments arbitraires à l'aide du découpage de base. Bien qu'il soit facile de supprimer les n premiers éléments et/ou les n derniers éléments ou de supprimer chaque élément x (l'argument de pas pour le découpage en tranches), c'est tout ce que vous pouvez faire avec.

Mais dans votre cas de suppression du dernier élément d'un tableau à une dimension, je vous recommanderais:

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

parce qu’ils expriment très clairement l’intention et que toute personne ayant une expérience Python/NumPy le reconnaîtra.

Les horaires

Basé sur le cadre temporel de cette réponse

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

J'obtiens les timings suivants sous forme de log-log plot pour couvrir tous les détails, plus le temps est bas, plus vite, mais l'intervalle entre deux graduations représente un ordre de grandeur au lieu d'un montant fixe. Si les valeurs spécifiques vous intéressent, je les ai copiées dans ce Gist :

enter image description here

Selon ces temps, ces deux approches sont aussi les plus rapides. (Python 3.6 et NumPy 1.14.0)

20
MSeifert

Pour supprimer le dernier élément d'un tableau NumPy à 1 dimension, utilisez la méthode numpy.delete , comme suit:

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)

Sortie: [1 2 3 4]

La dernière valeur du tableau NumPy, qui était 5, est maintenant supprimée.

0
David M. Helmuth