Search code examples
pythonnumpynumpy-ndarraynumpy-ufunc

element wise sum of structured arrays in numpy


I wonder if it is possible at all to perform element-wise sum (or other operations) of two structured numpy arrays of an identical shape.

arr1 = np.array([[1,2,3],[2,3,4]], dtype=[("x", "f8"),("y", "f8")])
arr2 = np.array([[5,4,3],[9,6,4]], dtype=[("x", "f8"),("y", "f8")])
arr3 = np.sum(arr1, arr2)

says "ufunc 'add' did not contain a loop with signature matching types dtype([('x', '

If it is not possible, it will be great to understand why that is impossible or impractical to implement in numpy.


Solution

  • With your array:

    In [236]: arr1 = np.array([[1,2,3],[2,3,4]], dtype=[("x", "f8"),("y", "f8")])
    In [237]: arr1
    Out[237]: 
    array([[(1., 1.), (2., 2.), (3., 3.)],
           [(2., 2.), (3., 3.), (4., 4.)]], dtype=[('x', '<f8'), ('y', '<f8')])
    In [238]: arr1['x']
    Out[238]: 
    array([[1., 2., 3.],
           [2., 3., 4.]])
    

    Normally the data for a structured array is provided in the form a list(s) of tuples., same as displayed in Out[237]. Without the tuples np.array assigns the same value to both fields.

    You have to do math on each field separately:

    In [239]: arr1['y'] *= 10
    In [240]: arr1
    Out[240]: 
    array([[(1., 10.), (2., 20.), (3., 30.)],
           [(2., 20.), (3., 30.), (4., 40.)]],
          dtype=[('x', '<f8'), ('y', '<f8')])
    

    Math operations are defined for simple dtypes like int and float, and uses compiled code where possible.

    This error means that the add ufunc has not been defined for this compound dtype. And I think that's true for all compound dtypes.

    In [242]: arr1 + arr1
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-242-345397c600ce> in <module>()
    ----> 1 arr1 + arr1
    
    TypeError: ufunc 'add' did not contain a loop with signature matching types dtype([('x', '<f8'), ('y', '<f8')]) dtype([('x', '<f8'), ('y', '<f8')]) dtype([('x', '<f8'), ('y', '<f8')])
    

    Since the fields in this case have the same base dtype, we can define another compound dtype that can 'view' it:

    In [243]: dt2 = np.dtype([('xy', 'f8', 2)])
    In [244]: arr2 = arr1.view(dt2)
    In [245]: arr2
    Out[245]: 
    array([[([ 1., 10.],), ([ 2., 20.],), ([ 3., 30.],)],
           [([ 2., 20.],), ([ 3., 30.],), ([ 4., 40.],)]],
          dtype=[('xy', '<f8', (2,))])
    In [246]: arr2['xy']
    Out[246]: 
    array([[[ 1., 10.],
            [ 2., 20.],
            [ 3., 30.]],
    
           [[ 2., 20.],
            [ 3., 30.],
            [ 4., 40.]]])
    

    Math on that field will be seen in the original array:

    In [247]: arr2['xy'] += .1
    In [248]: arr2
    Out[248]: 
    array([[([ 1.1, 10.1],), ([ 2.1, 20.1],), ([ 3.1, 30.1],)],
           [([ 2.1, 20.1],), ([ 3.1, 30.1],), ([ 4.1, 40.1],)]],
          dtype=[('xy', '<f8', (2,))])
    In [249]: arr1
    Out[249]: 
    array([[(1.1, 10.1), (2.1, 20.1), (3.1, 30.1)],
           [(2.1, 20.1), (3.1, 30.1), (4.1, 40.1)]],
          dtype=[('x', '<f8'), ('y', '<f8')])
    

    We can also view it as a simple dtype, but will have to adjust the shape:

    In [250]: arr3 = arr1.view('f8')
    In [251]: arr3
    Out[251]: 
    array([[ 1.1, 10.1,  2.1, 20.1,  3.1, 30.1],
           [ 2.1, 20.1,  3.1, 30.1,  4.1, 40.1]])
    In [252]: arr3.reshape(2,3,2)
    Out[252]: 
    array([[[ 1.1, 10.1],
            [ 2.1, 20.1],
            [ 3.1, 30.1]],
    
           [[ 2.1, 20.1],
            [ 3.1, 30.1],
            [ 4.1, 40.1]]])