Tutorial 3: Basic Visualization¶
Overview¶
Learn to create essential biomechanical visualizations: phase averages with standard deviation bands, spaghetti plots showing all cycles, and publication-ready figures.
Learning Objectives¶
- Compute and plot phase averages
- Add standard deviation shading
- Create spaghetti plots
- Customize plots for publication
- Compare multiple conditions
Setup¶
% Add library to path
addpath('user_libs/matlab');
% Load and filter data
loco = LocomotionData('converted_datasets/umich_2021_phase.parquet');
levelWalking = loco.filterTask('level_walking').filterSubject('SUB01');
% Set publication style
loco.setPublicationStyle('biomechanics');
% Load data directly
data = parquetread('converted_datasets/umich_2021_phase.parquet');
% Filter for level walking, SUB01
levelWalking = data(strcmp(data.task, 'level_walking') & ...
strcmp(data.subject, 'SUB01'), :);
% Set plotting defaults
set(groot, 'DefaultAxesFontSize', 12);
set(groot, 'DefaultLineLineWidth', 1.5);
Computing Phase Averages¶
Basic Phase Average¶
% Compute mean patterns using library method
meanPatterns = levelWalking.getMeanPatterns('SUB01', 'level_walking');
stdPatterns = levelWalking.getStdPatterns('SUB01', 'level_walking');
% Get knee flexion data
kneeMean = meanPatterns.knee_flexion_angle_ipsi_rad;
kneeStd = stdPatterns.knee_flexion_angle_ipsi_rad;
% Convert to degrees for plotting
kneeMeanDeg = rad2deg(kneeMean);
kneeStdDeg = rad2deg(kneeStd);
function [meanCurve, stdCurve] = computePhaseAverage(data, variable)
% Compute mean and std across cycles for each phase point
% Get unique phase percentages
phases = unique(data.phase_percent);
meanCurve = zeros(length(phases), 1);
stdCurve = zeros(length(phases), 1);
for i = 1:length(phases)
phaseData = data.(variable)(data.phase_percent == phases(i));
meanCurve(i) = mean(phaseData, 'omitnan');
stdCurve(i) = std(phaseData, 'omitnan');
end
end
% Compute average knee flexion
[kneeMean, kneeStd] = computePhaseAverage(levelWalking, 'knee_flexion_angle_ipsi_rad');
% Convert to degrees for plotting
kneeMeanDeg = rad2deg(kneeMean);
kneeStdDeg = rad2deg(kneeStd);
Using Built-in Methods¶
% Using LocomotionData methods
meanPatterns = levelWalking.getMeanPatterns('SUB01', 'level_walking');
% Get specific variables
kneeMean = meanPatterns.knee_flexion_angle_ipsi_rad;
hipMean = meanPatterns.hip_flexion_angle_ipsi_rad;
ankleMean = meanPatterns.ankle_flexion_angle_ipsi_rad;
% Get summary statistics
summary = levelWalking.getSummaryStatistics('SUB01', 'level_walking', ...
{'knee_flexion_angle_ipsi_rad'});
% Manual computation with groupsummary
meanPatterns = struct();
variables = {'knee_flexion_angle_ipsi_rad', 'hip_flexion_angle_ipsi_rad', ...
'ankle_flexion_angle_ipsi_rad'};
for i = 1:length(variables)
var = variables{i};
if any(strcmp(levelWalking.Properties.VariableNames, var))
meanData = groupsummary(levelWalking, 'phase_percent', 'mean', var);
meanPatterns.(var) = meanData.(['mean_' var]);
end
end
Basic Plotting¶
Simple Line Plot¶
% Plot using built-in method
figure();
loco.plotPhasePatterns('SUB01', 'level_walking', ...
{'knee_flexion_angle_ipsi_rad'}, ...
'Units', 'degrees');
% Manual plotting
phase = 0:100/149:100; % 150 points per cycle
figure();
plot(phase, kneeMeanDeg, 'b-', 'LineWidth', 2);
xlabel('Gait Cycle (%)');
ylabel('Knee Flexion Angle (deg)');
title('Mean Knee Flexion - Level Walking');
grid on;
xlim([0 100]);
Adding Standard Deviation Bands¶
% Plot with standard deviation bands
figure();
loco.plotPhasePatterns('SUB01', 'level_walking', ...
{'knee_flexion_angle_ipsi_rad'}, ...
'Units', 'degrees', 'ShowStd', true);
% Manual plotting with std bands
phase = 0:100/149:100;
figure();
hold on;
% Plot standard deviation band
upper = kneeMeanDeg + kneeStdDeg;
lower = kneeMeanDeg - kneeStdDeg;
fill([phase, fliplr(phase)], [upper', fliplr(lower')], ...
[0.7 0.7 1], 'FaceAlpha', 0.3, 'EdgeColor', 'none');
% Plot mean line
plot(phase, kneeMeanDeg, 'b-', 'LineWidth', 2);
xlabel('Gait Cycle (%)');
ylabel('Knee Flexion Angle (deg)');
title('Mean ± SD Knee Flexion - Level Walking');
grid on;
xlim([0 100]);
legend('±1 SD', 'Mean', 'Location', 'best');
Spaghetti Plots¶
All Individual Cycles¶
% Create spaghetti plot using library method
figure();
loco.plotSpaghettiPlot('SUB01', 'level_walking', ...
{'knee_flexion_angle_ipsi_rad'}, ...
'Units', 'degrees', 'ShowMean', true);
% Manual spaghetti plot
[data3D, featureNames] = levelWalking.getCycles('SUB01', 'level_walking', ...
{'knee_flexion_angle_ipsi_rad'});
kneeData = squeeze(data3D(:, :, 1)); % Get knee data
kneeDataDeg = rad2deg(kneeData); % Convert to degrees
phase = 0:100/149:100;
figure();
hold on;
% Plot all cycles with transparency
for cycle = 1:size(kneeDataDeg, 1)
if ~any(isnan(kneeDataDeg(cycle, :)))
plot(phase, kneeDataDeg(cycle, :), 'Color', [0.7 0.7 0.7 0.3]);
end
end
% Overlay mean
meanPattern = mean(kneeDataDeg, 1, 'omitnan');
plot(phase, meanPattern, 'b-', 'LineWidth', 3);
xlabel('Gait Cycle (%)');
ylabel('Knee Flexion Angle (deg)');
title('Individual Cycles - Knee Flexion');
grid on;
xlim([0 100]);
Customizing Spaghetti Plots¶
% Customized spaghetti plot
figure();
loco.plotSpaghettiPlot('SUB01', 'level_walking', ...
{'knee_flexion_angle_ipsi_rad'}, ...
'Units', 'degrees', ...
'Alpha', 0.2, ...
'Color', [0.8 0.2 0.2], ...
'ShowMean', true, ...
'ShowStd', true);
% Manual customized plotting
figure();
hold on;
% Plot cycles with custom color and transparency
for cycle = 1:size(kneeDataDeg, 1)
if ~any(isnan(kneeDataDeg(cycle, :)))
plot(phase, kneeDataDeg(cycle, :), 'Color', [0.8 0.2 0.2 0.2], ...
'LineWidth', 0.5);
end
end
% Add mean and std
meanPattern = mean(kneeDataDeg, 1, 'omitnan');
stdPattern = std(kneeDataDeg, 1, 'omitnan');
% Std band
upper = meanPattern + stdPattern;
lower = meanPattern - stdPattern;
fill([phase, fliplr(phase)], [upper, fliplr(lower)], ...
[0.8 0.2 0.2], 'FaceAlpha', 0.2, 'EdgeColor', 'none');
% Mean line
plot(phase, meanPattern, 'Color', [0.8 0.2 0.2], 'LineWidth', 3);
xlabel('Gait Cycle (%)');
ylabel('Knee Flexion Angle (deg)');
title('Individual Cycles with Mean ± SD');
grid on;
xlim([0 100]);
Multi-Variable Plots¶
Multiple Features in Subplots¶
% Plot multiple features
features = {'knee_flexion_angle_ipsi_rad', 'hip_flexion_angle_ipsi_rad', ...
'ankle_flexion_angle_ipsi_rad'};
figure();
loco.plotPhasePatterns('SUB01', 'level_walking', features, ...
'Units', 'degrees', 'ShowStd', true);
% Manual multi-feature plotting
features = {'knee_flexion_angle_ipsi_rad', 'hip_flexion_angle_ipsi_rad', ...
'ankle_flexion_angle_ipsi_rad'};
titles = {'Knee Flexion', 'Hip Flexion', 'Ankle Flexion'};
figure('Position', [100 100 1200 400]);
for i = 1:length(features)
[meanCurve, stdCurve] = computePhaseAverage(levelWalking, features{i});
meanDeg = rad2deg(meanCurve);
stdDeg = rad2deg(stdCurve);
subplot(1, 3, i);
hold on;
% Std band
upper = meanDeg + stdDeg;
lower = meanDeg - stdDeg;
fill([phase, fliplr(phase)], [upper', fliplr(lower')], ...
[0.7 0.7 1], 'FaceAlpha', 0.3, 'EdgeColor', 'none');
% Mean line
plot(phase, meanDeg, 'b-', 'LineWidth', 2);
xlabel('Gait Cycle (%)');
ylabel('Angle (deg)');
title(titles{i});
grid on;
xlim([0 100]);
end
sgtitle('Joint Angles - Level Walking');
Task Comparisons¶
Multiple Tasks¶
% Compare different tasks
tasks = {'level_walking', 'incline_walking', 'decline_walking'};
figure();
loco.plotTaskComparison('SUB01', tasks, {'knee_flexion_angle_ipsi_rad'}, ...
'Units', 'degrees');
% Manual task comparison
tasks = {'level_walking', 'incline_walking', 'decline_walking'};
colors = {[0 0.4 0.8], [0.8 0.4 0], [0.8 0 0.4]};
figure();
hold on;
for i = 1:length(tasks)
taskData = data(strcmp(data.task, tasks{i}) & ...
strcmp(data.subject, 'SUB01'), :);
if ~isempty(taskData)
[meanCurve, ~] = computePhaseAverage(taskData, 'knee_flexion_angle_ipsi_rad');
meanDeg = rad2deg(meanCurve);
plot(phase, meanDeg, 'Color', colors{i}, 'LineWidth', 2, ...
'DisplayName', strrep(tasks{i}, '_', ' '));
end
end
xlabel('Gait Cycle (%)');
ylabel('Knee Flexion Angle (deg)');
title('Task Comparison - Knee Flexion');
legend('show');
grid on;
xlim([0 100]);
Publication-Ready Figures¶
Setting Publication Style¶
% Set journal-specific style
loco.setPublicationStyle('biomechanics'); % or 'nature', 'ieee'
% Create publication figure
figure('Position', [100 100 800 600]);
loco.plotPhasePatterns('SUB01', 'level_walking', ...
{'knee_flexion_angle_ipsi_rad'}, ...
'Units', 'degrees', 'ShowStd', true);
% Save high-resolution figure
print('knee_flexion_publication', '-dpng', '-r300');
% Manual publication styling
set(groot, 'DefaultAxesFontSize', 12);
set(groot, 'DefaultAxesFontName', 'Arial');
set(groot, 'DefaultTextFontSize', 12);
set(groot, 'DefaultLineLineWidth', 1.5);
set(groot, 'DefaultAxesBox', 'off');
% Create figure with specific size
figure('Position', [100 100 800 600], 'Color', 'white');
% Plot with publication formatting
hold on;
fill([phase, fliplr(phase)], [upper', fliplr(lower')], ...
[0.7 0.7 1], 'FaceAlpha', 0.3, 'EdgeColor', 'none');
plot(phase, kneeMeanDeg, 'b-', 'LineWidth', 2);
xlabel('Gait Cycle (%)', 'FontSize', 14, 'FontName', 'Arial');
ylabel('Knee Flexion Angle (deg)', 'FontSize', 14, 'FontName', 'Arial');
title('Mean Knee Flexion - Level Walking', 'FontSize', 16, 'FontName', 'Arial');
grid on;
xlim([0 100]);
% Save high-resolution
print('knee_flexion_publication', '-dpng', '-r300');
print('knee_flexion_publication', '-depsc', '-r300'); % Vector format
Advanced Visualization¶
Multi-Panel Figures¶
% Create multi-panel comparison
subjects = {'SUB01', 'SUB02', 'SUB03'};
figure('Position', [100 100 1200 800]);
for i = 1:length(subjects)
subplot(2, 2, i);
subjectData = loco.filterSubject(subjects{i});
subjectData.plotPhasePatterns(subjects{i}, 'level_walking', ...
{'knee_flexion_angle_ipsi_rad'}, ...
'Units', 'degrees', 'ShowStd', true);
title(subjects{i});
end
% Add overall title
subplot(2, 2, 4);
% Summary plot or text
text(0.5, 0.5, 'Summary Statistics', 'HorizontalAlignment', 'center');
sgtitle('Multi-Subject Comparison');
% Manual multi-panel figure
subjects = {'SUB01', 'SUB02', 'SUB03'};
figure('Position', [100 100 1200 800]);
for i = 1:length(subjects)
subjectData = data(strcmp(data.subject, subjects{i}) & ...
strcmp(data.task, 'level_walking'), :);
if ~isempty(subjectData)
[meanCurve, stdCurve] = computePhaseAverage(subjectData, ...
'knee_flexion_angle_ipsi_rad');
meanDeg = rad2deg(meanCurve);
stdDeg = rad2deg(stdCurve);
subplot(2, 2, i);
hold on;
upper = meanDeg + stdDeg;
lower = meanDeg - stdDeg;
fill([phase, fliplr(phase)], [upper', fliplr(lower')], ...
[0.7 0.7 1], 'FaceAlpha', 0.3, 'EdgeColor', 'none');
plot(phase, meanDeg, 'b-', 'LineWidth', 2);
xlabel('Gait Cycle (%)');
ylabel('Knee Flexion (deg)');
title(subjects{i});
grid on;
xlim([0 100]);
end
end
sgtitle('Multi-Subject Comparison - Knee Flexion');
Best Practices¶
Data Quality Checks¶
% Check data completeness
[data3D, ~] = levelWalking.getCycles('SUB01', 'level_walking', ...
{'knee_flexion_angle_ipsi_rad'});
% Count valid cycles
validCycles = sum(~any(isnan(data3D(:, :, 1)), 2));
totalCycles = size(data3D, 1);
fprintf('Valid cycles: %d/%d (%.1f%%)\n', validCycles, totalCycles, ...
100 * validCycles / totalCycles);
Unit Conversions¶
% Always check and convert units for display
features = {'knee_flexion_angle_ipsi_rad', 'knee_flexion_velocity_ipsi_rad_s'};
for i = 1:length(features)
if contains(features{i}, 'angle')
fprintf('%s: radians → degrees\n', features{i});
elseif contains(features{i}, 'velocity')
fprintf('%s: rad/s → deg/s\n', features{i});
end
end
Saving Figures¶
% Save in multiple formats for different uses
figName = 'knee_flexion_analysis';
% High-resolution PNG for presentations
print(figName, '-dpng', '-r300');
% Vector format for publications
print(figName, '-depsc', '-r300');
% MATLAB figure for further editing
savefig([figName '.fig']);
Summary¶
You've learned to create essential biomechanical visualizations in MATLAB:
- Phase averages with mean and standard deviation patterns
- Spaghetti plots showing individual cycle variability
- Multi-variable comparisons across features and conditions
- Publication-ready formatting with appropriate styles
- Quality checks and unit conversions for reliable results
Next Steps¶
Continue to Tutorial 4: Cycle Analysis →
Learn to analyze individual gait cycles, calculate ROM and peaks, and identify patterns in your data.