Skip to content

Tutorial 6: Publication Outputs

Overview

Learn to create publication-ready figures, tables, and reproducible analysis reports from your biomechanical data.

Learning Objectives

  • Create multi-panel figures with consistent formatting
  • Generate publication-ready tables with statistics
  • Apply journal-specific formatting requirements
  • Ensure reproducibility of analyses
  • Prepare data for sharing and archiving

Setup

% Add library to path
addpath('user_libs/matlab');

% Load data
loco = LocomotionData('converted_datasets/umich_2021_phase.parquet');

% Set publication style
loco.setPublicationStyle('biomechanics');
% Add helper functions to path
addpath('user_libs/matlab');

% Load data
data = parquetread('converted_datasets/umich_2021_phase.parquet');

% Set publication style manually
set(groot, 'DefaultAxesFontSize', 12);
set(groot, 'DefaultAxesFontName', 'Arial');
set(groot, 'DefaultLineLineWidth', 1.5);

Multi-Panel Figures

Creating Figure Layouts

% Create a comprehensive multi-panel figure
subjects = {'SUB01', 'SUB02', 'SUB03'};
tasks = {'level_walking', 'incline_walking', 'decline_walking'};
features = {'knee_flexion_angle_ipsi_rad', 'hip_flexion_angle_ipsi_rad'};

% Create figure with specific dimensions for journal
fig = figure('Position', [100 100 1200 900], 'Color', 'white');
fig.PaperUnits = 'inches';
fig.PaperPosition = [0 0 12 9];

% Set up subplot grid (3 subjects × 2 features)
nRows = length(subjects);
nCols = length(features);
phase = 0:100/149:100;

for s = 1:length(subjects)
    subject = subjects{s};

    for f = 1:length(features)
        feature = features{f};

        subplot(nRows, nCols, (s-1)*nCols + f);
        hold on;

        % Plot each task with different colors
        colors = {[0 0.4 0.8], [0.8 0.4 0], [0.8 0 0.4]};

        for t = 1:length(tasks)
            task = tasks{t};
            taskData = loco.filterSubject(subject).filterTask(task);

            if taskData.length() > 0
                % Get mean pattern
                meanPatterns = taskData.getMeanPatterns(subject, task);
                stdPatterns = taskData.getStdPatterns(subject, task);

                if isfield(meanPatterns, feature)
                    meanCurve = meanPatterns.(feature);
                    stdCurve = stdPatterns.(feature);

                    % Convert to degrees if angle
                    if contains(feature, 'angle')
                        meanCurve = rad2deg(meanCurve);
                        stdCurve = rad2deg(stdCurve);
                        yLabel = 'Angle (deg)';
                    else
                        yLabel = strrep(feature, '_', ' ');
                    end

                    % Plot with confidence band
                    fill([phase, fliplr(phase)], ...
                         [meanCurve + stdCurve, fliplr(meanCurve - stdCurve)]', ...
                         colors{t}, 'FaceAlpha', 0.2, 'EdgeColor', 'none');

                    plot(phase, meanCurve, 'Color', colors{t}, 'LineWidth', 2, ...
                         'DisplayName', strrep(task, '_', ' '));
                end
            end
        end

        % Format subplot
        xlabel('Gait Cycle (%)');
        ylabel(yLabel);

        if s == 1 && f == 1
            legend('Location', 'best', 'FontSize', 8);
        end

        % Add subject label and feature title
        if f == 1
            title(sprintf('%s - %s', subject, strrep(feature, '_', ' ')), ...
                  'FontSize', 10, 'FontWeight', 'bold');
        else
            title(strrep(feature, '_', ' '), 'FontSize', 10, 'FontWeight', 'bold');
        end

        grid on;
        xlim([0 100]);

        % Set consistent y-axis limits for comparison
        if contains(feature, 'knee')
            ylim([-10 70]);
        elseif contains(feature, 'hip')
            ylim([-20 40]);
        end
    end
end

% Add overall title
sgtitle('Joint Kinematics Across Walking Conditions', ...
        'FontSize', 14, 'FontWeight', 'bold');

