Background

Using devices such as Jawbone Up, Nike FuelBand, and Fitbit it is now possible to collect a large amount of data about personal activity relatively inexpensively. These type of devices are part of the quantified self movement – a group of enthusiasts who take measurements about themselves regularly to improve their health, to find patterns in their behavior, or because they are tech geeks. One thing that people regularly do is quantify how much of a particular activity they do, but they rarely quantify how well they do it. In this project, your goal will be to use data from accelerometers on the belt, forearm, arm, and dumbell of 6 participants. They were asked to perform barbell lifts correctly and incorrectly in 5 different ways. More information is available from the website here: http://groupware.les.inf.puc-rio.br/har (see the section on the Weight Lifting Exercise Dataset).

Training Data: https://d396qusza40orc.cloudfront.net/predmachlearn/pml-training.csv

Test Data: https://d396qusza40orc.cloudfront.net/predmachlearn/pml-testing.csv

The data for this project come from this source: http://groupware.les.inf.puc-rio.br/har. If you use the document you create for this class for any purpose please cite them as they have been very generous in allowing their data to be used for this kind of assignment.

The goal of your project is to predict the manner in which they did the exercise. This is the “classe” variable in the training set. You may use any of the other variables to predict with. You should create a report describing:

You will also use your prediction model to predict 20 different test cases.

Libraries and environment

The required libraries are listed and loaded into the environment below. For reproduce-ability, we have also set a seed.

library(tidyverse)
library(data.table)
library(caret)
library(rattle)
library(parallel)
library(doParallel)
set.seed(19930212)

Load data

Load data into R.

train_url <- "https://d396qusza40orc.cloudfront.net/predmachlearn/pml-training.csv"
test_url <- "https://d396qusza40orc.cloudfront.net/predmachlearn/pml-testing.csv"

raw_train <- fread(train_url)
raw_test <- fread(test_url)

Feature selection

Some initial exploratory analysis was performed on the data. This can be found in notebooks repository in several different notebooks. The original data has dimensions of 19622, 160. This many features are likely not required and could result in over-fitting. The next section of this document will outline how the final features were selected for use in the model.

Cleaning the data

Before selecting any features, we will first clean the data so it is ready for use with the caret package.

# Create a new dataframe for training
training <- as.data.frame(raw_train)

# clean the data

# turn the classe column into a factor
training$classe <- factor(training$classe)

# turn all integers into numeric
for (i in colnames(training)){
  class_i <- class(training[[i]])
  if(class_i == "integer"){training[[i]] <- as.numeric(training[[i]])}
}

# how many features are there?
dim(training)
## [1] 19622   160

Removing zero covariates

Features that have little variance will not be useful to include in the model. We can use R to search for features with near zero variance, and remove them from the model.

# check for near zero variance
nsv <- nearZeroVar(training, saveMetrics = TRUE)
nsv[nsv$nzv == TRUE,]
# remove the columns with near zero variances
drop_cols <- rownames(nsv[nsv$nzv == TRUE,])
training <- training %>% select(-drop_cols)

# how many features are there?
dim(training)
## [1] 19622   124

Remove missing values

The data has some columns with many missing values. Generally, the columns that look to be summary level statistics are missing values in most cases (e.g. columns starting with max, min, var, etc.)

