% POP_LISTFACTORS - list independent variables factors for a given design
%
% Usage:
% >> factors = pop_listfactors(STUDY);
% >> factors = pop_listfactors(des);
%
% Inputs:
% STUDY - existing study structure.
% des - existing design
%
% Optional Inputs:
% 'gui' - ['on'|'off'] pop-up gui to show the list (default:off)
% 'splitreg' - ['on'|'off'] split regression for different categorical
% factors. Default is 'off'.
% 'interaction' - ['on'|'off'] compute interaction when using different
% categorical variables. This allows computing interactions
% between these variables at the second level. Default
% is 'off'.
% 'level' - ['one'|'two'|'both'] get only first level or second
% level factors. Default is 'both'.
%
% Author: Arnaud Delorme, UCSD, 2018
% Copyright (C) Arnaud Delorme, CNL / Salk Institute, arno@salk.edu
%
% This file is part of EEGLAB, see http://www.eeglab.org
% for the documentation and details.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are met:
%
% 1. Redistributions of source code must retain the above copyright notice,
% this list of conditions and the following disclaimer.
%
% 2. Redistributions in binary form must reproduce the above copyright notice,
% this list of conditions and the following disclaimer in the documentation
% and/or other materials provided with the distribution.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
% THE POSSIBILITY OF SUCH DAMAGE.
function allFactorsStruct = pop_listfactors(des, varargin)
if nargin < 1
help pop_listfactors;
end
g = finputcheck(varargin, { 'gui' 'string' { 'on' 'off' } 'on';
'splitreg' 'string' { 'on','off' } 'off';
'contrast' 'string' { 'on','off' } 'off';
'constant' 'string' { 'on','off' } 'on';
'level' 'string' { 'one','two','both'} 'both';
'vartype' 'string' { 'categorical','continuous','both'} 'both';
'interaction' 'string' { 'on','off' } 'off' });
if ischar(g)
error(g);
end
if isfield(des,'design')
STUDY = des;
des = des.design;
else
STUDY = [];
end
allFactors = {}; % (strings) still used to find unique values
allFactorsStruct = [];
count = 1;
for iDes = 1:length(des)
for iVar = 1:length(des(iDes).variable)
if strcmpi(des(iDes).variable(iVar).vartype, 'continuous')
allFactors{count} = sprintf('%s - continuous variable', des(iDes).variable(iVar).label);
allFactorsStruct(count).description = allFactors{count};
allFactorsStruct(count).vartype = 'continuous';
allFactorsStruct(count).label = des(iDes).variable(iVar).label;
allFactorsStruct(count).level = des(iDes).variable(iVar).level;
count = count+1;
else
strVals = getstrval(des(iDes).variable(iVar).value);
for iSubVal = 1:length(strVals)
allFactorsStruct(count).vartype = 'categorical';
allFactorsStruct(count).label = des(iDes).variable(iVar).label;
allFactorsStruct(count).level = des(iDes).variable(iVar).level;
allFactorsStruct(count).value = strVals{iSubVal};
if isnumeric(strVals{iSubVal})
allFactors{count} = sprintf('%s - %d', des(iDes).variable(iVar).label, strVals{iSubVal});
else
allFactors{count} = sprintf('%s - %s', des(iDes).variable(iVar).label, strVals{iSubVal});
end
allFactorsStruct(count).description = allFactors{count};
count = count + 1;
end
end
end
end
% remove duplicates
if length(allFactors) ~= length(unique(allFactors))
[~, inds ] = unique(allFactors);
inds = sort(inds);
allFactors = allFactors(inds);
allFactorsStruct = allFactorsStruct(inds);
end
% second level
if ~isempty(STUDY)
[indvar, indvarvals] = std_getindvar(STUDY, 'datinfo');
% remove non numerical values
for iVar = length(indvar):-1:1
allFactorsStruct(end+1).label = indvar{iVar};
allFactorsStruct(end ).level = 'two';
if ~isnumeric(indvarvals{iVar}{1})
allFactorsStruct(end).vartype = 'categorical';
allFactorsStruct(end).value = indvarvals{iVar}; % multiple values
allFactorsStruct(end).description = [ allFactorsStruct(end).label ' - categorical (several values)'];
else
allFactorsStruct(end).vartype = 'continuous';
allFactorsStruct(end).description = [ allFactorsStruct(end).label ' - continuous'];
end
end
end
% filter first or second level
if ~strcmpi(g.level, 'both')
allFactorsStruct = filterlevel(allFactorsStruct, g.level);
end
% redorders factors so that all variables are grouped
if strcmpi(g.gui, 'on')
if strcmpi(g.level, 'both')
allFactorsStruct1 = filterlevel(allFactorsStruct, 'one');
allFactorsStruct2 = filterlevel(allFactorsStruct, 'two');
[~,~,des1] = std_limodesign(allFactorsStruct1,[], 'desconly', 'on', 'splitreg', g.splitreg, 'interaction', g.interaction);
[~,~,des2] = std_limodesign(allFactorsStruct2,[], 'desconly', 'on', 'splitreg', g.splitreg, 'interaction', g.interaction);
elseif strcmpi(g.level, 'one')
[~,~,des1] = std_limodesign(allFactorsStruct,[], 'desconly', 'on', 'splitreg', g.splitreg, 'interaction', g.interaction);
des2 = {};
elseif strcmpi(g.level, 'two')
[~,~,des2] = std_limodesign(allFactorsStruct,[], 'desconly', 'on', 'splitreg', g.splitreg, 'interaction', g.interaction);
des1 = {};
end
% generate categorical labels
allLabels1 = getlabels_l1(des1);
allLabels2 = getlabels_l2(des1, des2);
listui = {};
if ~isempty(des2)
listui{2,1} = { 'Style', 'text', 'string' '2nd-level available statistics' 'fontweight' 'bold' };
listui{2,2} = { 'Style', 'text', 'string' '(inter subject)' };
for index = 1:length(allLabels2)
listui{2,index+2} = { 'Style', 'text', 'string' allLabels2{index} };
end
end
if ~isempty(des1)
listui{1,1} = { 'Style', 'text', 'string' '1st-level variables' 'fontweight' 'bold' };
listui{1,2} = { 'Style', 'text', 'string' '(intra subject)' };
for index = 1:length(allLabels1)
listui{1,index+2} = { 'Style', 'text', 'string' allLabels1{index} };
end
else
listui(1,:) = [];
end
if strcmpi(g.contrast, 'on')
listui{2,1} = { };
listui{2,2} = { 'Style', 'text', 'string' 'Weight' };
for index = 1:max(length(allLabels2), length(allLabels1))
listui{2,index+2} = { 'Style', 'edit', 'string' '' };
end
geometry = cell(1,size(listui,2));
geometry(:) = { [ 1 0.3 ] };
listui = listui(:)';
warningmsg = [ 'warndlg2([ ''A contrast is linear combination of variables (model parameters).'' 10 ' ...
'''Two types of contrasts are valid:'' 10 ' ...
''' '' 10 ' ...
'''- sum/averages: add together variables, e.g., weights of 0.5 (1st var),'' 10 ' ...
'''and 0.5 (2nd var) creates the average of variables 1 and 2.'' 10 ' ...
''' '' 10 ' ...
'''- differences: weight variables with coefficients adding up to zero, '' 10 ' ...
'''e.g. 0.5, 0.5, -0.5 and -0.5 creates the difference between the averages '' 10 ' ...
'''of variables 1&2 vs. 3&4. '' 10 ' ...
''' '' 10 ' ...
'''Make sure you create meaningful contrasts that are easy to interpret. '' 10 ' ...
'''Plotting and checking the values of an ERP/Spectra/ERSP contrast '' 10 ' ...
'''[0.5 0.5 -0.5 -0.5] is looking at the difference between two means. '' 10 ' ...
'''Plotting and checking the values of an ERP/Spectra/ERSP contrast '' 10 ' ...
'''[1 1 -1 -1] is looking at the difference between two sums. These two '' 10 ' ...
'''contrasts will give the same statistical result (T/p values) but the '' 10 ' ...
'''former is easier to interpret than the latter. Contrast validity is '' 10 ' ...
'''automatically checked.'' ]);' ];
[~, ~, allobj] = inputgui( 'geometry', geometry, 'uilist', listui, 'helpcom', warningmsg);
else
geometry = cell(1,size(listui,2));
geometry(:) = {ones(1,size(listui,1))};
listui = listui(:);
listui{end+1} = {};
geometry{end+1} = [1];
geometry = { geometry{:} 1 };
listui = {listui{:} { 'width',80,'align','center','Style', 'pushbutton', 'string', 'OK', 'callback', ['set(gcbf, ''userdata'', ''OK'');'] } };
fig = figure('visible', 'off');
[~, ~, allobj] = supergui( 'fig', fig, 'geomhoriz', geometry, 'uilist', listui, ...
'borders', [0.05 0.015 0.08 0.06], 'spacing', [0 0], 'horizontalalignment', 'left', 'adjustbuttonwidth', 'off' );
waitfor( fig, 'userdata');
end
try
result = get(fig, 'userdata');
close(fig);
drawnow;
catch
end
end
% add the constant level one
if ~strcmpi(g.level, 'two') && strcmpi(g.constant, 'on')
allFactorsStruct(end+1).label = 'constant';
allFactorsStruct(end ).level = 'one';
allFactorsStruct(end ).vartype = 'categorical';
allFactorsStruct(end ).description = 'Constant';
end
if ~strcmpi(g.vartype, 'both')
inds = strmatch(g.vartype, {allFactorsStruct.vartype});
allFactorsStruct = allFactorsStruct(inds);
end
% convert nested values to linear sequence
function res = getstrval(vals)
res = {};
if iscell(vals) && length(vals) == 1
vals = vals{1};
end
if iscell(vals) || (isnumeric(vals) && length(vals) > 1)
for iVal = 1:length(vals)
restmp = getstrval(vals(iVal));
res = { res{:} restmp{:} };
end
else
res = { vals };
end
% format string
function str = formatcond(cellVal)
for iItem = 1:2:length(cellVal)
if iItem+1>length(cellVal) || isempty(cellVal{iItem+1}) % continuous var
tmpFactor = sprintf('%s (continuous)', cellVal{iItem});
elseif isnumeric(cellVal{iItem+1})
tmpFactor = sprintf('%s = %d', cellVal{iItem}, cellVal{iItem+1});
elseif iscell(cellVal{iItem+1})
tmpFactor = sprintf('%s', cellVal{iItem});
else
tmpFactor = sprintf('%s = %s', cellVal{iItem}, cellVal{iItem+1});
end
if iItem == 1
str = tmpFactor;
else str = sprintf('%s & %s', str, tmpFactor);
end
end
% select variables at a specific level
function allFactorsStruct = filterlevel(allFactorsStruct, level)
rmInd = [];
for ind = 1:length(allFactorsStruct)
if ~strcmpi(allFactorsStruct(ind).level,level)
rmInd = [ rmInd ind ];
end
end
allFactorsStruct(rmInd) = [];
% -------
% level 1
% -------
function allLabels = getlabels_l1(des)
allLabels = {};
count = 1;
if isempty(des), return; end
for iCat = 1:length(des.categorical)
for iVal = 1:length(des.categorical{iCat})
allLabels{count} = [ int2str(count) '. ' formatcond(des.categorical{iCat}{iVal}) ];
count = count+1;
end
end
for iCont = 1:length(des.continuous)
if iscell(des.continuous{iCont})
allLabels{count} = [ int2str(count) '. ' formatcond(des.continuous{iCont}) ];
else
allLabels{count} = [ int2str(count) '. ' formatcond({ des.continuous{iCont} } ) ];
end
count = count+1;
end
% add constant
allLabels{count} = [ int2str(count) '. Constant' ];
% -------
% level 2
% -------
function allLabels = getlabels_l2(des1, des2)
allLabels = {};
count = 1;
if isempty(des2), return; end
allLabels{count} = '* One-sample t-test on 1st level var.'; count = count+1;
if ~isempty(des1.categorical)
allLabels{count} = '* Paire/unpaired t-test on 1st level var.'; count = count+1;
end
if any(cellfun(@length, des1.categorical) > 2)
allLabels{count} = '* ANOVA on 1st level var.'; count = count+1;
end
if length(des1.categorical) > 1 && length(des1.continuous) > 1
allLabels{count} = '* ANCOVA on 1st level var.'; count = count+1;
end
for iCat = 1:length(des2.categorical)
for iVal = 1:length(des2.categorical{iCat})
if ~isempty(des2.continuous)
allLabels{count} = [ '* t-test/ANOVA/ANCOVA using ' formatcond(des2.categorical{iCat}{iVal}) ];
else
allLabels{count} = [ '* t-test/ANOVA using ' formatcond(des2.categorical{iCat}{iVal}) ];
end
count = count+1;
end
end
for iCont = 1:length(des2.continuous)
if ~isempty(des2.continuous)
allLabels{count} = [ '* Regression/ANCOVA using ' des2.continuous{iCont}{1} ];
else
allLabels{count} = [ '* Regression using ' formatcond(des2.continuous{iCont}) ];
end
count = count+1;
end
% allLabels{count} = '* Use STUDY "group" for subjects'' groups'; count = count+1;
allLabels{count} = '* Regression/ANOVA/ANCOVA with text file'; count = count+1;
allLabels{count} = ' (text file must have 1 row per subject)'; count = count+1;
if length(allLabels) > 15
allLabels(16:end) = [];
allLabels{16} = '...';
end