diff --git a/manual_validation_file/Manual validation.xlsx b/manual_validation_file/Manual validation.xlsx index 0cc5c20..24105c5 100644 Binary files a/manual_validation_file/Manual validation.xlsx and b/manual_validation_file/Manual validation.xlsx differ diff --git a/manual_validation_file/Manual validation_March2024copy.xlsx b/manual_validation_file/Manual validation_March2024copy.xlsx new file mode 100644 index 0000000..0cc5c20 Binary files /dev/null and b/manual_validation_file/Manual validation_March2024copy.xlsx differ diff --git a/plots/combined_univariate_fmri_plots.m b/plots/combined_univariate_fmri_plots.m index 885d9ee..6c34195 100644 --- a/plots/combined_univariate_fmri_plots.m +++ b/plots/combined_univariate_fmri_plots.m @@ -192,7 +192,7 @@ 'For example, spike rates in sleep (bipolar reference) '... 'had the highest effect size (ANOVA: F(%d,%d) = %1.1f, '... 'p < 0.001, q = %1.3f, η2 = %1.2f). '... - 'Table S3 shows the corresponding statistics for the features '... + 'Table S2 shows the corresponding statistics for the features '... 'with the 30 highest effect sizes. '],... feature_dfs1(I(1)),feature_dfs2(I(1)),... feature_f(I(1)),qvalues(I(1)),feature_eta2(I(1))); @@ -345,7 +345,7 @@ 'Spike features significantly distinguished left from bilateral SOZs.'... ' For distinguishing '... 'right-sided SOZ, several other features performed best (though none were significant'... - ' after correcting for the false discovery rate). Tables S4 and S5 '... + ' after correcting for the false discovery rate). Tables S3 and S4 '... 'show the effect sizes and significance levels for the features '... 'with the highest effect sizes at differentiating left from bilateral '... 'SOZs and right from bilateral SOZs, respectively. This suggests that right-sided SOZs '... diff --git a/plots/make_table_1.asv b/plots/make_table_1.asv deleted file mode 100644 index 8c1cf8a..0000000 --- a/plots/make_table_1.asv +++ /dev/null @@ -1,771 +0,0 @@ -function make_table_1 - -% This can't be run by the general user because it requires pt.mt - -%% Parameters -rm_non_temporal = 1; - -%% Get file locs -locations = fc_toolbox_locs; -results_folder = [locations.main_folder,'results/']; -data_folder = [locations.main_folder,'data/']; -inter_folder = locations.el_data_folder; -alfredo_folder = [locations.main_folder,'Alfredo_code/fmri_analysis_AL_3_28_23/']; -plot_folder = [results_folder,'analysis/new_outcome/plots/']; -if ~exist(plot_folder,'dir') - mkdir(plot_folder) -end - -% add script folder to path -scripts_folder = locations.script_folder; -addpath(genpath(scripts_folder)); - -%% Initialize results file -fname = [plot_folder,'results.html']; -fid = fopen(fname,'a'); - -%% Load the file containing intermediate data -mt_data = load([inter_folder,'mt_out_epilepsy_laterality.mat']); -mt_data = mt_data.out; - -%% Run the lr_mt to extract features -[T,features,Ts] = lr_mt(mt_data,3,0); -empty_class = cellfun(@isempty,T.soz_lats); -T(empty_class,:) = []; - - -%% Load the pt file -pt = load([data_folder,'pt.mat']); -pt = pt.pt; - -%% Load Alfredo file for fMRI patients -f1T = readtable([alfredo_folder,'df.csv']); - -%% Load clinical file for additional info for fMRI patients -f2T = readtable([data_folder,'clinical_info/all_rids.csv']); - -%% Manual validation -manT = readtable([inter_folder,'Manual validation.xlsx'],'Sheet','SOZ'); -piT = readtable([inter_folder,'Manual validation.xlsx'],'Sheet','Pre-implant data'); -sT = readtable('Manual validation.xlsx','Sheet','EDF pipeline'); - -%% Load MUSC clinical folder -muscT = readtable([inter_folder,'LEN patient list research erin.xlsx']); -muscLocT = readtable([inter_folder,'MUSC_Emory_LEN_SOZ_type.xlsx']); - -% Get musc outcomes, surg -for ip = 1:size(T,1) - - if ~contains(T.names,'MP') - continue - end - - % find matching musc table patient - musc_row = contains(muscT.LENID_,T.names{ip}); - if sum(musc_row) == 1 - - % get surgery - if strcmp(muscT.TypeOfSurgery{musc_row},'ATL') || contains(muscT.TypeOfSurgery{musc_row},'Resection') - T.surgery{ip} = 'Resection'; - elseif strcmp(muscT.TypeOfSurgery{musc_row},'LITT') || contains(muscT.TypeOfSurgery{musc_row},'ablation') - T.surgery{ip} = 'Laser ablation'; - elseif strcmp(muscT.TypeOfSurgery{musc_row},'RNS') - T.surgery{ip} = 'RNS'; - end - - if contains(muscT.EngelYear1{musc_row},'N/a') % don't change format if empty outcome - T.engel_yr1{ip} = ''; - T.engel_yr2{ip} = ''; - T.ilae_yr1{ip} = ''; - T.ilae_yr2{ip} = ''; - continue - end - - % get outcomes - T.engel_yr1{ip} = muscT.EngelYear1{musc_row}; - T.engel_yr2{ip} = muscT.EngelYear2{musc_row}; - T.ilae_yr1{ip} = muscT.ILAEYear1{musc_row}; - T.ilae_yr2{ip} = muscT.ILAEYear2{musc_row}; - - - - end - - -end - -%% Add mtl vs neo, preimplant -for ip = 1:size(T,1) - - % find matching row of manual validation one - row = contains(manT.name,T.names{ip}); - if sum(row) ~= 1, error('what'); end - if contains(manT.region{row},'mesial temporal') - T.specific_loc{ip} = 'MTL'; - elseif contains(manT.region{row},'temporal neocortical') - T.specific_loc{ip} = 'Neo'; - else - T.specific_loc{ip} = 'Other'; - end - - % also temporal lesion - row2 = contains(piT.name,T.names{ip}); - % add error check once we have musc data - if sum(row2) == 0, continue; end - if find(row2) > length(piT.MRILesionalLocalization_temporal_Frontal_Other_Multifocal_Broad), continue; end - if contains(piT.MRILesionalLocalization_temporal_Frontal_Other_Multifocal_Broad{row2},'temporal','IgnoreCase',true) - T.temporal_lesion{ip} = 'Yes'; - else - T.temporal_lesion{ip} = 'No'; - end - - row3 = contains(sT.name,T.names{ip}); - % add error check once we have musc data - if sum(row3) == 0, continue; end - if find(row3) > length(sT.x_Correct_outOf50__bi_), continue; end - T.spike_accuracy(ip) = sT.x_Correct_outOf50__bi_(row3); - - -end -if 0 - table(T.names,T.specific_loc,T.temporal_lesion,T.spike_accuracy) -end - -%% Go through and remove non-temporal patients -soz_loc = T.soz_locs; -tle = contains(soz_loc,'temporal'); -etle = ~tle; - -if rm_non_temporal - % Remove from T (all subsequent things map to the patients in T) - T(etle,:) = []; - tle(etle,:) = []; - etle(etle,:) = []; - -end - -%% Grab demographic variables from table -% Get the patient names -name = T.names; -npts = length(name); - -% HUP vs musc -site = cell(npts,1); -is_hup = contains(name,'HUP'); -is_musc = contains(name,'MP'); -site(is_hup) = {'HUP'}; -site(is_musc) = {'MUSC'}; -nhup = sum(is_hup); -nmusc = sum(is_musc); - -% Get surgery and soz locs and lats -T.surgery(strcmp(T.names,'HUP224')) = {'none'}; % 224 did not have surgery, just planned to get it -surg = T.surgery; -resection = contains(surg,'Resection'); -ablation = contains(surg,'ablation'); -device = contains(surg,'RNS') | contains(surg,'DBS') | contains(surg,'VNS'); - - -% Get outcomes -engel_yr1 = cellfun(@(x) parse_outcome_num(x,'engel'),T.engel_yr1); -engel_yr2 = cellfun(@(x) parse_outcome_num(x,'engel'),T.engel_yr2); -ilae_yr1 = cellfun(@(x) parse_outcome_num(x,'ilae'),T.ilae_yr1); -ilae_yr2 = cellfun(@(x) parse_outcome_num(x,'ilae'),T.ilae_yr2); - - - -soz_lat = T.soz_lats; - -left = strcmp(soz_lat,'left'); -right = strcmp(soz_lat,'right'); -bilateral = strcmp(soz_lat,'bilateral'); -assert(sum(left)+sum(right)+sum(bilateral)==npts) - -% nummber of symmetric mt electrodes -n_symmetric = T.n_symmetric; - -% ADD IN PERCENT OF TIMES CLASSIFIED AS ASLEEP -n_wake = T.n_wake; -n_sleep = T.n_sleep; -n_connected = T.n_connected; -perc_wake = n_wake./n_connected; -perc_sleep = n_sleep./n_connected; - - -%% Get the other demographic variables from the pt.mat -% prep them -female = nan(npts,1); -age_onset = nan(npts,1); -age_implant = nan(npts,1); -has_dkt = zeros(npts,1); -has_atropos = zeros(npts,1); -rid = nan(npts,1); - - -% loop over patients -for ip = 1:length(pt) - % see if the name matches any of the main names - ip_name = pt(ip).name; - r = strcmp(ip_name,name); - - if sum(r) ~= 1, continue; end - - if isfield(pt(ip),'atropos') - if ~isempty(pt(ip).atropos) - has_atropos(r) = 1; - end - end - - if isfield(pt(ip),'dkt') - if ~isempty(pt(ip).dkt) - has_dkt(r) = 1; - end - end - - if ~isempty(pt(ip).rid) - rid(r) = pt(ip).rid; - end - - % get the demographics - if ~isfield(pt(ip),'clinical') || isempty(pt(ip).clinical) - continue; - end - - sex = pt(ip).clinical.sex; - if strcmp(sex,'Female') == 1 - female(r) = 1; - elseif strcmp(sex,'Male') == 1 - female(r) = 0; - else - female(r) = nan; - end - - age_onset(r) = pt(ip).clinical.age_onset; - age_implant(r) = pt(ip).clinical.age_implant; - - - -end - -%% Get MUSC demographics -for ip = 1:size(muscT,1) - musc_name = muscT.LENID_{ip}; - part_name = musc_name(4:end); - - % see if the name matches any of the main names - r = strcmp(part_name,name); - if sum(r) ~= 1, continue; end - - sex = muscT.Sex{ip}; - if strcmp(sex,'F') == 1 - female(r) = 1; - elseif strcmp(sex,'M') == 1 - female(r) = 0; - else - error('what') - end - - age_onset(r) = muscT.AgeOfSeizureOnset(ip); - age_implant(r) = muscT.AgeAtImplant(ip); - -end - -%% Maybe get some preimplant data from the manual validation file -% MRI, scalp seizure laterality, scalp spike laterality, PET -mT = readtable('Manual validation.xlsx','Sheet','Pre-implant data'); -no1_lat = cell(npts,1); -no2_lat = cell(npts,1); -for i = 1:npts - % See if you can find the patient in this table - curr_name = name{i}; - - r = strcmp(curr_name,mT.name); - - if sum(r) ~=1, continue; end - - % Get the laterality hypotheses - no1_lat{i} = lower(mT.x_1PreimplantHypothesisLaterality_left_Right_Bilateral_Broad_NA{r}); - no2_lat{i} = lower(mT.x_2PreimplantHypothesisLaterality_left_Right_Bilateral_Broad_NA{r}); - - - -end - -%% Add preimplant hypotheses from MUSC -for i = 1:npts - % See if you can find the patient in this table - curr_name = name{i}; - - r = contains(muscT.LENID_,['3T_',curr_name]); - if sum(r) ~=1, continue; end - - % Get the laterality hypotheses - hyp = muscT.Primary2HypothesesForEpilepsyLocalization{r}; - C = strsplit(hyp,'2'); - if length(C) ~=2, error('what'); end - - for ic = 1:2 - if contains(C{ic},'Right') - if ic == 1 - no1_lat{i} = 'right'; - elseif ic == 2 - no2_lat{i} = 'right'; - end - elseif contains(C{ic},'Left') - if ic == 1 - no1_lat{i} = 'left'; - elseif ic == 2 - no2_lat{i} = 'left'; - end - else - if strcmp(curr_name,'MP0029') - no2_lat{i} = 'right'; - else - error('what'); - end - end - end - - -end - -no1_lat(cellfun(@isempty,no1_lat)) = {''}; -no2_lat(cellfun(@isempty,no2_lat)) = {''}; - -% Do some logic to decide if the patient's top two hypotheses contain -% either 1) a bilateral hypothesis or 2) discordant lateralities -bilat_hypothesis = ismember(no1_lat,{'bilateral','broad'}) | ismember(no2_lat,{'bilateral','broad'}); -bilat_hypothesis = double(bilat_hypothesis); -bilat_hypothesis(strcmp(no1_lat,'') & strcmp(no2_lat,'')) = nan; -discordant_hypotheses = cellfun(@(x,y) ~strcmp(x,y),no1_lat,no2_lat); -bilat_or_discordant = bilat_hypothesis == 1 | discordant_hypotheses == 1; -bilat_or_discordant = double(bilat_or_discordant); -bilat_or_discordant(strcmp(no1_lat,'') & strcmp(no2_lat,'')) = nan; - -if 0 - table(name,no1_lat,no2_lat,bilat_or_discordant) -end - -%% Get information for the fMRI patients -fmri_non_control = strcmp(f1T.Control,'No'); nfmri_no_control = sum(fmri_non_control); -fmri_rids = f1T.Subject(fmri_non_control); -fmri_rid_nums = cellfun(@(x) str2num(x(end-2:end)),fmri_rids); - -% get age -age_fmri = f1T.Age(fmri_non_control); - -% age of onset -age_onset_fmri = f1T.SzOnset(fmri_non_control); - -% sex -sex_fmri = f1T.Sex(fmri_non_control); - -% lat -lat_fmri = f1T.Final_Lat(fmri_non_control); - -% lesional status -lesional_fmri = f1T.MRI_Lesional(fmri_non_control); -n_lesional_fmri = sum(strcmp(lesional_fmri,'Lesional')); -n_nonlesional_fmri = sum(~strcmp(lesional_fmri,'Lesional')); - -% get corresponding rows in my demographics table -[Lia,Locb]=ismember(fmri_rid_nums,f2T.record_id); % Locb has the corresponding rows -assert(sum(Lia==1)==length(Lia)) -assert(isequal(f2T.record_id(Locb),fmri_rid_nums)) - -% Get number with resection, ablation, and device -resection_fmri = (f2T.outcome_proctype___1(Locb)==1 | f2T.outcome_proctype___2(Locb)==1); -ablation_fmri = (f2T.outcome_proctype___4(Locb)==1); -device_fmri = (f2T.outcome_proctype___3(Locb)==1 | f2T.outcome_proctype___5(Locb)==1 | f2T.outcome_proctype___6(Locb)==1); -nresection_fmri = sum(resection_fmri); -nablation_fmri = sum(ablation_fmri); -ndevice_fmri = sum(device_fmri); - -% outcomes -tengel1 = f2T.demog_year(Locb & (resection_fmri | ablation_fmri)); -tilae1 = f2T.demog_ilae1year(Locb& (resection_fmri | ablation_fmri)); -tengel2 = f2T.demog_years2(Locb& (resection_fmri | ablation_fmri)); -tilae2 = f2T.demog_ilae2years(Locb& (resection_fmri | ablation_fmri)); -ilae1_fmri = get_ilae(tilae1); -engel1_fmri = get_engel(tengel1); -ilae2_fmri = get_ilae(tilae2); -engel2_fmri = get_engel(tengel2); -engel_yr1_fmri = cellfun(@(x) parse_outcome_num(x,'engel'),engel1_fmri); -engel_yr2_fmri = cellfun(@(x) parse_outcome_num(x,'engel'),engel2_fmri); -ilae_yr1_fmri = cellfun(@(x) parse_outcome_num(x,'ilae'),ilae1_fmri); -ilae_yr2_fmri = cellfun(@(x) parse_outcome_num(x,'ilae'),ilae2_fmri); - -if 0 - table(fmri_rids,f2T.outcome_proctype___1(Locb)==1 | f2T.outcome_proctype___2(Locb)==1,... - f2T.outcome_proctype___4(Locb)==1,... - f2T.outcome_proctype___3(Locb)==1 | f2T.outcome_proctype___5(Locb)==1 | f2T.outcome_proctype___6(Locb)==1,... - ilae1_fmri,ilae2_fmri,engel1_fmri,engel2_fmri,... - 'VariableNames',{'RID','Resection','Ablation','Device','ILAE1','ILAE2','Engel1','Engel2'}) -end - -% specific localization -neo = strcmp(T.specific_loc,'Neo'); -mtl = strcmp(T.specific_loc,'MTL'); -broad = strcmp(T.specific_loc,'Other'); -assert(sum(neo)+sum(mtl)+sum(broad)==66) - -% temporal lesion ieeg -lesional = strcmp(T.temporal_lesion,'Yes'); -non_lesional = strcmp(T.temporal_lesion,'No'); - -%% Put the table together -% Planning to have 4 total columns: the first column says the thing, the -% second is the data for HUP, the third is the data for MUSC, the fourth -% column is for fMRI -total_str = {'Total: N',sprintf('%d',nhup),sprintf('%d',nmusc),sprintf('%d',sum(fmri_non_control))}; -female_str = {'Female: N (%)',sprintf('%d (%1.1f%%)',sum(female==1 & is_hup),sum(female==1 & is_hup)/sum(~isnan(female(is_hup)))*100),... - sprintf('%d (%1.1f%%)',sum(female==1 & is_musc),sum(female==1 & is_musc)/sum(~isnan(female(is_musc)))*100),... - sprintf('%d (%1.1f%%)',sum(strcmp(sex_fmri,'F')),sum(strcmp(sex_fmri,'F'))/sum(fmri_non_control)*100)}; -age_onset_str = {'Age at onset in years: median (range)',... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(age_onset(is_hup)),min(age_onset(is_hup)),max(age_onset(is_hup))),... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(age_onset(is_musc)),min(age_onset(is_musc)),max(age_onset(is_musc))),... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(age_onset_fmri),min(age_onset_fmri),max(age_onset_fmri))}; -age_implant_str = {'Age at implant in years: median (range)',... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(age_implant(is_hup)),min(age_implant(is_hup)),max(age_implant(is_hup))),... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(age_implant(is_musc)),min(age_implant(is_musc)),max(age_implant(is_musc))),... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(age_fmri),min(age_fmri),max(age_fmri))}; -lesional_str = {''} -n_discordant_str = {'Bilateral or discordant pre-implant hypotheses: N (%)',... - sprintf('%d (%1.1f%%)',sum(bilat_or_discordant==1 & is_hup),sum(bilat_or_discordant==1 & is_hup)/sum(~isnan(bilat_or_discordant(is_hup)))*100),... - sprintf('%d (%1.1f%%)',sum(bilat_or_discordant==1 & is_musc),sum(bilat_or_discordant==1 & is_musc)/sum(~isnan(bilat_or_discordant(is_musc)))*100),... - 'NA'}; -n_elecs_str = {'Symmetric mesial temporal-targeted contacts: median (range)',... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(n_symmetric(is_hup)),min(n_symmetric(is_hup)),max(n_symmetric(is_hup))),... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(n_symmetric(is_musc)),min(n_symmetric(is_musc)),max(n_symmetric(is_musc))),... - 'NA'}; -%{ -loc_str = {'SOZ localization (clinician determination)','','',''}; - -tle_str = {'Temporal: N (%)',... - sprintf('%d (%1.1f%%)',sum(tle==1 & is_hup),sum(tle==1 & is_hup)/sum(is_hup)*100),... - sprintf('%d (%1.1f%%)',sum(tle==1 & is_musc),sum(tle==1 & is_musc)/sum(is_musc)*100)}; -etle_str = {'Extratemporal: N (%)',... - sprintf('%d (%1.1f%%)',sum(etle==1 & is_hup),sum(etle==1 & is_hup)/sum((is_hup))*100),... - sprintf('%d (%1.1f%%)',sum(etle==1 & is_musc),sum(etle==1 & is_musc)/sum(is_musc)*100)}; -%} -lat_str = {'SOZ lateralization (clinician determination)','','',''}; -left_str = {'Left: N (%)',... - sprintf('%d (%1.1f%%)',sum(left==1 & is_hup),sum(left==1 & is_hup)/sum(~isnan(left(is_hup)))*100),... - sprintf('%d (%1.1f%%)',sum(left==1 & is_musc),sum(left==1 & is_musc)/sum(~isnan(left(is_musc)))*100),... - sprintf('%d (%1.1f%%)',sum(strcmp(lat_fmri,'L')),sum(strcmp(lat_fmri,'L'))/sum(fmri_non_control)*100)}; -right_str = {'Right: N (%)',... - sprintf('%d (%1.1f%%)',sum(right==1 & is_hup),sum(right==1 & is_hup)/sum(~isnan(right(is_hup)))*100),... - sprintf('%d (%1.1f%%)',sum(right==1 & is_musc),sum(right==1 & is_musc)/sum(~isnan(right(is_musc)))*100),... - sprintf('%d (%1.1f%%)',sum(strcmp(lat_fmri,'R')),sum(strcmp(lat_fmri,'R'))/sum(fmri_non_control)*100)}; -bilat_str = {'Bilateral: N (%)',... - sprintf('%d (%1.1f%%)',sum(bilateral==1 & is_hup),sum(bilateral==1 & is_hup)/sum((is_hup))*100),... - sprintf('%d (%1.1f%%)',sum(bilateral==1 & is_musc),sum(bilateral==1 & is_musc)/sum((is_musc))*100),... - sprintf('%d (%1.1f%%)',sum(strcmp(lat_fmri,'B')),sum(strcmp(lat_fmri,'B'))/sum(fmri_non_control)*100)}; -loc_str = {'SOZ localization (clinician determination)','','',''}; -mtl_str = {'Mesial temporal: N (%)',... - sprintf('%d (%1.1f%%)',sum(mtl==1 & is_hup),sum(mtl==1 & is_hup)/sum(~isnan(mtl(is_hup)))*100),... - sprintf('%d (%1.1f%%)',sum(mtl==1 & is_musc),sum(mtl==1 & is_musc)/sum(~isnan(mtl(is_musc)))*100),... - sprintf('')}; -neo_str = {'Temporal neocortical: N (%)',... - sprintf('%d (%1.1f%%)',sum(neo==1 & is_hup),sum(neo==1 & is_hup)/sum(~isnan(neo(is_hup)))*100),... - sprintf('%d (%1.1f%%)',sum(neo==1 & is_musc),sum(neo==1 & is_musc)/sum(~isnan(neo(is_musc)))*100),... - sprintf('')}; -broad_str = {'Broad temporal: N (%)',... - sprintf('%d (%1.1f%%)',sum(broad==1 & is_hup),sum(broad==1 & is_hup)/sum((is_hup))*100),... - sprintf('%d (%1.1f%%)',sum(broad==1 & is_musc),sum(broad==1 & is_musc)/sum((is_musc))*100),... - sprintf('')}; -surg_str = {'Surgery performed','','',''}; -resection_str = {'Resection: N (%)',... - sprintf('%d (%1.1f%%)',sum(resection==1 & is_hup),sum(resection==1 & is_hup)/sum(is_hup)*100),... - sprintf('%d (%1.1f%%)',sum(resection==1 & is_musc),sum(resection==1 & is_musc)/sum(is_musc)*100),... - sprintf('%d (%1.1f%%)',nresection_fmri,nresection_fmri/sum(fmri_non_control)*100)}; -ablation_str = {'Ablation: N (%)',... - sprintf('%d (%1.1f%%)',sum(ablation==1 & is_hup),sum(ablation==1 & is_hup)/sum(is_hup)*100),... - sprintf('%d (%1.1f%%)',sum(ablation==1 & is_musc),sum(ablation==1 & is_musc)/sum(is_musc)*100),... - sprintf('%d (%1.1f%%)',nablation_fmri,nablation_fmri/sum(fmri_non_control)*100)}; -device_str = {'Device: N (%)',... - sprintf('%d (%1.1f%%)',sum(device==1 & is_hup),sum(device==1 & is_hup)/sum(is_hup)*100),... - sprintf('%d (%1.1f%%)',sum(device==1 & is_musc),sum(device==1 & is_musc)/sum(is_musc)*100),... - sprintf('%d (%1.1f%%)',ndevice_fmri,ndevice_fmri/sum(fmri_non_control)*100)}; -engel_str = {'Engel 1 year outcome',... - sprintf('N = %d with outcomes',sum(~isnan(engel_yr1(is_hup&(resection | ablation))))),... - sprintf('N = %d with outcomes',sum(~isnan(engel_yr1(is_musc&(resection | ablation))))),... - sprintf('N = %d with outcomes',sum(~isnan(engel_yr1_fmri)))}; -engel_one_str = {'Median (range)',... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(engel_yr1(is_hup&(resection | ablation))),min(engel_yr1(is_hup&(resection | ablation))),max(engel_yr1(is_hup&(resection | ablation)))),... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(engel_yr1(is_musc)),min(engel_yr1(is_musc)),max(engel_yr1(is_musc))),... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(engel_yr1_fmri),min(engel_yr1_fmri),max(engel_yr1_fmri))}; -engel_two_str = {'Year 2: median (range)',... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(engel_yr2(is_hup)),min(engel_yr2(is_hup)),max(engel_yr2(is_hup))),... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(engel_yr2(is_musc)),min(engel_yr2(is_musc)),max(engel_yr2(is_musc))),... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(engel_yr2_fmri),min(engel_yr2_fmri),max(engel_yr2_fmri))}; -ilae_str = {'ILAE 1 year outcome',... - sprintf('N = %d with outcomes',sum(~isnan(ilae_yr1(is_hup&(resection | ablation))))),... - sprintf('N = %d with outcomes',sum(~isnan(ilae_yr1(is_musc&(resection | ablation))))),... - sprintf('N = %d with outcomes',sum(~isnan(ilae_yr1_fmri)))}; -ilae_one_str = {'Median (range)',... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(ilae_yr1(is_hup)),min(ilae_yr1(is_hup)),max(ilae_yr1(is_hup))),... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(ilae_yr1(is_musc)),min(ilae_yr1(is_musc)),max(ilae_yr1(is_musc))),... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(ilae_yr1_fmri),min(ilae_yr1_fmri),max(ilae_yr1_fmri))}; -ilae_two_str = {'Year 2: median (range)',... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(ilae_yr2(is_hup)),min(ilae_yr2(is_hup)),max(ilae_yr2(is_hup))),... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(ilae_yr2(is_musc)),min(ilae_yr2(is_musc)),max(ilae_yr2(is_musc))),... - sprintf('%1.1f (%1.1f-%1.1f)',... - nanmedian(ilae_yr2_fmri),min(ilae_yr2_fmri),max(ilae_yr2_fmri))}; - -all = [total_str;... - female_str;... - age_onset_str;... - age_implant_str;... - n_discordant_str;... - n_elecs_str;... - % loc_str;... - % tle_str;... - % etle_str;... - lat_str;... - left_str;... - right_str;... - bilat_str;... - loc_str;... - mtl_str;... - neo_str;... - broad_str;... - surg_str;... - resection_str;... - ablation_str;... - device_str;... - engel_str;... - engel_one_str;... - %engel_two_str;... - ilae_str;... - ilae_one_str]; - %ilae_two_str]; - -T2 = cell2table(all); -writetable(T2,[plot_folder,'Table1.csv']); - - - -%% Get inclusion/exclusion numbers -% How many total HUP patients did I look at? -n_hup_total = sum(contains(Ts.names,'HUP')); -hup_pts = contains(Ts.names,'HUP'); - -% musc -musc_pts = contains(Ts.names,'MP'); -n_musc_total = sum(contains(Ts.names,'MP')); - -% How many did I exclude due to no symmetric mesial temporal contacts? -n_exclude_no_symm_hup = sum(hup_pts & Ts.n_symmetric == 0); -n_exclude_no_symm_musc = sum(musc_pts & Ts.n_symmetric == 0); - -% How many did I exclude due to etle -n_etle_hup = sum(hup_pts & ~contains(Ts.soz_locs,'temporal') & (Ts.n_symmetric ~= 0)); -n_etle_musc = sum(musc_pts & ~contains(Ts.soz_locs,'temporal') & (Ts.n_symmetric ~= 0)); - -% How many did I exclude due to most of the EEG disconnected? -n_exclude_no_sleep_conn_hup = sum(hup_pts & Ts.most_disconnected); % 0 -n_exclude_no_sleep_conn_musc = sum(musc_pts & Ts.most_disconnected & Ts.n_symmetric ~= 0 & contains(Ts.soz_locs,'temporal')); -assert(n_exclude_no_sleep_conn_hup == 0) -assert(n_exclude_no_sleep_conn_musc == 1) - -% total I think I should have excluded -n_hup_excluded = n_exclude_no_symm_hup + n_etle_hup; -n_hup_remaining = n_hup_total - n_hup_excluded; -n_musc_excluded = n_exclude_no_symm_musc + n_etle_musc + n_exclude_no_sleep_conn_musc; -n_musc_remaining = n_musc_total - n_musc_excluded; - -% Make sure it adds up to expected number of included patients -assert(sum(contains(T.names,'HUP'))==n_hup_remaining) -assert(sum(contains(T.names,'MP'))==n_musc_remaining) - -%% Get spike detector performance -snames = sT.name; -bi_good = sT.x_Correct_outOf50__bi_; -perc_good = bi_good/50; - -all_pts_sp = ismember(snames,T.names); -all_pts_hup_sp = ismember(snames,T.names) & contains(snames,'HUP'); -all_pts_musc_sp = ismember(snames,T.names) & contains(snames,'MP'); - -%% Compare spike detector performance between lesional and non lesional patients. -% allow MUSC patients once I get data - -[h,p,ci,stats] = ttest2(T.spike_accuracy(strcmp(T.temporal_lesion,'Yes') & ... - contains(T.names,'HUP')),... - T.spike_accuracy(strcmp(T.temporal_lesion,'No') & ... - contains(T.names,'HUP'))); - -%% Summary results -%fprintf(fid,'
Clinical information and summary of intracranial recording');
-%{
-fprintf(fid,['We examined %d consecutive patients at HUP for our univariate analyses, model '...
- 'development, and internal validation. We excluded %d patients without symmetric bitemporal '...
- 'electrode coverage, and an additional %d patients with '...
- 'extratemporal seizure onset zones, yielding %d patients included for analysis.'],...
- n_hup_total,n_exclude_no_symm_hup,n_etle_hup,n_hup_remaining);
-
-fprintf(fid,[' We examined %d consecutive patients at MUSC for external model validation. We excluded %d patients '...
- 'without symmetric bitemporal electrode coverage, an additional %d patient with extra-temporal seizure onset, '...
- 'and an additional %d patient for whom most of the EEG was disconnected, yielding %d MUSC patients who '...
- 'met inclusion criteria (Table 1).'],...
- n_musc_total,n_exclude_no_symm_musc,n_etle_musc,n_exclude_no_sleep_conn_musc,n_musc_remaining);
-%}
-fprintf(fid,['
We examined %d patients at HUP for model development and internal validation, '... - 'and %d patients at MUSC for external model validation (Table 1).'],n_hup_remaining,n_musc_remaining); -%{ -fprintf(fid,[' The majority of patients underwent bilateral electrode implantation '... - ' either because one of the two primary pre-implantation hypotheses was bilateral, '... - 'or because of discordant lateralities between the two primary pre-implant hypotheses.']); -%} - -fprintf(fid,['TLE lateralities were imbalanced across the two centers, with '... - 'left TLE being more prevalent at HUP (%1.1f left, %1.1f right, %1.1f bilateral),'... - 'and right TLE being more prevalent at MUSC '... - '(%1.1f left, %1.1f right, %1.1f bilateral)'],... - sum(left==1 & is_hup)/sum(~isnan(left(is_hup)))*100,... - sum(right==1 & is_hup)/sum(~isnan(right(is_hup)))*100,... - sum(bilateral==1 & is_hup)/sum(~isnan(bilateral(is_hup)))*100,... - sum(left==1 & is_musc)/sum(~isnan(left(is_musc)))*100,... - sum(right==1 & is_musc)/sum(~isnan(right(is_musc)))*100,... - sum(bilateral==1 & is_musc)/sum(~isnan(bilateral(is_musc)))*100); - -fprintf(fid,[' We visually validated a random sample of 50 automated spike detections from each patient '... - '(bipolar montage). The median (IQR) percentage of automatically-detected spikes '... - 'determined to be true spikes was '... - '%1.1f%% (%1.1f%%-%1.1f%%) for HUP and %1.1f%% (%1.1f%%-%1.1f%%) for MUSC.'],... - median(perc_good(all_pts_hup_sp))*100,prctile(perc_good(all_pts_hup_sp),25)*100,... - prctile(perc_good(all_pts_hup_sp),75)*100,... - median(perc_good(all_pts_musc_sp))*100,prctile(perc_good(all_pts_musc_sp),25)*100,... - prctile(perc_good(all_pts_musc_sp),75)*100); - -%% Compare spike detector performance between lesional and non lesional - -%% For revisions, add informaiton abdout % segments awake and % asleep -n_connected = T.n_connected; -n_wake = T.n_wake; -n_sleep = T.n_sleep; -fprintf(fid,[' Of the 72 time segments studied per patient, '... - 'a median of %1.1f (IQR %1.1f-%1.1f) were determined to represent '... - 'wakefulness, and %1.1f (IQR %1.1f-%1.1f) were determined to be in NREM sleep.
'],... - median(n_wake),prctile(n_wake,25),prctile(n_wake,75),... - median(n_sleep),prctile(n_sleep,25),prctile(n_sleep,75)); - -%% COmpare duration between left and right -if 0 - duration = age_implant-age_onset; - unpaired_plot(duration(left),duration(right),{'left','right'},'duration') - % looks like no difference -end - -%% Make another table with missing atlas data -mT = table(rid,name,has_atropos,has_dkt); -writetable(mT,[plot_folder,'missingAtlasTable.csv']); - -%% Prelim data for R01 aims -a = cellfun(@(x) regexp(x,'\d*','Match'),name); -b = cellfun(@(x) str2num(x), a); -N = sum(contains(name,'HUP')& ((b>=159 & b<=199) | (b == 140 | b ==143))); % 2018-2019 -Nbilat = sum((contains(name,'HUP')& ((b>=159 & b<=199) | (b == 140 | b ==143))) & bilateral == 1); -%{ -Looks like we started doing mostly stereo with HUP127, November 2016. Going -5 years out, this would take us up to Nov 2021, or HUP226. There are 57 -between hUP127 and HUP224, which is as far as I processed. HUP225 did not -have bilateral MT, but HUP226 did. So 58 over 5 years. 11.6/year. 23 in 2 -years. However, 37% of these were bilateral. So only 14.5 unilateral in 2 -years....... - -Ok what if I only look at 2018 and 2019 (two normal years, assuming 2020, -and 2021 screwed up due to COVID). HUP159-199 + 2 stragglers (HUP140 and -143). Then I get 29 bilateral MT implants, 9 of whom had bilateral SOZ -(31%). So then I can estimate 20 unilateral patients with bilateral MT implants, - which gets 90% power. -%} - - -end - -function engel = get_engel(tengel) - -engel = cell(length(tengel),1); -for i = 1:length(tengel) - switch tengel(i) - - case 1 - engel{i} = 'IA'; - case 5 - engel{i} = 'IB'; - case 6 - engel{i} = 'IC'; - case 7 - engel{i} = 'ID'; - case 2 - engel{i} = 'IIA'; - case 8 - engel{i} = 'IIB'; - case 9 - engel{i} = 'IIC'; - case 10 - engel{i} = 'IID'; - case 3 - engel{i} = 'IIIA'; - case 11 - engel{i} = 'IIIB'; - case 4 - engel{i} = 'IVA'; - case 12 - engel{i} = 'IVB'; - case 13 - engel{i} = 'IVC'; - otherwise - engel{i} = ''; - - end - -end -end - -function ilae = get_ilae(tilae) -ilae = cell(length(tilae),1); -for i = 1:length(ilae) - switch tilae(i) - - case 1 - ilae{i} = 'ILAE 1'; - case 2 - ilae{i} = 'ILAE 1a'; - case 3 - ilae{i} = 'ILAE 2'; - case 4 - ilae{i} = 'ILAE 3'; - case 5 - ilae{i} = 'ILAE 4'; - case 6 - ilae{i} = 'ILAE 5'; - case 7 - ilae{i} = 'ILAE 6'; - otherwise - ilae{i} = ''; - end -end - -end \ No newline at end of file diff --git a/plots/make_table_1.m b/plots/make_table_1.m index e827a58..c7045d4 100644 --- a/plots/make_table_1.m +++ b/plots/make_table_1.m @@ -15,6 +15,7 @@ if ~exist(plot_folder,'dir') mkdir(plot_folder) end +plot_folder % add script folder to path scripts_folder = locations.script_folder; @@ -47,7 +48,7 @@ %% Manual validation manT = readtable([inter_folder,'Manual validation.xlsx'],'Sheet','SOZ'); piT = readtable([inter_folder,'Manual validation.xlsx'],'Sheet','Pre-implant data'); -sT = readtable('Manual validation.xlsx','Sheet','EDF pipeline'); +sT = readtable([inter_folder,'Manual validation.xlsx'],'Sheet','EDF pipeline'); %% Load MUSC clinical folder muscT = readtable([inter_folder,'LEN patient list research erin.xlsx']); @@ -113,6 +114,17 @@ % add error check once we have musc data if sum(row2) == 0, continue; end if find(row2) > length(piT.MRILesionalLocalization_temporal_Frontal_Other_Multifocal_Broad), continue; end + %{ + % is there a unilateral temporal lesion? + if contains(piT.MRILesionalLocalization_temporal_Frontal_Other_Multifocal_Broad{row2},'temporal','IgnoreCase',true) && ... + (strcmpi(piT.MRILesionalLaterality_left_Right_Bilateral_Broad_NA__NAMeansNon{row2},'Right') || ... + strcmpi(piT.MRILesionalLaterality_left_Right_Bilateral_Broad_NA__NAMeansNon{row2},'Left')) + T.temporal_lesion{ip} = 'Yes'; + else + T.temporal_lesion{ip} = 'No'; + end + %} + % is there a temporal lesion (regardless of laterality)? if contains(piT.MRILesionalLocalization_temporal_Frontal_Other_Multifocal_Broad{row2},'temporal','IgnoreCase',true) T.temporal_lesion{ip} = 'Yes'; else @@ -364,8 +376,17 @@ lat_fmri = f1T.Final_Lat(fmri_non_control); % lesional status -lesional_fmri = f1T.MRI_Lesional(fmri_non_control); -n_lesional_fmri = sum(strcmp(lesional_fmri,'Lesional')); +n_non_controls = sum(fmri_non_control); +%lesional_fmri = f1T.MRI_Lesional(fmri_non_control); +%{ +n_lesional_fmri = sum(strcmp(f1T.MRI_Lesional,'Lesional') & ... + (strcmp(f1T.MRI_Lesion_Lat,'L') | strcmp(f1T.MRI_Lesion_Lat,'R')) & ... + (contains(f1T.MRI_Lesion_Loc,'temporal','ignorecase',true)) & ... + fmri_non_control); +%} +n_lesional_fmri = sum(strcmp(f1T.MRI_Lesional,'Lesional') & ... + contains(f1T.MRI_Lesion_Loc,'temporal','ignorecase',true) & ... + fmri_non_control); % get corresponding rows in my demographics table [Lia,Locb]=ismember(fmri_rid_nums,f2T.record_id); % Locb has the corresponding rows @@ -437,7 +458,7 @@ lesional_str = {'MRI lesional: N (%)',... sprintf('%d (%1.1f%%)',sum(lesional == 1 & is_hup),sum(lesional==1 & is_hup)/sum(is_hup)*100),... sprintf('%d (%1.1f%%)',sum(lesional == 1 & is_musc),sum(lesional==1 & is_musc)/sum(is_musc)*100),... - sprintf('%d (%1.1f%%)',n_lesional_fmri,n_lesional_fmri/length(lesional_fmri)*100)}; + sprintf('%d (%1.1f%%)',n_lesional_fmri,n_lesional_fmri/n_non_controls*100)}; n_discordant_str = {'Bilateral or discordant pre-implant hypotheses: N (%)',... sprintf('%d (%1.1f%%)',sum(bilat_or_discordant==1 & is_hup),sum(bilat_or_discordant==1 & is_hup)/sum(~isnan(bilat_or_discordant(is_hup)))*100),... sprintf('%d (%1.1f%%)',sum(bilat_or_discordant==1 & is_musc),sum(bilat_or_discordant==1 & is_musc)/sum(~isnan(bilat_or_discordant(is_musc)))*100),... diff --git a/plots/model_plots.asv b/plots/model_plots.asv deleted file mode 100644 index 9662180..0000000 --- a/plots/model_plots.asv +++ /dev/null @@ -1,657 +0,0 @@ -function model_plots - -%% Parameters -use_model_predict_fcn = 1; -which_refs = {'car','bipolar','machine'}; - -%% Get file locs -locations = epilepsy_laterality_locs; -plot_folder = locations.el_plots_folder; -if ~exist(plot_folder,'dir') - mkdir(plot_folder) -end - -%% Load sozT -sozT = readtable('Manual validation.xlsx','Sheet','SOZ'); - -%% Load preimplant data -piT = readtable('Manual validation.xlsx','Sheet','Pre-implant data'); - -% Loop over choice of reference -for ia = 1:length(which_refs) - - %% Load the intermediate file - out = load([plot_folder,sprintf('ext_models_%s.mat',which_refs{ia})]); - out = out.all; - - % If first reference, Loop over all patients vs good outcome HUP - % patients, otherwise just do all patients - if ia == 1 - no = 2; - else - no = 1; - end - - for io = 1:no - - - model = out.approach(io).model; - nmodels = length(model); - - %% Initialize results file - if ia == 1 && io == 1 - fname = [plot_folder,'results.html']; - fid = fopen(fname,'a'); - fprintf(fid,'Finally, we tested how the spike-only models performed in an '... - 'external dataset. We trained the models using only spike rate data '... - 'on all HUP patients, and tested the models on patients from MUSC. '... - 'The model AUCs on the MUSC patients were %1.2f and %1.2f for '... - 'the left-sided and right-sided models, respectively (Fig. 5G).'],... - model(2).val(2).side(1).result.AUC,model(2).val(2).side(2).result.AUC); - - end - - %% E-F Confusion matrix (spikes only, internal cross-validation) - curr = model(2).val(1); - both_bal_acc = nan(2,1); - both_opt_thresh = nan(2,1); - both_opt = nan(2,2); - for is = 1:2 - scores = curr.side(is).result.scores; - class = curr.side(is).result.class; - - classes = curr.side(is).result.unique_classes; - nclasses = length(classes); - pos_class = curr.side(is).result.pos_class; - neg_class = classes(~strcmp(classes,pos_class)); - - % put the positive class on top - if ~strcmp(curr.side(is).result.pos_class,classes{1}) - classes = classes([2 1]); - end - - - if use_model_predict_fcn - pred = curr.side(is).result.all_pred; - - else % find optimal point myself - % get cost for finding optimal ROC point - cost = [0 sum(strcmp(class,neg_class));sum(strcmp(class,pos_class)) 0]; - - % Get optimal ROC point - - [X,Y,T,~,opt] = perfcurve(class,scores,pos_class,'cost',cost); - %[X,Y,T,~,opt] = perfcurve(class,scores,pos_class); - opt_thresh = T((X==opt(1))&(Y==opt(2))); - %opt_thresh = my_opt(X,Y,T); - opt_thresh = 0.5; - both_opt_thresh(is) = opt_thresh; - both_opt(is,:) = opt; - - pred = cell(length(class),1); - pred(scores >= opt_thresh) = {pos_class}; - pred(scores < opt_thresh) = neg_class; - end - - % Rebuild C - positive = strcmp(class,pos_class); - negative = strcmp(class,neg_class); - pred_positive = strcmp(pred,pos_class); - pred_negative = strcmp(pred,neg_class); - C(1,1) = sum(positive & pred_positive); - C(1,2) = sum(positive & pred_negative); - C(2,1) = sum(negative & pred_positive); - C(2,2) = sum(negative & pred_negative); - - % Calculate accuracy - accuracy = sum(diag(C))/sum(C(:)); - % Balanced accuracy is the average across all classes of the number of - % data accurately predicted belonging to class m divided by the number of - % data belonging to class m - recall = nan(nclasses,1); - for i = 1:nclasses - tp = C(i,i); - fn = sum(C(i,~ismember(1:nclasses,i))); - recall(i) = tp/(tp+fn); % tp is number correctly predicted to be in class, tp + fn is everyone in the class - end - balanced_accuracy = mean(recall); - both_bal_acc(is) = balanced_accuracy; - - % Plot - nexttile(t) - % Map numbers onto 0 to 1 - new_numbers = map_numbers_onto_range(C,[1 0]); - Ccolor = cat(3,ones(nclasses,nclasses,1),repmat(new_numbers,1,1,2)); - D = diag(new_numbers); - Dcolor = [repmat(D,1,2),ones(length(D),1)]; - Ccolor(logical(repmat(eye(nclasses,nclasses),1,1,3))) = Dcolor; - imagesc(Ccolor) - - % replace classnames - pretty_name = classes; - pretty_name = strrep(pretty_name,'left','Left'); - pretty_name = strrep(pretty_name,'right','Right'); - pretty_name = strrep(pretty_name,'br','Right/bilateral'); - pretty_name = strrep(pretty_name,'bl','Left/bilateral'); - xticks(1:nclasses) - xticklabels((pretty_name)) - yticks(1:nclasses) - yticklabels((pretty_name)) - ytickangle(90) - xlabel('Predicted') - ylabel('True') - hold on - for i = 1:nclasses - for j = 1:nclasses - text(i,j,sprintf('%d',C(j,i)),'horizontalalignment','center','fontsize',20) - end - end - title(sprintf('Spikes (HUP cross-validation)\nBalanced accuracy %1.1f%%',balanced_accuracy*100)) - set(gca,'fontsize',15) - - end - - if ia == 1 && io == 1 - fprintf(fid,['
We further probed the accuracy of the spike-rate only model. '... - 'Confusion matrices for the left- and right-sided models at the optimal '... - 'operating points '... - 'are shown in Figs. 4E and 4F. The balanced accuracy '... - 'was %1.1f%% for the model predicting left vs. right/bilateral SOZ, '... - 'and %1.1f%% for the model predicting right vs. left/bilateral SOZ. '],... - both_bal_acc(1)*100,both_bal_acc(2)*100); - elseif ia == 1 && io == 2 - fprintf(sfid,['
We further probed the accuracy of the spike-rate only model. '... - 'Confusion matrices for the left- and right-sided models at the optimal '... - 'operating points '... - 'are shown in Figs. S3E and S3F. The balanced accuracy '... - 'was %1.1f%% for the model predicting left vs. right/bilateral SOZ, '... - 'and %1.1f%% for the model predicting right vs. left/bilateral SOZ. '],... - both_bal_acc(1)*100,both_bal_acc(2)*100); - end - - %% G: Subsampling plots, internal validation - if 1 - sub = out.cv_ss; % cross validation - - nexttile(t) - - durations = [1 5 10 20 30]; - - ndurs = length(durations); - nsamples = size(sub,4); - - - curr_ss = 2; % just do sleep - auc_l = squeeze(sub(curr_ss,1,:,:)); - auc_r = squeeze(sub(curr_ss,2,:,:)); - - median_l = nanmedian(auc_l,2); - median_r = nanmedian(auc_r,2); - P_l_25 = prctile(auc_l,[25],2); - P_r_25 = prctile(auc_r,[25],2); - P_l_75 = prctile(auc_l,[75],2); - P_r_75 = prctile(auc_r,[75],2); - - U_l = P_l_75-median_l; - U_r = P_r_75-median_r; - L_l = median_l - P_l_25; - L_r = median_r - P_r_25; - - % Plot it - - % - el = shaded_error_bars_fc_el(1:ndurs,median_l,[P_l_75';P_l_25'],[0, 0.4470, 0.7410]); - hold on - er = shaded_error_bars_fc_el(1:ndurs,median_r,[P_r_75';P_r_25'],[0.8500, 0.3250, 0.0980]); - - - errorbar(1:ndurs,median_l,L_l,U_l,'o','color',[0, 0.4470, 0.7410],... - 'LineWidth',2,'MarkerSize',10); - hold on - errorbar(1:ndurs,median_r,L_r,U_r,'o','color',[0.8500, 0.3250, 0.0980],... - 'LineWidth',2,'MarkerSize',10); - %} - - ylim([0.4 0.9]) - - legend([el,er],{'Left vs right/bilateral','Right vs left/bilateral'},... - 'location','southeast','fontsize',15) - xticks(1:ndurs) - xticklabels(arrayfun(@(x) sprintf('%d min',x),durations,'uniformoutput',false)) - ylabel('Median (IQR) AUC') - title(sprintf('Spike model accuracy by duration\n(HUP cross-validation)')) - set(gca,'fontsize',15) - - if ia == 1 && io == 1 - fprintf(fid,['Model accuracies rise quickly with duration sampled, achieving '... - 'an accuracy similar to the full-duration models with 5 minutes of sampling (Fig. 4G).']); - elseif ia == 1 && io == 2 - fprintf(sfid,['Model accuracies rise quickly with duration sampled, achieving '... - 'an accuracy similar to the full-duration models with 5 minutes of sampling (Fig. S3G).']); - end - - end - - - %% H-I: Confusion matrix (spikes only, external validation) - curr = model(2).val(2); % spike model, external validation - both_bal_acc = nan(2,1); - for is = 1:2 - scores = curr.side(is).result.scores; - class = curr.side(is).result.class; - - classes = curr.side(is).result.unique_classes; - nclasses = length(classes); - pos_class = curr.side(is).result.pos_class; - neg_class = classes(~strcmp(classes,pos_class)); - - % put the positive class on top - if ~strcmp(curr.side(is).result.pos_class,classes{1}) - classes = classes([2 1]); - end - - if use_model_predict_fcn - pred = curr.side(is).result.all_pred; - - else - % Get optimal ROC point - %[X,Y,T,~,opt] = perfcurve(class,scores,pos_class); - %opt_thresh = T((X==opt(1))&(Y==opt(2))); - %opt_thresh = my_opt(X,Y,T); - % use the optimal threshold from the internal cross validated model - opt_thresh = both_opt_thresh(is); - - pred = cell(length(class),1); - pred(scores >= opt_thresh) = {pos_class}; - pred(scores < opt_thresh) = neg_class; - end - - - % Rebuild C - positive = strcmp(class,pos_class); - negative = strcmp(class,neg_class); - pred_positive = strcmp(pred,pos_class); - pred_negative = strcmp(pred,neg_class); - C(1,1) = sum(positive & pred_positive); - C(1,2) = sum(positive & pred_negative); - C(2,1) = sum(negative & pred_positive); - C(2,2) = sum(negative & pred_negative); - - % Calculate accuracy - accuracy = sum(diag(C))/sum(C(:)); - % Balanced accuracy is the average across all classes of the number of - % data accurately predicted belonging to class m divided by the number of - % data belonging to class m - recall = nan(nclasses,1); - for i = 1:nclasses - tp = C(i,i); - fn = sum(C(i,~ismember(1:nclasses,i))); - recall(i) = tp/(tp+fn); % tp is number correctly predicted to be in class, tp + fn is everyone in the class - end - - balanced_accuracy = mean(recall); - both_bal_acc(is) = balanced_accuracy; - - % Plot - nexttile(t) - % Map numbers onto 0 to 1 - new_numbers = map_numbers_onto_range(C,[1 0]); - Ccolor = cat(3,ones(nclasses,nclasses,1),repmat(new_numbers,1,1,2)); - D = diag(new_numbers); - Dcolor = [repmat(D,1,2),ones(length(D),1)]; - Ccolor(logical(repmat(eye(nclasses,nclasses),1,1,3))) = Dcolor; - imagesc(Ccolor) - - % replace classnames - pretty_name = classes; - pretty_name = strrep(pretty_name,'left','Left'); - pretty_name = strrep(pretty_name,'right','Right'); - pretty_name = strrep(pretty_name,'br','Right/bilateral'); - pretty_name = strrep(pretty_name,'bl','Left/bilateral'); - xticks(1:nclasses) - xticklabels((pretty_name)) - yticks(1:nclasses) - yticklabels((pretty_name)) - ytickangle(90) - xlabel('Predicted') - ylabel('True') - hold on - for i = 1:nclasses - for j = 1:nclasses - text(i,j,sprintf('%d',C(j,i)),'horizontalalignment','center','fontsize',20) - end - end - title(sprintf('Spikes (MUSC external validation)\nBalanced accuracy %1.1f%%',balanced_accuracy*100)) - set(gca,'fontsize',15) - - end - - if ia == 1 && io == 1 - fprintf(fid,[' Finally, we tested how the spike-only models performed in the '... - 'external MUSC dataset.']); - fprintf(fid,[' The balanced accuracies were %1.1f%% and %1.1f%% '... - 'for the left-sided and right-sided models, respectively (Fig. 4H and 4I).
'],both_bal_acc(1)*100,... - both_bal_acc(2)*100); - - fprintf(fid,['These results suggest that models '... - 'using only spike rate asymmetry accurately distinguished left from right or bilateral SOZs '... - 'in both internal cross-validation and in a separate institution''s test dataset. However, '... - 'although right-sided SOZs could be distinguished from left/bilateral SOZs in the external '... - 'dataset set, they were not well-classified in the internal validation dataset.']); - - fprintf(fid,[' Results were similar, although with higher AUCs '... - 'across all models, when we restricted analysis of unilateral HUP patients to be those with '... - 'Engel 1 surgical outcomes to ' ... - 'build and internally validate the SOZ laterality classifier (Fig. S3).']); - - fprintf(fid,[' Results were also similar when we used spikes detected in bipolar and machine references to '... - 'build the SOZ laterality classifier (Fig. S4 and S5).']); - elseif ia == 1 && io == 2 - - fprintf(sfid,[' Finally, we tested how the spike-only models performed in the '... - 'external MUSC dataset.']); - fprintf(sfid,[' The balanced accuracies were %1.1f%% and %1.1f%% '... - 'for the left-sided and right-sided models, respectively (Fig. 4H and 4I).
'],both_bal_acc(1)*100,... - both_bal_acc(2)*100); - - fprintf(sfid,['These results were overall similar as those when we include '... - 'all patients regardless of surgical outcome, although higher AUCs were '... - 'achieved across all models when we restrict analysis of unilateral '... - 'patients to those with good outcomes.
']); - end - - - %% Add subtitles - annotation('textbox',[0 0.905 0.1 0.1],'String','A','LineStyle','none','fontsize',20) - annotation('textbox',[0.36 0.905 0.1 0.1],'String','B','LineStyle','none','fontsize',20) - annotation('textbox',[0.70 0.905 0.1 0.1],'String','C','LineStyle','none','fontsize',20) - annotation('textbox',[0 0.56 0.1 0.1],'String','D','LineStyle','none','fontsize',20) - annotation('textbox',[0.36 0.56 0.1 0.1],'String','E','LineStyle','none','fontsize',20) - annotation('textbox',[0.70 0.56 0.1 0.1],'String','F','LineStyle','none','fontsize',20) - annotation('textbox',[0 0.22 0.1 0.1],'String','G','LineStyle','none','fontsize',20) - annotation('textbox',[0.36 0.22 0.1 0.1],'String','H','LineStyle','none','fontsize',20) - annotation('textbox',[0.70 0.22 0.1 0.1],'String','I','LineStyle','none','fontsize',20) - - - - - if ia == 1 && io == 1 - %print(gcf,[plot_folder,'Fig3'],'-dpng') - print(gcf,[plot_folder,'Fig4'],'-dtiff') - elseif ia == 1 && io == 2 - %print(gcf,[plot_folder,'FigS3'],'-dpng') - print(gcf,[plot_folder,'FigS3'],'-dtiff') - elseif ia == 2 - %print(gcf,[plot_folder,'FigS4'],'-dpng') - print(gcf,[plot_folder,'FigS4'],'-dtiff') - elseif ia == 3 - %print(gcf,[plot_folder,'FigS5'],'-dpng') - print(gcf,[plot_folder,'FigS5'],'-dtiff') - end - - - %% Examining error sources - if ia ==1 && io == 1 - error_stats = nan(2,4); - mri_error_stats = nan(2,4); - - % loop over left and right - for is = 1:2 - - % Find the label of correct vs incorrect in both hup and musc - musc_agree = cellfun(@(x,y) strcmp(x,y),... - model(2).val(2).side(is).result.class,... - model(2).val(2).side(is).result.all_pred); - musc_names = model(2).val(2).side(is).result.names; - - hup_agree = cellfun(@(x,y) strcmp(x,y),... - model(2).val(1).side(is).result.class,... - model(2).val(1).side(is).result.all_pred); - hup_names = model(2).val(1).side(is).result.names; - - all_agree = [musc_agree;hup_agree]; - all_names = [musc_names;hup_names]; - - if 0 - table(all_names,all_agree) - end - - % Find the corresponding soz loc - soz_loc = cell(length(all_names),1); - for in = 1:length(all_names) - % find the corresponding row - r = strcmp(all_names{in},sozT.name); - if sum(r) ~= 1, error('what'); end - soz_loc{in} = sozT.region{r}; - end - soz_spec = cell(length(all_names),1); - soz_spec(contains(soz_loc,'mesial')) = {'mesial temporal'}; - soz_spec(contains(soz_loc,'neocortical')) = {'temporal neocortical'}; - soz_spec(cellfun(@isempty,soz_spec)) = {'other'}; - nother = sum(sum(strcmp(soz_spec,'other'))); - - % Find the corresponding lesional status - mri_loc_table = piT.MRILesionalLocalization_temporal_Frontal_Other_Multifocal_Broad; - mri_name = piT.name; - mri_loc = cell(length(all_names),1); - for in = 1:length(all_names) - % find the corresponding row - r = strcmp(all_names{in},mri_name); - if sum(r) > 1, error('what'); end - - % don't have musc yet - if sum(r) == 0 && ~contains(all_names{in},'MP'), error('what'); end - if sum(r) == 0, continue; end - mri_loc{in} = mri_loc_table{r}; - end - mri_spec = cell(length(all_names),1); - mri_loc(cellfun(@(x) isempty(x),mri_loc)) = {'na'}; - mri_spec(contains(mri_loc,'temporal','IgnoreCase',true)) = {'temporal'}; - mri_spec(~contains(mri_loc,'temporal','IgnoreCase',true)) = {'other'}; - - % make a table - errorT = table(all_names,all_agree,soz_spec); - mri_errorT = table(all_names,all_agree,mri_spec); - - % remove the others - errorT(strcmp(errorT.soz_spec,'other'),:) = []; - - % for mri table, remove musc - mri_errorT(contains(mri_errorT.all_names,'MP'),:) = []; - - % 2x2 table - [tbl2x2,~,~,labels] = crosstab(errorT.all_agree,errorT.soz_spec); - assert(strcmp(labels{1,1},'0')) - assert(strcmp(labels{1,2},'mesial temporal')) - [h,p,stats] = fishertest(tbl2x2); - error_stats(is,:) = [stats.OddsRatio, stats.ConfidenceInterval, p]; - - % mri 2x2 table - [tbl2x2,~,~,labels] = crosstab(mri_errorT.all_agree,mri_errorT.mri_spec); - assert(strcmp(labels{1,1},'0')) - assert(strcmp(labels{1,2},'temporal')) - [h,p,stats] = fishertest(tbl2x2); - mri_error_stats(is,:) = [stats.OddsRatio, stats.ConfidenceInterval, p]; - - if 0 - heatmap(mri_errorT,'all_agree','mri_spec') - end - - end - - fprintf(fid,[' We next examined whether model accuracy was associated '... - 'with the precise localization of the SOZ. We aggregated patients '... - 'from both HUP and MUSC, and selected only those patients whose '... - 'clinician-defined SOZ was either mesial temporal (N = %d) or '... - 'temporal neocortical (N = %d), excluding %d patients with broader '... - 'temporal localizations. We tested for the association between mesial temporal '... - 'vs. temporal neocortical localization and correct vs. incorrect '... - 'laterality classification with a Fisher''s exact test. '... - 'There was no significant association between'... - ' model accuracy and mesial temporal vs. temporal neocortical '... - 'localization for either the left- or right-sided model '... - '(left-sided model odds-ratio: %1.1f (95%% CI %1.1f-%1.1f), '... - 'p = %1.2f; right-sided model: %1.1f (%1.1f-%1.1f), '... - 'p = %1.2f).'],... - sum(strcmp(soz_spec,'mesial temporal')),sum(strcmp(soz_spec,'temporal neocortical')),nother,... - error_stats(1,1),error_stats(1,2),error_stats(1,3),error_stats(1,4),... - error_stats(2,1),error_stats(2,2),error_stats(2,3),error_stats(2,4)); - end - - end -end - -end \ No newline at end of file diff --git a/plots/model_plots.m b/plots/model_plots.m index bb20067..0a9d24d 100644 --- a/plots/model_plots.m +++ b/plots/model_plots.m @@ -611,20 +611,29 @@ % Find the corresponding lesional status mri_loc_table = piT.MRILesionalLocalization_temporal_Frontal_Other_Multifocal_Broad; + mri_lat_table = piT.MRILesionalLaterality_left_Right_Bilateral_Broad_NA__NAMeansNon; mri_name = piT.name; mri_loc = cell(length(all_names),1); + mri_lat = cell(length(all_names),1); for in = 1:length(all_names) % find the corresponding row r = strcmp(all_names{in},mri_name); if sum(r) > 1, error('what'); end % don't have musc yet - if sum(r) == 0 && ~contains(all_names{in},'MP'), error('what'); end - if sum(r) == 0, continue; end + if sum(r) == 0, error('what'); end mri_loc{in} = mri_loc_table{r}; + mri_lat{in} = mri_lat_table{r}; end mri_spec = cell(length(all_names),1); mri_loc(cellfun(@(x) isempty(x),mri_loc)) = {'na'}; + mri_lat(cellfun(@(x) isempty(x),mri_lat)) = {'na'}; + %{ + mri_spec(contains(mri_loc,'temporal','IgnoreCase',true) & ... + (strcmpi(mri_lat,'left') | strcmpi(mri_lat,'right'))) = {'unilateral temporal'}; + mri_spec(~contains(mri_loc,'temporal','IgnoreCase',true) | ... + ~(strcmpi(mri_lat,'left') | strcmpi(mri_lat,'right'))) = {'other'}; + %} mri_spec(contains(mri_loc,'temporal','IgnoreCase',true)) = {'temporal'}; mri_spec(~contains(mri_loc,'temporal','IgnoreCase',true)) = {'other'}; @@ -635,9 +644,7 @@ % remove the others errorT(strcmp(errorT.soz_spec,'other'),:) = []; - % for mri table, remove musc - mri_errorT(contains(mri_errorT.all_names,'MP'),:) = []; - + % 2x2 table [tbl2x2,~,~,labels] = crosstab(errorT.all_agree,errorT.soz_spec); assert(strcmp(labels{1,1},'0')) @@ -671,10 +678,20 @@ 'localization for either the left- or right-sided model '... '(left-sided model odds-ratio: %1.1f (95%% CI %1.1f-%1.1f), '... 'p = %1.2f; right-sided model: %1.1f (%1.1f-%1.1f), '... - 'p = %1.2f).'],... + 'p = %1.2f).'],... sum(strcmp(soz_spec,'mesial temporal')),sum(strcmp(soz_spec,'temporal neocortical')),nother,... error_stats(1,1),error_stats(1,2),error_stats(1,3),error_stats(1,4),... error_stats(2,1),error_stats(2,2),error_stats(2,3),error_stats(2,4)); + + fprintf(fid,[' Similarly, there was no significant difference in model accuracy between patients '... + 'with temporal lobe lesions on MRI (N = %d) and '... + 'patients whose MRI had other lesions or no lesion (N = %d) '... + '(left-sided model odds-ratio: %1.1f (95%% CI %1.1f-%1.1f), '... + 'p = %1.2f; right-sided model: %1.1f (%1.1f-%1.1f), '... + 'p = %1.2f).'],... + sum(strcmp(mri_spec,'temporal')),sum(strcmp(mri_spec,'other')),... + mri_error_stats(1,1),mri_error_stats(1,2),mri_error_stats(1,3),mri_error_stats(1,4),... + mri_error_stats(2,1),mri_error_stats(2,2),mri_error_stats(2,3),mri_error_stats(2,4)); end end diff --git a/plots/outcome_plots.m b/plots/outcome_plots.m index 95dde17..2068e2c 100644 --- a/plots/outcome_plots.m +++ b/plots/outcome_plots.m @@ -465,7 +465,7 @@ 'The performance of the model validated using leave-one-out classification had '... 'an AUC of %1.2f for predicting Engel outcome and %1.2f for predicting ILAE outcome (Fig. S6). '... 'Together, these results suggest that a model trained to predict the SOZ using spike rate '... - 'asymmetry is also associated with surgical outcome.'],... + 'asymmetry is also associated with surgical outcome, although it has only modest ability to predict outcome.'],... prob_stats(1,1),prob_stats(1,2),prob_stats(1,3),prob_stats(1,4),prob_stats(1,5),prob_stats(1,6),... get_p_html_el(prob_stats(1,7)),... prob_stats(2,1),prob_stats(2,2),prob_stats(2,3),prob_stats(2,4),prob_stats(2,5),prob_stats(2,6),...