# total missing values
sum(is.na(training))
## [1] 1250007
# missing values by column
sapply(training, function(x) sum(is.na(x))/nrow(training))
##                       V1                user_name     raw_timestamp_part_1 
##                0.0000000                0.0000000                0.0000000 
##     raw_timestamp_part_2           cvtd_timestamp               num_window 
##                0.0000000                0.0000000                0.0000000 
##                roll_belt               pitch_belt                 yaw_belt 
##                0.0000000                0.0000000                0.0000000 
##         total_accel_belt       kurtosis_roll_belt      kurtosis_picth_belt 
##                0.0000000                0.9798186                0.9809398 
##       skewness_roll_belt     skewness_roll_belt.1            max_roll_belt 
##                0.9797676                0.9809398                0.9793089 
##           max_picth_belt             max_yaw_belt            min_roll_belt 
##                0.9793089                0.9798186                0.9793089 
##           min_pitch_belt             min_yaw_belt      amplitude_roll_belt 
##                0.9793089                0.9798186                0.9793089 
##     amplitude_pitch_belt     var_total_accel_belt            avg_roll_belt 
##                0.9793089                0.9793089                0.9793089 
##         stddev_roll_belt            var_roll_belt           avg_pitch_belt 
##                0.9793089                0.9793089                0.9793089 
##        stddev_pitch_belt           var_pitch_belt             avg_yaw_belt 
##                0.9793089                0.9793089                0.9793089 
##          stddev_yaw_belt             var_yaw_belt             gyros_belt_x 
##                0.9793089                0.9793089                0.0000000 
##             gyros_belt_y             gyros_belt_z             accel_belt_x 
##                0.0000000                0.0000000                0.0000000 
##             accel_belt_y             accel_belt_z            magnet_belt_x 
##                0.0000000                0.0000000                0.0000000 
##            magnet_belt_y            magnet_belt_z                 roll_arm 
##                0.0000000                0.0000000                0.0000000 
##                pitch_arm                  yaw_arm          total_accel_arm 
##                0.0000000                0.0000000                0.0000000 
##            var_accel_arm              gyros_arm_x              gyros_arm_y 
##                0.9793089                0.0000000                0.0000000 
##              gyros_arm_z              accel_arm_x              accel_arm_y 
##                0.0000000                0.0000000                0.0000000 
##              accel_arm_z             magnet_arm_x             magnet_arm_y 
##                0.0000000                0.0000000                0.0000000 
##             magnet_arm_z        kurtosis_roll_arm       kurtosis_picth_arm 
##                0.0000000                0.9832841                0.9833860 
##         kurtosis_yaw_arm        skewness_roll_arm       skewness_pitch_arm 
##                0.9798695                0.9832331                0.9833860 
##         skewness_yaw_arm            max_picth_arm              max_yaw_arm 
##                0.9798695                0.9793089                0.9793089 
##              min_yaw_arm        amplitude_yaw_arm            roll_dumbbell 
##                0.9793089                0.9793089                0.0000000 
##           pitch_dumbbell             yaw_dumbbell   kurtosis_roll_dumbbell 
##                0.0000000                0.0000000                0.9795638 
##  kurtosis_picth_dumbbell   skewness_roll_dumbbell  skewness_pitch_dumbbell 
##                0.9794109                0.9795128                0.9793599 
##        max_roll_dumbbell       max_picth_dumbbell         max_yaw_dumbbell 
##                0.9793089                0.9793089                0.9795638 
##        min_roll_dumbbell       min_pitch_dumbbell         min_yaw_dumbbell 
##                0.9793089                0.9793089                0.9795638 
##  amplitude_roll_dumbbell amplitude_pitch_dumbbell     total_accel_dumbbell 
##                0.9793089                0.9793089                0.0000000 
##       var_accel_dumbbell        avg_roll_dumbbell     stddev_roll_dumbbell 
##                0.9793089                0.9793089                0.9793089 
##        var_roll_dumbbell       avg_pitch_dumbbell    stddev_pitch_dumbbell 
##                0.9793089                0.9793089                0.9793089 
##       var_pitch_dumbbell         avg_yaw_dumbbell      stddev_yaw_dumbbell 
##                0.9793089                0.9793089                0.9793089 
##         var_yaw_dumbbell         gyros_dumbbell_x         gyros_dumbbell_y 
##                0.9793089                0.0000000                0.0000000 
##         gyros_dumbbell_z         accel_dumbbell_x         accel_dumbbell_y 
##                0.0000000                0.0000000                0.0000000 
##         accel_dumbbell_z        magnet_dumbbell_x        magnet_dumbbell_y 
##                0.0000000                0.0000000                0.0000000 
##        magnet_dumbbell_z             roll_forearm            pitch_forearm 
##                0.0000000                0.0000000                0.0000000 
##              yaw_forearm    kurtosis_roll_forearm   kurtosis_picth_forearm 
##                0.0000000                0.9835898                0.9836408 
##    skewness_roll_forearm   skewness_pitch_forearm        max_picth_forearm 
##                0.9835389                0.9836408                0.9793089 
##          max_yaw_forearm        min_pitch_forearm          min_yaw_forearm 
##                0.9835898                0.9793089                0.9835898 
##  amplitude_pitch_forearm      total_accel_forearm        var_accel_forearm 
##                0.9793089                0.0000000                0.9793089 
##          gyros_forearm_x          gyros_forearm_y          gyros_forearm_z 
##                0.0000000                0.0000000                0.0000000 
##          accel_forearm_x          accel_forearm_y          accel_forearm_z 
##                0.0000000                0.0000000                0.0000000 
##         magnet_forearm_x         magnet_forearm_y         magnet_forearm_z 
##                0.0000000                0.0000000                0.0000000 
##                   classe 
##                0.0000000
# Remove columns that do not have enough data. We will remove any column that does not have data for at least 95% of observations.
greater_than_95 <- sapply(training, function(x) sum(is.na(x))/nrow(training)<0.05)
training <- training[, greater_than_95]

