The question, methods & findings
Project overview
I studied whether smartphone accelerometer and gyroscope signals could classify twelve activities and postural transitions. I worked with a course version of a dataset from 30 volunteers aged 19–48, recorded at 50 Hz. I extracted time- and frequency-domain features from 128-sample epochs and compared four classifiers with ten-fold cross-validation. Multinomial logistic regression had the highest accuracy in the original notebook: 89.21%.
Results
| Classifier | Best epoch-level CV accuracy |
|---|---|
| Multinomial logistic regression | 89.21% |
| Scaled KNN | 87.57% |
| LDA | 86.68% |
| KNN | 74.45% |
The table comes from original_model_comparison.csv. I retain the original notebook and tuning results. These are the original coursework results; I have not rerun the full comparison with the current script.
Limitations
Epochs from the same person can enter different folds, so the reported accuracy does not estimate performance on unseen people. I filtered predictors before cross-validation, which may inflate the estimates. The withheld test labels prevent me from reporting test accuracy.
Credits
I worked on this project with Yuxuan Xiang. Project authors: Laura Maria Fetz and Yuxuan Xiang. We shared feature extraction and model selection; I wrote the notebook text and prepared its layout and figures. The dataset documentation in data/README.txt credits Jorge L. Reyes-Ortiz, Davide Anguita, Luca Oneto, Xavier Parra, and collaborators.
From the project documentation. Download the overview.
Read the analysis
Original notebook
This is the original coursework notebook, including its saved outputs.
Human activity recognition
I classified twelve activities and postural transitions from smartphone accelerometer and gyroscope signals. The dataset contains thirty volunteers aged 19–48, measured at 50 Hz. This is my original coursework notebook. The README reports the original CV results.
Project authors: Laura Maria Fetz and Yuxuan Xiang.
R code Cell 02
# Loading packages
library(tidyverse)
library(dplyr)
library(readr)
library(caret)
library(nnet)
# Creating a path to the files
list.files(path = "../input")
# Coping all files to the working directory
system(paste0("cp -r ", list.files("../input", pattern = "recognition", full.names=TRUE), "/* ./"))
# List files in the current working directory
list.files()- 'activity_labels.txt'
- 'example_submission.csv'
- 'RawData'
- 'README.txt'
- 'Rplot001.png'
Read the data
I read the activity definitions and training segment labels.
R code Cell 04
# Read the text file containing the activity labels
act_labels = read_delim("activity_labels.txt"," ",col_names=FALSE,trim_ws=TRUE)
act_labels = act_labels %>% select(X1,X2)
act_labelsWarning message: “[1m[22mOne or more parsing issues, call `problems()` on your data frame for details, e.g.: dat <- vroom(...) problems(dat)” [1mRows: [22m[34m12[39m [1mColumns: [22m[34m13[39m [36m──[39m [1mColumn specification[22m [36m────────────────────────────────────────────────────────[39m [1mDelimiter:[22m " " [31mchr[39m (1): X2 [32mdbl[39m (1): X1 [33mlgl[39m (11): X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13 [36mℹ[39m Use `spec()` to retrieve the full column specification for this data. [36mℹ[39m Specify the column types or set `show_col_types = FALSE` to quiet this message.
| X1 | X2 |
|---|---|
| <dbl> | <chr> |
| 1 | WALKING |
| 2 | WALKING_UPSTAIRS |
| 3 | WALKING_DOWNSTAIRS |
| 4 | SITTING |
| 5 | STANDING |
| 6 | LAYING |
| 7 | STAND_TO_SIT |
| 8 | SIT_TO_STAND |
| 9 | SIT_TO_LIE |
| 10 | LIE_TO_SIT |
| 11 | STAND_TO_LIE |
| 12 | LIE_TO_STAND |
Sample labels
I use each segment’s start and end indices to label the raw sensor samples.
R code Cell 06
# Loading the signals from the training data and adding column names
labels = read_delim("./RawData/Train/labels_train.txt", " ", col_names = F)
colnames(labels) <- c('trial', 'userid', 'activity', 'start', 'end')
# Matching the labels with the activites
labels = labels %>% mutate(activity = act_labels$X2[activity])
# Look at our new dataframe
print(labels)[1mRows: [22m[34m849[39m [1mColumns: [22m[34m5[39m [36m──[39m [1mColumn specification[22m [36m────────────────────────────────────────────────────────[39m [1mDelimiter:[22m " " [32mdbl[39m (5): X1, X2, X3, X4, X5 [36mℹ[39m Use `spec()` to retrieve the full column specification for this data. [36mℹ[39m Specify the column types or set `show_col_types = FALSE` to quiet this message.
[90m# A tibble: 849 × 5[39m trial userid activity start end [3m[90m<dbl>[39m[23m [3m[90m<dbl>[39m[23m [3m[90m<chr>[39m[23m [3m[90m<dbl>[39m[23m [3m[90m<dbl>[39m[23m [90m 1[39m 1 1 STANDING 250 [4m1[24m232 [90m 2[39m 1 1 STAND_TO_SIT [4m1[24m233 [4m1[24m392 [90m 3[39m 1 1 SITTING [4m1[24m393 [4m2[24m194 [90m 4[39m 1 1 SIT_TO_STAND [4m2[24m195 [4m2[24m359 [90m 5[39m 1 1 STANDING [4m2[24m360 [4m3[24m374 [90m 6[39m 1 1 STAND_TO_LIE [4m3[24m375 [4m3[24m662 [90m 7[39m 1 1 LAYING [4m3[24m663 [4m4[24m538 [90m 8[39m 1 1 LIE_TO_SIT [4m4[24m539 [4m4[24m735 [90m 9[39m 1 1 SITTING [4m4[24m736 [4m5[24m667 [90m10[39m 1 1 SIT_TO_LIE [4m5[24m668 [4m5[24m859 [90m# ℹ 839 more rows[39m
I keep the participant, trial, and activity identifiers when expanding the segment labels.
R code Cell 08
# Add the sequence start:end to each row in a list, creating a nester tibble
sample_labels_nested =
labels %>%
rowwise() %>% # do next operation(s) rowwise
mutate(sampleid = list(start:end)) %>%
ungroup()
# Check the resulting table
print(sample_labels_nested)[90m# A tibble: 849 × 6[39m trial userid activity start end sampleid [3m[90m<dbl>[39m[23m [3m[90m<dbl>[39m[23m [3m[90m<chr>[39m[23m [3m[90m<dbl>[39m[23m [3m[90m<dbl>[39m[23m [3m[90m<list>[39m[23m [90m 1[39m 1 1 STANDING 250 [4m1[24m232 [90m<int [983]>[39m [90m 2[39m 1 1 STAND_TO_SIT [4m1[24m233 [4m1[24m392 [90m<int [160]>[39m [90m 3[39m 1 1 SITTING [4m1[24m393 [4m2[24m194 [90m<int [802]>[39m [90m 4[39m 1 1 SIT_TO_STAND [4m2[24m195 [4m2[24m359 [90m<int [165]>[39m [90m 5[39m 1 1 STANDING [4m2[24m360 [4m3[24m374 [90m<int [1,015]>[39m [90m 6[39m 1 1 STAND_TO_LIE [4m3[24m375 [4m3[24m662 [90m<int [288]>[39m [90m 7[39m 1 1 LAYING [4m3[24m663 [4m4[24m538 [90m<int [876]>[39m [90m 8[39m 1 1 LIE_TO_SIT [4m4[24m539 [4m4[24m735 [90m<int [197]>[39m [90m 9[39m 1 1 SITTING [4m4[24m736 [4m5[24m667 [90m<int [932]>[39m [90m10[39m 1 1 SIT_TO_LIE [4m5[24m668 [4m5[24m859 [90m<int [192]>[39m [90m# ℹ 839 more rows[39m
I unnest the sample indices. I keep a segment identifier to distinguish repeated bouts of the same activity.
R code Cell 10
# Unnest the nested tabel.
sample_labels =
sample_labels_nested %>%
# Rows are segments, we need to keep track of different segements
mutate(segment = row_number() ) %>%
# Expand the data frame to one sample per row
unnest(cols = c(sampleid)) %>%
# Remove columns we don't need anymore
select(-start, -end)
# Check the result (first few rows are not interesting; rows 977-990 are)
print(sample_labels[0:1000, ])[90m# A tibble: 1,000 × 5[39m trial userid activity sampleid segment [3m[90m<dbl>[39m[23m [3m[90m<dbl>[39m[23m [3m[90m<chr>[39m[23m [3m[90m<int>[39m[23m [3m[90m<int>[39m[23m [90m 1[39m 1 1 STANDING 250 1 [90m 2[39m 1 1 STANDING 251 1 [90m 3[39m 1 1 STANDING 252 1 [90m 4[39m 1 1 STANDING 253 1 [90m 5[39m 1 1 STANDING 254 1 [90m 6[39m 1 1 STANDING 255 1 [90m 7[39m 1 1 STANDING 256 1 [90m 8[39m 1 1 STANDING 257 1 [90m 9[39m 1 1 STANDING 258 1 [90m10[39m 1 1 STANDING 259 1 [90m# ℹ 990 more rows[39m
Accelerometer recordings
I first inspect participant 1, trial 1. Acceleration is measured in units of g.
R code Cell 12
# Identify the file name and extract the 'username' (participant ID) and 'expname' (experimental trial)
filename_acc = "RawData/Train/acc_exp01_user01.txt"
username = gsub(".+user(\\d+).+", "\\1", filename_acc) %>% as.integer()
expname = gsub(".+exp(\\d+).+", "\\1", filename_acc) %>% as.integer()
# Import the data from the file
acc_data = read.delim(filename_acc, " ", header = FALSE) %>%
setNames(c("X1", "X2", "X3"))
# Creating a plot
options(repr.plot.width=20)
plot.ts(acc_data, xlab="Sample number")Gyroscope recordings
I inspect the matching angular-velocity recording. Its values are measured in radians per second.
R code Cell 14
# Identify the file name and extract the 'username' (participant ID) and 'expname' (experimental trial)
filename_gyro = "RawData/Train/gyro_exp01_user01.txt"
username = gsub(".+user(\\d+).+", "\\1", filename_gyro) %>% as.integer()
expname = gsub(".+exp(\\d+).+", "\\1", filename_gyro) %>% as.integer()
# Read gyro_data and give names to variables
gyro_data <- read.delim(filename_gyro, " ", header = FALSE) %>%
setNames(c("X1", "X2", "X3"))
# Creating a plot
options(repr.plot.width=20)
plot.ts(gyro_data, xlab="Sample number")Merge recordings and labels
I join the sample labels to the accelerometer and gyroscope measurements.
R code Cell 16
# Combine accelometer and gyroscope data
combined_data <- cbind(acc_data, gyro_data)
# Store signals of the first participant for both acc and gyro data in a data frame, with 'userid' and 'trial'
user_df =
data.frame(userid = username, trial = expname, combined_data) %>%
mutate(sampleid = 0:(nrow(combined_data)-1) ) %>%
left_join(sample_labels)
# Check the result (first few rows are not interesting; the following are)
print(user_df[1227:1239, ])[1m[22mJoining with `by = join_by(userid, trial, sampleid)`
userid trial X1 X2 X3 X1.1 X2.1
1227 1 1 1.022222 -0.1375000 0.07500000 -0.007941248 0.001832596
1228 1 1 1.019445 -0.1347222 0.07361111 -0.014355334 0.011301007
1229 1 1 1.022222 -0.1375000 0.07777778 -0.015271631 0.014966198
1230 1 1 1.025000 -0.1375000 0.07638889 -0.013133603 0.041844271
1231 1 1 1.030556 -0.1444445 0.07916667 -0.034819320 0.076663591
1232 1 1 1.016667 -0.1430556 0.05833334 -0.006108652 0.032986723
1233 1 1 1.015278 -0.1416667 0.05972222 0.031459559 0.003359759
1234 1 1 1.020833 -0.1638889 0.05555556 0.072998397 0.033903021
1235 1 1 1.027778 -0.1736111 0.06944445 0.103847094 0.064140849
1236 1 1 1.025000 -0.1638889 0.05833334 0.103847094 0.045204028
1237 1 1 1.022222 -0.1472222 0.04861111 0.130114302 0.033292156
1238 1 1 1.022222 -0.1597222 0.04305556 0.192727983 0.013744468
1239 1 1 1.027778 -0.1625000 0.04861111 0.226631001 0.059253927
X3.1 sampleid activity segment
1227 0.008246681 1226 STANDING 1
1228 0.010995574 1227 STANDING 1
1229 0.007024950 1228 STANDING 1
1230 0.021991149 1229 STANDING 1
1231 0.022602014 1230 STANDING 1
1232 0.009773844 1231 STANDING 1
1233 0.014966198 1232 STANDING 1
1234 -0.003054326 1233 STAND_TO_SIT 2
1235 0.005192355 1234 STAND_TO_SIT 2
1236 0.012828170 1235 STAND_TO_SIT 2
1237 0.012828170 1236 STAND_TO_SIT 2
1238 0.014966198 1237 STAND_TO_SIT 2
1239 0.007635816 1238 STAND_TO_SIT 2
Accelerometer plot
I plot the three acceleration channels against the activity labels.
R code Cell 18
# Set plot width options for the visualization
options(repr.plot.width=20)
# Create a ggplot visualization using user_df data
user_df %>%
ggplot(aes(x = sampleid, y = X1, col = factor(activity), group = segment)) +
# Add a line plot to the ggplot visualization
geom_line()Gyroscope plot
I plot the three angular-velocity channels against the same labels.
R code Cell 20
# Set plot width options for the visualization
options(repr.plot.width=20)
# Create a ggplot visualization using user_df data
user_df %>%
ggplot(aes(x = sampleid, y = X1.1, col = factor(activity), group = segment)) +
# Add a line plot to the ggplot visualization
geom_line()Extract features
I divide the labelled signals into 128-sample epochs, about 2.56 seconds each.
R code Cell 22
# Split data into epochs and add epoch ID
user_df %>%
mutate(epoch = sampleid %/% 128) %>%
group_by(epoch) %>%
head(5)| userid | trial | X1 | X2 | X3 | X1.1 | X2.1 | X3.1 | sampleid | activity | segment | epoch |
|---|---|---|---|---|---|---|---|---|---|---|---|
| <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <int> | <chr> | <int> | <dbl> |
| 1 | 1 | 0.9180556 | -0.11250000 | 0.5097223 | -0.05497787 | -0.06963864 | -0.030848695 | 0 | NA | NA | 0 |
| 1 | 1 | 0.9111111 | -0.09305556 | 0.5375000 | -0.01252274 | 0.01924226 | -0.038484510 | 1 | NA | NA | 0 |
| 1 | 1 | 0.8819445 | -0.08611111 | 0.5138889 | -0.02351831 | 0.27641651 | 0.006414085 | 2 | NA | NA | 0 |
| 1 | 1 | 0.8819445 | -0.08611111 | 0.5138889 | -0.09346239 | 0.36774087 | 0.001221730 | 3 | NA | NA | 0 |
| 1 | 1 | 0.8791667 | -0.10000000 | 0.5055556 | -0.12431107 | 0.47678033 | -0.022907447 | 4 | NA | NA | 0 |
Feature functions
I calculate descriptive statistics and correlations for each epoch.
R code Cell 24
# Lagged cor feature
lagged_cor = function(x, y=x, lag=0) {
lagged_cor = cor(x, dplyr::lag(y, lag), use='pairwise')
return(lagged_cor)
}Skewness and kurtosis
I calculate features that describe the shape of the signal distribution.
R code Cell 26
# Skewness feature
skewness <- function(x) {
n <- length(x)
mean_x <- mean(x, na.rm = TRUE)
s <- sqrt(sum((x - mean_x)^2, na.rm = TRUE) / (n - 1))
skewness <- sum(((x - mean_x) / s)^3, na.rm = TRUE) / n
return(skewness)
}
# Kurtosis feature
kurtosis <- function(x) {
n <- length(x)
mean_x <- mean(x, na.rm = TRUE)
s <- sqrt(sum((x - mean_x)^2, na.rm = TRUE) / (n - 1))
kurtosis <- sum(((x - mean_x) / s)^4, na.rm = TRUE) / n - 3
return(kurtosis)
}Energy
I calculate signal energy within each epoch.
R code Cell 28
# Energy feature
energy = function(x) {
mean(x^2)
}Spectral features
I calculate features from the frequency representation of each signal.
R code Cell 30
# Spectral mean
spe_mean <- function(var) {
spec <- spectrum(var, plot = FALSE)
df <- spec$freq[2] - spec$freq[1]
spe_mean <- sum(spec$freq * spec$spec * df)
return(spe_mean)
}
# Spectral variance
spe_var <- function(var) {
spec <- spectrum(var, plot = FALSE)
df <- spec$freq[2] - spec$freq[1]
spe_var <- sum((spec$freq - mean(var))^2 * spec$spec * df)
return(spe_var)
}
# Spectral peak frequency function
spectral_peak = function(x) {
spec = spectrum(x, plot = FALSE)
peak = spec$freq[which.max(spec$spec)]
return(peak)
}
Activity label
I assign the most frequent sample label to each epoch.
R code Cell 32
# Most common value feature
most_common_value = function(x) {
counts = table(x, useNA='no')
most_frequent = which.max(counts)
return(names(most_frequent))
}Root mean square
I calculate RMS features for the signal channels.
R code Cell 34
# Magnitude feature
rms <- function(x){
sqrt((1/length(x)*(sum(x^2))))
}Assemble the feature table
I combine the epoch features before fitting classifiers.
R code Cell 36
# Listing all raw data files
list.files("./RawData/Train/", pattern = "^acc")
list.files("./RawData/Train/", pattern = "^gyro")- 'acc_exp01_user01.txt'
- 'acc_exp02_user01.txt'
- 'acc_exp05_user03.txt'
- 'acc_exp06_user03.txt'
- 'acc_exp09_user05.txt'
- 'acc_exp10_user05.txt'
- 'acc_exp11_user06.txt'
- 'acc_exp12_user06.txt'
- 'acc_exp13_user07.txt'
- 'acc_exp14_user07.txt'
- 'acc_exp15_user08.txt'
- 'acc_exp16_user08.txt'
- 'acc_exp22_user11.txt'
- 'acc_exp23_user11.txt'
- 'acc_exp28_user14.txt'
- 'acc_exp29_user14.txt'
- 'acc_exp30_user15.txt'
- 'acc_exp31_user15.txt'
- 'acc_exp32_user16.txt'
- 'acc_exp33_user16.txt'
- 'acc_exp34_user17.txt'
- 'acc_exp35_user17.txt'
- 'acc_exp38_user19.txt'
- 'acc_exp39_user19.txt'
- 'acc_exp42_user21.txt'
- 'acc_exp43_user21.txt'
- 'acc_exp44_user22.txt'
- 'acc_exp45_user22.txt'
- 'acc_exp46_user23.txt'
- 'acc_exp47_user23.txt'
- 'acc_exp50_user25.txt'
- 'acc_exp51_user25.txt'
- 'acc_exp52_user26.txt'
- 'acc_exp53_user26.txt'
- 'acc_exp54_user27.txt'
- 'acc_exp55_user27.txt'
- 'acc_exp56_user28.txt'
- 'acc_exp57_user28.txt'
- 'acc_exp58_user29.txt'
- 'acc_exp59_user29.txt'
- 'acc_exp60_user30.txt'
- 'acc_exp61_user30.txt'
- 'gyro_exp01_user01.txt'
- 'gyro_exp02_user01.txt'
- 'gyro_exp05_user03.txt'
- 'gyro_exp06_user03.txt'
- 'gyro_exp09_user05.txt'
- 'gyro_exp10_user05.txt'
- 'gyro_exp11_user06.txt'
- 'gyro_exp12_user06.txt'
- 'gyro_exp13_user07.txt'
- 'gyro_exp14_user07.txt'
- 'gyro_exp15_user08.txt'
- 'gyro_exp16_user08.txt'
- 'gyro_exp22_user11.txt'
- 'gyro_exp23_user11.txt'
- 'gyro_exp28_user14.txt'
- 'gyro_exp29_user14.txt'
- 'gyro_exp30_user15.txt'
- 'gyro_exp31_user15.txt'
- 'gyro_exp32_user16.txt'
- 'gyro_exp33_user16.txt'
- 'gyro_exp34_user17.txt'
- 'gyro_exp35_user17.txt'
- 'gyro_exp38_user19.txt'
- 'gyro_exp39_user19.txt'
- 'gyro_exp42_user21.txt'
- 'gyro_exp43_user21.txt'
- 'gyro_exp44_user22.txt'
- 'gyro_exp45_user22.txt'
- 'gyro_exp46_user23.txt'
- 'gyro_exp47_user23.txt'
- 'gyro_exp50_user25.txt'
- 'gyro_exp51_user25.txt'
- 'gyro_exp52_user26.txt'
- 'gyro_exp53_user26.txt'
- 'gyro_exp54_user27.txt'
- 'gyro_exp55_user27.txt'
- 'gyro_exp56_user28.txt'
- 'gyro_exp57_user28.txt'
- 'gyro_exp58_user29.txt'
- 'gyro_exp59_user29.txt'
- 'gyro_exp60_user30.txt'
- 'gyro_exp61_user30.txt'
Apply the feature functions
I apply the feature calculations to each labelled epoch.
R code Cell 38
# Feature function
extractFeatures <- function(filename, sample_labels) {
# Extract user and experimental run ID's from file name
username = gsub(".+user(\\d+).+", "\\1", filename) %>% as.numeric()
expname = gsub( ".+exp(\\d+).+", "\\1", filename) %>% as.numeric()
# Import the sensor signals from the file
user01 <- read_delim(filename, " ", col_names = F, progress = TRUE,
col_types = "ddd")
# Merge signals with labels
user_df <-
data.frame(userid = username, trial = expname, user01) %>%
mutate(sampleid = 0:(nrow(user01)-1) ) %>%
left_join(sample_labels, by = c('userid','trial','sampleid'))
# Split in epochs of 128 samples and compute features per epoch
usertimedom <- user_df %>%
# Add an epoch ID variable (on epoch = 2.56 sec)
mutate(epoch = sampleid %/% 128) %>%
# Extract statistical features from each epoch
group_by(epoch) %>%
summarise(
# Keep track of user and experiment information
user_id = username,
exp_id = expname,
# Epoch's activity labels and start sample
activity = most_common_value(c("-", activity)),
sampleid = sampleid[1],
# Adding Features ---------------------------
# Mean
m1 = mean(X1),
m2 = mean(X2),
m3 = mean(X3),
# Mean difference
mdif1_2 = m1 - m2,
mdif1_3 = m1 - m3,
mdif2_3 = m2 - m3,
# Minimum
min1 = min(X1),
min2 = min(X2),
min3 = min(X3),
# Maximum
max1 = max(X1),
max2 = max(X2),
max3 = max(X3),
# Standard deviation
sd1 = sd(X1),
sd2 = sd(X2),
sd3 = sd(X3),
# Median
median1 = median(X1),
median2 = median(X2),
median3 = median(X3),
# Range
range1 = max(X1) - min (X1),
range2 = max(X2) - min (X2),
range3 = max(X3) - min (X3),
# 25th percentile
q1_25 = quantile(X1, .25),
q2_25 = quantile(X2, .25),
q3_25 = quantile(X3, .25),
# 50th percentile
q1_50 = quantile(X1, .50),
q2_50 = quantile(X2, .50),
q3_50 = quantile(X3, .50),
# 75th percentile
q1_75 = quantile(X1, .75),
q2_75 = quantile(X2, .75),
q3_75 = quantile(X3, .75),
# Autocorrelation for X1(t1) and X1(t2) etc.
AR1.1 = cor(X1, lag(X1), use = "pairwise"),
AR1.2 = cor(X1, lag(X1, n = 2), use = "pairwise"),
AR2.1 = cor(X1, lag(X2), use = "pairwise"),
AR2.2 = cor(X1, lag(X2, n = 2), use = "pairwise"),
AR3.1 = cor(X1, lag(X3), use = "pairwise"),
AR3.2 = cor(X1, lag(X3, n = 2), use = "pairwise"),
# Cross-lagged correlation for X1(t1) and X2(t0) etc.
AR12.1 = cor(X1, lag(X2), use = "pairwise"),
AR12.2 = cor(X1, lag(X2), use = "pairwise"),
AR13.1 = cor(X1, lag(X2), use = "pairwise"),
AR13.2 = cor(X1, lag(X2), use = "pairwise"),
AR23.1 = cor(X1, lag(X2), use = "pairwise"),
AR23.2 = cor(X1, lag(X2), use = "pairwise"),
# Skewness
skew1 = e1071::skewness(X1),
skew2 = e1071::skewness(X2),
skew3 = e1071::skewness(X3),
# Kurtosis
kurt1 = e1071::kurtosis(X1),
kurt2 = e1071::kurtosis(X2),
kurt3 = e1071::kurtosis(X3),
# Energy
energy_1 = mean(X1^2),
energy_2 = mean(X2^2),
energy_3 = mean(X3^2),
# Spectral mean
spec_mean_1 = spe_mean(X1),
spec_mean_2 = spe_mean(X2),
spec_mean_3 = spe_mean(X3),
# Spectral variance
spec_var_1 = spe_var(X1),
spec_var_2 = spe_var(X2),
spec_var_3 = spe_var(X3),
# Spectral peak
spectral_peak1 = spectral_peak(X1),
spectral_peak2 = spectral_peak(X2),
spectral_peak3 = spectral_peak(X3),
# RMS
rms_1 = rms(X1),
rms_1 = rms(X2),
rms_1 = rms(X3),
n_samples = n()
)
usertimedom
}
# Loading both acc and gyro data ---------------------------
filename_acc <- dir("./RawData/Train/", "^acc", full.names = TRUE)
filename_gyro <- dir("./RawData/Train/", "^gyro", full.names = TRUE)
# Using map_dfr to run the function `extractFeatures` on all elements
myData_acc = map_dfr(filename_acc, extractFeatures, sample_labels)
myData_gyro = map_dfr(filename_gyro, extractFeatures, sample_labels)
# Check the results
print(myData_acc)
print(myData_gyro)[90m# A tibble: 6,252 × 67[39m epoch user_id exp_id activity sampleid m1 m2 m3 mdif1_2 mdif1_3 [3m[90m<dbl>[39m[23m [3m[90m<dbl>[39m[23m [3m[90m<dbl>[39m[23m [3m[90m<chr>[39m[23m [3m[90m<int>[39m[23m [3m[90m<dbl>[39m[23m [3m[90m<dbl>[39m[23m [3m[90m<dbl>[39m[23m [3m[90m<dbl>[39m[23m [3m[90m<dbl>[39m[23m [90m 1[39m 0 1 1 - 0 0.909 -[31m0[39m[31m.[39m[31m165[39m 0.252 1.07 0.657 [90m 2[39m 1 1 1 STANDING 128 1.02 -[31m0[39m[31m.[39m[31m132[39m 0.050[4m9[24m 1.15 0.965 [90m 3[39m 2 1 1 STANDING 256 1.02 -[31m0[39m[31m.[39m[31m124[39m 0.098[4m7[24m 1.14 0.920 [90m 4[39m 3 1 1 STANDING 384 1.02 -[31m0[39m[31m.[39m[31m125[39m 0.092[4m6[24m 1.15 0.927 [90m 5[39m 4 1 1 STANDING 512 1.02 -[31m0[39m[31m.[39m[31m130[39m 0.085[4m1[24m 1.15 0.935 [90m 6[39m 5 1 1 STANDING 640 1.02 -[31m0[39m[31m.[39m[31m129[39m 0.085[4m0[24m 1.15 0.936 [90m 7[39m 6 1 1 STANDING 768 1.02 -[31m0[39m[31m.[39m[31m138[39m 0.073[4m9[24m 1.16 0.947 [90m 8[39m 7 1 1 STANDING 896 1.02 -[31m0[39m[31m.[39m[31m131[39m 0.076[4m5[24m 1.15 0.945 [90m 9[39m 8 1 1 STANDING [4m1[24m024 1.02 -[31m0[39m[31m.[39m[31m136[39m 0.075[4m0[24m 1.16 0.946 [90m10[39m 9 1 1 STANDING [4m1[24m152 1.02 -[31m0[39m[31m.[39m[31m129[39m 0.092[4m5[24m 1.15 0.927 [90m# ℹ 6,242 more rows[39m [90m# ℹ 57 more variables: mdif2_3 <dbl>, min1 <dbl>, min2 <dbl>, min3 <dbl>,[39m [90m# max1 <dbl>, max2 <dbl>, max3 <dbl>, sd1 <dbl>, sd2 <dbl>, sd3 <dbl>,[39m [90m# median1 <dbl>, median2 <dbl>, median3 <dbl>, range1 <dbl>, range2 <dbl>,[39m [90m# range3 <dbl>, q1_25 <dbl>, q2_25 <dbl>, q3_25 <dbl>, q1_50 <dbl>,[39m [90m# q2_50 <dbl>, q3_50 <dbl>, q1_75 <dbl>, q2_75 <dbl>, q3_75 <dbl>,[39m [90m# AR1.1 <dbl>, AR1.2 <dbl>, AR2.1 <dbl>, AR2.2 <dbl>, AR3.1 <dbl>, …[39m [90m# A tibble: 6,252 × 67[39m epoch user_id exp_id activity sampleid m1 m2 m3 mdif1_2 [3m[90m<dbl>[39m[23m [3m[90m<dbl>[39m[23m [3m[90m<dbl>[39m[23m [3m[90m<chr>[39m[23m [3m[90m<int>[39m[23m [3m[90m<dbl>[39m[23m [3m[90m<dbl>[39m[23m [3m[90m<dbl>[39m[23m [3m[90m<dbl>[39m[23m [90m 1[39m 0 1 1 - 0 -[31m0[39m[31m.[39m[31m135[39m -[31m0[39m[31m.[39m[31m209[39m -[31m0[39m[31m.[39m[31m0[39m[31m23[4m4[24m[39m 0.074[4m2[24m [90m 2[39m 1 1 1 STANDING 128 0.023[4m4[24m 0.051[4m7[24m 0.004[4m0[24m[4m7[24m -[31m0[39m[31m.[39m[31m0[39m[31m28[4m3[24m[39m [90m 3[39m 2 1 1 STANDING 256 0.008[4m6[24m[4m2[24m -[31m0[39m[31m.[39m[31m00[39m[31m2[4m7[24m[4m3[24m[39m 0.002[4m4[24m[4m0[24m 0.011[4m3[24m [90m 4[39m 3 1 1 STANDING 384 0.006[4m2[24m[4m5[24m -[31m0[39m[31m.[39m[31m00[39m[31m6[4m7[24m[4m7[24m[39m 0.005[4m7[24m[4m0[24m 0.013[4m0[24m [90m 5[39m 4 1 1 STANDING 512 0.001[4m2[24m[4m6[24m 0.000[4m0[24m[4m2[24m[4m3[24m9 -[31m0[39m[31m.[39m[31m000[39m[31m[4m2[24m[4m5[24m[4m3[24m[39m 0.001[4m2[24m[4m3[24m [90m 6[39m 5 1 1 STANDING 640 0.001[4m4[24m[4m6[24m -[31m0[39m[31m.[39m[31m00[39m[31m5[4m6[24m[4m8[24m[39m 0.007[4m4[24m[4m4[24m 0.007[4m1[24m[4m4[24m [90m 7[39m 6 1 1 STANDING 768 0.005[4m3[24m[4m3[24m 0.000[4m2[24m[4m4[24m[4m8[24m -[31m0[39m[31m.[39m[31m00[39m[31m1[4m7[24m[4m1[24m[39m 0.005[4m0[24m[4m8[24m [90m 8[39m 7 1 1 STANDING 896 0.007[4m8[24m[4m1[24m 0.001[4m4[24m[4m9[24m 0.003[4m3[24m[4m0[24m 0.006[4m3[24m[4m1[24m [90m 9[39m 8 1 1 STANDING [4m1[24m024 0.005[4m3[24m[4m5[24m -[31m0[39m[31m.[39m[31m00[39m[31m1[4m2[24m[4m4[24m[39m 0.002[4m0[24m[4m4[24m 0.006[4m5[24m[4m9[24m [90m10[39m 9 1 1 STANDING [4m1[24m152 0.139 0.008[4m8[24m[4m8[24m 0.006[4m7[24m[4m6[24m 0.131 [90m# ℹ 6,242 more rows[39m [90m# ℹ 58 more variables: mdif1_3 <dbl>, mdif2_3 <dbl>, min1 <dbl>, min2 <dbl>,[39m [90m# min3 <dbl>, max1 <dbl>, max2 <dbl>, max3 <dbl>, sd1 <dbl>, sd2 <dbl>,[39m [90m# sd3 <dbl>, median1 <dbl>, median2 <dbl>, median3 <dbl>, range1 <dbl>,[39m [90m# range2 <dbl>, range3 <dbl>, q1_25 <dbl>, q2_25 <dbl>, q3_25 <dbl>,[39m [90m# q1_50 <dbl>, q2_50 <dbl>, q3_50 <dbl>, q1_75 <dbl>, q2_75 <dbl>,[39m [90m# q3_75 <dbl>, AR1.1 <dbl>, AR1.2 <dbl>, AR2.1 <dbl>, AR2.2 <dbl>, …[39m
Combine both sensors
I join accelerometer and gyroscope features by their epoch identifiers.
R code Cell 40
# Create a combined dataset
myData <- myData_acc %>%
left_join(myData_gyro, by = c("user_id", "exp_id", "activity", "sampleid", "epoch", "n_samples"))
# Print the combined dataset
head(myData)
# #Check dim
dim(myData)| epoch | user_id | exp_id | activity | sampleid | m1.x | m2.x | m3.x | mdif1_2.x | mdif1_3.x | ⋯ | spec_mean_1.y | spec_mean_2.y | spec_mean_3.y | spec_var_1.y | spec_var_2.y | spec_var_3.y | spectral_peak1.y | spectral_peak2.y | spectral_peak3.y | rms_1.y |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| <dbl> | <dbl> | <dbl> | <chr> | <int> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | ⋯ | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> |
| 0 | 1 | 1 | - | 0 | 0.908941 | -0.1649089 | 0.25218100 | 1.073850 | 0.6567600 | ⋯ | 1.597967e-02 | 3.941905e-02 | 1.764163e-02 | 1.014774e-02 | 4.497898e-02 | 4.245081e-03 | 0.0156250 | 0.0078125 | 0.0468750 | 0.518407957 |
| 1 | 1 | 1 | STANDING | 128 | 1.015723 | -0.1324110 | 0.05088976 | 1.148134 | 0.9648329 | ⋯ | 1.200725e-03 | 2.435418e-02 | 3.629848e-03 | 2.736837e-04 | 4.224565e-03 | 9.422170e-04 | 0.0625000 | 0.0546875 | 0.0234375 | 0.316900721 |
| 2 | 1 | 1 | STANDING | 256 | 1.019217 | -0.1241102 | 0.09873047 | 1.143327 | 0.9204862 | ⋯ | 3.785223e-06 | 2.675050e-06 | 3.296914e-06 | 6.570965e-07 | 8.137783e-07 | 1.000899e-06 | 0.0312500 | 0.0156250 | 0.0078125 | 0.006480723 |
| 3 | 1 | 1 | STANDING | 384 | 1.020074 | -0.1254232 | 0.09264323 | 1.145497 | 0.9274306 | ⋯ | 5.830368e-06 | 4.833804e-06 | 4.049781e-06 | 7.517440e-07 | 1.298793e-06 | 7.509417e-07 | 0.0312500 | 0.0234375 | 0.0078125 | 0.011226791 |
| 4 | 1 | 1 | STANDING | 512 | 1.020172 | -0.1297960 | 0.08509115 | 1.149968 | 0.9350804 | ⋯ | 5.064767e-06 | 4.562142e-06 | 3.406414e-06 | 8.908834e-07 | 1.117966e-06 | 7.320533e-07 | 0.0078125 | 0.0312500 | 0.0312500 | 0.009186572 |
| 5 | 1 | 1 | STANDING | 640 | 1.020757 | -0.1285590 | 0.08496094 | 1.149316 | 0.9357965 | ⋯ | 4.914521e-06 | 3.769410e-06 | 4.274318e-06 | 8.420718e-07 | 8.804029e-07 | 7.971838e-07 | 0.0312500 | 0.0781250 | 0.0546875 | 0.014473497 |
- 6252
- 128
Clean predictors
I prepare the feature table for model fitting.
R code Cell 42
# Remove rows with NA values
myData1 <- myData[myData$activity != "-", ]
# Remove rows with epoch not 128
myData2 <- myData1 %>% filter(n_samples == 128)
# Check dim
dim(myData2)- 4900
- 128
Near-zero variance
I remove predictors with little variation.
R code Cell 44
# Coloums we want to keep
drop.cols <- c("epoch", "user_id", "exp_id", "activity", "sampleid", "n_samples")
check_data <- myData2 %>%
select(-one_of(drop.cols))
# Check whether any predictors are at or near zero variance
nzv <- nearZeroVar(check_data)
nzv_names <- colnames(check_data)[nzv]
nzv_names
# Remove any predictors near zero variance
myData3 <- myData2 %>%
select(-one_of(nzv_names))
# Check dim
dim(myData3)character(0)
- 4900
- 128
Correlated predictors
I remove highly correlated predictors.
R code Cell 46
# Create a dataframe containing only numeric values
myData3_num <- myData3 %>% select_if(is.numeric)
# Remove constant variables
myData3_num <- myData3_num %>%
select(-where(~ sd(.) == 0))
# Check for missing values
if (anyNA(myData3_num)) {
myData3_num <- myData3_num %>% na.omit() # This removes rows with missing values
}
# Calculate the correlation matrix
R <- cor(myData3_num, use = "pairwise.complete.obs")
# Specify the variables to keep
variables_to_keep <- c("epoch", "user_id", "exp_id", "activity", "sampleid", "n_samples")
# Identify the indices of variables to remove based on high correlation
cor_indices <- caret::findCorrelation(R, cutoff = 0.8)
# Find the variable names to remove, excluding the specified variables to keep
vars_to_remove <- names(myData3_num)[cor_indices]
vars_to_remove <- setdiff(vars_to_remove, variables_to_keep)
# Remove the highly correlated variables from myData2
myData4 <- myData3 %>% select(-one_of(vars_to_remove))
# Check the resulting dataset
head(myData4)
# Check dim
dim(myData4)| epoch | user_id | exp_id | activity | sampleid | mdif2_3.x | max2.x | max3.x | q1_25.x | AR1.2.x | ⋯ | energy_3.y | spec_mean_1.y | spec_mean_2.y | spec_mean_3.y | spec_var_1.y | spec_var_2.y | spec_var_3.y | spectral_peak1.y | spectral_peak2.y | spectral_peak3.y |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| <dbl> | <dbl> | <dbl> | <chr> | <int> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | ⋯ | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> |
| 1 | 1 | 1 | STANDING | 128 | -0.1833008 | 0.005555556 | 0.34722224 | 0.9861112 | 0.064119281 | ⋯ | 1.004261e-01 | 1.200725e-03 | 2.435418e-02 | 3.629848e-03 | 2.736837e-04 | 4.224565e-03 | 9.422170e-04 | 0.0625000 | 0.0546875 | 0.0234375 |
| 2 | 1 | 1 | STANDING | 256 | -0.2228407 | -0.115277783 | 0.10972223 | 1.0180556 | -0.135593631 | ⋯ | 4.199977e-05 | 3.785223e-06 | 2.675050e-06 | 3.296914e-06 | 6.570965e-07 | 8.137783e-07 | 1.000899e-06 | 0.0312500 | 0.0156250 | 0.0078125 |
| 3 | 1 | 1 | STANDING | 384 | -0.2180664 | -0.108333336 | 0.10972223 | 1.0180556 | -0.005905649 | ⋯ | 1.260408e-04 | 5.830368e-06 | 4.833804e-06 | 4.049781e-06 | 7.517440e-07 | 1.298793e-06 | 7.509417e-07 | 0.0312500 | 0.0234375 | 0.0078125 |
| 4 | 1 | 1 | STANDING | 512 | -0.2148872 | -0.118055555 | 0.10138889 | 1.0180556 | -0.132593922 | ⋯ | 8.439310e-05 | 5.064767e-06 | 4.562142e-06 | 3.406414e-06 | 8.908834e-07 | 1.117966e-06 | 7.320533e-07 | 0.0078125 | 0.0312500 | 0.0312500 |
| 5 | 1 | 1 | STANDING | 640 | -0.2135200 | -0.111111120 | 0.10416667 | 1.0194445 | 0.178460566 | ⋯ | 2.094821e-04 | 4.914521e-06 | 3.769410e-06 | 4.274318e-06 | 8.420718e-07 | 8.804029e-07 | 7.971838e-07 | 0.0312500 | 0.0781250 | 0.0546875 |
| 6 | 1 | 1 | STANDING | 768 | -0.2124024 | -0.125000002 | 0.09027778 | 1.0194445 | -0.058692423 | ⋯ | 7.809171e-05 | 3.424867e-06 | 3.047141e-06 | 2.725354e-06 | 6.472644e-07 | 6.346981e-07 | 6.838516e-07 | 0.0234375 | 0.0703125 | 0.0078125 |
- 4900
- 59
Identifier columns
I exclude identifiers from the predictor set.
R code Cell 48
# Creating a new dataframe only including the classes to predict and the predictors
cleaned_data <- myData4 %>%
select(-epoch, -user_id, -exp_id, -sampleid, -n_samples)
# Visualizing the clean data
head(cleaned_data)
# Check dim
dim(cleaned_data)| activity | mdif2_3.x | max2.x | max3.x | q1_25.x | AR1.2.x | AR2.2.x | AR3.1.x | skew1.x | skew2.x | ⋯ | energy_3.y | spec_mean_1.y | spec_mean_2.y | spec_mean_3.y | spec_var_1.y | spec_var_2.y | spec_var_3.y | spectral_peak1.y | spectral_peak2.y | spectral_peak3.y |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| <chr> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | ⋯ | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> |
| STANDING | -0.1833008 | 0.005555556 | 0.34722224 | 0.9861112 | 0.064119281 | 0.085554567 | -0.03499456 | -0.07560850 | -0.03534337 | ⋯ | 1.004261e-01 | 1.200725e-03 | 2.435418e-02 | 3.629848e-03 | 2.736837e-04 | 4.224565e-03 | 9.422170e-04 | 0.0625000 | 0.0546875 | 0.0234375 |
| STANDING | -0.2228407 | -0.115277783 | 0.10972223 | 1.0180556 | -0.135593631 | -0.005873311 | -0.10414516 | 0.01030481 | -0.16029915 | ⋯ | 4.199977e-05 | 3.785223e-06 | 2.675050e-06 | 3.296914e-06 | 6.570965e-07 | 8.137783e-07 | 1.000899e-06 | 0.0312500 | 0.0156250 | 0.0078125 |
| STANDING | -0.2180664 | -0.108333336 | 0.10972223 | 1.0180556 | -0.005905649 | -0.007810915 | -0.14667653 | -0.37654877 | 0.42325422 | ⋯ | 1.260408e-04 | 5.830368e-06 | 4.833804e-06 | 4.049781e-06 | 7.517440e-07 | 1.298793e-06 | 7.509417e-07 | 0.0312500 | 0.0234375 | 0.0078125 |
| STANDING | -0.2148872 | -0.118055555 | 0.10138889 | 1.0180556 | -0.132593922 | 0.064557081 | -0.09294806 | -0.28076432 | 0.07004263 | ⋯ | 8.439310e-05 | 5.064767e-06 | 4.562142e-06 | 3.406414e-06 | 8.908834e-07 | 1.117966e-06 | 7.320533e-07 | 0.0078125 | 0.0312500 | 0.0312500 |
| STANDING | -0.2135200 | -0.111111120 | 0.10416667 | 1.0194445 | 0.178460566 | -0.230092431 | -0.32227365 | -0.03199556 | -0.97726258 | ⋯ | 2.094821e-04 | 4.914521e-06 | 3.769410e-06 | 4.274318e-06 | 8.420718e-07 | 8.804029e-07 | 7.971838e-07 | 0.0312500 | 0.0781250 | 0.0546875 |
| STANDING | -0.2124024 | -0.125000002 | 0.09027778 | 1.0194445 | -0.058692423 | -0.103462750 | 0.01222745 | -0.09266163 | -0.37172185 | ⋯ | 7.809171e-05 | 3.424867e-06 | 3.047141e-06 | 2.725354e-06 | 6.472644e-07 | 6.346981e-07 | 6.838516e-07 | 0.0234375 | 0.0703125 | 0.0078125 |
- 4900
- 54
Fit classifiers
I compare four classifiers with ten-fold cross-validation at the epoch level.
R code Cell 50
# Cross validate our models using 10 folds
trcntr <- trainControl('cv', number = 10, p = 0.8)Multinomial logistic regression
I fit a multiclass logistic model.
R code Cell 52
# Fitting Multinomial logistic regression
fit_multinom = caret::train(activity ~ ., data = cleaned_data,
method = "multinom",
trControl = trcntr,
MaxNWts = 1500,
verbose = FALSE,
trace = FALSE)
# Storing Results
fit_multinom$result| decay | Accuracy | Kappa | AccuracySD | KappaSD | |
|---|---|---|---|---|---|
| <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | |
| 1 | 0e+00 | 0.8904028 | 0.8718951 | 0.011619118 | 0.013624341 |
| 2 | 1e-04 | 0.8906178 | 0.8721476 | 0.011429757 | 0.013395675 |
| 3 | 1e-01 | 0.8924445 | 0.8741821 | 0.008213072 | 0.009669994 |
Linear discriminant analysis
I fit an LDA classifier.
R code Cell 54
# Fitting LDA
fit_lda <- caret::train(activity ~., data = cleaned_data,
method = 'lda',
trControl = trcntr)
# Storing Results
fit_lda$result| parameter | Accuracy | Kappa | AccuracySD | KappaSD | |
|---|---|---|---|---|---|
| <chr> | <dbl> | <dbl> | <dbl> | <dbl> | |
| 1 | none | 0.8640917 | 0.8411544 | 0.01615548 | 0.01888035 |
K-nearest neighbours
I compare the neighbour counts specified below.
R code Cell 56
# FittingkNN
fit_knn = caret::train(activity ~ ., data = cleaned_data,
method="knn",
trControl = trcntr)
# Storing Results
fit_knn$result| k | Accuracy | Kappa | AccuracySD | KappaSD | |
|---|---|---|---|---|---|
| <int> | <dbl> | <dbl> | <dbl> | <dbl> | |
| 1 | 5 | 0.7391782 | 0.6934371 | 0.02120219 | 0.02501264 |
| 2 | 7 | 0.7404040 | 0.6947158 | 0.02018013 | 0.02379809 |
| 3 | 9 | 0.7393599 | 0.6932666 | 0.01485224 | 0.01759701 |
Scaled K-nearest neighbours
I repeat KNN with standardized predictors.
R code Cell 58
# Fitting KNN
fit_KNN1 <- caret::train(activity ~ ., data = cleaned_data,
method = "knn",
trControl = trcntr,
preProcess = "scale",
metric = "Accuracy")
# Storing Results
fit_KNN1$results| k | Accuracy | Kappa | AccuracySD | KappaSD | |
|---|---|---|---|---|---|
| <int> | <dbl> | <dbl> | <dbl> | <dbl> | |
| 1 | 5 | 0.8793736 | 0.8588036 | 0.014675466 | 0.01720100 |
| 2 | 7 | 0.8789608 | 0.8582581 | 0.009870772 | 0.01159560 |
| 3 | 9 | 0.8781528 | 0.8572684 | 0.013123999 | 0.01544385 |
Compare results
I compare the best CV accuracy from each classifier.
R code Cell 60
# Create a data frame with model names and accuracy scores
model_names <- c("multinom", "lda", "knn", "knn1") # Update with your actual model names
Acc <- sapply(list(fit_multinom, fit_lda, fit_knn, fit_KNN1), function(mdl) max(mdl$results$Accuracy))
model_data <- data.frame(Model = model_names, Accuracy = Acc)
# Find the best performing model
best_model <- model_data[which.max(model_data$Accuracy), ]
# Create a bar plot with ggplot2 with pastel blue and green colors, increased font size, and thinner bars
ggplot(model_data, aes(x = Model, y = Accuracy, fill = Model)) +
geom_bar(stat = "identity", width = 0.5) + # Adjust the width parameter for thinner bars
scale_fill_manual(values = c("multinom" = "#66c2a5", "lda" = "#fc8d62", "knn" = "#8da0cb", "knn1" = "#e78ac3")) + # Specify pastel colors
labs(title = "Model Performance for Physical Activity Classifier",
x = "Model",
y = "Accuracy") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 30, hjust = 1, size = 30),
axis.text.y = element_text(size = 30),
axis.title = element_text(size = 30),
plot.title = element_text(size = 30))
Prepare test predictions
I extract the same features from the test recordings.
R code Cell 62
# Iterate through accelerometer filenames
filenames_test_acc = list.files("./RawData/Test/", "^acc", full.names = TRUE)
test_acc <- map_dfr(filenames_test_acc, extractFeatures, sample_labels)
# Iterate through gyroscope filenames
filenames_test_gyro = list.files("./RawData/Test/", "^gyro", full.names = TRUE)
test_gyro <- map_dfr(filenames_test_gyro, extractFeatures, sample_labels)
# Merge together
myData_test <- test_acc %>%
left_join(test_gyro,
by = c("user_id", "exp_id", "activity", "sampleid", "epoch", "n_samples"))
# Take a look
head(myData_test)| epoch | user_id | exp_id | activity | sampleid | m1.x | m2.x | m3.x | mdif1_2.x | mdif1_3.x | ⋯ | spec_mean_1.y | spec_mean_2.y | spec_mean_3.y | spec_var_1.y | spec_var_2.y | spec_var_3.y | spectral_peak1.y | spectral_peak2.y | spectral_peak3.y | rms_1.y |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| <dbl> | <dbl> | <dbl> | <chr> | <int> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | ⋯ | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> |
| 0 | 2 | 3 | - | 0 | 0.1693034 | -0.2382921 | 0.8003690 | 0.4075955 | -0.6310656 | ⋯ | 6.649702e-02 | 8.405664e-02 | 1.299314e-02 | 8.140186e-01 | 2.149156e-01 | 7.925323e-02 | 0.0390625 | 0.0078125 | 0.0078125 | 1.00235456 |
| 1 | 2 | 3 | - | 128 | 0.8997071 | -0.3948568 | -0.2429905 | 1.2945639 | 1.1426975 | ⋯ | 2.112746e-02 | 4.620913e-02 | 2.985326e-02 | 3.599322e-03 | 5.963980e-03 | 6.690383e-02 | 0.0703125 | 0.0859375 | 0.0156250 | 1.04465051 |
| 2 | 2 | 3 | - | 256 | 0.9974935 | -0.2712565 | 0.1135525 | 1.2687500 | 0.8839410 | ⋯ | 7.125756e-04 | 1.885431e-03 | 4.497974e-04 | 1.642089e-04 | 3.012466e-04 | 9.166789e-05 | 0.0625000 | 0.0234375 | 0.0312500 | 0.13675246 |
| 3 | 2 | 3 | - | 384 | 0.9928928 | -0.2702691 | 0.1391276 | 1.2631619 | 0.8537652 | ⋯ | 1.936236e-05 | 1.576628e-05 | 1.157673e-05 | 1.059964e-06 | 2.485608e-06 | 1.569735e-06 | 0.0078125 | 0.0156250 | 0.0078125 | 0.01994949 |
| 4 | 2 | 3 | - | 512 | 0.9908854 | -0.2861979 | 0.1286350 | 1.2770834 | 0.8622504 | ⋯ | 8.137027e-06 | 7.494763e-06 | 6.702299e-06 | 7.778906e-07 | 1.065188e-06 | 7.606784e-07 | 0.0078125 | 0.0312500 | 0.0390625 | 0.01951123 |
| 5 | 2 | 3 | - | 640 | 0.9909180 | -0.2904948 | 0.1215712 | 1.2814128 | 0.8693468 | ⋯ | 6.573663e-06 | 1.111430e-05 | 1.244990e-05 | 9.572516e-07 | 1.402107e-06 | 1.640208e-06 | 0.0312500 | 0.0468750 | 0.0468750 | 0.01805453 |
Predict activities
I apply the selected classifier to the test feature table.
R code Cell 64
# Create a df to use for creating predictions
pred_df <- myData_test %>%
select(-epoch, -user_id, -exp_id, -sampleid, n_samples)
# We create predictinos based on fit_lda_clean
predictions <- predict(object = fit_multinom, newdata = myData_test[,-1])
# Put the predictions into test_df
myData_test["activity"] <- predictions
# Take a look
head(myData_test)| epoch | user_id | exp_id | activity | sampleid | m1.x | m2.x | m3.x | mdif1_2.x | mdif1_3.x | ⋯ | spec_mean_1.y | spec_mean_2.y | spec_mean_3.y | spec_var_1.y | spec_var_2.y | spec_var_3.y | spectral_peak1.y | spectral_peak2.y | spectral_peak3.y | rms_1.y |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| <dbl> | <dbl> | <dbl> | <fct> | <int> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | ⋯ | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> | <dbl> |
| 0 | 2 | 3 | LIE_TO_STAND | 0 | 0.1693034 | -0.2382921 | 0.8003690 | 0.4075955 | -0.6310656 | ⋯ | 6.649702e-02 | 8.405664e-02 | 1.299314e-02 | 8.140186e-01 | 2.149156e-01 | 7.925323e-02 | 0.0390625 | 0.0078125 | 0.0078125 | 1.00235456 |
| 1 | 2 | 3 | STAND_TO_SIT | 128 | 0.8997071 | -0.3948568 | -0.2429905 | 1.2945639 | 1.1426975 | ⋯ | 2.112746e-02 | 4.620913e-02 | 2.985326e-02 | 3.599322e-03 | 5.963980e-03 | 6.690383e-02 | 0.0703125 | 0.0859375 | 0.0156250 | 1.04465051 |
| 2 | 2 | 3 | STANDING | 256 | 0.9974935 | -0.2712565 | 0.1135525 | 1.2687500 | 0.8839410 | ⋯ | 7.125756e-04 | 1.885431e-03 | 4.497974e-04 | 1.642089e-04 | 3.012466e-04 | 9.166789e-05 | 0.0625000 | 0.0234375 | 0.0312500 | 0.13675246 |
| 3 | 2 | 3 | STANDING | 384 | 0.9928928 | -0.2702691 | 0.1391276 | 1.2631619 | 0.8537652 | ⋯ | 1.936236e-05 | 1.576628e-05 | 1.157673e-05 | 1.059964e-06 | 2.485608e-06 | 1.569735e-06 | 0.0078125 | 0.0156250 | 0.0078125 | 0.01994949 |
| 4 | 2 | 3 | STANDING | 512 | 0.9908854 | -0.2861979 | 0.1286350 | 1.2770834 | 0.8622504 | ⋯ | 8.137027e-06 | 7.494763e-06 | 6.702299e-06 | 7.778906e-07 | 1.065188e-06 | 7.606784e-07 | 0.0078125 | 0.0312500 | 0.0390625 | 0.01951123 |
| 5 | 2 | 3 | STANDING | 640 | 0.9909180 | -0.2904948 | 0.1215712 | 1.2814128 | 0.8693468 | ⋯ | 6.573663e-06 | 1.111430e-05 | 1.244990e-05 | 9.572516e-07 | 1.402107e-06 | 1.640208e-06 | 0.0312500 | 0.0468750 | 0.0468750 | 0.01805453 |
Export predictions
I format the predictions for the course competition.
R code Cell 66
# Formating our data for the competition
myData_test %>%
# Prepend "user" and "exp" to user_id and exp_id
mutate(
user_id = paste(ifelse(user_id < 10, "user0", "user"), user_id, sep=""),
exp_id = paste(ifelse(exp_id < 10, "exp0", "exp"), exp_id, sep="")
) %>%
# Unite columns user_id, exp_id and sample_id into a string
# Separated by "_" and store it in the new variable `Id`
unite(Id, user_id, exp_id, sampleid) %>%
# Retain only the `Id` and predictions
select(Id, Predicted = activity) %>%
# Write to file
write_csv("submission_final.csv")
# Check the result: print first 20 lines in the submission file
cat(readLines("submission_final.csv",20), sep="\n")Id,Predicted user02_exp03_0,LIE_TO_STAND user02_exp03_128,STAND_TO_SIT user02_exp03_256,STANDING user02_exp03_384,STANDING user02_exp03_512,STANDING user02_exp03_640,STANDING user02_exp03_768,STANDING user02_exp03_896,STANDING user02_exp03_1024,STANDING user02_exp03_1152,STANDING user02_exp03_1280,STANDING user02_exp03_1408,STANDING user02_exp03_1536,STANDING user02_exp03_1664,SITTING user02_exp03_1792,SITTING user02_exp03_1920,SITTING user02_exp03_2048,SITTING user02_exp03_2176,SITTING user02_exp03_2304,SITTING
Dataset credits
I used the Smartphone-Based Recognition of Human Activities and Postural Transitions dataset. The original documentation credits Jorge L. Reyes-Ortiz, Davide Anguita, Luca Oneto, Xavier Parra, and collaborators. I retain the full source documentation in data/README.txt.
Project credits
Project authors: Laura Maria Fetz and Yuxuan Xiang. I wrote the notebook text and prepared its layout and figures. Yuxuan and I shared feature extraction and model selection.