(problem solved, question revised) How to select table rows with varargin and not using logical condition as input argument.

11 次查看(过去 30 天)
(original question)"I have tables. Selecting rows where values meet logical conditions from them is a frequent task. It becomes tiresome when it's done over many tables and logical conditions, and error-prone. So I try to make it easier that I don't need to type repetitive logical conditions. My current solution is using inputParser to re-syntax it as name-value argument.
Because this is the first time I use inputParser, my solution is done in an ad-hoc manner. Any suggestons for improvement is greatly appereciated. For example, in addParameters( ) how do I handle the default values, which I don't need to specify at all."
(revised question) My original question did not really ask what I want to ask. The more accurately way to state my question is: Is there any way to set default input argument to be of all possible values. So when an input argument is not specified, all possible values will be included in a selection logic?
Demo data
T = cell2table({'income', 2020, 1; 'income', 2020, 2; 'income', 2021, 1; 'income', 2021, 2; ...
'expense', 2020, 1; 'expense', 2020, 2}, "VariableNames", {'account', 'year', 'month'});
The output I want for my codes to achieve but fail.
ix = T.account=="income";
% when year and month are not specified in the selection,
% all years and months will be included.
T(ix, :)
ans = 4×3 table
account year month __________ ____ _____ {'income'} 2020 1 {'income'} 2020 2 {'income'} 2021 1 {'income'} 2021 2
% when account and year are assigned values but month is not,
% all months will be included in the selection logic.
ix = T.account=="income";
iy = T.year==2020;
T(ix&iy,:)
ans = 2×3 table
account year month __________ ____ _____ {'income'} 2020 1 {'income'} 2020 2
However, my codes fail to get similar outputs.
foo(T, 'account', 'expense')
ans = 2×3 table
account year month ___________ ____ _____ {'expense'} 2020 1 {'expense'} 2020 2
% I want all years and months included in the output.
My codes
function out = foo(T, varargin)
% ???? How to set default values to be all possible values ????%
defaultAccount='income';
defaultYear = 2020;
defaultMonth = 1;
p = inputParser;
addRequired(p, 'T');
addParameter(p, 'account', defaultAccount);
addParameter(p, 'year', defaultYear);
addParameter(p, 'month', defaultMonth);
parse(p, T, varargin{:});
if ismember('account', p.UsingDefaults)
ixAccount = true;
else
ixAccount = T.account==string(p.Results.account);
end
if ismember('year', p.UsingDefaults)
ixYear = true;
else
ixYear = T.year==p.Results.year;
end
if ismember('month', p.UsingDefaults)
ixMonth = true;
else
ixMonth = T.month==p.Results.month;
end
% ???? When year or month is not specified, how to
% make ixYear or ixMonth not appear in the final
% selection logic or they would be set 'true'????
ixRows = ixAccount&ixYear&ixMonth;
out = T(ixRows, :);
end

采纳的回答

