% Cesanek et al. (2021) Figure 3 Source Code (MATLAB code - rename with .m suffix to run)
% Creates all figure subplots and runs all relevant reported analyses

clearvars
close all
clc

if exist('gramm','file')==0
    error('The gramm plotting library is required to run this code.\nYou can download it from https://github.com/piermorel/gramm).\n');
end

if ~exist('Figure3_SourceData1.txt','file')
    error('This analysis requires the following source data files: Figure3_SourceData1.txt.');
end
F3 = readtable('Figure3_SourceData1.txt');

Subject = F3.Subject';
ExperimentName = F3.ExperimentName';
Block = F3.Block';
ObjID = F3.ObjID';
AF = F3.AF';
RF = round(F3.RF,4)';
Phase = F3.Phase';
TrialsSinceSameObject = F3.TrialsSinceSameObject';

famcolors = [255 124  49;
             255  97  20;
             215  77  18;
             175  56  18;
             137  34  17]/255;

%% Fig 3, left: Reverse conditions AF timelines
subset = TrialsSinceSameObject~=0;
[groups, ~, b, p, o, w, e] = findgroups(Subject(subset),Block(subset),Phase(subset),ObjID(subset),RF(subset),ExperimentName(subset));
AF_m = splitapply(@nanmean,AF(subset),groups);
RF_m = splitapply(@unique,RF(subset),groups);

% Merge phases 1 and 2 (there was no break between them)
p(p>=2) = p(p>=2) - 1;
% Rescale the Training phase x-values to reflect 1 trial cycle = 1 trial
b(b<=30) = 31*0.75 + b(b<=30)*0.25;

close(figure(31));
figh = figure(31);
row_order = [2 1];
pixPerInch = 500/6.95;
WidthInches = 4.5;
HeightInches = 2.35;
figh.Position(1) = 0;
figh.Position(2) = 0;
figh.Position(3) = pixPerInch*WidthInches;
figh.Position(4) = pixPerInch*HeightInches;

clear g
g = gramm('x',b, 'y',AF_m, 'color',w, 'group',p, 'subset', o~=3);
g.set_layout_options('redraw',false,'margin_height',[.1 .02],'margin_width',[.07 .02],'gap',[0.02 0.02]);
g.no_legend();
g.set_names('x','','y','','color','','Column','','Row','');
g.axe_property('XLim',[30*0.75+0.25 10*ceil(max(b-.5)/10)+1],'XTick',[23.5 25.75 28.25 30.75 40:10:80],'YLim',[4 18.5]);
g.facet_grid(e,[],'scale','free_y','space','free_y');
g.set_order_options('row',row_order);
g.set_point_options('base_size',2);
g.set_line_options('base_size',0.5);
g.set_color_options('map',famcolors([1 2 4 5],:),'n_color',4,'n_lightness',1);
g.stat_summary('geom',{'area'},'width',1,'type','sem');
g.draw();
g.update('y',RF_m);
g.no_legend();
g.stat_summary('geom',{'line'});
g.set_line_options('styles',{':'},'base_size',0.5);
g.draw();
g.update('y',RF_m,'color',[],'subset',o==3);
g.no_legend();
g.set_point_options('base_size',3);
g.set_line_options('base_size',1);
g.set_color_options('map',famcolors(3,:),'n_color',1,'n_lightness',1);
g.stat_summary('geom',{'line'});
g.set_line_options('styles',{':'});
g.draw();
g.update('y',AF_m);
g.no_legend();
g.set_line_options('styles',{'-'});
g.stat_summary('geom',{'area','point'});
g.draw();

%% Fig 3, right: Reverse conditions end of test phase averages
NumEndTestBins = 16;
% Data for the family
TimePeriods = (Block<71 & Block>70-NumEndTestBins);
subset = TimePeriods & ObjID~=3;
[groups, ~, e, ~, p] = findgroups(Subject(subset), ExperimentName(subset), ObjID(subset), Phase(subset));
AF_fam = splitapply(@nanmean,AF(subset),groups);
Mass_fam = splitapply(@nanmean,RF(subset)/9.81,groups);
p(p==3) = 4;