# how many features are there?
dim(training)
## [1] 19622    59

Remove specific columns

Lastly, based on our knowledge of the data and problem we are looking to solve, we will remove specific features that are not relevant:

  • “V1” should be removed as it is an index and is random
  • “user_name” should be removed since we want to predict the outcome based on the sensor data, not the person performing the exercise
  • “raw_timestamp_part_1”, “raw_timestamp_part_2”, “cvtd_timestamp”, “new_window”, and “num_window” should all be removed because they are timestamps, and do not have any meaningful relationship to how well the movement was performed
  • any column that starts with “total” because it is summary statistic and not capturing the same level of detail as the rest of the data
# drop selected columns
drop_cols <- c("V1", "user_name", "raw_timestamp_part_1", "raw_timestamp_part_2", "cvtd_timestamp", "new_window", "num_window")
training <- training %>% select(-one_of(drop_cols))
## Warning: Unknown columns: `new_window`
# remove columns that start with "total"
training <- training %>% select(-starts_with("total"))

# how many features are there?
dim(training)
## [1] 19622    49

Selected features

Our model will use the following features.

str(training)
## 'data.frame':    19622 obs. of  49 variables:
##  $ roll_belt        : num  1.41 1.41 1.42 1.48 1.48 1.45 1.42 1.42 1.43 1.45 ...
##  $ pitch_belt       : num  8.07 8.07 8.07 8.05 8.07 8.06 8.09 8.13 8.16 8.17 ...
##  $ yaw_belt         : num  -94.4 -94.4 -94.4 -94.4 -94.4 -94.4 -94.4 -94.4 -94.4 -94.4 ...
##  $ gyros_belt_x     : num  0 0.02 0 0.02 0.02 0.02 0.02 0.02 0.02 0.03 ...
##  $ gyros_belt_y     : num  0 0 0 0 0.02 0 0 0 0 0 ...
##  $ gyros_belt_z     : num  -0.02 -0.02 -0.02 -0.03 -0.02 -0.02 -0.02 -0.02 -0.02 0 ...
##  $ accel_belt_x     : num  -21 -22 -20 -22 -21 -21 -22 -22 -20 -21 ...
##  $ accel_belt_y     : num  4 4 5 3 2 4 3 4 2 4 ...
##  $ accel_belt_z     : num  22 22 23 21 24 21 21 21 24 22 ...
##  $ magnet_belt_x    : num  -3 -7 -2 -6 -6 0 -4 -2 1 -3 ...
##  $ magnet_belt_y    : num  599 608 600 604 600 603 599 603 602 609 ...
##  $ magnet_belt_z    : num  -313 -311 -305 -310 -302 -312 -311 -313 -312 -308 ...
##  $ roll_arm         : num  -128 -128 -128 -128 -128 -128 -128 -128 -128 -128 ...
##  $ pitch_arm        : num  22.5 22.5 22.5 22.1 22.1 22 21.9 21.8 21.7 21.6 ...
##  $ yaw_arm          : num  -161 -161 -161 -161 -161 -161 -161 -161 -161 -161 ...
##  $ gyros_arm_x      : num  0 0.02 0.02 0.02 0 0.02 0 0.02 0.02 0.02 ...
##  $ gyros_arm_y      : num  0 -0.02 -0.02 -0.03 -0.03 -0.03 -0.03 -0.02 -0.03 -0.03 ...
##  $ gyros_arm_z      : num  -0.02 -0.02 -0.02 0.02 0 0 0 0 -0.02 -0.02 ...
##  $ accel_arm_x      : num  -288 -290 -289 -289 -289 -289 -289 -289 -288 -288 ...
##  $ accel_arm_y      : num  109 110 110 111 111 111 111 111 109 110 ...
##  $ accel_arm_z      : num  -123 -125 -126 -123 -123 -122 -125 -124 -122 -124 ...
##  $ magnet_arm_x     : num  -368 -369 -368 -372 -374 -369 -373 -372 -369 -376 ...
##  $ magnet_arm_y     : num  337 337 344 344 337 342 336 338 341 334 ...
##  $ magnet_arm_z     : num  516 513 513 512 506 513 509 510 518 516 ...
##  $ roll_dumbbell    : num  13.1 13.1 12.9 13.4 13.4 ...
##  $ pitch_dumbbell   : num  -70.5 -70.6 -70.3 -70.4 -70.4 ...
##  $ yaw_dumbbell     : num  -84.9 -84.7 -85.1 -84.9 -84.9 ...
##  $ gyros_dumbbell_x : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ gyros_dumbbell_y : num  -0.02 -0.02 -0.02 -0.02 -0.02 -0.02 -0.02 -0.02 -0.02 -0.02 ...
##  $ gyros_dumbbell_z : num  0 0 0 -0.02 0 0 0 0 0 0 ...
##  $ accel_dumbbell_x : num  -234 -233 -232 -232 -233 -234 -232 -234 -232 -235 ...
##  $ accel_dumbbell_y : num  47 47 46 48 48 48 47 46 47 48 ...
##  $ accel_dumbbell_z : num  -271 -269 -270 -269 -270 -269 -270 -272 -269 -270 ...
##  $ magnet_dumbbell_x: num  -559 -555 -561 -552 -554 -558 -551 -555 -549 -558 ...
##  $ magnet_dumbbell_y: num  293 296 298 303 292 294 295 300 292 291 ...
##  $ magnet_dumbbell_z: num  -65 -64 -63 -60 -68 -66 -70 -74 -65 -69 ...
##  $ roll_forearm     : num  28.4 28.3 28.3 28.1 28 27.9 27.9 27.8 27.7 27.7 ...
##  $ pitch_forearm    : num  -63.9 -63.9 -63.9 -63.9 -63.9 -63.9 -63.9 -63.8 -63.8 -63.8 ...
##  $ yaw_forearm      : num  -153 -153 -152 -152 -152 -152 -152 -152 -152 -152 ...
##  $ gyros_forearm_x  : num  0.03 0.02 0.03 0.02 0.02 0.02 0.02 0.02 0.03 0.02 ...
##  $ gyros_forearm_y  : num  0 0 -0.02 -0.02 0 -0.02 0 -0.02 0 0 ...
##  $ gyros_forearm_z  : num  -0.02 -0.02 0 0 -0.02 -0.03 -0.02 0 -0.02 -0.02 ...
##  $ accel_forearm_x  : num  192 192 196 189 189 193 195 193 193 190 ...
##  $ accel_forearm_y  : num  203 203 204 206 206 203 205 205 204 205 ...
##  $ accel_forearm_z  : num  -215 -216 -213 -214 -214 -215 -215 -213 -214 -215 ...
##  $ magnet_forearm_x : num  -17 -18 -18 -16 -17 -9 -18 -9 -16 -22 ...
##  $ magnet_forearm_y : num  654 661 658 658 655 660 659 660 653 656 ...
##  $ magnet_forearm_z : num  476 473 469 469 473 478 470 474 476 473 ...
##  $ classe           : Factor w/ 5 levels "A","B","C","D",..: 1 1 1 1 1 1 1 1 1 1 ...

