Background
I should start off by saying I am not a mathematician and please excuse simple/stupid mistakes!
The goal of my exercise is to find the “best-fitting” model for the purpose of prediction. However, there are a couple of caveats:
I am only interested in predicting over the upper quartile of the response points;
under-predicting the value of the response is MUCH worse than over-predicting, and;
I have three specific values I wish to calculate – lets say 90, 99 and 99.99.
In this case I am working with support vector machines (SVMs) which my previous investigations have shown to be the best performing models out of a wide range of possible model types. For this particular exercise the array of possible models are SVMs with the same formula but different tuning parameters.
This is an example of some analogous data:
df <- structure(list(Covariate = c(100, 91.1242603550296, 86.9822485207101,
100, 0, 0, 90.5325443786982, 95.8579881656805, 88.7573964497041,
96.4497041420118, 82.2485207100592, 99.4082840236686, 99.4082840236686,
98.8165680473373, 91.7159763313609, 59.1715976331361, 44.9704142011834,
0, 100, 95.2662721893491, 100, 82.8402366863905, 7.69230769230769,
81.6568047337278, 62.7218934911243, 97.6331360946746, 73.9644970414201,
8.87573964497041, 0, 98.8165680473373, 78.1065088757396, 98.2248520710059,
52.6627218934911, 96.4497041420118, 52.0710059171598, 0, 62.043795620438,
84.6715328467153, 97.8102189781022, 4.37956204379562, 89.051094890511,
99.2700729927007, 99.2700729927007, 97.0802919708029, 81.7518248175183,
80.2919708029197, 90.5109489051095, 99.2700729927007, 96.3503649635037,
0, 0, 94.8905109489051, 79.5620437956204, 67.8832116788321, 73.7226277372263,
100, 97.0802919708029, 93.4306569343066, 86.8613138686131, 33.5766423357664,
32.1167883211679, 46.7153284671533, 98.5401459854015, 95.6204379562044,
86.1313868613139, 14.5985401459854, 92.7007299270073, 86.1313868613139,
0, 77.3722627737226, 89.051094890511, 80.2919708029197, 98.1818181818182,
96.3636363636364, 30.9090909090909, 0, 60.9090909090909, 100,
0, 83.6363636363636, 88.1818181818182, 97.2727272727273, 0, 0,
99.0909090909091, 100, 100, 91.8181818181818, 88.1818181818182,
46.3636363636364, 50.9090909090909, 99.0909090909091, 97.2727272727273,
100, 0, 92.7272727272727, 60.9090909090909, 90.9090909090909,
57.2727272727273, 76.3636363636364, 94.5454545454545, 50, 98.1818181818182,
16.3636363636364, 87.2727272727273, 92.7272727272727, 87.2727272727273,
88.1818181818182, 10.7438016528926, 91.7355371900827, 98.3471074380165,
60.3305785123967, 95.8677685950413, 0, 63.6363636363636, 71.900826446281,
0, 74.3801652892562, 76.8595041322314, 0, 61.9834710743802, 0,
0, 0, 84.297520661157, 47.1074380165289, 69.4214876033058, 97.5206611570248,
100, 61.1570247933884, 90.0826446280992, 78.5123966942149, 10.7438016528926,
100, 98.3471074380165, 100, 98.3471074380165, 93.3884297520661,
90.9090909090909, 57.8512396694215, 57.8512396694215, 92.5619834710744,
77.6859504132231, 69.4214876033058), Response = c(20, 14, 14,
20, 0, 0, 14, 14, 14, 16, 10, 20, 20, 20, 16, 10, 10, 0, 16,
16, 16, 10, 0, 12, 10, 12, 12, 0, 0, 20, 12, 16, 10, 12, 12,
0, 14, 14, 16, 0, 14, 20, 16, 20, 14, 12, 12, 20, 20, 0, 0, 14,
12, 10, 10, 20, 16, 16, 14, 10, 10, 10, 20, 16, 10, 0, 12, 12,
0, 12, 16, 14, 16, 14, 0, 0, 12, 20, 0, 12, 14, 14, 0, 0, 20,
20, 20, 14, 14, 10, 10, 20, 16, 16, 0, 12, 10, 10, 10, 16, 16,
12, 20, 10, 12, 12, 16, 14, 0, 16, 20, 12, 14, 10, 10, 0, 0,
12, 12, 10, 10, 0, 0, 0, 14, 12, 12, 20, 20, 14, 14, 14, 12,
20, 20, 20, 16, 16, 14, 10, 10, 16, 16, 16)), row.names = 433:576, class = "data.frame")
To these ends @missuse has written some code to use leave-one-out cross-validation over the upper quartile of time points. At each iteration of the cross-validation I am currently calculating a weighted root mean square error (wRMSE), where I have made a simple (read crude) alteration which penalises model under-prediction 3× as harshly as over-predictions.
Here is the wRMSE function which I am feeding into the caret::train
function:
wRMSESummary <- function (data,lev = NULL,model = NULL) {Res <- (data$obs - data$pred)
Res[Res < 0] <- Res[Res < 0] * 3 #Finishing wRMSE calculation
out <- mean(sqrt(Res^2), na.rm = T)
names(out) <- "wRMSE"
out}
And here is the code I am using to carry out LOOCV over the upper range of the response which @missuse wrote:
`#Creating folds for upper quartile LOO-CV
df %>%
mutate(id = row_number()) %>% #create a column called id which will hold the row numbers
filter(TrtTime >= unname(summary(df$Response)[5])) %>% #subset data frame according to your description
split(.$id) %>% #split the data frame into lists by id (row number)
map(~ .x %>% select(id) %>% #clean up so it works with indexOut argument in trainControl
unlist %>%
unname) -> dfFolds #Turning test sets into training sets
dfFolds <- lapply(dfFolds, function(x) setdiff(1:nrow(df), x))`
and finally the code for feeding both these into the
caret::train
function via caret::traincontrol
:
#Fitting traincontrol
dfcontrol <- trainControl(index = dfFolds, method = "cv", summaryFunction = wRMSESummary)
#Fitting SVM
Poly.SVR2 <- train(log(Response + 0.0001) ~ Covariate+ I(Covariate^2),
data = df,
tuneLength = 5, method = "svmPoly",
trControl = dfcontrol)
Problem
I want to find a less arbitrary value than just multiplying the error of under-predictions by 3. Hopefully one that gives me some idea of the chance my model is under-predicting (if possible). By multiplying by a scalar as I outlined above, I believe (please correct me if I am wrong) that I will have diminishing returns as the value of the scalar increases- i.e. the PE will tend towards a particular model as the scalar increases. Or put another way the point estimates that I am interested in calculating (see point 3 in the background) will converge as my scalar tends towards infinity. If so, I was wondering if it would be possible to calculate the receiver-operator curve (ROC) that would characterise the loss in change and from here work out the area under the curve (AUC) which could in turn be used to determine what the optimal scalar would be, by say trying to account for 95% of the change in the fitted values across the upper quartile.
Does that even make sense? This is really out of my comfort zone but any guidance would be greatly appreciated!
I am completely open to alternative ideas and really looking for critiques as to whether or not this makes sense! If it does, would anybody be able to show me how I might go about determining this optimal value, preferably in R
.