% Save in multiple formats
print(fig, 'joint_kinematics_comparison', '-dpng', '-r300');
print(fig, 'joint_kinematics_comparison', '-depsc', '-r300');
savefig(fig, 'joint_kinematics_comparison.fig');

Statistical Summary Figures

% Create a figure showing group statistics
allSubjects = loco.getSubjects();
task = 'level_walking';
feature = 'knee_flexion_angle_ipsi_rad';

% Collect ROM data across subjects
romData = [];
subjectLabels = {};

for i = 1:min(10, length(allSubjects))  % Limit to first 10 subjects
    subject = allSubjects{i};
    subjectData = loco.filterSubject(subject).filterTask(task);

    if subjectData.length() > 0
        romResult = subjectData.calculateROM(subject, task, {feature}, true);

        if isfield(romResult, feature)
            romData(end+1) = rad2deg(mean(romResult.(feature), 'omitnan'));
            subjectLabels{end+1} = subject;
        end
    end
end

% Create publication-quality bar plot
fig = figure('Position', [100 100 800 600], 'Color', 'white');

% Bar plot with error bars
meanROM = mean(romData);
stdROM = std(romData);
semROM = stdROM / sqrt(length(romData));

bar(1, meanROM, 'FaceColor', [0.6 0.6 0.8], 'EdgeColor', 'black');
hold on;
errorbar(1, meanROM, semROM, 'k-', 'LineWidth', 2, 'CapSize', 10);

% Add individual data points
scatter(ones(size(romData)) + 0.1*randn(size(romData)), romData, ...
        50, 'filled', 'MarkerFaceColor', [0.2 0.2 0.8], ...
        'MarkerFaceAlpha', 0.6, 'MarkerEdgeColor', 'black');

% Format
xlim([0.5 1.5]);
set(gca, 'XTick', 1, 'XTickLabel', {'Knee Flexion ROM'});
ylabel('Range of Motion (deg)', 'FontSize', 12);
title(sprintf('Knee Flexion ROM - %s (n=%d)', strrep(task, '_', ' '), length(romData)), ...
      'FontSize', 14, 'FontWeight', 'bold');

% Add statistics text
text(1, max(romData)*0.9, sprintf('Mean ± SEM\n%.1f ± %.1f°', meanROM, semROM), ...
     'HorizontalAlignment', 'center', 'FontSize', 10, ...
     'BackgroundColor', 'white', 'EdgeColor', 'black');

grid on;

% Save figure
print(fig, 'knee_rom_statistics', '-dpng', '-r300');
print(fig, 'knee_rom_statistics', '-depsc', '-r300');

Publication Tables

Demographic and Clinical Characteristics

% Create demographic table (example with mock data)
fprintf('Creating demographic table...\n');

% Mock demographic data structure
demographics = struct();
demographics.subjects = allSubjects(1:min(20, length(allSubjects)));
demographics.age = 25 + 30*rand(length(demographics.subjects), 1);  % Age 25-55
demographics.height = 1.6 + 0.3*rand(length(demographics.subjects), 1);  % Height 1.6-1.9m
demographics.mass = 60 + 30*rand(length(demographics.subjects), 1);  % Mass 60-90kg
demographics.sex = repmat({'M'; 'F'}, ceil(length(demographics.subjects)/2), 1);
demographics.sex = demographics.sex(1:length(demographics.subjects));

% Create and save demographic table
demoTable = table(demographics.subjects, demographics.age, demographics.height, ...
                  demographics.mass, demographics.sex, ...
                  'VariableNames', {'Subject', 'Age_years', 'Height_m', 'Mass_kg', 'Sex'});

% Calculate summary statistics
fprintf('\nDemographic Characteristics (n=%d):\n', height(demoTable));
fprintf('=========================================\n');
fprintf('Age: %.1f ± %.1f years (range: %.1f - %.1f)\n', ...
        mean(demoTable.Age_years), std(demoTable.Age_years), ...
        min(demoTable.Age_years), max(demoTable.Age_years));
fprintf('Height: %.2f ± %.2f m (range: %.2f - %.2f)\n', ...
        mean(demoTable.Height_m), std(demoTable.Height_m), ...
        min(demoTable.Height_m), max(demoTable.Height_m));
