Cadence Evaluation#

This example shows how to apply evaluation algorithms to cadence results and thus how to rate the performance of a cadence calculation algorithm.

from pprint import pprint

import numpy as np
import pandas as pd
from mobgap.data import LabExampleDataset

Example Data#

We load example data from the lab dataset. We will use a longer trial from an “MS” participant for this example. Additionally, we will load the reference cadence values to compare the results and evaluate the performance of the cadence calculation algorithm. To calculate the cadence, we will then use the initial contacts measured from the reference system.

def load_data():
    """Load example data from the lab dataset."""
    lab_example_data = LabExampleDataset(reference_system="INDIP")
    long_trial = lab_example_data.get_subset(
        cohort="MS", participant_id="001", test="Test11", trial="Trial1"
    )
    sampling_rate_hz = long_trial.sampling_rate_hz
    return long_trial, sampling_rate_hz


def load_reference(data):
    """Load reference from the INDIP reference system."""
    reference_gs = data.reference_parameters_.wb_list
    reference_ic = data.reference_parameters_relative_to_wb_.ic_list
    return reference_gs, reference_ic


test_data, sampling_rate_hz = load_data()
reference_gs, reference_ic = load_reference(test_data)
/home/docs/checkouts/readthedocs.org/user_builds/mobgap/checkouts/v0.9.0/mobgap/data/_mobilised_matlab_loader.py:1082: UserWarning: There were multiple ICs with the same index value, but different LR labels. This is likely an issue with the reference system you should further investigate. For now, we set the `lr_label` of the stride corresponding to this IC to Nan. However, both values still remain in the IC list.
  return parse_reference_parameters(
/home/docs/checkouts/readthedocs.org/user_builds/mobgap/checkouts/v0.9.0/mobgap/data/_mobilised_matlab_loader.py:1091: UserWarning: There were multiple ICs with the same index value, but different LR labels. This is likely an issue with the reference system you should further investigate. For now, we set the `lr_label` of the stride corresponding to this IC to Nan. However, both values still remain in the IC list.
  return parse_reference_parameters(

From the reference data, we can see that our example data contains several gait sequences. For each gait sequence, the reference system provides an average cadence value.

reference_gs[["avg_cadence_spm"]]
avg_cadence_spm
wb_id
0 107.795850
1 93.396106
2 75.981133
3 92.337768
4 87.915774
5 95.365740


Furthermore, the reference data includes a list of initial contacts for each gait sequence. We will use these reference initial contacts as input for the cadence calculation algorithm, to enable evaluating the performance of the cadence calculation in isolation.

ic lr_label
wb_id step_id
0 0 0 right
1 46 left
2 110 right
3 153 left
4 217 right
... ... ... ...
5 7 518 right
8 573 left
9 664 right
10 711 left
11 750 right

93 rows × 2 columns



Applying the Cadence Calculation Algorithm#

In this example, we will use the CadFromIc algorithm to calculate the cadence from the reference initial contacts.

from mobgap.cadence import CadFromIc

cad_from_ic = CadFromIc()

As this algorithm is designed to work on a single gait sequence at a time, we will iterate over the gait sequences present in the example data and calculate the cadence for each of them. This is done using the GsIterator class. How the GsIterator class works in detail together with different application examples is explained in its dedicated example.

The detected cadences per second for all gait sequences can then be accessed from the results_ attribute of the GsIterator object.

cadence_spm
wb_id r_gs_id sec_center_samples
0 0 1069 109.090909
1169 112.149533
1269 106.194690
1369 112.149533
1469 90.909091
... ... ... ...
5 0 21728 102.783726
21828 101.694915
21928 109.090909
22028 127.659574
22128 153.846154

69 rows × 1 columns



Comparison with Reference#

To evaluate the performance of the cadence calculation algorithm, we can compare the calculated cadence values with the reference cadence values. For this purpose, as there are is only one reference cadence value per gait sequence, we will first average the calculated cadence values per gait sequence.

cadence_spm
wb_id
0 98.129901
1 91.064415
2 68.021660
3 93.531544
4 104.407653
5 112.940841


Next, the detected and reference cadences values are concatenated into a single DataFrame. As columns, a multilevel index is used that contains the type of metric (cadence_spm) in the first and the source of the data (detected or reference) in the second level.

reference_cadence = reference_gs[["avg_cadence_spm"]].rename(
    columns={"avg_cadence_spm": "cadence_spm"}
)
combined_cad = {"detected": avg_cadence_per_gs, "reference": reference_cadence}
combined_cad = pd.concat(
    combined_cad, axis=1, keys=combined_cad.keys()
).reorder_levels((1, 0), axis=1)
combined_cad
cadence_spm
detected reference
wb_id
0 98.129901 107.795850
1 91.064415 93.396106
2 68.021660 75.981133
3 93.531544 92.337768
4 104.407653 87.915774
5 112.940841 95.365740


The concatenated DataFrame can then be used to evaluate the performance of the cadence calculation algorithm. For this purpose, first, the errors between the detected and reference cadence values are calculated.

Estimate Errors in cadence data#

We can calculate a variety of error metrics to evaluate the performance of the cadence calculation algorithm, ranging from the simple difference between the estimated and ground truth values (simply referred to as error) to its absolute value (absolute_error). Both can also be set in relation to the reference value (relative_error and absolute_relative_error). To apply these errors, we first need to build a list specifying the error functions to be applied. All the above-mentioned error functions are provided by the ErrorTransformFuncs class. Note that you can also apply custom functions instead of the predefined ones. For more information on how to define custom error functions, see the general example on DMO evaluation.

from mobgap.pipeline.evaluation import ErrorTransformFuncs as E

errors = [("cadence_spm", [E.error, E.abs_error, E.rel_error, E.abs_rel_error])]
pprint(errors)
[('cadence_spm',
  [<function error at 0x7fdc83ac5480>,
   <function abs_error at 0x7fdc82f9b640>,
   <function rel_error at 0x7fdc82f9b5b0>,
   <function abs_rel_error at 0x7fdc82f9b6d0>])]

The error functions can be applied to the combined cadence data using the apply_transformations function. The resulting DataFrame contains the error values for each gait sequence.

wb_id 0 1 2 3 4 5
cadence_spm error -9.665949 -2.331691 -7.959474 1.193776 16.491879 17.575101
abs_error 9.665949 2.331691 7.959474 1.193776 16.491879 17.575101
rel_error -0.089669 -0.024966 -0.104756 0.012928 0.187587 0.184292
abs_rel_error 0.089669 0.024966 0.104756 0.012928 0.187587 0.184292


Before we now aggregate the results, we can also combine the error metrics with the reference and detected values to have all the information in one dataframe.

cadence_spm
detected reference error abs_error rel_error abs_rel_error
wb_id
0 98.129901 107.795850 -9.665949 9.665949 -0.089669 0.089669
1 91.064415 93.396106 -2.331691 2.331691 -0.024966 0.024966
2 68.021660 75.981133 -7.959474 7.959474 -0.104756 0.104756
3 93.531544 92.337768 1.193776 1.193776 0.012928 0.012928
4 104.407653 87.915774 16.491879 16.491879 0.187587 0.187587
5 112.940841 95.365740 17.575101 17.575101 0.184292 0.184292


Aggregate Errors#

Finally, the estimated errors can be aggregated to provide a summary of the performance of the cadence calculation. For this purpose, different aggregation functions can be applied to the error metrics, ranging from simple, built-in aggregations like the mean or standard deviation to more complex functions like the limits of agreement or 5th and 95th percentiles. This can be done using the apply_aggregations function. Possible aggregations are provided by the CustomErrorAggregations class. There are two ways to define such aggregations:

  1. As a list of tuples in the format (<identifier>, <aggregation>) with <identifier> being the key for accessing the column to evaluate, and <aggregation> being the aggregation function(s) to apply. A valid list of aggregations could look like this:

from mobgap.pipeline.evaluation import CustomErrorAggregations as A

aggregations_simple = [
    *(
        (("cadence_spm", origin), [np.mean, A.quantiles])
        for origin in ["detected", "reference", "abs_error", "abs_rel_error"]
    ),
    *(
        (("cadence_spm", origin), [np.mean, A.loa])
        for origin in ["error", "rel_error"]
    ),
]
pprint(aggregations_simple)
[(('cadence_spm', 'detected'),
  [<function mean at 0x7fdca23e7a30>, <function quantiles at 0x7fdc82f9b880>]),
 (('cadence_spm', 'reference'),
  [<function mean at 0x7fdca23e7a30>, <function quantiles at 0x7fdc82f9b880>]),
 (('cadence_spm', 'abs_error'),
  [<function mean at 0x7fdca23e7a30>, <function quantiles at 0x7fdc82f9b880>]),
 (('cadence_spm', 'abs_rel_error'),
  [<function mean at 0x7fdca23e7a30>, <function quantiles at 0x7fdc82f9b880>]),
 (('cadence_spm', 'error'),
  [<function mean at 0x7fdca23e7a30>, <function loa at 0x7fdc82f9b910>]),
 (('cadence_spm', 'rel_error'),
  [<function mean at 0x7fdca23e7a30>, <function loa at 0x7fdc82f9b910>])]
  1. As a named tuple of Type CustomOperation taking three values: identifier, function, and column_name. identifier is a valid loc identifier selecting one or more columns from the dataframe, function is the aggregation function or list of functions to apply, and column_name is the identifier of the resulting column in the output dataframe. This allows for more complex aggregations that require multiple columns as input, for example, the intra-class correlation coefficient (ICC). A valid aggregation list for calculating the ICC of all DMOs would look like this:

from mobgap.utils.df_operations import CustomOperation

aggregations_custom = [
    CustomOperation(
        identifier="cadence_spm",
        function=A.icc,
        column_name=("cadence_spm", "all"),
    )
]
pprint(aggregations_custom)
[CustomOperation(identifier='cadence_spm', function=<function icc at 0x7fdc82f9b7f0>, column_name=('cadence_spm', 'all'))]

For more detailed information on the aggregation types and their usage, check out the detailed example on it in the general example on DMO evaluation.

Both types of aggregations can be combined and applied in a single call to the apply_aggregations function. This returns a pandas Series with the aggregated values for each aggregation function and origin for the metric cadence. For better readability, we sort and format the resulting dataframe.

from mobgap.utils.df_operations import apply_aggregations

aggregations = aggregations_simple + aggregations_custom
agg_results = (
    apply_aggregations(combined_cad_with_errors, aggregations)
    .rename_axis(index=["aggregation", "metric", "origin"])
    .reorder_levels(["metric", "origin", "aggregation"])
    .sort_index(level=0)
    .to_frame("values")
)
agg_results
values
metric origin aggregation
cadence_spm abs_error mean 9.202978
quantiles (1.4782545184450235, 17.304295196502732)
abs_rel_error mean 0.1007
quantiles (0.015937671449301465, 0.18676332986307062)
all icc (0.625933703835827, [-0.16, 0.94])
detected mean 94.682669
quantiles (73.7823485337994, 110.80754397719134)
error loa (-20.730997551818866, 25.83221149212917)
mean 2.550607
reference mean 92.132062
quantiles (78.96479355928136, 104.68832292084684)
rel_error loa (-0.22707473497999797, 0.2822136055400246)
mean 0.027569


Total running time of the script: (0 minutes 2.912 seconds)

Estimated memory usage: 9 MB

Gallery generated by Sphinx-Gallery