Hoseung Choi
Hoseung Choi

Reputation: 1097

Add a 2d array(field) to a numpy recarray

I want to add a 2D field to an existing recarray, using numpy.lib.recfunctions.append_fields. Let's say I made a recarray.

> arr = np.recarray(10, [("afield", "<f8"), ('pos', '<f8', (3,))])
> arr.dtype
dtype((numpy.record, [('afield', '<f8'), ('pos', '<f8', (3,))]))

and I want to add a field so that the arr looks like:

> arr.dtype
dtype((numpy.record, [('afield', '<f8'), ('pos', '<f8', (3,)), ('vel', '<f8', (3,))]))

I am not sure what to pass to the dtypes= parameter. I tried dtypes =np.dtype("f8",(3,)) with no success.

> from numpy.lib.recfunctions import append_fields
> data = arr["pos"][:]
> new_arr = append_fields(arr, 'vel', data, dtypes =np.dtype("f8",(3,)),usemask=False)
ValueError: could not broadcast input array from shape (10,3) into shape (10)

Or, if I pass a one-element list, I get another error.

> new_arr = append_fields(arr, 'vel', data, dtypes =[("f8",(3,))],usemask=False)
ValueError: could not broadcast input array from shape (10,3) into shape (10,3,3)

I want a shape of (10,3), but I can get only (10,) or (10,3,3).

Upvotes: 4

Views: 519

Answers (2)

hpaulj
hpaulj

Reputation: 231395

append_fields, and most of the other recarray functions, creates a new dtype, and an empty array, and then copies fields, by name from the original(s) to the result.

I'll illustrate with a structured array

Original dtype and array:

In [102]: dt=np.dtype([('afield','f'),('pos','f',(3,))])
In [103]: dt
Out[103]: dtype([('afield', '<f4'), ('pos', '<f4', (3,))])
In [104]: arr = np.ones((3,),dtype=dt)
In [105]: arr
Out[105]: 
array([(1.0, [1.0, 1.0, 1.0]), (1.0, [1.0, 1.0, 1.0]),
       (1.0, [1.0, 1.0, 1.0])], 
      dtype=[('afield', '<f4'), ('pos', '<f4', (3,))])

modified dtype:

In [106]: dt1=np.dtype([('afield','f'),('pos','f',(3,)),('vel','f',(2,))])
In [107]: arr1 = np.empty((3,),dtype=dt1)
In [108]: arr1
Out[108]: 
array([(0.0, [0.0, 0.0, 0.0], [0.0, 0.0]),
       (0.0, [0.0, 0.0, 0.0], [0.0, 0.0]),
       (0.0, [0.0, 0.0, 0.0], [0.0, 0.0])], 
      dtype=[('afield', '<f4'), ('pos', '<f4', (3,)), ('vel', '<f4', (2,))])
In [109]: for name in dt.names:
   .....:     arr1[name] = arr[name]

In [110]: arr1
Out[110]: 
array([(1.0, [1.0, 1.0, 1.0], [0.0, 0.0]),
       (1.0, [1.0, 1.0, 1.0], [0.0, 0.0]),
       (1.0, [1.0, 1.0, 1.0], [0.0, 0.0])], 
      dtype=[('afield', '<f4'), ('pos', '<f4', (3,)), ('vel', '<f4', (2,))])

recarray is the same thing but with the ability to access fields as attributes (arr.pos).

Adding a simple integer field:

In [118]: rf.append_fields(arr, 'vel', np.arange(3),usemask=False)
Out[118]: 
array([(1.0, [1.0, 1.0, 1.0], 0), (1.0, [1.0, 1.0, 1.0], 1),
       (1.0, [1.0, 1.0, 1.0], 2)], 
      dtype=[('afield', '<f4'), ('pos', '<f4', (3,)), ('vel', '<i4')])

With a (2,) field I was getting errors in the recursive_fill step. With a proper input I can use that to fill my dt1 array:

In [206]: arr = np.ones((3,),dtype=dt)
In [207]: arr1 = np.zeros((3,),dtype=dt1)
In [208]: rf.recursive_fill_fields(arr,arr1)
Out[208]: 
array([(1.0, [1.0, 1.0, 1.0], [0.0, 0.0]),
       (1.0, [1.0, 1.0, 1.0], [0.0, 0.0]),
       (1.0, [1.0, 1.0, 1.0], [0.0, 0.0])], 
      dtype=[('afield', '<f4'), ('pos', '<f4', (3,)), ('vel', '<f4', (2,))])

In [210]: x = np.ones((3,),dtype=[('vel','f',(2,))])
In [211]: x['vel'] *= 2
In [212]: rf.recursive_fill_fields(x,arr1)
Out[212]: 
array([(1.0, [1.0, 1.0, 1.0], [2.0, 2.0]),
       (1.0, [1.0, 1.0, 1.0], [2.0, 2.0]),
       (1.0, [1.0, 1.0, 1.0], [2.0, 2.0])], 
      dtype=[('afield', '<f4'), ('pos', '<f4', (3,)), ('vel', '<f4', (2,))])

Now try that x in append_fields:

In [213]: rf.append_fields(arr, 'vel', x, usemask=False)
Out[213]: 
array([(1.0, [1.0, 1.0, 1.0], ([2.0, 2.0],)),
       (1.0, [1.0, 1.0, 1.0], ([2.0, 2.0],)),
       (1.0, [1.0, 1.0, 1.0], ([2.0, 2.0],))], 
      dtype=[('afield', '<f4'), ('pos', '<f4', (3,)), ('vel', [('vel', '<f4', (2,))])])

Oops - I'm getting a nesting field. It's back to my original example - create the target and fill it by name.

merge_arrays does better - with flatten

In [247]: rf.merge_arrays((arr,x),flatten=True)
Out[247]: 
array([(1.0, [1.0, 1.0, 1.0], [2.0, 2.0]),
       (1.0, [1.0, 1.0, 1.0], [2.0, 2.0]),
       (1.0, [1.0, 1.0, 1.0], [2.0, 2.0])], 
      dtype=[('afield', '<f4'), ('pos', '<f4', (3,)), ('vel', '<f4', (2,))])

Another way to create an appropriate array for that new field

In [248]: dx = [('f0','f',(2,))]
In [250]: y=np.zeros((3,), dtype=dx)
In [251]: y['f0'] = np.arange(6.).reshape(3,2)

Create and fill often seems the be the best way of making these complicated structured arrays.

Upvotes: 2

Eric
Eric

Reputation: 97591

Here's a step in the right direction:

data = arr['pos']
data = np.ascontiguousarray(data)  # next line fails otherwise - bug?
data = data.view([('vel', (np.float, 3))])
data = data.reshape(data.shape[:-1])  # view doesn't ever remove a dimension
assert data.shape == (10,)

Unfortunately, when you now do:

new_arr = append_fields(arr, 'vel', data,usemask=False)

You end up with new_arr['vel']['vel']

Upvotes: 0

Related Questions