Skip to content

Commit 79bef17

Browse files
committed
Balancing sections 2 and 3.
1 parent 3e23bf2 commit 79bef17

File tree

2 files changed

+146
-54
lines changed

2 files changed

+146
-54
lines changed

_02_Material.qmd

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Material {#sec-material}
22

3-
This section is dedicated to describing the data that we used for segmenting gait in the present work. Essentially, we used two sources of data. First, we clipped a 9-axis inertial measurement unit (IMU) sensor at the level of the right hip and measured its orientation (that we assimilate to the hip orientation) over time during walking sessions. The data is recorded in the form of a unit quaternion time series. @sec-quaternions provides a brief overview of unit quaternions and their properties. Second, we used a pressure-sensitive walkway (GAITRite® mat) as a gold standard to label the gait events. @sec-data-acquisition elicits the data acquisition protocol while @sec-data-sets summarizes the two data sets. Finally, @sec-feature-space details the feature space that we constructed from the raw data to feed our machine learning models.
3+
In this work, the objective is to detect right and left heel strike and toe off events from hip orientation data over time. Knowledge of the occurences of these events is critical to compute key gait parameters such as the mean and the variability of stride duration, metrics of asymmetry or ratio stance/swing which have been proven to be clinically relevant [@annweiler2009risk;@beauchet2016poor]. We propose to address this problem by training, tuning and comparing several supervised classification models. In details, we consider the timepoints as the statistical units (observations) and we aim at training models to affect a *label* to each of them. This requires to elaborate a *labelled gait data* set in which we know which timepoints correspond to the occurence of the key gait events.
4+
5+
For this purpose, we used two sources of data. First, we clipped a 9-axis inertial measurement unit (IMU) at the level of the right hip and measured its orientation (that we assimilate to the hip orientation) over time during walking sessions. The data is recorded in the form of a unit quaternion time series. @sec-quaternions provides a brief overview of unit quaternions and their properties. Second, we used a pressure-sensitive walkway (GAITRite® mat) as a gold standard to record the occurences of the gait events of interest. @sec-data-acquisition elicits the data acquisition protocol while @sec-data-sets summarizes the two collected data sets and the elaboration of the final labelled data set. Finally, @sec-feature-space details the feature space that we constructed from the raw data to feed our machine learning models.
46

57
## Unit quaternions {#sec-quaternions}
68

@@ -197,7 +199,7 @@ smoothed_qts |>
197199
198200
The code above illustrates some other nice S3 specializations implemented in the [{squat}](https://cran.r-project.org/package=squat/) package such as the `log()` and `exp()` functions to compute the logarithm and exponential of a unit QTS respectively. As mentioned in @sec-quaternions, the logarithm of a unit quaternion has a null scalar part, which is why we set the *w* coordinate to zero in the code above and only smooth the three other coordinates. The function `squat::qts2sqts()` is dedicated to performing this exact computation. @fig-smoothed-qts nicely shows the smoothing effect with subtle variations along the curves that are smoothed out.
199201
200-
### Pressure mat data
202+
### Pressure mat data {#sec-gaitrite-data}
201203
202204
The GAITRite® mat records the positions of the feet on the mat through pressure-sensitive sensors hidden beneath the mat. It returns a table of spatio-temporal parameters such as stride duration, stride length, walking speed, etc. @tbl-gaitrite-params in the Appendix provides the exhaustive list of all spatio-temporal gait parameters that the walkway outputs. It also returns the time of each event happening during a gait cycle such as the time where a foot touches or leaves the ground. These are the times we use to label our data to predict these events. Since the two devices were triggered simultaneously, the IMU sensor and the GAITRite® mat are assumed to share the same time clock. We use the pressure mat as a gold standard to label the observations into the different classes and train models on this labeled data.
203205
@@ -392,6 +394,75 @@ plot_ts(imu_data[[33]], rhs33, rto33, lhs33, lto33)
392394
393395
## Feature space {#sec-feature-space}
394396
397+
In this work, the objective is to identify which timepoints of unit QTS correspond to the RHS, LTO, LHS and RTO events. For this purpose, we consider the timepoints as statistical units (observations) and we aim at labelling them by means of supervised classification models. We therefore need to define a so-called *feature space* which consists of a data table listing the timepoints by row and collecting a number of features for each of them. A first important feature is the actual label that we want to predict with the trained model. @sec-labelled-gait-data details the elaboration of what
398+
399+
### Labelled gait data {#sec-labelled-gait-data}
400+
401+
In this view, we can first create the data set that we will use for training. The following code achieves this task by binding together all timepoints from all walking sessions while attaching to each timepoint:
402+
403+
- an `event_type` which affects it to one of the five gait events defined in @sec-gaitrite-data;
404+
- a `phase_type` which affects it one of the four gait pahses defined in @sec-gaitrite-data.
405+
406+
```{r}
407+
events_to_phases <- function(events) {
408+
events_of_interest <- events != "None"
409+
first_event <- events[events_of_interest][1]
410+
phase_durations <- diff(c(0, sort(which(events_of_interest))))
411+
n_phases <- length(phase_durations)
412+
phase_names <- switch(
413+
first_event,
414+
"RHS" = rep(
415+
c("Swing", "Pre-Stance", "Stance", "Pre-Swing"),
416+
times = n_phases
417+
)[1:n_phases],
418+
"LTO" = rep(
419+
c("Pre-Stance", "Stance", "Pre-Swing", "Swing"),
420+
times = n_phases
421+
)[1:n_phases],
422+
"LHS" = rep(
423+
c("Stance", "Pre-Swing", "Swing", "Pre-Stance"),
424+
times = n_phases
425+
)[1:n_phases],
426+
"RTO" = rep(
427+
c("Pre-Swing", "Swing", "Pre-Stance", "Stance"),
428+
times = n_phases
429+
)[1:n_phases]
430+
)
431+
purrr::map2(
432+
phase_names,
433+
phase_durations,
434+
\(phase_name, phase_duration) rep(phase_name, times = phase_duration)
435+
) |>
436+
purrr::list_c()
437+
}
438+
439+
labelled_gait_data <- purrr::map(1:nrow(bhg), \(session_index) {
440+
gaitrite_data <- gaitrite_data |>
441+
dplyr::filter(session == session_index) |>
442+
dplyr::select(-session)
443+
imu_data[[session_index]] |>
444+
dplyr::left_join(gaitrite_data, by = c("time" = "event_time")) |>
445+
dplyr::mutate(
446+
event_type = dplyr::if_else(is.na(event_type), "None", event_type),
447+
phase_type = events_to_phases(event_type)
448+
)
449+
}) |>
450+
dplyr::bind_rows(.id = "session") |>
451+
dplyr::mutate(
452+
session = as.numeric(session),
453+
event_type = factor(
454+
event_type,
455+
levels = c("RHS", "LTO", "LHS", "RTO", "None")
456+
),
457+
phase_type = factor(
458+
phase_type,
459+
levels = c("Pre-Stance", "Stance", "Pre-Swing", "Swing")
460+
)
461+
)
462+
class(labelled_gait_data) <- class(labelled_gait_data)[-1]
463+
head(labelled_gait_data)
464+
```
465+
395466
The feature space is an important piece of machine learning models as it defines the data that will be used to train them. In our application, we constructed a feature space from the raw QTS data recorded by the IMU sensor. First, we need to define what is an observation in our context. An observation corresponds to the data recorded at a given time point $t_j$. Since QTS are ordered sets of unit quaternions, for the $j$-*th* observation (row) of the feature space, we can then use features computed from the data observed at time $t_j$ or any other time points preceding $t_j$. One feature is of course the label of the observation that we get from the gold standard. This variable is available for the train/test process but will not be available at prediction time since it corresponds to the events that we want to predict. The other features are predictors that we compute from the sensor data. The ultimate goal is to design a feature space to label each observation $t_j$ with the gait event happening at that time (if any) using only the predictors computed from the QTS at time points $t_k \le t_j$, with $1 \le k \le j$. In the remainder of this section, we describe the predictors that we computed to build our feature space.
396467
397468
Angular velocity and acceleration vectors

_03_Methods.qmd

Lines changed: 73 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,59 +2,12 @@
22

33
## Classification strategies {#sec-classification-strategies}
44

5-
Gait event detection is performed by evaluating and comparing two strategies to classify the observations.
5+
In this work, the objective is to identify which timepoints of unit QTS correspond to the RHS, LTO, LHS and RTO events. For this purpose, we consider the timepoints as statistical units (observations) and we aim at labelling them by means of classification models. In this view, we can first create the data set that we will use for training. The following code achieves this task by binding together all timepoints from all walking sessions while attaching to each timepoint:
66

7-
Strategy E: Predicting gait [E]{.underline}vents
8-
9-
: The strategy E pertains to directly predicting the gait events occuring when walking. Specifically, time points are viewed as statistical units (observations) and we aim at classifying them into five categories:
10-
11-
- *Right Heel Strike*,
12-
- *Left Toe Off*,
13-
- *Left Heel Strike*,
14-
- *Right Toe Off*,
15-
- *None* (all other times not corresponding to a certain event).
16-
17-
The first four events (RHS, LTO, LHS and RTO) are coined *events of interest* while the last one encodes the so-called *negative* class. While conveniently aiming at directly predicting the occurrence of gait events of interest, this strategy suffers from a severe class imbalance issue, with the *None* (negative) class being widely over-represented as summarized in @tbl-class-imbalance.
7+
- an `event_type` which affects it to one of the five gait events defined in @sec-gaitrite-data;
8+
- a `phase_type` which affects it one of the four gait pahses defined in @sec-gaitrite-data.
189

1910
```{r}
20-
#| label: tbl-class-imbalance
21-
#| tbl-cap: "Strategy E: Count and proportion of observations in each class."
22-
#| tbl-pos: "H"
23-
tibble::tibble(
24-
class = c(
25-
"Right Heel Strike",
26-
"Left Toe Off",
27-
"Left Heel Strike",
28-
"Right Toe Off",
29-
"None"
30-
),
31-
nb_obs = c("973", "1004", "994", "982", "158401"),
32-
prop = c("0.60%", "0.62%", "0.61%", "0.60%", "97.57%")
33-
) |>
34-
gt::gt() |>
35-
gt::cols_label(
36-
class = "Class",
37-
nb_obs = "Number of observations",
38-
prop = "Proportion"
39-
) |>
40-
gt::cols_align(align = "center") |>
41-
gt::tab_style(
42-
style = list(gt::cell_text(style = "italic")),
43-
locations = gt::cells_body(columns = class)
44-
) |>
45-
gt::tab_options(column_labels.background.color = "#616161")
46-
```
47-
48-
[AST] TO MODIFY
49-
50-
Finally, the following code creates the labelled data set that we will use to elaborate the feature space and produces @tbl-class-summary which exhibits class frequencies whether we focus on gait events () or gait phases ().
51-
52-
```{r}
53-
#| label: tbl-class-summary
54-
#| tbl-cap: Two tables
55-
#| tbl-subcap: ["mtcars", "Just cars"]
56-
#| layout-ncol: 2
57-
#| classes: plain
5811
events_to_phases <- function(events) {
5912
events_of_interest <- events != "None"
6013
first_event <- events[events_of_interest][1]
@@ -110,14 +63,82 @@ labelled_gait_data <- purrr::map(1:nrow(bhg), \(session_index) {
11063
levels = c("Pre-Stance", "Stance", "Pre-Swing", "Swing")
11164
)
11265
)
66+
class(labelled_gait_data) <- class(labelled_gait_data)[-1]
67+
head(labelled_gait_data)
68+
```
69+
70+
We first need to decide what models should predict. In effect, we can adopt two different strategies.
71+
72+
The most straightforward way pertains to predicting the gait events of interest themselves. We call it **Strategy E**, where **E** stands for [E]{.underline}vents. Following this strategy, this means that we must design a multiclass prediction model with 5 classes (RHS, LTO, LHS, RTO and None) as defined in @sec-gaitrite-data. The first four events (RHS, LTO, LHS and RTO) are coined *events of interest* while the last one encodes the so-called *negative* class. While conveniently aiming at directly predicting the occurrence of gait events of interest, this strategy suffers from a severe class imbalance issue, with the *None* (negative) class being widely over-represented as shown by @tbl-e-counts.
73+
74+
A solution to mitigate this severe class imbalance issue is to predict gait phases instead of events at the cost of some post-processing efforts needed to identify the occurences of RHS, LTO, LHS and RTO after the phase prediction step. As defined in @sec-gaitrite-data, there are four phases to predict (pre-stance, stance, pre-swing and swing). We call this strategy **Strategy P**, where **P** stands for [P]{.underline}hases. @tbl-p-counts exhibits the frequency of timepoints in each phase, which demonstrate that this strategy successfully reduces dramatically class imbalance.
75+
76+
```{r}
77+
#| label: tbl-class-imbalance
78+
#| tbl-cap: "Strategy E: Count and proportion of observations in each class."
79+
#| tbl-pos: "H"
80+
tibble::tibble(
81+
class = c(
82+
"Right Heel Strike",
83+
"Left Toe Off",
84+
"Left Heel Strike",
85+
"Right Toe Off",
86+
"None"
87+
),
88+
nb_obs = c("973", "1004", "994", "982", "158401"),
89+
prop = c("0.60%", "0.62%", "0.61%", "0.60%", "97.57%")
90+
) |>
91+
gt::gt() |>
92+
gt::cols_label(
93+
class = "Class",
94+
nb_obs = "Number of observations",
95+
prop = "Proportion"
96+
) |>
97+
gt::cols_align(align = "center") |>
98+
gt::tab_style(
99+
style = list(gt::cell_text(style = "italic")),
100+
locations = gt::cells_body(columns = class)
101+
) |>
102+
gt::tab_options(column_labels.background.color = "#616161")
103+
```
104+
105+
[AST] TO MODIFY
106+
107+
Finally, the following code creates the labelled data set that we will use to elaborate the feature space and produces @tbl-class-summary which exhibits class frequencies whether we focus on gait events () or gait phases ().
113108

109+
```{r}
110+
#| label: tbl-class-summary
111+
#| tbl-cap: Two tables
112+
#| tbl-subcap: ["mtcars", "Just cars"]
113+
#| layout-ncol: 2
114+
#| html-table-processing: none
114115
labelled_gait_data |>
115116
dplyr::count(event_type) |>
116-
gt::gt()
117+
gt::gt() |>
118+
gt::cols_label(
119+
event_type = "Event",
120+
n = "Frequency"
121+
) |>
122+
gt::opt_stylize(style = 6, color = 'gray') |>
123+
gt::cols_align(align = "center") |>
124+
gt::tab_style(
125+
style = "vertical-align:top",
126+
locations = gt::cells_column_labels()
127+
)
117128
118129
labelled_gait_data |>
119130
dplyr::count(phase_type) |>
120-
gt::gt()
131+
gt::gt() |>
132+
gt::cols_label(
133+
phase_type = "Phase",
134+
n = "Frequency"
135+
) |>
136+
gt::opt_stylize(style = 6, color = 'gray') |>
137+
gt::cols_align(align = "center") |>
138+
gt::tab_style(
139+
style = "vertical-align:top",
140+
locations = gt::cells_column_labels()
141+
)
121142
```
122143

123144
We can notice that gait events of interest are largely under-represented while this class imbalance issue is moderate when we put the focus on gait phases.

0 commit comments

Comments
 (0)