fprintf('Mass: %.1f ± %.1f kg (range: %.1f - %.1f)\n', ...
        mean(demoTable.Mass_kg), std(demoTable.Mass_kg), ...
        min(demoTable.Mass_kg), max(demoTable.Mass_kg));
fprintf('Sex: %d Male, %d Female\n', ...
        sum(strcmp(demoTable.Sex, 'M')), sum(strcmp(demoTable.Sex, 'F')));

% Export to CSV for manuscript
writetable(demoTable, 'demographic_table.csv');

Biomechanical Results Table

% Create comprehensive results table
tasks = {'level_walking', 'incline_walking', 'decline_walking'};
features = {'knee_flexion_angle_ipsi_rad', 'hip_flexion_angle_ipsi_rad', 'ankle_flexion_angle_ipsi_rad'};

% Initialize results structure
results = struct();
results.Task = {};
results.Variable = {};
results.Mean = [];
results.StdDev = [];
results.Min = [];
results.Max = [];
results.SampleSize = [];

rowCount = 0;

for t = 1:length(tasks)
    task = tasks{t};

    for f = 1:length(features)
        feature = features{f};

        % Collect data across subjects
        subjectValues = [];

        for s = 1:min(10, length(allSubjects))  % Limit subjects for demo
            subject = allSubjects{s};
            subjectData = loco.filterSubject(subject).filterTask(task);

            if subjectData.length() > 0
                romResult = subjectData.calculateROM(subject, task, {feature}, true);

                if isfield(romResult, feature)
                    subjectValues(end+1) = mean(romResult.(feature), 'omitnan');
                end
            end
        end

        if ~isempty(subjectValues)
            rowCount = rowCount + 1;

            % Convert to degrees if angle
            if contains(feature, 'angle')
                subjectValues = rad2deg(subjectValues);
                units = '(deg)';
            else
                units = '';
            end

            results.Task{rowCount} = strrep(task, '_', ' ');
            results.Variable{rowCount} = [strrep(feature, '_', ' '), ' ROM ', units];
            results.Mean(rowCount) = mean(subjectValues);
            results.StdDev(rowCount) = std(subjectValues);
            results.Min(rowCount) = min(subjectValues);
            results.Max(rowCount) = max(subjectValues);
            results.SampleSize(rowCount) = length(subjectValues);
        end
    end
end

% Create table
resultsTable = struct2table(results);

% Display formatted table
fprintf('\nBiomechanical Results Summary:\n');
fprintf('==============================\n');
for i = 1:height(resultsTable)
    fprintf('%-15s %-30s %6.1f ± %4.1f (%4.1f - %4.1f) [n=%d]\n', ...
            resultsTable.Task{i}, resultsTable.Variable{i}, ...
            resultsTable.Mean(i), resultsTable.StdDev(i), ...
            resultsTable.Min(i), resultsTable.Max(i), ...
            resultsTable.SampleSize(i));
end

% Export to CSV
writetable(resultsTable, 'biomechanical_results_table.csv');

% Create formatted table for LaTeX
fid = fopen('results_table_latex.txt', 'w');
fprintf(fid, '\\begin{table}[ht]\n');
fprintf(fid, '\\centering\n');
fprintf(fid, '\\caption{Biomechanical Results Summary}\n');
fprintf(fid, '\\begin{tabular}{lllc}\n');
fprintf(fid, '\\hline\n');
fprintf(fid, 'Task & Variable & Mean ± SD & n \\\\\n');
fprintf(fid, '\\hline\n');

for i = 1:height(resultsTable)
    fprintf(fid, '%s & %s & %.1f ± %.1f & %d \\\\\n', ...
            resultsTable.Task{i}, resultsTable.Variable{i}, ...
            resultsTable.Mean(i), resultsTable.StdDev(i), ...
            resultsTable.SampleSize(i));
end

fprintf(fid, '\\hline\n');
fprintf(fid, '\\end{tabular}\n');
fprintf(fid, '\\end{table}\n');
fclose(fid);

fprintf('\nLaTeX table saved to: results_table_latex.txt\n');

Data Archiving and Sharing

Preparing Supplementary Data

% Create supplementary data package for publication
fprintf('Creating supplementary data package...\n');

