How to fix this error extracting empty structure fields from a cell array when using cellfun()

57 Views Asked by At

I have a cell array in Matlab, with each cell representing a financial transaction as a structure object (the cell array is earlier imported from a json file).

Here is an example to create a test set which replicates the features of my data, representing 4 individual transactions in this case:

transaction_1 = struct( 'created_on' , '2023-01-22' , 'amount' , 9.60 , 'merchant' , struct('name','ebay') );
transaction_2 = struct( 'created_on' , '2022-11-22' , 'amount' , 6.20 , 'merchant' , struct('name','ikea') );
transaction_3 = struct( 'created_on' , '2019-06-19' , 'amount' , 3.65 , 'merchant' , [] );

transactions = { transaction_1 ; transaction_2 ; transaction_3 };

% Add a fourth which is not "compatible" in dimension with the others
transactions{4} = struct( 'created_on' , '2019-06-19' , 'amount' , 1.25 , 'merchant' , [] , 'location' , 'London');

Each structure in the cell array contains three fields:

  1. A date for the transaction
  2. The amount of the transaction
  3. The third field represents the name of the merchant. If there is no merchant information, then this field is empty. However, if there is info, then this field is another structure with the field 'name'.
  4. Some of the structures also have a 'location' field as well.

The third transaction does not have merchant information in the test set, so transactions{3}.merchant returns empty, while the fourth transaction also has location data.

Now I wish to extract field values from the individual structures into separate cell arrays. The following code works correctly for the dates and amounts

created_on = cellfun(@(x) x.created_on , transactions , 'UniformOutput' , false);
amounts = cellfun(@(x) x.amount , transactions , 'UniformOutput' , false);
merchant_names = cellfun(@(x) x.merchant.name , transactions , 'UniformOutput' , false);

but fails for the merchants because the name doesn't exist in some cases. Is there a nice way to do this without resorting to a loop over all entries?

2

There are 2 best solutions below

0
Wolfie On BEST ANSWER

You could make a helper function to handle the different cases, then just use that in your cellfun.

merchant_names = cellfun( @(x) structValIfExists(x.merchant, 'name'), transactions, 'uni', 0 );

% >> merchant_names = {'ebay', 'ikea', ''}

function v = structValIfExists( s, fld )
    if isstruct( s ) && isfield( s, fld )
        v = s.(fld);
    else
        v = '';
    end
end
0
Cris Luengo On

I would start by extracting the merchant field to a cell array, and then processing that.

First of all, converting the cell array of structs to a struct array will make everything easier. Assuming all the structs are compatible, as in your example, then we can do:

transactions = [transactions{:}];

We now have a 1x3 struct array. We can now easily extract individual fields into a cell array as follows:

merchant = {transactions.merchant}

Some of the cells will be an empty array, others will be a struct. Let's find out which is which:

index = ~cellfun('isempty', merchant)

We now create a cell array to contain the merchant names, and we fill it with the name where the merchant exists:

merchant_name = cell(size(index));
merchant_name(:) = {''};
merchant_name(index) = {merchant(index).name};

Now merchant_name is the cell array:

merchant_name =

  1x3 cell array

    {'ebay'}    {'ikea'}    {0x0 char}