% Helper functions
mySlope = @(x,y) subsref( ([ones(length(x),1) x']\y'), struct('type','()','subs',{{2}}));
myIntercept = @(x,y) subsref( ([ones(length(x),1) x']\y'), struct('type','()','subs',{{1}}));
% Fit a linear function for each participant using OLS on Training Objects
[groupsLR, ~, eLR] = findgroups(Subject(subset),ExperimentName(subset));
WindowSlopes = splitapply(mySlope,RF(subset)/9.81,AF(subset),groupsLR);
WindowIntercepts = splitapply(myIntercept,RF(subset)/9.81,AF(subset),groupsLR);
xpred = 0.5:0.01:1.3;
ypred = repmat(WindowIntercepts',1,length(xpred))+repmat(WindowSlopes',1,length(xpred)).*repmat(xpred,length(WindowSlopes),1);

% Data for the outlier
TimePeriods = (Block<71 & Block>70-NumEndTestBins);
subset2 = TimePeriods & ObjID==3;
[groups2, ~, e2, ~] = findgroups(Subject(subset2),ExperimentName(subset2),Phase(subset2));
WindowOutlierAF = splitapply(@nanmean, AF(subset2), groups2);
WindowOutlierIW = repmat(0.9,size(WindowOutlierAF));

close(figure(32));
figh = figure(32);

row_order = [2 1];
pixPerInch = 500/6.95;
WidthInches = 1.2;
HeightInches = 2.35;
figh.Position(1) = 0;
figh.Position(2) = 0;
figh.Position(3) = pixPerInch*WidthInches;
figh.Position(4) = pixPerInch*HeightInches;

clear g
% First we draw the regression line for the family
g = gramm('x',xpred,'y',ypred);
g.set_layout_options('redraw',false,'margin_height',[.1 .02],'margin_width',[.15 .02],'gap',[0.02 0.02]);
g.no_legend();
g.set_names('x','', 'y','', 'color','','Row','','Column','');
g.axe_property('XLim',[.5 1.3],'YLim',[4 18.5],'XTick',[.6 .75 .9 1.05 1.2],'TickLength',[0.03 0.03]);
g.set_order_options('row',row_order);
g.set_line_options('base_size',1);
g.set_color_options('lightness',0,'chroma',0);
g.facet_grid(eLR,[]);
g.geom_abline('slope',9.81,'intercept',0,'style','k:'); % If Mass on x-axis, slope of unity line = gravity
g.stat_summary('type','sem');
g.draw();
% Plot family data points over the regression line
g.update('x',Mass_fam,'y',AF_fam,'color',Mass_fam);
g.no_legend();
g.facet_grid(e,[]);
g.set_point_options('base_size',5);
g.set_color_options('map',famcolors([1 2 4 5],:),'n_color',4,'n_lightness',1);
g.stat_summary('geom',{'point'},'type','sem');
g.draw();
g.update();
g.no_legend();
g.set_line_options('base_size',1.5);
g.set_color_options('map',famcolors([1 2 4 5],:)*0.8,'n_color',4,'n_lightness',1);
g.stat_summary('geom',{'errorbar'},'type','sem');
g.draw();
% Plot the outlier average
g.update('x',WindowOutlierIW,'y',WindowOutlierAF);
g.no_legend();
g.set_point_options('base_size',5);
g.set_color_options('map',famcolors(3,:),'n_color',1,'n_lightness',1);
g.facet_grid(e2,[]);
g.stat_summary('geom',{'point'},'type','sem');
g.draw();
g.update();
g.no_legend();
g.set_line_options('base_size',1.5);
g.set_color_options('map',famcolors(3,:)*0.8,'n_color',1,'n_lightness',1);
g.stat_summary('geom',{'errorbar'},'type','sem','width',0);
g.draw();
% Draw lines for the actual outlier weights
RFs_byExp = [1.2 1.5]*9.81; % Y-axis positions
exps = unique(e);
exps = exps(row_order);
phases = unique(p);
outlierX = .9; % X-axis position (expected mass for density = 1.5 gm/cm3)
lineWidth = 0.075;
xData = repmat(outlierX+[-1 1]*lineWidth,1,length(exps));
yData = repelem(RFs_byExp,2);
eData = repelem(exps,2); 
pData = repelem(phases,4); 
g.update('x',xData,'y',yData);
g.no_legend();
g.facet_grid(eData,pData);
g.set_color_options('map',famcolors(3,:),'n_color',1,'n_lightness',1);
g.set_line_options('base_size',1.5,'styles',{':'});
g.geom_line();
g.draw();

%% Analysis

% Map binary test outcomes onto appropriate text for console output
outcomes = {'not significant', 'significant'};

% Easier way to remember how the tails on paired t-tests work (see 'comparison' variable below) 
tails = {'both','left','right'};
comps = {'different','less','greater'};

%% Test of Outlier Learning: AF minus Family-predicted weight
window = 'End of Test'; NumLateTestBins = 16;
% Helper functions
mySlope = @(x,y) subsref( ([ones(length(x(~isnan(y))),1) x(~isnan(y))']\y(~isnan(y))'), struct('type','()','subs',{{2}}));
myIntercept = @(x,y) subsref( ([ones(length(x(~isnan(y))),1) x(~isnan(y))']\y(~isnan(y))'), struct('type','()','subs',{{1}}));

fprintf('\n\n** Test for Outlier Learning @ %s (%i trial cycles) (Fig. 3) **\n',window,NumLateTestBins);
exps = {'+Linear','++Linear'};
insideWindow = Block>(max(Block(Phase<5))-NumLateTestBins) & Block<=max(Block(Phase<5));

% Fit a linear function for each participant using OLS on Training Objects
subset = ismember(ExperimentName,exps) & insideWindow & ObjID~=3;
groups = findgroups(Subject(subset),ExperimentName(subset));
WindowSlopes = splitapply(mySlope,RF(subset),AF(subset),groups);
WindowIntercepts = splitapply(myIntercept,RF(subset),AF(subset),groups);

% Get the Anticipatory Force for the Test Object
subset2 = ismember(ExperimentName,exps) & insideWindow & ObjID==3;
[groups2, s2, e2] = findgroups(Subject(subset2),ExperimentName(subset2));
WindowOutlierAF = splitapply(@nanmean, AF(subset2), groups2);

familyPredicted = WindowSlopes*0.9*9.81+WindowIntercepts;
plusNull = 1.2*9.81;
plusPlusNull = 1.5*9.81;

c1_sub = s2(strcmpi(e2,'+Linear'));
c1=WindowOutlierAF(strcmpi(e2,'+Linear'));
familyPredicted_c1 = familyPredicted(strcmpi(e2,'+Linear'));
[~,~,CI,~] = ttest(c1);
[~,~,CIpred,~] = ttest(familyPredicted_c1);
fprintf('\n+Linear anticipatory force: %.2f N, 95%% CI = [%.2f, %.2f]',mean(CI),CI);
fprintf('\n+Linear family-predicted weight: %.2f N, 95%% CI = [%.2f, %.2f]\n',mean(CIpred),CIpred);
comparison = find(strcmpi(comps,'greater'));
[H,P,~,STATS] = ttest(c1,familyPredicted_c1,'tail',tails{comparison});
fprintf('AF %sly %s than family-predicted weight (t(%i) = %.2f, p = %.2g)\n',outcomes{H+1},comps{comparison}, STATS.df, STATS.tstat, P);

c2_sub = s2(strcmpi(e2,'++Linear'));
c2=WindowOutlierAF(strcmpi(e2,'++Linear'));
familyPredicted_c2 = familyPredicted(strcmpi(e2,'++Linear'));
[~,~,CI,~] = ttest(c2);
[~,~,CIpred,~] = ttest(familyPredicted_c2);
fprintf('\n++Linear anticipatory force: %.2f N, 95%% CI = [%.2f, %.2f]',mean(CI),CI);
fprintf('\n++Linear family-predicted weight: %.2f N, 95%% CI = [%.2f, %.2f]\n',mean(CIpred),CIpred);
comparison = find(strcmpi(comps,'greater'));
[H,P,~,STATS] = ttest(c2,familyPredicted_c2,'tail',tails{comparison});
fprintf('AF %sly %s than family-predicted weight (t(%i) = %.2f, p = %.2g)\n',outcomes{H+1},comps{comparison}, STATS.df, STATS.tstat, P);
