Converting a 3D List to a 3D NumPy array

前端 未结 5 1107
北海茫月
北海茫月 2021-01-18 16:19

Currently, I have a 3D Python list in jagged array format.
A = [[[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0], [0], [0]]]

Is there any way I could convert

5条回答
  •  醉酒成梦
    2021-01-18 16:43

    Since numpy can only work with regular-shaped arrays, it checks that all the elements of a nested iterable are the same length for a given dimension. If they are not, it still creates an array, but of type np.object instead of np.int as you would expect:

    >>> B = np.array(A)
    >>> B
    array([[[0, 0, 0], [0, 0, 0], [0, 0, 0]],
           [[0], [0], [0]]], dtype=object)
    

    In this case, the "objects" are lists. Addition is defined for lists, but only in terms of other lists that extend the original, hence your error. [0, 0] + 4 is an error, while [0, 0] + [4] is [0, 0, 4]. Neither is what you want.

    It may be interesting that numpy will make the object portion of your array nest as low as possible. Array you created is actually a 2D numpy array containing lists, not a 1D array containing nested lists:

    >>> B[0, 0]
    [0, 0, 0]
    >>> B[0, 0, 0]
    Traceback (most recent call last):
    
      File "", line 1, in 
        B[0, 0, 0]
    
    IndexError: too many indices for array
    

    As you pointed out, you have two options when it comes to ragged arrays. The first is to pad the array so it is non-ragged, convert it to numpy, and only use the elements you care about. This does not seem very convenient in your case.

    The other method is to apply functions to your nested array directly. Luckily for you, I wrote a snippet/recipe in response to this question, which does exactly what you need, down to being able to support arbitrary levels of nesting and your choice of operators. I have upgraded it here to accept non-iterable nested elements anywhere along the list, including the original input and do a primitive form of broadcasting:

    from itertools import repeat
    
    def elementwiseApply(op, *iters):
        def isIterable(x):
            """
            This function is also defined in numpy as `numpy.iterable`.
            """
            try:
                iter(x)
            except TypeError:
                return False
            return True
    
        def apply(op, *items):
            """
            Applies the operator to the given arguments. If any of the
            arguments are iterable, the non-iterables are broadcast by
            `itertools.repeat` and the function is applied recursively
            on each element of the zipped result.
            """
            elements = []
            count = 0
            for iter in items:
                if isIterable(iter):
                    elements.append(iter)
                    count += 1
                else:
                    elements.append(itertools.repeat(iter))
            if count == 0:
                return op(*items)
            return [apply(op, *items) for items in zip(*elements)]
    
        return apply(op, *iters)
    

    This is a pretty general solution that will work with just about any kind of input. Here are a couple of sample runs showing how it is relevant to your question:

    >>> from operator import add
    >>> elementwiseApply(add, 4, 4)
    8
    >>> elementwiseApply(add, [4, 0], 4)
    [8, 4]
    >>> elementwiseApply(add, [(4,), [0, (1, 3, [1, 1, 1])]], 4)
    [[8], [4, [5, 7, [5, 5, 5]]]]
    >>> elementwiseApply(add, [[0, 0, 0], [0, 0], 0], [[4, 4, 4], [4, 4], 4])
    [[4, 4, 4], [4, 4], 4]
    >>> elementwiseApply(add, [(4,), [0, (1, 3, [1, 1, 1])]], [1, 1, 1])
    [[5], [1, [2, 4, [2, 2, 2]]]]
    

    The result is always a new list or scalar, depending on the types of the inputs. The number of inputs must be the number accepted by the operator. operator.add always takes two inputs, for example.

提交回复
热议问题