Steven Lord
Steven Lord 2023-7-4
For this particular application, I wouldn't use the dot notation to access the variables in your table. I'd use one of the other ways to access data in a table, specifically curly brace indexing, to extract the contents of the variable.
load patients
T = table(LastName, Age, Weight);
head(T) % Show just the first few rows
LastName Age Weight ____________ ___ ______ {'Smith' } 38 176 {'Johnson' } 43 163 {'Williams'} 38 131 {'Jones' } 40 133 {'Brown' } 49 119 {'Davis' } 46 142 {'Miller' } 33 142 {'Wilson' } 40 180
If I just wanted to get all the patients whose Age is 38, I could do so in a couple different ways. I could hard-code that I want to use the Age variable:
A1 = T.Age == 38;
or I could use a more general indexing approach.
variableName = 'Age';
A2 = T{:, variableName} == 38;
isequal(A1, A2)
ans = logical
1
So if I wanted to select those patients who are 38 and weigh 131 pounds, I could hard code those constraints.
selectedPatients = T.Age == 38 & T.Weight == 131;
Or I could use that curly brace indexing. Let's start off with our criteria and a vector indicating all patients are still eligible for selection (as an initial condition.)
criteria = {'Age', 38, 'Weight', 131};
eligible = true(height(T), 1);
Now iterate.
for whichCriterion = 1:2:length(criteria)
name = criteria{whichCriterion};
value = criteria{whichCriterion+1};
eligible = eligible & T{:, name} == value;
end
Compare the two approaches to check:
isequal(selectedPatients, eligible)
ans = logical
1
This approach doesn't care how long your criteria cell array is (so long as it has an even length.) It gets a little more complicated if you wanted to use different comparisons (less than, greater than, etc.) instead of or in addition to equality testing, but not significantly more so.
selectedPatients = T.Age > 35 & T.Weight < 150;
Here the criteria is a cell array with three elements: name, comparison method, and value to compare the variable value against.
criteria = {'Age', @(x, y) x > y, 35, 'Weight', @(x, y) x < y, 150};
eligible = true(height(T), 1);
Now iterate.
for whichCriterion = 1:3:length(criteria)
name = criteria{whichCriterion};
comparisonMethod = criteria{whichCriterion+1};
value = criteria{whichCriterion+2};
eligible = eligible & comparisonMethod(T{:, name}, value);
end
Compare the two approaches to check:
isequal(selectedPatients, eligible)
ans = logical
1
Instead of using the anonymous functions I could have just used the names of the functions associated with the relational operators. gt, > is greater-than and lt, < is less-than.
criteria = {'Age', @gt, 35, 'Weight', @lt, 150}
criteria = 1×6 cell array
{'Age'} {@gt} {[35]} {'Weight'} {@lt} {[150]}
eligible = true(height(T), 1);
for whichCriterion = 1:3:length(criteria)
name = criteria{whichCriterion};
comparisonMethod = criteria{whichCriterion+1};
value = criteria{whichCriterion+2};
eligible = eligible & comparisonMethod(T{:, name}, value);
end
isequal(selectedPatients, eligible)
ans = logical
1
Note that while I created criteria manually, you could do it as inputs to a function. Compare what the following function shows it receives as input with the criteria variable above.
showInputsAsCell('Age', @gt, 35, 'Weight', @lt, 150)
{'Age'} {@gt} {[35]} {'Weight'} {@lt} {[150]}
function showInputsAsCell(varargin)
disp(varargin)
end

更多回答(2 个)

Satwik
Satwik 2023-6-26
Hi Simon,
I understand that you are using inputParser in your code but don't want to specify the default values as there is no logic associated to the default values.
The inputParser function requires that a default value be provided for each parameter in case the user does not pass in a value for that parameter. This is to ensure that the function can be parsed correctly even if the user does not pass in all of the required parameters.
For more details go through the documentation InputParser.
Hope this helps!

Simon
Simon 2023-7-7
编辑:Simon 2023-7-9
@Steven Lord I think I finally figure out how to do it based on your algorithm. Great thanks for your generous help. I think the solution to my question should be useful to other poeple. So I am thinking about uploading it to File Exchange. I wonder if you would grant me the permission of using your algorithm?
load patients
T = table(LastName, Age, Weight);
foo(T, 'Age', 38)
ans = 6×3 table
LastName Age Weight ____________ ___ ______ {'Smith' } 38 176 {'Williams'} 38 131 {'Carter' } 38 128 {'Cook' } 38 124 {'Bailey' } 38 130 {'Butler' } 38 184
foo(T, 'Age', 38, 'Weight', 131)
ans = 1×3 table
LastName Age Weight ____________ ___ ______ {'Williams'} 38 131
foo(T, 'Age', 38:40, 'Weight', 131:170)
ans = 5×3 table
LastName Age Weight ____________ ___ ______ {'Williams'} 38 131 {'Jones' } 40 133 {'Allen' } 39 143 {'Lopez' } 40 137 {'Mitchell'} 39 164
function out = foo(T, varargin)
criteria = varargin;
eligible = true(height(T),1);
for whichCriterion=1:2:length(criteria)
name = criteria{whichCriterion};
value = criteria{whichCriterion+1};
eligible = eligible & ismember(T{:,name},value);
end
out = T(eligible,:);
end

类别

Help CenterFile Exchange 中查找有关 Argument Definitions 的更多信息

产品


版本

R2023a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by