Is rowfun Showing Strange Behavior when the Target Function Returns an Empty Output?
Show older comments
Define a function that echos a scalar input, unless the input is 3, which returns an empty row vector 1x0.
function y = fun1(x)
if x == 3
y = zeros(1,0);
else
y = x;
end
end
Create a table
T = table((1:5).','VariableNames',"x");
As expected, the size of T is 5 x 1 (though I don't understand the return of ans here; I don't see ans when running locally with R2024a)
size(T)
Now call @doc:rowfun to execute fun1() on each row of T.x. What might be the impact of fun1 returning an empty matrix for row 3?
foo = rowfun(@fun1,T,"InputVariables","x","OutputVariableNames","y")
Not sure what happened there. Why isn't there any output displayed? What actually is foo?
foo
Still no output displayed.
Apparently foo is 5x1 table, even though rowfun presumably only returned four rows (because of the empty)
whos foo
We can access the variable, but foo.y only has four rows
foo.y
while the size of foo is 5 x 1 (and why is ans being shown)?
size(foo)
Common use of rowfun is to append to the original table.
T = [T,rowfun(@fun1,T,"InputVariables","x","OutputVariableNames","y")]
T
Why wasn't there any output? And why isn't there an error somewhere?
T is now being shown as 5 x 2
size(T)
x doesn't change
T.x
But T.y only has four rows. How can two variables in the same table have different number of rows?
T.y
Just out of curiosity, the third row of T is
T(3,:)
We can't access the "end" of the table (and I believe we'd get the same error if tyring T(5,:) )
try
T(end,:)
catch ME
ME.message
end
The behavior of the call to rowfun is different on R2024a, where it throws an error (f there is an anonymous function that is functionally equivalent to fun1 here)
But that output showing foo as a 5x1 table before the error message seems strange. Shouldn't the error prevent that output? And if that output is there, shouldn't that output show what foo actually is? And the error message doesn't really make sense anyway. Prior to that line foo wasn't even defined, so if rowfun is returning a 4x1, it should not be a problem to assign that output to the previously-nonexistent LHS variable.
Anyway, continuing in 2026a ...
Change the function so that an input of 3 returns a 0x0 empty
function y = fun2(x)
if x == 3
y = [];
else
y = x;
end
end
T = table((1:5).','VariableNames',"x");
T = [T,rowfun(@fun2,T,"InputVariables","x","OutputVariableNames","y")]
Now rowfun fails, as probably should be the expected behavior, but the error message makes no sense. How could fun2 return an output with more than one row when applied to the third row of T when the output in that case should be 0x0?
Should rowfun always throw an error if the output doesn't have the same number of rows as the input, as seems to be the case in 2024a? Maybe not if rowfun is being used in isolation, but then there should definitely be an error when that output of rowfun is horizontally concatenated with the original table.
And why is there no output from lines that don't end with a semicolon?
Answers (2)
Interesting.
Apparently you have discovered a way to generate a fundamentally malformed table :)
My guess about the behaviors that you demonstrated is that they are caused by the lack of a single point of truth declaring the table to be invalid, so each operation or function decides for itself how it can handle the malformed table:
- Operations that only consult the table header (size, height, horzcat) succeed, because they never touch the columns/variables.
- Operations that use the actual column data (display, T.y, T(X,:)) will either fail outright or return the true length, because they're bound by the real data, not the table header.
This explains the observed behavior:
- The lack of display may be caused by the overloaded DISPLAY (or similar) routine attempting/checking that all columns/variables have the same number of rows (a prerequisite for displaying), which fails... and because this was not a forseen use-case it simply has no alternative display or even any error message. It simply fails by doing nothing.
- "How can two variables in the same table have different number of rows?" by tricking it, as you just did.
- "the third row of T is..." this works because the overloaded indexing actively goes through each column/variable and selects that data. When you try to access the 5th row it fails due to indexing into a non-existent element of x.
- The size is 5x2 because the size is stored in the table header as its own data. Operations that refer to the size but do not need to access the actual columns/variables still work, e.g. SIZE, HEIGHT, HORZCAT as you demonstrate, etc.
This behavior matches earlier explanations of the table implementation:
So the answer to your title is: yes, rowfun should not let that occur.
I do not understand your comments regarding ans.
3 Comments
Consider the table bytes listed here:
V = 1:5;
T = array2table(V(:),'VariableNames',"x");
U = rowfun(@fun1,T,"InputVariables","x","OutputVariableNames","y")
whos
The difference is 8 bytes, i.e. one element of a double array. So apparently WHOS checks the actual table data.
function y = fun1(x)
if x==3
y = nan(1,0);
else
y = x;
end
end
Matt J
about 24 hours ago
How could fun2 return an output with more than one row when applied to the third row of T when the output in that case should be 0x0?
The relevant section of rowfun is,
n = size(b_data{igrp,jout},1);
if grouped
...
elseif any(n ~= 1) % ~grouped
error(message('MATLAB:table:rowfun:UngroupedRowSize',funName,ordinalString(igrp)));
end
Here, n=0 but the code assumes that n~=1 is the same as n>1.
Categories
Find more on Loops and Conditional Statements in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!