% Create directory structure
if ~exist('supplementary_data', 'dir')
    mkdir('supplementary_data');
end

% 1. Subject-level mean patterns
mkdir('supplementary_data/subject_mean_patterns');

task = 'level_walking';
features = {'knee_flexion_angle_ipsi_rad', 'hip_flexion_angle_ipsi_rad'};

for s = 1:min(5, length(allSubjects))  % Limit for demo
    subject = allSubjects{s};
    subjectData = loco.filterSubject(subject).filterTask(task);

    if subjectData.length() > 0
        meanPatterns = subjectData.getMeanPatterns(subject, task);

        % Create table with phase and mean patterns
        phase = (0:100/149:100)';
        subjectTable = table(phase, 'VariableNames', {'phase_percent'});

        for f = 1:length(features)
            feature = features{f};
            if isfield(meanPatterns, feature)
                % Convert angles to degrees
                if contains(feature, 'angle')
                    values = rad2deg(meanPatterns.(feature));
                    colName = strrep(feature, 'rad', 'deg');
                else
                    values = meanPatterns.(feature);
                    colName = feature;
                end

                subjectTable.(colName) = values;
            end
        end

        % Save subject data
        filename = sprintf('supplementary_data/subject_mean_patterns/%s_%s.csv', ...
                          subject, task);
        writetable(subjectTable, filename);
    end
end

% 2. Group summary statistics
groupSummary = struct();
groupSummary.task = task;
groupSummary.n_subjects = subjectCount;
groupSummary.features = features;

% Save group summary
save('supplementary_data/group_summary.mat', 'groupSummary');

% 3. Analysis parameters
analysisParams = struct();
analysisParams.data_file = 'converted_datasets/umich_2021_phase.parquet';
analysisParams.analysis_date = datestr(now);
analysisParams.matlab_version = version;
analysisParams.points_per_cycle = 150;
analysisParams.tasks_analyzed = tasks;
analysisParams.features_analyzed = features;

% Save parameters
save('supplementary_data/analysis_parameters.mat', 'analysisParams');

% Create README file
fid = fopen('supplementary_data/README.txt', 'w');
fprintf(fid, 'Supplementary Data Package\n');
fprintf(fid, '==========================\n\n');
fprintf(fid, 'Analysis Date: %s\n', datestr(now));
fprintf(fid, 'MATLAB Version: %s\n\n', version);
fprintf(fid, 'Contents:\n');
fprintf(fid, '---------\n');
fprintf(fid, 'subject_mean_patterns/ : Individual subject mean patterns (CSV format)\n');
fprintf(fid, 'group_summary.mat      : Group-level summary statistics\n');
fprintf(fid, 'analysis_parameters.mat: Analysis parameters and settings\n');
fprintf(fid, 'README.txt            : This file\n\n');
fprintf(fid, 'Data Format:\n');
fprintf(fid, '------------\n');
fprintf(fid, 'Phase data normalized to 150 points per gait cycle (0-100%%)\n');
fprintf(fid, 'Angular data converted from radians to degrees\n');
fprintf(fid, 'Missing values represented as NaN\n\n');
fprintf(fid, 'Contact: [Author email]\n');
fclose(fid);

fprintf('Supplementary data package created in: supplementary_data/\n');

Journal-Specific Formatting

Apply Journal Styles

% Function to apply different journal styles
function applyJournalStyle(journalName)
    switch lower(journalName)
        case 'jbiomech'
            % Journal of Biomechanics style
            set(groot, 'DefaultAxesFontSize', 12);
            set(groot, 'DefaultAxesFontName', 'Arial');
            set(groot, 'DefaultTextFontSize', 12);
            set(groot, 'DefaultLineLineWidth', 1.5);
            set(groot, 'DefaultFigureColor', 'white');

        case 'gaitposture'
            % Gait & Posture style
            set(groot, 'DefaultAxesFontSize', 10);
            set(groot, 'DefaultAxesFontName', 'Times');
            set(groot, 'DefaultTextFontSize', 10);
            set(groot, 'DefaultLineLineWidth', 1.2);

        case 'ieee'
            % IEEE Transactions style
            set(groot, 'DefaultAxesFontSize', 9);
            set(groot, 'DefaultAxesFontName', 'Times');
            set(groot, 'DefaultTextFontSize', 9);
            set(groot, 'DefaultLineLineWidth', 1);
    end