Cross validation

Partition the training data into a subset so we can better estimate the out of sample error rate.

inTrain <- createDataPartition(y = training$classe, p = 0.6, list = FALSE)
training_train <- training[inTrain,]
training_test <- training[-inTrain,]

Model: Random Forests

Random forests was selected as the model of choice due to the high accuracy of the predictions. Originally, a classification tree model was built using “rpart”. While this model was highly interpret-able, the accuracy was not very good. Using rpart I was not able to achieve the 80% accuracy required to pass the week 4 quiz. This lead to the random forest model described below as the final model.

Setting up parallel implementation

Random forests was selected as model due to its ability to make very accurate predictions. On this large dataset, random forest can be quite slow. To speed up the model tips were used from this blog post on parrallel implementation.

# Step 1: configure parallel processing
cluster <- makeCluster(detectCores() - 1) # leave one core for the OS
registerDoParallel(cluster)

# Step 2: Configure train control object
fitControl <- trainControl(method = "cv", # use cross validation, faster than random forest defaults
                           number = 5, # using larger number could improve accuracy, but 5 should be suffecient
                           allowParallel = TRUE)

# Step 3: Develop training model
model_1 <- train(classe ~ .,
                 data = training_train,
                 method = "rf",
                 trControl = fitControl)

# Step 4: De-register the parallel processing cluster
stopCluster(cluster)
registerDoSEQ()

Assess the model results

# view model
model_1$finalModel
## 
## Call:
##  randomForest(x = x, y = y, mtry = param$mtry) 
##                Type of random forest: classification
##                      Number of trees: 500
## No. of variables tried at each split: 2
## 
##         OOB estimate of  error rate: 0.85%
## Confusion matrix:
##      A    B    C    D    E class.error
## A 3341    4    0    1    2 0.002090800
## B   21 2251    7    0    0 0.012286090
## C    0   19 2032    3    0 0.010710808
## D    0    0   35 1893    2 0.019170984
## E    0    0    1    5 2159 0.002771363

