15

If I have a list of numpy arrays, then using remove method returns a value error.

For example:

import numpy as np

l = [np.array([1,1,1]),np.array([2,2,2]),np.array([3,3,3])]

l.remove(np.array([2,2,2]))

Would give me

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

I can't seem to get the all() to work, is it just not possible?

matt_s
  • 1,037
  • 1
  • 10
  • 17
  • Just so you know, it isn't a good idea to use `list` as a variable since it is a keyword in Python. It can come back to bite you later on. – Justin Peel Jul 01 '10 at 17:40
  • Yes thanks, I was bitten whilst playing around trying to solve this problem, converting the arrays to lists using list() then using remove and so on. – matt_s Jul 02 '10 at 10:24

4 Answers4

15

The problem here is that when two numpy arrays are compared with ==, as in the remove() and index() methods, a numpy array of boolean values (the element by element comparisons) is returned which is interpretted as being ambiguous. A good way to compare two numpy arrays for equality is to use numpy's array_equal() function.

Since the remove() method of lists doesn't have a key argument (like sort() does), I think that you need to make your own function to do this. Here's one that I made:

def removearray(L,arr):
    ind = 0
    size = len(L)
    while ind != size and not np.array_equal(L[ind],arr):
        ind += 1
    if ind != size:
        L.pop(ind)
    else:
        raise ValueError('array not found in list.')

If you need it to be faster then you could Cython-ize it.

Justin Peel
  • 46,722
  • 6
  • 58
  • 80
3

Here you go:

list.pop(1)

Update:

list.pop(list.index(element))

I don't think you can get around traversing the list to find the position of the element. Don't worry about it. Python will, by default use a good searching algorithm to find it at least cost for you.

lprsd
  • 84,407
  • 47
  • 135
  • 168
  • Thanks, I realise that works for my example but where I actually need to do this I don't know the position of the array I want to remove. Rather than using a loop I thought there might be a nicer way using the remove method. – matt_s Jul 01 '10 at 12:49
  • 4
    Thanks for helping me with this. If I use list.index() for a numpy array I get the ambiguous truth error again, hmm. – matt_s Jul 01 '10 at 13:11
2

Use Python Basic Functionalities

The following solution uses the list.index(element) method from the list of arrays.

Searching for a numpy.ndarray needs to be able to hash numpy.ndarray instances. Therefore, we need to implement a hashing algorithm. This is fairly simple, although the code presented looks a bit long, most of the lines are used for checking for edge cases or the addition of comments.

You can copy paste the code into a file and run it from the command line or a SDK as PyCharm.

You need to know about

  • [].index(element)
  • hash (hashing and hash collisions)
  • how to hash a numpy array

Note:

  • hash collisions can lead to wrong decisions, you need to decide yourself about the probability and impact
  • only the first occurence of the array is removed, should there be several with the same data.

import numpy as np


def remove(array, arrays):
    """
    Remove the `array` from the `list` of `arrays`
    Operates inplace on the `list` of `arrays` given

    :param array: `np.ndarray`
    :param arrays: `list:np.ndarray`
    :return: None
    """

    assert isinstance(arrays, list), f'Expected a list, got {type(arrays)} instead'
    assert isinstance(array, np.ndarray), f'Expected a numpy.ndarray, got {type(array)} instead'
    for a in arrays:
        assert isinstance(a, np.ndarray), f'Expected a numpy.ndarray instances in arrays, found {type(a)} instead'

    # Numpy ndarrays are not hashable by default, so we create
    # our own hashing algorithm. The following will do the job ...
    def _hash(a):
        return hash(a.tobytes())

    try:
        # We create a list of hashes and search for the index
        # of the hash of the array we want to remove.
        index = [_hash(a) for a in arrays].index(_hash(array))
    except ValueError as e:
        # It might be, that the array is not in the list at all.
        print(f'Array not in list. Leaving input unchanged.')
    else:
        # Only in the case of no exception we pop the array
        # with the same index/position from the original
        # arrays list
        arrays.pop(index)


if __name__ == '__main__':

    # Let's start with the following arrays as given in the question
    arrays = [np.array([1, 1, 1]), np.array([2, 2, 2]), np.array([3, 3, 3])]
    print(arrays)

    # And remove this array instance from it.
    # Note, this is a new instance, so the object id is
    # different. Structure and values coincide.
    remove(np.array([2, 2, 2]), arrays)

    # Let's check the result
    print(arrays)

    # Let's check, whether our edge case handling works.
    remove(np.array([1, 2, 3]), arrays)
thomas
  • 319
  • 3
  • 9
1

Use Base Functionalities from Python and Numpy

You can run the following one-liner to get the result ...

import numpy as np

# Your inputs ...
l = [np.array([1, 1, 1]), np.array([2, 2, 2]), np.array([3, 3, 3])]
array_to_remove = np.array([2, 2, 2])

# My result ...
result = [a for a, skip in zip(l, [np.allclose(a, array_to_remove) for a in l]) if not skip]

print(result)

... or copy paste the following in a script and experiment a bit.

You need

  • numpy.allclose to compare numpy arrays up to floating point representation error
  • zip
  • list comprehension
  • the concept of a mask

Note, ...

  • this solution returns a list without all occurencies of the array we searched for
  • the returned list has references to the np.ndarray instances also referred from the initial list. There are no copies!

import numpy as np


def remove(array, arrays):
    """
    Remove the `array` from the `list` of `arrays`
    Returns list with remaining arrays by keeping the order.

    :param array: `np.ndarray`
    :param arrays: `list:np.ndarray`
    :return: `list:np.ndarray`
    """

    assert isinstance(arrays, list), f'Expected a list, got {type(arrays)} instead'
    assert isinstance(array, np.ndarray), f'Expected a numpy.ndarray, got {type(array)} instead'
    for a in arrays:
        assert isinstance(a, np.ndarray), f'Expected a numpy.ndarray instances in arrays, found {type(a)} instead'

    # We use np.allclose for comparing arrays, this will work even if there are
    # floating point representation differences.
    # The idea is to create a boolean mask of the same lenght as the input arrays.
    # Then we loop over the arrays-elements and the mask-elements and skip the
    # flagged elements
    mask = [np.allclose(a, array) for a in arrays]
    return [a for a, skip in zip(arrays, mask) if not skip]


if __name__ == '__main__':

    # Let's start with the following arrays as given in the question
    arrays = [np.array([1, 1, 1]), np.array([2, 2, 2]), np.array([3, 3, 3])]
    print(arrays)

    # And remove this array instance from it.
    # Note, this is a new instance, so the object id is
    # different. Structure and values coincide.
    _arrays = remove(np.array([2, 2, 2]), arrays)

    # Let's check the result
    print(_arrays)

    # Let's check, whether our edge case handling works.
    print(arrays)
    _arrays = remove(np.array([1, 2, 3]), arrays)
    print(_arrays)
thomas
  • 319
  • 3
  • 9