Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
138 commits
Select commit Hold shift + click to select a range
1d34efe
Refactor HRF_Est_Toolbox2 with end-to-end user pipeline
Michael-Sun May 4, 2026
2994fb1
Merge pull request #1 from Michael-Sun/codex/refactor-hrf-estimation-…
Michael-Sun May 4, 2026
c591c94
Fix HRF toolbox bugs in SPM handling, condition TS, and plotting
Michael-Sun May 4, 2026
8f3ee53
Merge branch 'master' into codex/refactor-hrf-estimation-pipeline-for…
Michael-Sun May 4, 2026
787db65
Merge pull request #2 from Michael-Sun/codex/refactor-hrf-estimation-…
Michael-Sun May 4, 2026
5e41f39
Fix signature-mode conditional in run_hrf_pipeline
Michael-Sun May 4, 2026
5dae221
Merge branch 'master' into codex/refactor-hrf-estimation-pipeline-for…
Michael-Sun May 4, 2026
4bf114a
Merge pull request #3 from Michael-Sun/codex/refactor-hrf-estimation-…
Michael-Sun May 4, 2026
765cc23
Fix apply_all_signatures compatibility and signature metadata extraction
Michael-Sun May 4, 2026
4137583
Merge branch 'master' into codex/refactor-hrf-estimation-pipeline-for…
Michael-Sun May 4, 2026
6d78057
Merge pull request #4 from Michael-Sun/codex/refactor-hrf-estimation-…
Michael-Sun May 4, 2026
5548434
Handle single-signature extraction robustly and add shaded plotting
Michael-Sun May 4, 2026
61da1fd
Merge branch 'master' into codex/refactor-hrf-estimation-pipeline-for…
Michael-Sun May 4, 2026
04f578a
Merge pull request #5 from Michael-Sun/codex/refactor-hrf-estimation-…
Michael-Sun May 4, 2026
84cdf1c
Add multilevel time-unfolding significance pipeline and plots
Michael-Sun May 4, 2026
696db3e
Merge branch 'master' into codex/refactor-hrf-estimation-pipeline-for…
Michael-Sun May 4, 2026
3cf436f
Merge pull request #6 from Michael-Sun/codex/refactor-hrf-estimation-…
Michael-Sun May 4, 2026
3bc706e
Add atlas ROI signal source and fast montage animation helper
Michael-Sun May 5, 2026
42e08bd
Merge branch 'master' into codex/refactor-hrf-estimation-pipeline-for…
Michael-Sun May 5, 2026
00785fb
Merge pull request #7 from Michael-Sun/codex/refactor-hrf-estimation-…
Michael-Sun May 5, 2026
c86c8e2
Update HRF Toolbox for Slurm Pipeline and animations
Michael-Sun May 6, 2026
d3526e0
Update Fit_Spline to output warning if you cannot create bsplines.
Michael-Sun May 6, 2026
d06ba15
Fix Typo
Michael-Sun May 6, 2026
7cd7334
Fix bug in table header reading
Michael-Sun May 6, 2026
fae9c9c
Minor Bug Fix
Michael-Sun May 6, 2026
a1197bb
Minor Bugfix
Michael-Sun May 6, 2026
1127692
Bugfixes to slurm script
Michael-Sun May 6, 2026
58631c1
Deal with empty results if there's a problem with the file.
Michael-Sun May 7, 2026
293c5af
Fix Typo
Michael-Sun May 7, 2026
ac688ab
Tweak second-level pipeline
Michael-Sun May 9, 2026
906344a
Updates to animation and second-level pipeline
Michael-Sun May 11, 2026
b83cd30
Update to accept regular expressions for conditions
Michael-Sun May 13, 2026
b0c6e0c
Add model options to plotting with standard errors
Michael-Sun May 14, 2026
1a69c3d
Output 4d niftis on the basis of model selected
Michael-Sun May 15, 2026
b55ce2e
Bug fix to allow plotting of other models other than FIR after slurm …
Michael-Sun May 15, 2026
36c8cbd
Update to write files for fits other than FIR
Michael-Sun May 18, 2026
bb8e55e
Big updates for error checking and error correction
Michael-Sun May 20, 2026
ec2bd28
Big bugfix incorporating repeated measures within subject
Michael-Sun May 21, 2026
9ff5e44
Bug fixes and Slurm job audit helpers
Michael-Sun May 21, 2026
fb912f3
Update to adjust run-level SE estimation
Michael-Sun May 24, 2026
e59a12b
Compare 2x2 Multiple Score Structures
Michael-Sun May 25, 2026
9badb00
Patched to fix animations and overwrite stale score CSVs
Michael-Sun May 27, 2026
e74ebb0
Phase 1: factor scoring into shared helper + add RepairMissing audit …
Michael-Sun May 28, 2026
52afa74
Phase 2 (1/3): merge HRF_Est_Toolbox2 pipeline into HRF_Est_Toolbox4/…
Michael-Sun May 28, 2026
bb58d4d
Phase 2 (1b/3): apply TB2 content to the 4 diverged files in TB4
Michael-Sun May 28, 2026
1d9aeb5
Phase 2 (2/3): fix post-move path references
Michael-Sun May 28, 2026
daf6e25
Phase 2 (3/3): remove Old_stuff/ archives, .asv autosave, empty TB2 d…
Michael-Sun May 28, 2026
5276bbf
Phase 3 v0: scaffold @fmri_hrf and @statistic_hrf class folders + pai…
Michael-Sun May 28, 2026
9c0f6de
Phase 3 v0 fix: drop redefined metadata_table property in fmri_hrf
Michael-Sun May 28, 2026
5bd381a
Cache per-prefix wholebrain loads in hrf_make_average_montage_animations
Michael-Sun May 28, 2026
c183d19
Phase 3 v0 fixes: row-shape conditions, hand-rolled cat + to_statisti…
Michael-Sun May 28, 2026
495b19f
Add verbose progress feedback to hrf_make_average_montage_animations
Michael-Sun May 28, 2026
107159d
docs: add Tutorial and Architecture for the pipeline
Michael-Sun May 28, 2026
60540bf
animations: constant color limits + threshold subject-level animations
Michael-Sun May 28, 2026
74f6873
scoring: append missing signature sets into existing CSVs instead of …
Michael-Sun May 28, 2026
4d26eca
scoring: add atlas region-mean extraction to hrf_score_one_prefix
Michael-Sun May 28, 2026
9f03d62
scoring: add 'Regions' to atlas extraction + per-region append matching
Michael-Sun May 28, 2026
99110c1
atlas extraction: deterministic subset + L1 normalization (default)
Michael-Sun May 28, 2026
3e72a5b
atlas extraction: flip Normalize default from 'l1' to 'mean'
Michael-Sun May 28, 2026
9341765
analytics: hrf_curve_summaries — per-curve shape metrics from score CSVs
Michael-Sun May 28, 2026
a162f33
hrf_curve_summaries: significance-driven onset/offset + bipolar peaks
Michael-Sun May 28, 2026
e71ec85
analytics: hrf_curve_summary_groupstats — group-level pooling
Michael-Sun May 28, 2026
7ce1cdf
animations: pass cmaprange + colormaps + gray_buffer to montage directly
Michael-Sun May 29, 2026
4977b51
hrf_curve_summaries: fix VariableTypes/VariableNames count mismatch
Michael-Sun May 29, 2026
d4587f2
hrf_curve_summaries: bipolar peak NaN-trap fix (Inf*0 in additive mask)
Michael-Sun May 29, 2026
b043f9b
plot_hrf_atlas_curves: HRF time series for the top activating atlas r…
Michael-Sun May 31, 2026
5d09be7
plot_hrf_atlas_curves: AtlasObj-driven matching + groupsummary->split…
Michael-Sun May 31, 2026
e6352bb
plot_hrf_atlas_curves: Verbose mode for diagnosing model/path mismatches
Michael-Sun May 31, 2026
96ddfd0
plot_hrf_atlas_curves: cheaper verbose stats + diagnostic error on em…
Michael-Sun Jun 3, 2026
4aeec96
atlas extraction: call extract_roi_averages per region, not in bulk
Michael-Sun Jun 3, 2026
fde1ded
atlas extraction: pass fmri_data to extract_roi_averages; Regions sub…
Michael-Sun Jun 3, 2026
548ea06
hrf_score_wholebrain_input_table: parfor by default + fmri_data reloa…
Michael-Sun Jun 4, 2026
24bdfd7
hrf_write_slurm_study_script: thread AtlasObj/Regions/Normalize to th…
Michael-Sun Jun 4, 2026
0efdc2d
hrf_write_slurm_study_script: addpath canlab_root BEFORE load(config_…
Michael-Sun Jun 4, 2026
2f76282
atlas extraction: cap column names at 63 chars (table-variable name l…
Michael-Sun Jun 5, 2026
3de2712
plot_hrf_atlas_curves: multi-source mode for cross-study comparison
Michael-Sun Jun 5, 2026
ab22b95
plot_hrf_atlas_curves + hrf_curve_summaries: wildcard conditions and …
Michael-Sun Jun 5, 2026
ed93873
plot_hrf_atlas_curves: actionable error when requested Regions match …
Michael-Sun Jun 8, 2026
d6312b5
plot_hrf_atlas_curves: CollapseConditions to average matched conditio…
Michael-Sun Jun 8, 2026
3b2b975
plot_hrf_atlas_curves: balanced hierarchical nesting (default), no ba…
Michael-Sun Jun 9, 2026
2a63180
plot_hrf_atlas_curves: generalize to signature + imageset (network) s…
Michael-Sun Jun 9, 2026
485f492
Rename plot_hrf_atlas_curves -> plot_hrf_curves; per-panel y-scale + …
Michael-Sun Jun 9, 2026
1b6a260
plot_hrf_curves: per-series line color / style / width customization
Michael-Sun Jun 10, 2026
dea8fe3
Phase 4 v0: hrf_misspec_metrics (curve-shape misspecification vs refe…
Michael-Sun Jun 10, 2026
76ac5f8
Phase 4: hrf_residual_diagnostics (residual-based misspecification me…
Michael-Sun Jun 10, 2026
62a9606
plot_hrf_curves: reliability / SNR / shape RankBy modes (surface clea…
Michael-Sun Jun 11, 2026
9ec824a
plot_hrf_curves: hrf_match ranker (offset-removed, latency-flexible m…
Michael-Sun Jun 11, 2026
4d2074c
plot_hrf_curves: Contrast arg -- rank/plot the paired condition diffe…
Michael-Sun Jun 11, 2026
9dfbe35
plot_hrf_curves: Contrast across study labels (same condition, differ…
Michael-Sun Jun 11, 2026
3863803
Merge branch 'canlab:master' into master
Michael-Sun Jun 12, 2026
85ed505
RSA/RSM toolkit: @rsm class, fmri_data RSA methods, RSA_tools, search…
Michael-Sun Jun 12, 2026
1115415
hrf_fit_wholebrain_stats: SPM GKWY compatibility (high-pass / whiteni…
Michael-Sun Jun 12, 2026
d7a7501
HRF GKWY: per-subject Tier B in study/SLURM + data-estimated AR white…
Michael-Sun Jun 12, 2026
252fe23
Gitignore the 158 MB bmrk3 RSA sample dataset
Michael-Sun Jun 12, 2026
3781647
Revert "Gitignore the 158 MB bmrk3 RSA sample dataset"
Michael-Sun Jun 13, 2026
e3422f9
Restore multi-atlas + per-session-SPM HRF work orphaned by branch sur…
Michael-Sun Jun 13, 2026
75bf679
hrf_score_one_prefix: fix empty atlas columns when scoring in-memory …
Michael-Sun Jun 14, 2026
e5c1d7a
hrf_fit_wholebrain_stats: normalize SPM whitening scale so sFIR stays…
Michael-Sun Jun 15, 2026
156f5fa
hrf_apply_maps_to_wholebrain: don't let one bad signature/imageset ab…
Michael-Sun Jun 15, 2026
9730e0a
Add hrf_audit_score_freshness: post-run staleness/consistency audit o…
Michael-Sun Jun 15, 2026
e7fc321
hrf_misspec_metrics: add GroupCurveFirst (score the group-mean HRF, n…
Michael-Sun Jun 16, 2026
ae6395b
Add hrf_misspec_report: one-figure dashboard + auto next-steps for th…
Michael-Sun Jun 16, 2026
d836740
hrf_misspec_report: glob wildcards in 'Condition' (match the rest of …
Michael-Sun Jun 17, 2026
02b8fe4
hrf_misspec_metrics: Source can be 'all' or a cellstr (atlas + signat…
Michael-Sun Jun 19, 2026
fe9350f
hrf_misspec_metrics: add Set/Names selectors to pick atlas/sig/imageset
Michael-Sun Jun 19, 2026
e577c4b
hrf_misspec_report: add Source/Set/Names selectors (mirror metrics)
Michael-Sun Jun 19, 2026
e3cb3d2
hrf_misspec_metrics: GroupCurveFirst pooling robust to partial CSVs
Michael-Sun Jun 19, 2026
33ba21c
hrf causality v0: HRF-informed deconvolution + Granger
Michael-Sun Jun 26, 2026
9cb95e8
hrf_causality_analyze: deconv -> per-subject Granger -> group stats
Michael-Sun Jun 26, 2026
738d74f
hrf_causality: IO driver (out dir -> deconvolved Granger + group stats)
Michael-Sun Jun 27, 2026
e43681b
hrf_plot_causality: 3-panel directed net-flow visualization
Michael-Sun Jun 29, 2026
9693d82
hrf_animate_wordcloud: animated term cloud over HRF lags
Michael-Sun Jun 29, 2026
e2c45e1
hrf causality: multi-dir pooling + condition-specific GC + contrast
Michael-Sun Jun 29, 2026
1a0e031
hrf_pooled_wholebrain_animation: pool dirs -> group HRF montage movie
Michael-Sun Jun 29, 2026
4e187e9
hrf causality: trial-level mediation method (X -> M -> Y)
Michael-Sun Jun 29, 2026
0cc7e60
hrf_dcm: bilinear DCM directed connectivity via SPM
Michael-Sun Jun 30, 2026
6caed26
hrf_animate_wordcloud: native multi-directory pooling
Michael-Sun Jun 30, 2026
c3edae3
hrf_animate_wordcloud: statistical threshold replaces top-N selection
Michael-Sun Jun 30, 2026
b80549b
Major RSA tools update
Michael-Sun Jul 1, 2026
a40d944
hrf_animate_wordcloud: non-overlapping wordcloud packing
Michael-Sun Jul 1, 2026
d508774
hrf_causality_mediation: pair X/Y events columns to the trial
Michael-Sun Jul 2, 2026
26c9591
hrf_causality: apply only the requested node maps (big extract speedup)
Michael-Sun Jul 2, 2026
00a06eb
hrf_animate_wordcloud: permutation/cluster correction over term x lag
Michael-Sun Jul 2, 2026
f6b1f89
hrf_group_stats: shared permutation engine wired across the causality…
Michael-Sun Jul 2, 2026
cff5459
hrf_animate_wordcloud: word-cloud atlas regions + signatures, not jus…
Michael-Sun Jul 2, 2026
493deac
hrf: label prettifier + synchronized animation montage
Michael-Sun Jul 2, 2026
3f25f6e
Convenience tools for RSM and HRF documentation
Michael-Sun Jul 4, 2026
bf70ec6
@rsm/count_map: subject-consistency count-maps + count-tables for RSMs
Michael-Sun Jul 9, 2026
5c447cb
@rsm: count_models + count_regions + count_map doplot/fallback
Michael-Sun Jul 10, 2026
63cdff1
RSA examples: object-oriented tour (.m + .mlx), now with the count layer
Michael-Sun Jul 10, 2026
417df49
hrf: wire shared permutation engine into the per-lag timecourse tests
Michael-Sun Jul 10, 2026
3782e56
HRF examples: object-oriented tour (.m + .mlx)
Michael-Sun Jul 10, 2026
15f6080
HRF OO tour: expand to the real DistractMap feature set
Michael-Sun Jul 11, 2026
94cb703
Merge branch 'master' of https://github.com/canlab/CanlabCore into ca…
Michael-Sun Jul 11, 2026
fe5c400
Merge branch 'canlab-master'
Michael-Sun Jul 11, 2026
1cf662d
First upload of object oriented demos of HRF and RSA Toolboxes
Michael-Sun Jul 13, 2026
facc69f
Merge pull request #8 from canlab/master
Michael-Sun Jul 13, 2026
087d459
Remove trash files
Michael-Sun Jul 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,022 changes: 1,022 additions & 0 deletions CanlabCore/@fmri_data/compute_rsm.m

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions CanlabCore/@fmri_data/hrf_fit.m
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,10 @@

if iscell(obj)

for i = numel(obj)
for i = 1:numel(obj)


[params_obj{i}, hrf_obj{i}, params_obj_dat{i}, hrf_obj_dat{i}] = hrf_fit(obj{i},TR,Runc,T,method,mode, varargin);
[params_obj{i}, hrf_obj{i}, params_obj_dat{i}, hrf_obj_dat{i}] = hrf_fit(obj{i},TR,Runc,T,method,mode, varargin{:});


end
Expand Down Expand Up @@ -203,8 +203,7 @@
end


hrf_fit(obj)

error('Struct SPM input handling is not supported in this entrypoint. Use EstHRF_inAtlas/hrf_fit for SPM workflows.');

end

Expand Down
6 changes: 6 additions & 0 deletions CanlabCore/@fmri_data/rsa_compare_models.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
function varargout = rsa_compare_models(dat, varargin)
% rsa_compare_models Thin alias for rsa_compare_lme_models.
%
% See rsa_compare_lme_models.m for full documentation.
[varargout{1:nargout}] = rsa_compare_lme_models(dat, varargin{:});
end
113 changes: 113 additions & 0 deletions CanlabCore/@fmri_data/rsa_lm.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
function [mdl, tbl, info] = rsa_lm(dat, varargin)
% rsa_lm Fixed-effects multi-level RSA via fitlm.
%
% Pools all (i, j) upper-triangle pairs from the omnibus image-level RSM
% (including cross-subject pairs by default per design doc §6.6) and fits
% a linear model with same-vs-different predictors. Subject is treated as
% a same-vs-different predictor itself (SameSubject), NOT as a random effect.
%
% Use rsa_lme() instead if you want random effects.
%
% Usage examples
% --------------
% % Default: all pairs, all predictors
% mdl = rsa_lm(dat, ...
% 'predictors', {'subject_id','session_number','condition','bodysite'});
%
% % Within-subject only (subset of the data; conceptually equivalent to
% % the LME fixed-effect part)
% mdl = rsa_lm(dat, ...
% 'predictors', {'condition','bodysite','session_number'}, ...
% 'pair_scope', 'within_subject');
%
% % With interactions
% mdl = rsa_lm(dat, ...
% 'predictors', {'condition','bodysite','session_number'}, ...
% 'interactions', {{'condition','bodysite'},{'session_number','condition'}});
%
% Optional name-value
% -------------------
% 'predictors' cellstr of metadata columns.
% 'interactions' cell of pair-cellstr.
% 'three_way' cell of triple-cellstr.
% 'subject_var' Default 'subject_id'.
% 'pair_scope' 'all' (default) | 'within_subject'.
% 'response_transform' 'fisherz' (default) | 'none' | 'rank'.
% 'metric' 'correlation' (default) | 'spearman' | 'cosine'.
% 'standardize' logical (default false). Standardize predictors
% before fit.
% 'verbose' logical (default true).
%
% Outputs
% -------
% mdl LinearModel object
% tbl long-format table used for fitting
% info struct from assemble_lme_table

p = inputParser;
p.addParameter('predictors', {}, @(x) iscellstr(x) || isstring(x) || isempty(x)); %#ok<ISCLSTR>
p.addParameter('interactions', {}, @iscell);
p.addParameter('three_way', {}, @iscell);
p.addParameter('subject_var', 'subject_id', @(x) ischar(x) || isstring(x));
p.addParameter('pair_scope', 'all', @(x) (ischar(x) || isstring(x)) && ismember(lower(char(x)), {'all','within_subject'}));
p.addParameter('response_transform', 'fisherz', @(x) ischar(x) || isstring(x));
p.addParameter('metric', 'correlation', @(x) ischar(x) || isstring(x));
p.addParameter('standardize', false, @(x) islogical(x) || isnumeric(x));
p.addParameter('verbose', true, @(x) islogical(x) || isnumeric(x));
p.parse(varargin{:});
opt = p.Results;

predictors = cellstr(opt.predictors);
if isempty(predictors)
error('rsa_lm:noPredictors', 'Pass at least one predictor.');
end

% =========================================================================
% Assemble table
% =========================================================================
[tbl, info] = assemble_lme_table(dat, ...
'predictors', predictors, ...
'interactions', opt.interactions, ...
'three_way', opt.three_way, ...
'subject_var', opt.subject_var, ...
'pair_scope', opt.pair_scope, ...
'response_transform', opt.response_transform, ...
'metric', opt.metric, ...
'verbose', opt.verbose);

% =========================================================================
% Build formula
% =========================================================================
% All predictor + interaction Same<X> columns are the fixed effects.
% For pair_scope='all', subject_var was folded into predictors by
% assemble_lme_table, so SameSubject is already in info.predictor_names.
rhs_terms = [info.predictor_names, info.interaction_names, info.three_way_names];
rhs_terms = unique(rhs_terms, 'stable'); % guard against any duplication

% Optional standardize: z-score predictor columns in-place
if logical(opt.standardize)
for i = 1:numel(rhs_terms)
col = rhs_terms{i};
v = tbl.(col);
if std(v) > 0, tbl.(col) = (v - mean(v)) ./ std(v); end
end
end

formula = sprintf('Y ~ %s', strjoin(rhs_terms, ' + '));

if opt.verbose
fprintf('rsa_lm: fitting formula:\n %s\n (n=%d rows, scope=%s)\n', ...
formula, height(tbl), opt.pair_scope);
end

% =========================================================================
% Fit
% =========================================================================
mdl = fitlm(tbl, formula);

if opt.verbose
fprintf('rsa_lm: R^2 = %.4f, adjusted R^2 = %.4f\n', ...
mdl.Rsquared.Ordinary, mdl.Rsquared.Adjusted);
end

end
134 changes: 134 additions & 0 deletions CanlabCore/@fmri_data/rsa_lm_by_subject.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
function [T, mdls, info] = rsa_lm_by_subject(dat, varargin)
% rsa_lm_by_subject Per-subject fitlm fits with aggregated coefficient table.
%
% Replicates the per-subject loop from `08072024 Run-Level RDM Analysis with
% RSA Toolbox.mlx` lines 1879-1900 + lines 2050-2079 (partial R²): for each
% subject, fits a separate `fitlm` of similarity ~ same-vs-different predictors
% on that subject's within-subject pairs only. Returns a long-format table of
% coefficients with subject IDs so you can run group-level inference on the
% per-subject betas (paired ttest across subjects, etc.).
%
% Differs from rsa_lme: per-subject FIXED-effects fits aggregated post hoc,
% rather than one mixed-effects model treating Subject as a random effect.
% Useful for assessing between-subject variability in coefficients and for
% sanity-checking the LME estimates.
%
% Usage
% -----
% T = rsa_lm_by_subject(dat, ...
% 'predictors', {'condition','bodysite','sesno'}, ...
% 'interactions', {{'condition','bodysite'}}, ...
% 'subject_var', 'sub');
%
% Optional name-value
% -------------------
% Same as rsa_lme except no random-effects spec. Plus:
% 'partial_r2' logical (default true). Compute per-term partial R² by
% refitting the reduced model dropping each term. Adds
% columns to T.
% 'verbose' logical (default true).
%
% Outputs
% -------
% T long-format table:
% sub | term | beta | se | t | p | partial_R2 | full_R2 | full_adj_R2
% mdls cell array of fitted LinearModel objects, one per subject
% info struct from the underlying assemble_lme_table call (uses the
% first subject's metadata as representative)

p = inputParser;
p.addParameter('predictors', {}, @(x) iscellstr(x) || isstring(x) || isempty(x)); %#ok<ISCLSTR>
p.addParameter('interactions', {}, @iscell);
p.addParameter('three_way', {}, @iscell);
p.addParameter('subject_var', 'subject_id', @(x) ischar(x) || isstring(x));
p.addParameter('response_transform', 'fisherz', @(x) ischar(x) || isstring(x));
p.addParameter('metric', 'correlation', @(x) ischar(x) || isstring(x));
p.addParameter('partial_r2', true, @(x) islogical(x) || isnumeric(x));
p.addParameter('verbose', true, @(x) islogical(x) || isnumeric(x));
p.parse(varargin{:});
opt = p.Results;

predictors = cellstr(opt.predictors);
if isempty(predictors)
error('rsa_lm_by_subject:noPredictors', 'Pass at least one predictor.');
end

% Build the full within-subject pairs table; we'll slice by Subject below
[tbl_all, info] = assemble_lme_table(dat, ...
'predictors', predictors, ...
'interactions', opt.interactions, ...
'three_way', opt.three_way, ...
'subject_var', opt.subject_var, ...
'pair_scope', 'within_subject', ...
'response_transform', opt.response_transform, ...
'metric', opt.metric, ...
'verbose', opt.verbose);

sub_short = info.subject_var_short;
sub_levels = categories(tbl_all.(sub_short));
n_subj = numel(sub_levels);

% Predictor + interaction column names in the table
term_cols = [info.predictor_names, info.interaction_names, info.three_way_names];
n_terms = numel(term_cols);

% Build base formula (no random effects)
rhs = strjoin(term_cols, ' + ');
formula = sprintf('Y ~ %s', rhs);

% =========================================================================
% Per-subject fits
% =========================================================================
mdls = cell(n_subj, 1);
rows = cell(n_subj, 1);

for s = 1:n_subj
is_s = tbl_all.(sub_short) == sub_levels{s};
tbl_s = tbl_all(is_s, :);
mdls{s} = fitlm(tbl_s, formula);

n_coefs = mdls{s}.NumCoefficients;
sub_col = repmat({char(sub_levels{s})}, n_coefs, 1);
coef_name = mdls{s}.Coefficients.Properties.RowNames;
betas = mdls{s}.Coefficients.Estimate;
ses = mdls{s}.Coefficients.SE;
ts = mdls{s}.Coefficients.tStat;
ps = mdls{s}.Coefficients.pValue;
full_r2 = repmat(mdls{s}.Rsquared.Ordinary, n_coefs, 1);
full_adj_r2 = repmat(mdls{s}.Rsquared.Adjusted, n_coefs, 1);

% Partial R² per term: refit dropping that term, R²_full - R²_reduced
partial_r2 = nan(n_coefs, 1);
if logical(opt.partial_r2)
for c = 1:n_coefs
name = coef_name{c};
if strcmp(name, '(Intercept)'), continue; end
others = setdiff(term_cols, {name});
if isempty(others)
f_red = 'Y ~ 1';
else
f_red = sprintf('Y ~ %s', strjoin(others, ' + '));
end
try
mdl_red = fitlm(tbl_s, f_red);
partial_r2(c) = mdls{s}.Rsquared.Ordinary - mdl_red.Rsquared.Ordinary;
catch
partial_r2(c) = NaN;
end
end
end

rows{s} = table(sub_col, coef_name, betas, ses, ts, ps, partial_r2, ...
full_r2, full_adj_r2, ...
'VariableNames', {sub_short, 'term', 'beta', 'se', 't', 'p', ...
'partial_R2', 'full_R2', 'full_adj_R2'});
end

T = vertcat(rows{:});

if opt.verbose
fprintf('rsa_lm_by_subject: fit %d per-subject models with %d terms each.\n', ...
n_subj, n_terms);
end

end
Loading
Loading