From 1101fa446da37063ba9991f70d545fd156c72693 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 30 Jul 2024 13:47:53 +0200 Subject: [PATCH] fix and implement F test --- .../openneuro/models/model-ds003397_smdl.json | 37 +++++++++ .../stats/setBatchGroupLevelContrasts.m | 75 ++++++++++++++----- src/workflows/stats/bidsResults.m | 24 +++--- .../model-vismotionOneWayANOVA_smdl.json | 18 +++++ .../subject_level/test_specifyContrasts.m | 2 + .../stats/test_bidsRFX_3_groups.m | 13 +++- 6 files changed, 132 insertions(+), 37 deletions(-) diff --git a/demos/openneuro/models/model-ds003397_smdl.json b/demos/openneuro/models/model-ds003397_smdl.json index f59f3f52..eec44fec 100644 --- a/demos/openneuro/models/model-ds003397_smdl.json +++ b/demos/openneuro/models/model-ds003397_smdl.json @@ -92,6 +92,35 @@ "Test": "t" } }, + { + "Level": "Dataset", + "Name": "dataset_level", + "Description": "average across all subjects", + "GroupBy": [ + "contrast" + ], + "Model": { + "Type": "glm", + "X": [ + 1 + ] + } + }, + { + "Level": "Dataset", + "Name": "within_group", + "Description": "within group t-test", + "GroupBy": [ + "contrast", + "group" + ], + "Model": { + "Type": "glm", + "X": [ + 1 + ] + } + }, { "Level": "Dataset", "Name": "between_groups", @@ -161,6 +190,14 @@ "Source": "run_level", "Destination": "subject_level" }, + { + "Source": "subject_level", + "Destination": "dataset_level" + }, + { + "Source": "subject_level", + "Destination": "within_group" + }, { "Source": "subject_level", "Destination": "between_groups", diff --git a/src/batches/stats/setBatchGroupLevelContrasts.m b/src/batches/stats/setBatchGroupLevelContrasts.m index 53429e4c..825170f3 100644 --- a/src/batches/stats/setBatchGroupLevelContrasts.m +++ b/src/batches/stats/setBatchGroupLevelContrasts.m @@ -88,35 +88,37 @@ thisContrast = bm.get_contrasts('Name', nodeName); + if strcmp(groupGlmType, 'two_sample_t_test') + label = '2samplesTTest'; + else + label = '1WayANOVA'; + end + for j = 1:numel(contrastsList) spmMatFile = fullfile(getRFXdir(opt, ... nodeName, ... contrastsList{j}, ... - '1WayANOVA'), ... + label), ... 'SPM.mat'); for iCon = 1:numel(thisContrast) - % Sort conditions and weights - ConditionList = strrep(thisContrast{iCon}.ConditionList, ... - [groupColumnHdr, '.'], ... - ''); - [ConditionList, I] = sort(ConditionList); - Weights = thisContrast{iCon}.Weights(I); + convec = generateConStruct(thisContrast{iCon}, ... + availableGroups, ... + groupColumnHdr); - % Create contrast vectors by what was passed in the model - convec = zeros(size(availableGroups)); - for iGroup = 1:numel(availableGroups) - index = strcmp(availableGroups{iGroup}, ConditionList); - if any(index) - convec(iGroup) = Weights(index); - end - end + if strcmp(thisContrast{iCon}.Test, 't') + consess{iCon}.tcon.name = thisContrast{iCon}.Name; %#ok<*AGROW> + consess{iCon}.tcon.sessrep = 'none'; + consess{iCon}.tcon.convec = convec; + + elseif strcmp(thisContrast{iCon}.Test, 'F') + consess{iCon}.fcon.name = thisContrast{iCon}.Name; + consess{iCon}.fcon.sessrep = 'none'; + consess{iCon}.fcon.convec = convec; - consess{iCon}.tcon.name = thisContrast{iCon}.Name; - consess{iCon}.tcon.convec = convec; - consess{iCon}.tcon.sessrep = 'none'; + end end matlabbatch = setBatchContrasts(matlabbatch, opt, spmMatFile, consess); @@ -144,3 +146,40 @@ matlabbatch = setBatchContrasts(matlabbatch, opt, spmMatFile, consess); end + +function convec = generateConStruct(thisContrast, availableGroups, groupColumnHdr) + % Sort conditions and weights + ConditionList = strrep(thisContrast.ConditionList, ... + [groupColumnHdr, '.'], ... + ''); + [ConditionList, I] = sort(ConditionList); + + if strcmp(thisContrast.Test, 't') + + Weights = thisContrast.Weights(I); + + % Create contrast vectors by what was passed in the model + convec = zeros(size(availableGroups)); + for iGroup = 1:numel(availableGroups) + index = strcmp(availableGroups{iGroup}, ConditionList); + if any(index) + convec(iGroup) = Weights(index); + end + end + + elseif strcmp(thisContrast.Test, 'F') + + Weights = thisContrast.Weights(I, :); + + % Create contrast vectors by what was passed in the mode + convec = zeros(size(Weights, 1), numel(availableGroups)); + for iGroup = 1:numel(availableGroups) + index = strcmp(availableGroups{iGroup}, ConditionList); + if any(index) + convec(:, iGroup) = Weights(:, index); + end + end + + end + +end diff --git a/src/workflows/stats/bidsResults.m b/src/workflows/stats/bidsResults.m index 2d560106..d675a115 100644 --- a/src/workflows/stats/bidsResults.m +++ b/src/workflows/stats/bidsResults.m @@ -497,25 +497,19 @@ end - case 'two_sample_t_test' + case {'two_sample_t_test', 'one_way_anova'} - thisContrast = bm.get_contrasts('Name', result.nodeName); - - result.dir = getRFXdir(opt, result.nodeName, name); - - for iCon = 1:numel(thisContrast) - result.name = [thisContrast{iCon}.Name ' - ' name]; - result.contrastNb = iCon; - matlabbatch = appendToBatch(matlabbatch, opt, result); - end - - case 'one_way_anova' - - contrastsList = getContrastsListForFactorialDesign(opt, result.nodeName); + edge = bm.get_edge('Destination', result.nodeName); + contrastsList = edge.Filter.contrast; for iCon = 1:numel(contrastsList) - label = '1WayANOVA'; + if strcmp(glmType, 'two_sample_t_test') + label = '2samplesTTest'; + else + label = '1WayANOVA'; + end + result.dir = getRFXdir(opt, result.nodeName, contrastsList{iCon}, label); load(fullfile(result.dir, 'SPM.mat'), 'SPM'); diff --git a/tests/data/models/model-vismotionOneWayANOVA_smdl.json b/tests/data/models/model-vismotionOneWayANOVA_smdl.json index 5185c4e2..584e2b43 100644 --- a/tests/data/models/model-vismotionOneWayANOVA_smdl.json +++ b/tests/data/models/model-vismotionOneWayANOVA_smdl.json @@ -157,6 +157,24 @@ 1 ], "Test": "t" + }, + { + "Name": "F_test", + "ConditionList": [ + "diagnostic.blind", + "diagnostic.relative" + ], + "Weights": [ + [ + 1, + 0 + ], + [ + 0, + 1 + ] + ], + "Test": "F" } ] } diff --git a/tests/tests_stats/subject_level/test_specifyContrasts.m b/tests/tests_stats/subject_level/test_specifyContrasts.m index f06726d4..72856d6f 100644 --- a/tests/tests_stats/subject_level/test_specifyContrasts.m +++ b/tests/tests_stats/subject_level/test_specifyContrasts.m @@ -11,6 +11,8 @@ function test_specifyContrasts_bug_854() % no error when no contrast to build + skipIfOctave('mixed-string-concat warning thrown'); + % GIVEN subLabel = '01'; diff --git a/tests/tests_workflows/stats/test_bidsRFX_3_groups.m b/tests/tests_workflows/stats/test_bidsRFX_3_groups.m index 97f52366..613f966d 100644 --- a/tests/tests_workflows/stats/test_bidsRFX_3_groups.m +++ b/tests/tests_workflows/stats/test_bidsRFX_3_groups.m @@ -40,20 +40,25 @@ function test_bidsRFX_one_way_anova_contrast() rfxDir = getRFXdir(opt, nodeName, contrastName, '1WayANOVA'); assertEqual(fileparts(matlabbatch{1}.spm.stats.con.spmmat{1}), rfxDir); - assertEqual(numel(matlabbatch{1}.spm.stats.con.consess), 2); + assertEqual(numel(matlabbatch{1}.spm.stats.con.consess), 3); + assertEqual(matlabbatch{1}.spm.stats.con.consess{1}.tcon.name, 'relative_gt_ctrl'); assertEqual(matlabbatch{1}.spm.stats.con.consess{1}.tcon.convec, [0; -1; 1]); assertEqual(matlabbatch{1}.spm.stats.con.consess{2}.tcon.name, 'blind_gt_relative'); assertEqual(matlabbatch{1}.spm.stats.con.consess{2}.tcon.convec, [-1; 0; 1]); + assertEqual(matlabbatch{1}.spm.stats.con.consess{3}.fcon.name, 'F_test'); + assertEqual(matlabbatch{1}.spm.stats.con.consess{3}.fcon.convec, [1, 0, 0; 0, 0, 1]); contrastName = 'VisStat_gt_VisMot'; rfxDir = getRFXdir(opt, nodeName, contrastName, '1WayANOVA'); assertEqual(fileparts(matlabbatch{2}.spm.stats.con.spmmat{1}), rfxDir); - assertEqual(numel(matlabbatch{2}.spm.stats.con.consess), 2); - assertEqual(matlabbatch{1}.spm.stats.con.consess{1}.tcon.name, 'relative_gt_ctrl'); + assertEqual(numel(matlabbatch{2}.spm.stats.con.consess), 3); + assertEqual(matlabbatch{2}.spm.stats.con.consess{1}.tcon.name, 'relative_gt_ctrl'); assertEqual(matlabbatch{2}.spm.stats.con.consess{1}.tcon.convec, [0; -1; 1]); - assertEqual(matlabbatch{1}.spm.stats.con.consess{2}.tcon.name, 'blind_gt_relative'); + assertEqual(matlabbatch{2}.spm.stats.con.consess{2}.tcon.name, 'blind_gt_relative'); assertEqual(matlabbatch{2}.spm.stats.con.consess{2}.tcon.convec, [-1; 0; 1]); + assertEqual(matlabbatch{1}.spm.stats.con.consess{3}.fcon.name, 'F_test'); + assertEqual(matlabbatch{1}.spm.stats.con.consess{3}.fcon.convec, [1, 0, 0; 0, 0, 1]); end