end

% Example: Create figure in Journal of Biomechanics style
applyJournalStyle('jbiomech');

fig = figure('Position', [100 100 800 600]);
% ... create your figure ...

% Save with journal specifications
print(fig, 'figure_jbiomech_style', '-dpng', '-r600');  % High resolution for submission

Reproducibility Checklist

% Create reproducibility report
function generateReproducibilityReport()
    fid = fopen('reproducibility_report.txt', 'w');

    fprintf(fid, 'Reproducibility Report\n');
    fprintf(fid, '======================\n\n');

    % System information
    fprintf(fid, 'System Information:\n');
    fprintf(fid, '-------------------\n');
    fprintf(fid, 'MATLAB Version: %s\n', version);
    fprintf(fid, 'Operating System: %s\n', computer);
    fprintf(fid, 'Analysis Date: %s\n\n', datestr(now));

    % Data information
    fprintf(fid, 'Data Information:\n');
    fprintf(fid, '-----------------\n');
    fprintf(fid, 'Dataset: converted_datasets/umich_2021_phase.parquet\n');
    fprintf(fid, 'Data Structure: Phase-indexed (150 points per cycle)\n');
    fprintf(fid, 'Units: Angles in radians, converted to degrees for display\n\n');

    % Analysis parameters
    fprintf(fid, 'Analysis Parameters:\n');
    fprintf(fid, '--------------------\n');
    fprintf(fid, 'Statistical tests: Two-sample t-tests\n');
    fprintf(fid, 'Significance level: α = 0.05\n');
    fprintf(fid, 'Missing data handling: Excluded from calculations\n');
    fprintf(fid, 'Outlier detection: Not applied\n\n');

    % Code availability
    fprintf(fid, 'Code Availability:\n');
    fprintf(fid, '------------------\n');
    fprintf(fid, 'Analysis scripts: Available upon request\n');
    fprintf(fid, 'LocomotionData library: Open source\n');
    fprintf(fid, 'Tutorial code: docs/users/tutorials/matlab/\n\n');

    fclose(fid);

    fprintf('Reproducibility report saved to: reproducibility_report.txt\n');
end

generateReproducibilityReport();

Best Practices Summary

Figure Quality Guidelines

% Guidelines for publication-quality figures
fprintf('Publication Figure Guidelines:\n');
fprintf('==============================\n');
fprintf('✓ Use vector formats (EPS, SVG) for line plots\n');
fprintf('✓ Use high resolution (≥300 DPI) for raster images\n');
fprintf('✓ Ensure readable font sizes (≥8pt for small text)\n');
fprintf('✓ Use colorblind-friendly color schemes\n');
fprintf('✓ Include proper axis labels with units\n');
fprintf('✓ Add statistical annotations where appropriate\n');
fprintf('✓ Use consistent styling across all figures\n');
fprintf('✓ Test figures at final print size\n');

Data Sharing Standards

% Data sharing best practices
fprintf('\nData Sharing Guidelines:\n');
fprintf('========================\n');
fprintf('✓ Provide processed data in standard formats (CSV, MAT)\n');
fprintf('✓ Include detailed metadata and variable descriptions\n');
fprintf('✓ Document all preprocessing and analysis steps\n');
fprintf('✓ Use persistent identifiers (DOI) for datasets\n');
fprintf('✓ Follow FAIR data principles (Findable, Accessible, Interoperable, Reusable)\n');
fprintf('✓ Include analysis code with sufficient documentation\n');
fprintf('✓ Specify software versions and dependencies\n');

Summary

You've learned to create professional publication outputs in MATLAB:

  • Multi-panel figures with journal-quality formatting
  • Statistical summary tables with comprehensive metrics
  • Data archiving packages for reproducible research
  • Journal-specific styling for different publication venues
  • Reproducibility documentation following best practices

Additional Resources

Further Learning

MATLAB Community

This completes the MATLAB tutorial series. You now have comprehensive tools for biomechanical data analysis from loading through publication!

Happy analyzing! 🎉