2
\$\begingroup\$

Could someone kindly help me review and fix any errors or inefficiencies in this MATLAB script? I’m trying to make sure it’s clean, robust, and optimized.

Brief description of what the code does:

This MATLAB script processes biomechanical data from different subject groups (e.g., Healthy, PD, Stroke, TBI) during gait tasks. It loads and combines motion capture data, filters for valid entries, computes spatiotemporal gait metrics (e.g., stride length, speed, duration), calculates variability measures like coefficient of variation (CV), phase coordination index (PCI), and gait asymmetry (GA), and conducts statistical analysis (ANOVA and Tukey HSD) to compare gait characteristics across groups. It also includes pre-processing for sensor signals and attempts to correlate gait events with signal features (e.g., heel strikes via accelerometer peaks).

clear
path = 'C:\Users\franc\Desktop\BIOMECCANICA';
pop = dir(path); pop = {pop(3:end).name};
[indx,~] = listdlg('ListString',pop);
pop_true = pop(indx);
file_to_load = {'ROOT' 'INDIP' 'LWRIST' 'RWRIST' 'LFOOT' 'RFOOT' 'LBACK' 'STERN' 'FHEAD'};
[indx,~] = listdlg('ListString',file_to_load);
file_true = file_to_load(indx);
TAB = table;
TAB1 = table;
%Carico tutta la tabella TAB
for po = 1:length(pop_true)
 files = dir([path '\' char(pop_true(po))]); files = {files(3:end).name};
 files_true = files(contains(files,file_true));
 for fi = 1:length(files_true)
 temp = load([path '\' char(pop_true(po)) '\' char(files_true(fi))]);
 fld = char(fieldnames(temp)); ind = strfind(fld,'_');
 if contains(fld,'INDIP') == 1 || contains(fld,'ROOT') == 1
 temp = temp.(fld);
 else
 temp = table(temp.(fld));
 temp.Properties.VariableNames = {fld(ind+1:end)};
 end
 TAB1 = [TAB1 temp];
 end
 TAB = [TAB; TAB1];
 TAB1 = table;
end
TAB = removevars(TAB,'b2');
varnames = TAB.Properties.VariableNames;
varnames(contains(varnames,'b1')) = {'INDIP'};
TAB.Properties.VariableNames = varnames;
TAB = movevars(TAB,'GROUP','Before',1); TAB = movevars(TAB,{'SUBJECT' 'TASK' 'TRIAL' 'ORIGINAL PATH' 'AGE' 'MASS' 'HEIGHT' 'INDIP'},'After','GROUP');
for ii = 1:size(TAB,1)
 TAB.HEIGHT{ii} = cell2mat(TAB.HEIGHT(ii))*0.53;
end
c = contains(TAB.("ORIGINAL PATH"),'FAST');
TAB = TAB(c,:);
timing = repmat({'T0'},size(TAB,1),1);
TAB.TIMING = timing;
TAB = movevars(TAB,'TIMING','Before','TASK');
%% CLEANING
for ii = 1:size(TAB,1)
 mwb = TAB.INDIP(ii).MicroWB;
 ind(ii) = isempty(mwb);
end
ind = logical(ind);
TAB(ind,:) = [];
ind = [];
for ii = 1:size(TAB,1)
 mwb = TAB.FHEAD(ii).acc;
 ind(ii) = isempty(mwb);
end
ind = logical(ind);
TAB(ind,:) = [];
ind = [];
for ii = 1:size(TAB,1)
 mwb = TAB.LBACK(ii).acc;
 ind(ii) = isempty(mwb);
end
ind = logical(ind);
TAB(ind,:) = [];
ind = [];
for ii = 1:size(TAB,1)
 mwb = TAB.STERN(ii).acc;
 ind(ii) = isempty(mwb);
end
ind = logical(ind);
TAB(ind,:) = [];
ind = [];
% %% GET SPATIOTEMPORAL
for ii = 1:size(TAB,1)
 mwb = TAB.INDIP(ii).MicroWB;
 TAB.STRIDE_FQ(ii) = {nanmean(cell2mat({mwb.StrideFrequency}))};
 spa = [];
 for jj = 1:size(mwb,2)
 temp = mwb(jj).Stride_Length;
 spa = [spa temp];
 end
 TAB.STRIDE_LENGTH(ii) = {(spa)};
 spa = [];
 for jj = 1:size(mwb,2)
 temp = mwb(jj).Stride_Speed;
 spa = [spa temp];
 end
 TAB.STRIDE_SPEED(ii) = {(spa)};
 spa = [];
 for jj = 1:size(mwb,2)
 temp = mwb(jj).Stride_Duration;
 spa = [spa temp];
 end
 TAB.STRIDE_DURATION(ii) = {(spa)};
 spa = [];
 for jj = 1:size(mwb,2)
 temp = mwb(jj).Stride_Length;
 spa = [spa temp];
 end
 TAB.STRIDE_LENGTH(ii) = {(spa)};
 spa = [];
 for jj = 1:size(mwb,2)
 temp = mwb(jj).Stance_Duration;
 spa = [spa temp];
 end
 TAB.STANCE_DURATION(ii) = {(spa)};
 spa = [];
 for jj = 1:size(mwb,2)
 temp = mwb(jj).Stance_Length;
 spa = [spa temp];
 end
 TAB.STANCE_LENGTH(ii) = {(spa)};
 spa = [];
 for jj = 1:size(mwb,2)
 temp = mwb(jj).Stance_Speed;
 spa = [spa temp];
 end
 TAB.STANCE_SPEED(ii) = {(spa)};
 spa = [];
 for jj = 1:size(mwb,2)
 temp = mwb(jj).Swing_Duration;
 spa = [spa temp];
 end
 TAB.SWING_DURATION(ii) = {(spa)};
 spa = [];
 for jj = 1:size(mwb,2)
 temp = mwb(jj).Swing_Length;
 spa = [spa temp];
 end
 TAB.SWING_LENGTH(ii) = {(spa)};
 spa = [];
 for jj = 1:size(mwb,2)
 temp = mwb(jj).Swing_Speed;
 spa = [spa temp];
 end
 TAB.SWING_SPEED(ii) = {(spa)};
 spa = [];
 for jj = 1:size(mwb,2)
 temp = mwb(jj).SingleSupport_Duration;
 spa = [spa temp];
 end
 TAB.SS_DURATION(ii) = {(spa)};
 spa = [];
 for jj = 1:size(mwb,2)
 temp = mwb(jj).DoubleSupport_Duration;
 spa = [spa temp];
 end
 TAB.DS_DURATION(ii) = {(spa)};
 spa = [];
 for jj = 1:size(mwb,2)
 temp = mwb(jj).Stride_InitialContacts;
 spa = [spa; temp];
 end
 [r,c] = find(isnan(spa)); r = unique(r); spa(r,:) = [];
 TAB.CONTANCTS(ii) = {(spa)};
 spa = [];
 for jj = 1:size(mwb,2)
 temp = mwb(jj).FinalContact_LeftRight;
 spa = [spa temp];
 end
 % spa(:,r) = [];
 TAB.SIDE(ii) = {(spa)};
end
Dividiamo in TAB10 e TAB8
% Selezioniamo le righe in cui TASK è '10MWT' (cammino rettilineo)
TAB10 = TAB(strcmp(TAB.TASK, '10MWT'), :)
% Selezioniamo le righe in cui TASK è '8MWT' (cammino a 8)
TAB8 = TAB(strcmp(TAB.TASK, 'Fo8WT'), :)
Creo una tabella ridotta (TABreduced), rispetto al task '10MTW' (TAB10), contenente le prime 20 righe per ogni malattia
TABreduced = table; % Inizializzo una tabella vuota per il risultato
% Filtro i gruppi per ciascun tipo di malattia solo per TASK = '10MWT'+
groups = {'Healthy_Control', 'PD', 'STROKE', 'TBI'};
start_rows = [1, 254, 413, 602]; % Indici di inizio per ogni gruppo
end_rows = [253, 412, 601, 680]; % Indici di fine per ogni gruppo
for i = 1:length(groups)
 % Seleziono le righe per il gruppo corrente da TAB10
 group_data = TAB10(strcmp(TAB10.GROUP, groups{i}), :);
 % Prendo solo le prime 15 righe 
 num_rows = min(15, size(group_data, 1)); %prendo al massimo 15 righe del gruppo, ma se il gruppo ha meno di 15 righe, allora prenderai tutte le righe disponibili. Quindi, se il gruppo ha meno di 15 righe, il codice prende tutte le righe, altrimenti prende solo le prime 15 righe.
 group_data_reduced = group_data(1:num_rows, :); %Seleziona le righe dalla 1 alla num_rows (ad esempio, se ci sono 10 righe, prende le righe da 1 a 10).
 % Aggiungo i dati ridotti a TABreduced
 TABreduced = [TABreduced; group_data_reduced];
 end
Calcolo, per tutte le malattie, il CV.
CV = 100SD∕mean where mean is the mean step length and SD is the standard deviation over the entire step length for each subject. The higher the CV, the higher is the variability of the step length. Un valore basso del CV indica una maggiore regolarità (i passi sono più simili tra loro), mentre un valore alto indica una maggiore variabilità tra gli stride.
stride=l'intervallo di movimento compreso tra due appoggi consecutivi dello stesso piede.
CV_individuale = zeros(height(TABreduced),1); 
for ii = 1:height(TABreduced)
 stride = TABreduced.STRIDE_LENGTH{ii}; 
 CV_ind = 100 * std(stride,'omitmissing') / mean(stride,'omitmissing');
 CV_individuale(ii) = CV_ind;
end
% Aggiungo la colonna alla tabella
TABreduced.CV_individuale = CV_individuale;
CV della stride_length medio per gruppo
CV_Healthy = mean(TABreduced.CV_individuale(1:15))
CV_PD = mean(TABreduced.CV_individuale(16:30))
CV_ST = mean(TABreduced.CV_individuale(31:45))
CV_TBI= mean(TABreduced.CV_individuale(46:60))
PCI individuale per tutti i pazienti
PCI_individuale = zeros(height(TABreduced),1); %Creo nuova colonna
for ii = 1:height(TABreduced)
ic_times = TABreduced.INDIP(ii).ContinuousWalkingPeriod.InitialContact_Event;
ic_side = TABreduced.INDIP(ii).ContinuousWalkingPeriod.InitialContact_LeftRight;
ic_times = ic_times(:);
ic_side = ic_side(:);
right_idx = [];
left_idx = [];
for c = 1:length(ic_side)
% Separazione in destra e sinistra
 if ic_side(c) == "Right"
 right_idx = [right_idx c];
else
 left_idx = [left_idx c];
 end
end
% Assumo A = destro, B = sinistro
% Calcolo fasi φi tra un heel strike sinistro tra due destri (o viceversa)
phi = [];
for i = 1:length(right_idx)-1
 A1 = ic_times(right_idx(i));
 A2 = ic_times(right_idx(i+1));
 
 % Cerco heel strike sinistro che cade tra A1 e A2
 mid_L_idx = find(ic_times(left_idx) > A1 & ic_times(left_idx) < A2);
 
 if ~isempty(mid_L_idx)
 B = ic_times(left_idx(mid_L_idx(1)));
 phi_i = ((B - A1) / (A2 - A1)) * 360;
 phi(end+1) = phi_i;
 end
% PCI = CV_phi + ME_phi
mean_phi = mean(phi);
std_phi = std(phi);
CV_phi = (std_phi / mean_phi) * 100;
ME_phi = mean(abs(phi - 180));
PCI = CV_phi + ME_phi;
PCI_individuale(ii) = PCI;
 end
end
% Aggiungo la colonna alla tabella
TABreduced.PCI_individuale = PCI_individuale;
% Crea il vettore dei gruppi (20 per ciascuno)
%analisi statistica
n = 15;
gruppi = [repmat({'Healthy_Control'}, n, 1); ...
 repmat({'PD'}, n, 1); ...
 repmat({'STROKE'}, n, 1); ...
 repmat({'TBI'}, n, 1)];
gruppi = categorical(gruppi);
% Esempio: PCI = dati a caso (solo per test)
PCI = TABreduced.PCI_individuale; 
% ANOVA a una via
[p, tbl, stats] = anova1(PCI, gruppi);
% Test post-hoc Tukey HSD
figure;
[c,~,~,gnames] = multcompare(stats);
%p-value per confermare differenze viste nel multcompare
tbl = array2table(c, 'VariableNames', ...
 ["GroupA", "GroupB", "LowerLimit", "A_B", "UpperLimit", "P_value"]);
tbl.GroupA = gnames(tbl.GroupA);
tbl.GroupB = gnames(tbl.GroupB)
GAIT ASIMMETRY (GA)
GA_individuale = zeros(height(TABreduced),1); % create new column
for ii = 1:height(TABreduced)
 swing_times = TABreduced.INDIP(ii).ContinuousWalkingPeriod.Swing_Duration;
 swing_side = TABreduced.INDIP(ii).ContinuousWalkingPeriod.InitialContact_LeftRight;
 % Ensure these are columns
 swing_times = swing_times(:);
 swing_side = swing_side(:);
 right_idx_swing = [];
 left_idx_swing = [];
 % Separate right and left swings
 for c = 1:length(swing_side)
 if swing_side(c) == "Right"
 right_idx_swing = [right_idx_swing c];
 else
 left_idx_swing = [left_idx_swing c];
 end
 end
 % Assumptions: C = right, D = left
 GA = []; % Initialize GA for this individual
 if length(right_idx_swing) && ~isempty(left_idx_swing)
 for i = 1:(length(right_idx_swing)-1)
 C1 = swing_times(right_idx_swing(i)); 
 C2 = swing_times(right_idx_swing(i+1));
 mid_L_idx_swing = find(swing_times(left_idx_swing) > C1 & swing_times(left_idx_swing) < C2);
 if ~isempty(mid_L_idx_swing) && all(left_idx_swing(mid_L_idx_swing) <= length(swing_times))
 D = swing_times(left_idx_swing(mid_L_idx_swing(1))); 
 end
 % Calculate GA for this pair (use log to calculate asymmetry)
 GA = 100 * log(mean(C1) / mean(D));
 end
 end
end
GA_individuale(ii) = GA; 
TABreduced.GA_individuale = GA_individuale;
 
HEALTHY
%% calcolo Healthy solo per TABreduced, prendendo i dati da 1 a 20 (tutti gli Healthy per il cammino rettilineo nella tabella ridotta)
for ii = 1:15
 lb_acc_Healthy10 = TABreduced.LBACK(ii).acc; lb_acc_Healthy10 = fillmissing(lb_acc_Healthy10,'spline'); lb_acc_Healthy10 = bwfilt(lb_acc_Healthy10,2,128,10,'low');
 lb_gyr_Healthy10 = TABreduced.LBACK(ii).gyro; lb_gyr_Healthy10 = fillmissing(lb_gyr_Healthy10,'spline'); lb_gyr_Healthy10 = bwfilt(lb_gyr_Healthy10,2,128,6,'low');
 % 
 % st_acc = TAB10.STERN(ii).acc; st_acc = fillmissing(st_acc,'spline'); st_acc = bwfilt(st_acc,2,128,10,'low');
 % st_gyr = TAB10.STERN(ii).gyro; st_gyr = fillmissing(st_gyr,'spline'); st_gyr = bwfilt(st_gyr,2,128,6,'low');
 % 
 % fh_acc = TAB10.FHEAD(ii).acc; fh_acc = fillmissing(fh_acc,'spline'); fh_acc = bwfilt(fh_acc,2,128,10,'low');
 % fh_gyr = TAB10.FHEAD(ii).gyro; fh_gyr = fillmissing(fh_gyr,'spline'); fh_gyr = bwfilt(fh_gyr,2,128,6,'low');
 % 
 contacts = TABreduced.CONTANCTS{ii};
 side = TABreduced.SIDE{ii};
 [r,c] = (find(isnan(contacts))); r = unique(r); contacts(r,:) = []; %TAB.CONTANCTS{ii} = contacts;
 side(:,r) = []; %TAB.SIDE{ii} = side;
 
 for co = 1:length(contacts)
% if
 end
end
DST (Double support Time) ->è un indice temporale che permette di discriminare la gravità del parkinson, non è un indice di variabilità motoria. pazienti con patologie neurologiche tipo parkinson hanno un DST piu lungo perche per paura di cadere rimangono piu tempo con i piedi a terra a differenza degli Healthy che sono piu sicuri durante la camminata
IL CoV di DST POTREBBE ESSERE CALCOLATO
% DefiniscO un threshold per rilevare i picchi nei segnali di accelerazione
threshold = 0.5;
% TrovO i picchi nel segnale di accelerazione
acc_z = lb_acc_Healthy10(:,3); % Prendo solo la componente Z
[peaks, locs] = findpeaks(abs((acc_z)), 'MinPeakHeight', threshold, 'MinPeakDistance', 200); 
% Aggiungo un ciclo per associare i picchi con i contatti
heel_strikes = {}; % Inizializzo una lista per i momenti di heel strike
% Associo i picchi ai momenti di contatto
for i = 1:length(ic_times)
 contact_time = ic_times(i); % Tempo del contatto
 side = ic_side(i); % Sinistro o destro
 % Trova i picchi che avvengono vicino al tempo del contatto (0.8s)
 %dal grafico abbiamo visto la distanza tra una linea tratteggiata, che rappresenta il contact time, el'altra
 idx = find(locs/128 >= contact_time - 1 & locs/128 <= contact_time + 1);
t = (1:length(acc_z)) / 128; % asse dei tempi
plot(t, acc_z); hold on;
plot(locs/128, acc_z(locs), 'rv'); % picchi
xline(contact_time, 'g--'); % tempo del contatto
legend('acc_z','picchi','contact time');
 if ~isempty(idx)
 for j = 1:length(idx)
 
 heel_strikes{end+1,1} = contact_time;
 heel_strikes{end, 2} = side; % stringa 'Right' o 'Left'
 heel_strikes{end, 3} = locs(idx(j))/128; % tempo del picco
 heel_strikes{end, 4} = peaks(idx(j));
 end
 end
end
%nel workspace, in heel_strikes troviamo: prima colonna è il contact time
%(quando il piede tocca terra), la seconda (il piede), la terza (istante in
%cui si rileva un picco, che corrisponde a quando uno dei due piedi tocca
%terra), la quarta rappresenta il valore del picco dell'accelerazione del
%piede che tocca terra, vedi foto gruppo
rs = []; % Right side
ls = []; % Left side
% Scorri tutte le righe di heel_strikes
for i = 1:size(heel_strikes, 1)
 side = heel_strikes{i, 2}; % 'Right' o 'Left'
 contact_time = heel_strikes{i, 1}; % tempo di contatto
 % Aggiungi il contact_time al vettore corrispondente
 if strcmp(side, 'Right')
 rs(end+1, 1) = contact_time;
 elseif strcmp(side, 'Left')
 ls(end+1, 1) = contact_time;
 end
end
% STEP 2: CALCOLO DST PER OGNI CICLO
numCycles = min(length(rs), length(ls));
DST = zeros(numCycles-1,1);
for k = 2:numCycles
 % sovrapposizione: da max dei due contatti precedenti a min dei due successivi
 DST(k-1) = min(rs(k), ls(k)) - max(rs(k-1), ls(k-1));
end
% STEP 3: AGGIUNTA ALLA TABELLA
TABreduced.DST{ii} = DST; % vettore DST per trial
TABreduced.MeanDST(ii) = mean(DST,'omitnan'); % media DST
TABreduced.CVDST(ii) = 100 * std(DST,'omitnan') / TABreduced.MeanDST(ii); % coeff. di variazione
PD
%% calcolo PD solo per TAB10
for ii = 16:30
 lb_acc_PD10 = TAB10.LBACK(ii).acc; lb_acc_PD10 = fillmissing(lb_acc_PD10,'spline'); lb_acc_PD10 = bwfilt(lb_acc_PD10,2,128,10,'low');
 lb_gyr_PD10 = TAB10.LBACK(ii).gyro; lb_gyr_PD10 = fillmissing(lb_gyr_PD10,'spline'); lb_gyr_PD10 = bwfilt(lb_gyr_PD10,2,128,6,'low');
 % st_acc = TAB10.STERN(ii).acc; st_acc = fillmissing(st_acc,'spline'); st_acc = bwfilt(st_acc,2,128,10,'low');
 % st_gyr = TAB10.STERN(ii).gyro; st_gyr = fillmissing(st_gyr,'spline'); st_gyr = bwfilt(st_gyr,2,128,6,'low');
 % 
 % fh_acc = TAB10.FHEAD(ii).acc; fh_acc = fillmissing(fh_acc,'spline'); fh_acc = bwfilt(fh_acc,2,128,10,'low');
 % fh_gyr = TAB10.FHEAD(ii).gyro; fh_gyr = fillmissing(fh_gyr,'spline'); fh_gyr = bwfilt(fh_gyr,2,128,6,'low');
 contacts = TAB10.CONTANCTS{ii};
 side = TAB10.SIDE{ii};
 [r,c] = (find(isnan(contacts))); r = unique(r); contacts(r,:) = []; %TAB.CONTANCTS{ii} = contacts;
 side(:,r) = []; %TAB.SIDE{ii} = side;
 for co = 1:length(contacts)
%if
 end 
end
 ST
for ii = 31:45
 lb_acc_ST10 = TAB10.LBACK(ii).acc; lb_acc_ST10 = fillmissing(lb_acc_ST10,'spline'); lb_acc_ST10 = bwfilt(lb_acc_ST10,2,128,10,'low');
 lb_gyr_ST10 = TAB10.LBACK(ii).gyro; lb_gyr_ST10 = fillmissing(lb_gyr_ST10,'spline'); lb_gyr_ST10 = bwfilt(lb_gyr_ST10,2,128,6,'low');
 % st_acc = TAB10.STERN(ii).acc; st_acc = fillmissing(st_acc,'spline'); st_acc = bwfilt(st_acc,2,128,10,'low');
 % st_gyr = TAB10.STERN(ii).gyro; st_gyr = fillmissing(st_gyr,'spline'); st_gyr = bwfilt(st_gyr,2,128,6,'low');
 % 
 % fh_acc = TAB10.FHEAD(ii).acc; fh_acc = fillmissing(fh_acc,'spline'); fh_acc = bwfilt(fh_acc,2,128,10,'low');
 % fh_gyr = TAB10.FHEAD(ii).gyro; fh_gyr = fillmissing(fh_gyr,'spline'); fh_gyr = bwfilt(fh_gyr,2,128,6,'low');
 contacts = TAB10.CONTANCTS{ii};
 side = TAB10.SIDE{ii};
 [r,c] = (find(isnan(contacts))); r = unique(r); contacts(r,:) = []; %TAB.CONTANCTS{ii} = contacts;
 side(:,r) = []; %TAB.SIDE{ii} = side;
 for co = 1:length(contacts)
%if
 
 end
end
% TBI
 %% For TBI (Traumatic Brain Injury)
 for ii = 46:60 
 lb_acc_TBI10 = TAB10.LBACK(ii).acc; lb_acc_TBI10 = fillmissing(lb_acc_TBI10,'spline'); lb_acc_TBI10 = bwfilt(lb_acc_TBI10,2,128,10,'low');
 lb_gyr_TBI10 = TAB10.LBACK(ii).gyro; lb_gyr_TBI10 = fillmissing(lb_gyr_TBI10,'spline'); lb_gyr_TBI10 = bwfilt(lb_gyr_TBI10,2,128,6,'low');
 % st_acc = TAB10.STERN(ii).acc; st_acc = fillmissing(st_acc,'spline'); st_acc = bwfilt(st_acc,2,128,10,'low');
 % st_gyr = TAB10.STERN(ii).gyro; st_gyr = fillmissing(st_gyr,'spline'); st_gyr = bwfilt(st_gyr,2,128,6,'low');
 % 
 % fh_acc = TAB10.FHEAD(ii).acc; fh_acc = fillmissing(fh_acc,'spline'); fh_acc = bwfilt(fh_acc,2,128,10,'low');
 % fh_gyr = TAB10.FHEAD(ii).gyro; fh_gyr = fillmissing(fh_gyr,'spline'); fh_gyr = bwfilt(fh_gyr,2,128,6,'low');
 contacts = TAB10.CONTANCTS{ii};
 side = TAB10.SIDE{ii};
 [r,c] = (find(isnan(contacts))); r = unique(r); contacts(r,:) = []; %TAB.CONTANCTS{ii} = contacts;
 side(:,r) = []; %TAB.SIDE{ii} = side;
 for co = 1:length(contacts)
%if
 end
 end
 
Estrae il segnale Z da ogni soggetto.
Riempie i dati mancanti e applica un filtro passa basso.
Rimuove il trend dal segnale.
Costruisce lo spazio delle fasi (embedding) con:
Dimensione m = 5
Ritardo τ = 10 (≈ 80 ms se fs = 128 Hz)
Trova per ogni punto il vicino più vicino non temporale.
Calcola la divergenza logaritmica nel tempo per 20 passi.
Usa una regressione lineare per stimare la pendenza = Lyapunov exponent.
 %% Calcolo dell’Esponente di Lyapunov per ogni soggetto in TABreduced
LyE_individuale = zeros(height(TABreduced), 1); % inizializzazione
for ii = 1:height(TABreduced)
 % Dati accelerometrici: scegli asse verticale (Z)
 acc_data = TABreduced.LBACK(ii).acc;
 
 if isempty(acc_data)
 LyE_individuale(ii) = NaN;
 continue;
 end
 acc_data = fillmissing(acc_data,'spline');
 acc_data = bwfilt(acc_data,2,128,10,'low'); % filtro passa basso
 signal = acc_data(:,3); % asse Z
 signal = detrend(signal); % rimuove trend lineare
 % Parametri per embedding
 m = 5; % dimensione dell'embedding
 tau = 10; % ritardo in punti (≈80ms con 128Hz)
 fs = 128; % frequenza di campionamento
 % Ricostruzione dello spazio delle fasi
 N = length(signal) - (m-1)*tau;
 X = zeros(N, m);
 for k = 1:m
 X(:,k) = signal(1+(k-1)*tau : N+(k-1)*tau);
 end
 % Calcolo delle distanze iniziali
 d0 = zeros(N,1);
 for k = 1:N
 % Trova punto vicino escludendo vicini temporali
 dist = vecnorm(X - X(k,:), 2, 2);
 dist(abs((1:N)' - k) < 30) = Inf; % escludi 30 campioni vicini
 [d0(k), j(k)] = min(dist);
 end
% Calcolo divergenza media nel tempo
 max_iter = 20; % numero di passi temporali
 divergence = zeros(max_iter,1);
 for n = 1:max_iter
 valid = (j+n <= N) & (1:N-n)';
 dist_n = vecnorm(X(valid + n,:) - X(j(valid) + n,:), 2, 2);
 divergence(n) = mean(log(dist_n ./ d0(valid)), 'omitnan');
 end
 % Regressione lineare per stimare la Lyapunov exponent
 t = (1:max_iter) / fs;
 p = polyfit(t, divergence(1:max_iter)', 1);
 LyE_individuale(ii) = p(1); % coefficiente angolare = esponente di Lyapunov
end
% Aggiungo alla tabella
TABreduced.LyE = LyE_individuale;
% Visualizzazione
figure;
boxplot(LyE_individuale, gruppi);
ylabel('Lyapunov Exponent');
title('LyE per Gruppo (Healthy vs Patologici)');
% ANOVA
[p_lye, tbl_lye, stats_lye] = anova1(LyE_individuale, gruppi);
[c_lye,gnames_lye] = multcompare(stats_lye);
toolic
14.7k5 gold badges29 silver badges204 bronze badges
asked May 8 at 9:42
\$\endgroup\$
0

1 Answer 1

3
\$\begingroup\$

use functions

make sure it's clean ...

The Review Context introduces this as a single matlab "script", and that's certainly accurate. It would be much nicer if it was organized as, say, a 4-line script,

  • load()
  • clean()
  • analyze()
  • display()

with the meat of it buried in helper functions.

As written, the OP code does not lend itself to interactive debugging, nor to automated unit tests. In fact, it appears that we seldom run it all the way through, since we see lines like "GAIT ASIMMETRY (GA)" and "HEALTHY" that don't correspond to valid matlab syntax.

There's nearly 700 lines of source code presented. That's too much to place in just a single unit. Define helper functions to impose some structure.

helpful comments

There's already a bunch of very nice comments calling out different processing sections. Use them to invent new function names for those sections.

coupling

Putting all identifiers at top level, in matlab or in any other language, leads to excessive coupling, with an unwieldy number of globals in scope. No one will be able to remember the details of all of those, such as whether t currently has one meaning or if at this point in the code it has been redefined to have a different meaning.

The great thing about functions, especially "short" functions, is that temp vars go out of scope upon return. And typically there's just a single return value to worry about and reason about.

long lines

This is a very nice comment. But by putting it all on a single line, requiring lots of horizontal scrolling, you're essentially telling folks "don't read this!".

DST (Double support Time) ->è un indice temporale che permette di discriminare la gravità del parkinson, non è un indice di variabilità motoria. pazienti con patologie neurologiche tipo parkinson hanno un DST piu lungo perche per paura di cadere rimangono piu tempo con i piedi a terra a differenza degli Healthy che sono piu sicuri durante la camminata

Prefer to wrap it, as seen in the subsequent "%nel workspace .. foto gruppo" comment.

Also, many of your comments oddly omit "%". This seems to suggest that large portions of the script are seldom tested / executed.

commented code

 lb_gyr_Healthy10 = TABreduced.LBACK(ii).gyro; lb_gyr_Healthy10 = fillmissing(lb_gyr_Healthy10,'spline'); lb_gyr_Healthy10 = bwfilt(lb_gyr_Healthy10,2,128,6,'low');
 % 
 % st_acc = TAB10.STERN(ii).acc; st_acc = fillmissing(st_acc,'spline'); st_acc = bwfilt(st_acc,2,128,10,'low');
 % st_gyr = TAB10.STERN(ii).gyro; st_gyr = fillmissing(st_gyr,'spline'); st_gyr = bwfilt(st_gyr,2,128,6,'low');
 % 
 % fh_acc = TAB10.FHEAD(ii).acc; fh_acc = fillmissing(fh_acc,'spline'); fh_acc = bwfilt(fh_acc,2,128,10,'low');
 % fh_gyr = TAB10.FHEAD(ii).gyro; fh_gyr = fillmissing(fh_gyr,'spline'); fh_gyr = bwfilt(fh_gyr,2,128,6,'low');

Take care to remove such statements before requesting review.

% if

I imagine you sometimes write if false in order to prevent a section of code from executing. This is a symptom, a code smell. The code is telling you that it really wants to be broken up into smaller functions, so you may more easily work with it.

Rather than putting adjacent end statements at the identical indent level, prefer to use indenting to reveal how deeply nested a block of code is.

copy-n-paste

 spa = [];
 for jj = 1:size(mwb,2)
 temp = mwb(jj).Stance_Duration;
 spa = [spa temp];
 end
 TAB.STANCE_DURATION(ii) = {(spa)};
 spa = [];
 for jj = 1:size(mwb,2)
 temp = mwb(jj).Stance_Length;
 spa = [spa temp];
 end
 TAB.STANCE_LENGTH(ii) = {(spa)};
 spa = [];
 for jj = 1:size(mwb,2)
 temp = mwb(jj).Stance_Speed;
 spa = [spa temp];
 end
 TAB.STANCE_SPEED(ii) = {(spa)};

When you see yourself repeatedly pasting the "same" code into the editor, and then tweaking it a bit, stop. Take a step back. See if you can generalize what's happening here, and capture that in a suitably parameterized helper function.

answered May 8 at 16:34
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.