In sample error

First we will assess the accuracy of our model using the same data that was used to build the model.

predict_in_sample <- predict(model_1$finalModel, newdata = training_train, type = "class")
conf_in_sample <- confusionMatrix(predict_in_sample, training_train$classe)
conf_in_sample
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction    A    B    C    D    E
##          A 3348    0    0    0    0
##          B    0 2279    0    0    0
##          C    0    0 2054    0    0
##          D    0    0    0 1930    0
##          E    0    0    0    0 2165
## 
## Overall Statistics
##                                      
##                Accuracy : 1          
##                  95% CI : (0.9997, 1)
##     No Information Rate : 0.2843     
##     P-Value [Acc > NIR] : < 2.2e-16  
##                                      
##                   Kappa : 1          
##                                      
##  Mcnemar's Test P-Value : NA         
## 
## Statistics by Class:
## 
##                      Class: A Class: B Class: C Class: D Class: E
## Sensitivity            1.0000   1.0000   1.0000   1.0000   1.0000
## Specificity            1.0000   1.0000   1.0000   1.0000   1.0000
## Pos Pred Value         1.0000   1.0000   1.0000   1.0000   1.0000
## Neg Pred Value         1.0000   1.0000   1.0000   1.0000   1.0000
## Prevalence             0.2843   0.1935   0.1744   0.1639   0.1838
## Detection Rate         0.2843   0.1935   0.1744   0.1639   0.1838
## Detection Prevalence   0.2843   0.1935   0.1744   0.1639   0.1838
## Balanced Accuracy      1.0000   1.0000   1.0000   1.0000   1.0000
conf_in_sample$overall[[1]]
## [1] 1

Our model has predicted the correct classe with an accuracy of 1. This number gives us a good sense of what the out of sample error may be. However we can get a better estimate by using the portion of the training data that we set aside to estimate the out of sample error rate.

Out of sample error

To get a better estimate of the true out of sample error rate, we can predict using the training data that was set aside.

predict_out_sample <- predict(model_1$finalModel, newdata = training_test, type = "class")
conf_out_sample <- confusionMatrix(predict_out_sample, training_test$classe)
conf_out_sample
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction    A    B    C    D    E
##          A 2230   13    0    0    0
##          B    1 1503   20    0    0
##          C    0    2 1346   38    0
##          D    0    0    2 1248    6
##          E    1    0    0    0 1436
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9894          
##                  95% CI : (0.9869, 0.9916)
##     No Information Rate : 0.2845          
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.9866          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: A Class: B Class: C Class: D Class: E
## Sensitivity            0.9991   0.9901   0.9839   0.9705   0.9958
## Specificity            0.9977   0.9967   0.9938   0.9988   0.9998
## Pos Pred Value         0.9942   0.9862   0.9711   0.9936   0.9993
## Neg Pred Value         0.9996   0.9976   0.9966   0.9942   0.9991
## Prevalence             0.2845   0.1935   0.1744   0.1639   0.1838
## Detection Rate         0.2842   0.1916   0.1716   0.1591   0.1830
## Detection Prevalence   0.2859   0.1942   0.1767   0.1601   0.1832
## Balanced Accuracy      0.9984   0.9934   0.9889   0.9846   0.9978

Our model has predicted the correct classe with an accuracy of 0.9894214. This number is a good estimate of the out of sample error rate, as these records were not used to build the model.

Predictions

The final step is to apply our model to the test data. Before applying the model, we must first make the same transformations to the test data that we made to the training data.

# Create a new data frame
testing <- as.data.frame(raw_test)

# turn all integers into numeric
for (i in colnames(testing)){
  class_i <- class(testing[[i]])
  if(class_i == "integer"){testing[[i]] <- as.numeric(testing[[i]])}
}

# Keep only the same columns that were used in the training model
keep_cols <- names(training)
keep_cols <- keep_cols[!keep_cols == "classe"] # remove classe, since this does not exist in the testing data
testing <- testing[ ,keep_cols]

Our data is now ready to be fed into the model for predictions.

predict_final <- predict(model_1$finalModel, newdata = testing, type = "class")
(predict_final)
##  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 
##  B  A  B  A  A  E  D  B  A  A  B  C  B  A  E  E  A  B  B  B 
## Levels: A B C D E

These predictions were submitted into week 4 Coursera project quiz with 100% accuracy.