Back testing for Stock Market with R - r

I am very new user for R and want to use R for back testing my Strategy. I try to combine some scripts found in web. However, it did not work according my idea. My problem is the transaction date cannot be generated according to my strategy design date.
library(quantmod)
library(lubridate)
stock1<-getSymbols("AAPL",src="yahoo",from="2016-01-01",auto.assign=F)
stock1<-na.locf(stock1)
stock1$EMA9<-EMA(Cl(stock1),n=9)
stock1$EMA19<-EMA(Cl(stock1),n=19)
stock1$EMACheck<-ifelse(stock1$EMA9>stock1$EMA19,1,0)
stock1$EMA_CrossOverUp<-ifelse(diff(stock1$EMACheck)==1,1,0)
stock1$EMA_CrossOverDown<-ifelse(diff(stock1$EMACheck)==-1,-1,0)
stock1<-stock1[index(stock1)>="2016-01-01",]
stock1_df<-data.frame(index(stock1),coredata(stock1))
colnames(stock1_df)<-c("Date","Open","High","Low","Close","Volume","Adj","EMA9","EMA19","EMACheck","EMACheck_up","EMACheck_down")
#To calculate the number of crossoverup transactions during the duration from 2016-01-01
sum(stock1_df$EMACheck_up==1 & index(stock1)>="2016-01-01",na.rm=T)
stock1_df$Date[stock1_df$EMACheck_up==1 & index(stock1)>="2016-01-01"]
sum(stock1_df$EMACheck_down==-1 & index(stock1)>="2016-01-01",na.rm=T)
stock1_df$Date[stock1_df$EMACheck_down==-1 & index(stock1)>="2016-01-01"]
#To generate the transcation according to the strategy
transaction_dates<-function(stock2,Buy,Sell)
{
Date_buy<-c()
Date_sell<-c()
hold<-F
stock2[["Hold"]]<-hold
for(i in 1:nrow(stock2)) {
if(hold == T) {
stock2[["Hold"]][i]<-T
if(stock2[[Sell]][i] == -1) {
#stock2[["Hold"]][i]<-T
hold<-F
}
} else {
if(stock2[[Buy]][i] == 1) {
hold<-T
stock2[["Hold"]][i]<-T
}
}
}
stock2[["Enter"]]<-c(0,ifelse(diff(stock2[["Hold"]])==1,1,0))
stock2[["Exit"]]<-c(ifelse(diff(stock2[["Hold"]])==-1,-1,0),0)
Buy_date <- stock2[["Date"]][stock2[["Enter"]] == 1]
Sell_date <- stock2[["Date"]][stock2[["Exit"]] == -1]
if (length(Sell_date)<length(Buy_date)){
#Sell_date[length(Sell_date)+1]<-tail(stock2[["Date"]],n=2)[1]
Buy_date<-Buy_date[1:length(Buy_date)-1]
}
return(list(DatesBuy=Buy_date,DatesSell=Sell_date))
}
#transaction dates generate:
stock1_df <- na.locf(stock1_df)
transactionDates<-transaction_dates(stock1_df,"EMACheck_up","EMACheck_down")
transactionDates
num_transaction1<-length(transactionDates[[1]])
Open_price<-function(df,x) {df[as.integer(rownames(df[df[["Date"]]==x,]))+1,][["Open"]]}
transactions_date<-function(df,x) {df[as.integer(rownames(df[df[["Date"]]==x,]))+1,][["Date"]]}
transactions_generate<-function(df,num_transaction)
{
price_buy<-sapply(1:num_transaction,function(x) {Open_price(df,transactionDates[[1]][x])})
price_sell<-sapply(1:num_transaction,function(x) {Open_price(df,transactionDates[[2]][x])})
Dates_buy<-as.Date(sapply(1:num_transaction,function(x) {transactions_date(df,transactionDates[[1]][x])}))
Dates_sell<-as.Date(sapply(1:num_transaction,function(x) {transactions_date(df,transactionDates[[2]][x])}))
transactions_df<-data.frame(DatesBuy=Dates_buy,DatesSell=Dates_sell,pricesBuy=price_buy,pricesSell=price_sell)
#transactions_df$return<-100*(transactions_df$pricesSell-transactions_df$pricesBuy)/transactions_df$pricesBuy
transactions_df$Stop_loss<-NA
return(transactions_df)
}
transaction_summary<-transactions_generate(stock1_df,num_transaction1)
transaction_summary$Return<-100*(transaction_summary$pricesSell-transaction_summary$pricesBuy)/transaction_summary$pricesBuy
transaction_summary
sum(transaction_summary$Return,na.rm=T)
Hi, I am very new user for R and want to use R for back testing my Strategy. I try to combine some scripts found in web. However, it did not work according my idea. My problem is the transaction date cannot be generated according to my strategy design date.
problem as this image

The code you have is to complicated for it's own good.
The issue lies in the fact that the functions Open_price and transactions_date look for use rownames to find a record number and then take the next one. But then instead of looking for the rownames again, it is used as an index. There it goes wrong.
If you look at the following result for the first date, it returns 40.
as.integer(rownames(stock1_df[stock1_df[["Date"]] == "2016-03-01", ]))
[1] 40
So the next record it would look for will be 41. But stock_df[41, ] is not the same as rowname 41. An issue with rownames is that if you filter / remove records from the data.frame the rownames don't change. To get the correct index number you should use which. If you look at the stock1_df, you can see that it returns 21 and we need record 22
which(stock1_df[["Date"]] == "2016-03-01")
[1] 21
I changed the Open_price and transactions_date functions to use the which function. This will now return the correct results.
Open_price <- function(df, x) {
df[which(df[["Date"]] == x) + 1, ][["Open"]]
}
transactions_date <- function(df, x) {
df[which(df[["Date"]] == x) + 1, ][["Date"]]
}
head(transaction_summary)
DatesBuy DatesSell pricesBuy pricesSell Stop_loss Return
1 2016-03-02 2016-04-25 100.51 105.00 NA 4.467215
2 2016-05-27 2016-06-20 99.44 96.00 NA -3.459374
3 2016-07-13 2016-09-12 97.41 102.65 NA 5.379322
4 2016-09-15 2016-11-02 113.86 111.40 NA -2.160547
5 2016-12-12 2017-06-13 113.29 147.16 NA 29.896728
6 2017-07-17 2017-09-19 148.82 159.51 NA 7.183166
A bit of advice, try to use spaces in your code. That makes it more readable. Look for example at this style guide. Your whole code be rewritten to only use stock1 without the need to turning it into a data.frame halfway your code. But for now the code does what it needs to do.

Related

Attribute labels to scored items [function]

I don't know if the subject has already been find but here my problem :
I have a dataset from behaviors personality items scored from 1 to 8 and I would like to convert each scored according a range (e.g. 1-2 = Rare ; 3-5 = Occasionally ; 6-8 = Frequent).
I succeed to create new columns and put labels in it but I don't understand why I have same repetition in others columns :
Beh_data[,c(2,3,4,32,33,34)
enter image description here
You can see that columns with "_class" had the same outputs, and there are mistakes about correct match between labels and scores (e.g. row4 -- 8 put as Occasionally)
Here the function code :
l = unlist(names(Beh_data[,2:28]))
for (j in 1:length(l)) {
cl[j] = list(paste(l[j],"class",sep="_"))
for (k in 1:length(cl)) {
Beh_data[,cl[[k]] ] <- cl[[k]]
for(i in 1:nrow(Beh_data)){
Beh_data[,cl[[k]] ][i] <-ifelse(Beh_data[,l[j] ][i]<3, "Rare", Beh_data[,cl[[k]] ][i])
Beh_data[,cl[[k]] ][i] <-ifelse(Beh_data[,l[j] ][i]>2 & Beh_data[,l[j] ][i]<6, "Occasionally", Beh_data[,cl[[k] ] ][i])
Beh_data[,cl[[k]] ][i] <-ifelse(Beh_data[,l[j] ][i]>5, "Frequent", Beh_data[,cl[[k]] ][i])
}
}
}
I tried to see if it's could from a wrong annotation as cl[[k]] ] or something like this but it steels doesn't work
Do you have any ideas please ?
If you're open to a dplyr solution, I think its across and case_when functions are helpful here. It should also run faster since it's vectorized. This will create new columns like aff_sum_class which use the categorization you've specified.
library(dplyr)
Beh_data |>
mutate(across(aff_sum:qui_sum,
~case_when(. >= 6 ~ "Frequent",
. >= 3 ~ "Occasionally",
TRUE ~ "Rare"),
.names = "{.col}_class"))

Using R to create a new dataframe using tests from values in another dataframe

I have salesorder data in the following (sample) format:
salesorder <- c('TM001', 'TM002', 'TM003', 'TM004')
esttxndate <- as.Date(c('2018-10-01', '2018-10-01', '2018-10-04', '2018-10-06'))
potxndate <- as.Date(c('2018-10-07', '2018-10-06', '2018-10-14', '2018-10-18'))
intxndate <- as.Date(c('2018-11-06', '2018-11-05', '2018-11-13', '2018-11-17'))
salesorder <- data.frame(salesorder, esttxndate, potxndate, intxndate)
salesorder esttxndate potxndate intxndate
1 TM001 2018-10-01 2018-10-07 2018-11-06
2 TM002 2018-10-01 2018-10-06 2018-11-05
3 TM003 2018-10-04 2018-10-14 2018-11-13
4 TM004 2018-10-06 2018-10-18 2018-11-17
I am trying to create a new dataframe which looks at the dates of each salesorder and outputs the status on each date:
date TM001 TM002 TM003 TM004
01 2018-10-01 est est dne dne
02 2018-10-02 est est dne dne
.
07 2018-10-07 pro pro est est
.
32 2018-11-01 pro pro pro pro
.
37 2018-11-06 inv inv pro pro
.
48 2018-11-17 inv inv inv inv
I was able to get the list of dates out using the min and max functions (saved as mindate & maxdate). I then started a new data.frame with the values from the date range as:
mindate <- min(esttxndate, potxndate, intxndate)
maxdate <- max(esttxndate, potxndate, intxndate)
dates <- data.frame(as.Date(as.Date(mindate):as.Date(maxdate), origin="1970-01-01"))
names(dates)[1] <- "date"
I am at a loss for what to do next as I have tried to utilize user-defined functions and applying across rows on both the newly created dates dataframe and on the previous salesorder dataframe.
I am coming from a background in Stata and was able to produce the desired dataset by first going through and saving temp values for each date (ex. local variable potxndate_TM001 = 2018-10-07)
ds *date
foreach dt in `r(varlist)' {
forval i = 1/`=_N' {
local so = salesorder[`i']
local `dt'_`so' = `dt'[`i']
}
}
Once all the dates are saved as local variables I dropped all the variables besides salesorder, transposed the table and created a new variable date ranging from the minimum date to the maximum date. I then ran the following to get the values based on the date column and the locally saved variables.
ds TM*
foreach so in `r(varlist)' {
forval i = 1/`=_N' {
if `intxndate_`so'' <= date[`i'] {
replace `so' = "inv" in `i'
}
else if `potxndate_`so'' <= date[`i'] {
replace `so' = "pro" in `i'
}
else if `esttxndate_`so'' <= date[`i'] {
replace `so' = "est" in `i'
}
else if `esttxndate_`so'' > date[`i'] {
replace `so' = "dne" in `i'
}
}
}
I believe there is a way to do this in R without creating the intermediate local variables / modifying original dataset, which should be much more efficient and faster (?).
Loops in R tend to be slow, a faster solution is to use functional programming tools such as those in the purrr package or the function apply rather than loops as you have used in stata.
To solve this, I have written my own function, most_recent_txn which returns the status of a given sales order on a given date, then applied this function to all dates in the vector dates$date, using purrr::map_chr().
Then, to do this for all sales orders (rows in the original dataframe, salesorder), I have written a function which carries this out for a given row and applied this to all rows using the apply function.
most_recent_txn <- function(as_of_date, order_dates) {
# return the column name of the last txn step compleated, as of the date given.
last_step = "dne"
# if there is any recorded activity at that point, we assign the most recent
# activity to last step
if(max(which(t(order_dates)<=as_of_date))>0){
last_step = names(order_dates)[max(which(t(order_dates)<=as_of_date))]
}
return(last_step)
}
progress_of_sales_order <- function(order) {
purrr::map_chr(dates$date,most_recent_txn,order_dates=order[2:4])
}
status = cbind(dates$date,
apply(salesorder,1,FUN=progress_of_sales_order))
Not the most elegant solution, it relies on using the column order of salesorder dataframe to indicate the step in the process and implicitly assumes that the status of a sales order cannot go backward (eg. purchase order after invoice).
Created a function called status
status <- function(tstdate, estdate, podate, invdate) {
ifelse (tstdate >= invdate, "inv",
ifelse (tstdate >= podate, "pro",
ifelse (tstdate >= estdate, "est", "dne")))
}
I was then able to run the following across the dates:
final <- data.frame(apply(dates,c(1,2),function(x) {
status(x, salesorder$esttxndate, salesorder$potxndate, salesorder$intxndate)
}
The rest is formatting to get the data frame to my liking.
Minor note: this solution will have rows & columns reversed from how the question proposes for desired results. The issue with this way is not being able to name the columns dates, as numbers cannot be used in names.

Back testing for Stock analysis with R

I am a newbie of "R" and I want to write a script for back testing my strategy of buy and sell according to the EMA. I write the following code according to some reference from Web. However, the script got an error message in line 72 but I cannot figure out the problem. Anybody can help to solve my problem? Thanks in advance.
library(quantmod)
stock1<-getSymbols("^DJI",src="yahoo",from="2010-01-01",auto.assign=F)
stock1<-na.locf(stock1)
stock1$EMA9<-EMA(Cl(stock1),n=9)
stock1$EMA19<-EMA(Cl(stock1),n=19)
stock1$EMACheck<-ifelse(stock1$EMA9>stock1$EMA19,1,0)
stock1$EMA_CrossOverUp<-ifelse(diff(stock1$EMACheck)==1,1,0)
stock1$EMA_CrossOverDown<-ifelse(diff(stock1$EMACheck)==-1,-1,0)
stock1<-stock1[index(stock1)>="2010-01-01",]
stock1_df<-data.frame(index(stock1),coredata(stock1))
colnames(stock1_df)<-c("Date","Open","High","Low","Close","Volume","Adj","EMA9","EMA19","EMACheck","EMACheck_up","EMACheck_down")
head(stock1_df)
#To calculate the number of crossoverup transactions during the duration from 2010-01-01
sum(stock1_df$EMACheck_up==1 & index(stock1)>="2010-01-01",na.rm=T)
stock1_df$Date[stock1_df$EMACheck_up==1 & index(stock1)>="2010-01-01"]
sum(stock1_df$EMACheck_down==-1 & index(stock1)>="2010-01-01",na.rm=T)
stock1_df$Date[stock1_df$EMACheck_down==-1 & index(stock1)>="2010-01-01"]
#To generate the transcation according to the strategy
transaction_dates<-function(stock2,Buy,Sell)
{
Date_buy<-c()
Date_sell<-c()
hold<-F
stock2[["Hold"]]<-hold
for(i in 1:nrow(stock2)) {
if(hold == T) {
stock2[["Hold"]][i]<-T
if(stock2[[Sell]][i] == -1) {
#stock2[["Hold"]][i]<-T
hold<-F
}
} else {
if(stock2[[Buy]][i] == 1) {
hold<-T
stock2[["Hold"]][i]<-T
}
}
}
stock2[["Enter"]]<-c(0,ifelse(diff(stock2[["Hold"]])==1,1,0))
stock2[["Exit"]]<-c(ifelse(diff(stock2[["Hold"]])==-1,-1,0),0)
Buy_date <- stock2[["Date"]][stock2[["Enter"]] == 1]
Sell_date <- stock2[["Date"]][stock2[["Exit"]] == -1]
if (length(Sell_date)<length(Buy_date)){
#Sell_date[length(Sell_date)+1]<-tail(stock2[["Date"]],n=2)[1]
Buy_date<-Buy_date[1:length(Buy_date)-1]
}
return(list(DatesBuy=Buy_date,DatesSell=Sell_date))
}
#transaction dates generate:
transactionDates<-transaction_dates(stock1_df,"EMACheck_up","EMACheck_down")
transactionDates
num_transaction1<-length(transactionDates[[1]])
Open_price<-function(df,x) {df[as.integer(rownames(df[df[["Date"]]==x,]))+1,][["Open"]]}
transactions_date<-function(df,x) {df[as.integer(rownames(df[df[["Date"]]==x,]))+1,][["Date"]]}
transactions_generate<-function(df,num_transaction)
{
price_buy<-sapply(1:num_transaction,function(x) {Open_price(df,transactionDates[[1]][x])})
price_sell<-sapply(1:num_transaction,function(x) {Open_price(df,transactionDates[[2]][x])})
Dates_buy<-as.Date(sapply(1:num_transaction,function(x) {transactions_date(df,transactionDates[[1]][x])}))
Dates_sell<-as.Date(sapply(1:num_transaction,function(x) {transactions_date(df,transactionDates[[2]][x])}))
transactions_df<-data.frame(DatesBuy=Dates_buy,DatesSell=Dates_sell,pricesBuy=price_buy,pricesSell=price_sell)
#transactions_df$return<-100*(transactions_df$pricesSell-transactions_df$pricesBuy)/transactions_df$pricesBuy
transactions_df$Stop_loss<-NA
return(transactions_df)
}
transaction_summary<-transactions_generate(stock1_df,num_transaction1)
transaction_summary$Return<-100*(transaction_summary$pricesSell-transaction_summary$pricesBuy)/transaction_summary$pricesBuy
transaction_summary
Your code fails on this line:
transactionDates<-transaction_dates(stock1_df,"EMACheck_up","EMACheck_down")
The reason is that the first 19 records of stock1_df contain NA values in the columns "EMACheck_up" and "EMACheck_down".
head(stock1_df)
EMACheck_up EMACheck_down
1 NA NA
2 NA NA
3 NA NA
4 NA NA
5 NA NA
6 NA NA
You can solve your issue by running na.locf before running the offending line of code.
stock1_df <- na.locf(stock1_df)
transactionDates <-
transaction_dates(stock1_df, "EMACheck_up", "EMACheck_down")
Skipping the first 19 rows (or first month) would also work.
You might want to look into quantstrat if you want to do more in backtesting strategies. But what you have now does the trick.

How to subset 'n' number of rows past a certain value?

I'm trying to subset a data.frame based on a 1 or 0 value the data.frame.
Here is some sample code;
> Test
Close High Low Dn.BB MaVg Up.BB Per.BB Dn.Brk
2007-02-27 6286.1 6434.7 6270.5 6305.813 6389.679 6473.544 -0.11752900 1
2007-02-28 6171.5 6286.1 6166.2 6237.635 6377.186 6516.737 -0.23695539 1
2007-03-01 6116.0 6230.7 6038.9 6164.470 6358.129 6551.787 -0.12514308 1
2007-03-02 6116.2 6164.4 6085.6 6110.807 6341.179 6571.550 0.01170495 0
2007-03-05 6058.7 6116.2 5989.6 6047.421 6318.100 6588.779 0.02083561 0
2007-03-06 6138.5 6138.5 6058.7 6018.953 6297.907 6576.861 0.21427696 0
2007-03-07 6156.5 6167.6 6106.1 6001.139 6278.136 6555.133 0.28043853 0
2007-03-08 6227.7 6233.1 6156.5 5997.989 6264.436 6530.882 0.43106389 0
2007-03-09 6245.2 6255.8 6190.3 6003.152 6250.207 6497.262 0.48986661 0
2007-03-12 6233.3 6276.3 6219.3 6007.297 6237.421 6467.546 0.49104464 0
2007-03-13 6161.2 6240.7 6161.2 6000.401 6223.429 6446.457 0.36049188 0
Here, I would like to have something that iterates along the data.frame and then splits out the subsets based on Dn.Brk > 0. I can only think of a loop method here and am not to familiar with sub-setting, so was wondering if anyone could point me in the right direction / provide some tips of functions / packages that could achive this?
A little more detail below;
Sub <- rep(0,nrow(Test))
for (i in nrow(Test)){
if (Test[i,8] > 0){Sub = Test(i:i+10,1)}
}
So, the above would, at every point where Test[i,8] > 0, select, Test$Close from i:i+10.
Ideally, I'd like every sample to be stored in a separate row/column in a new df. Is that possible?
You can use sapply here:
sapply(which(Test[, 8] > 0), function(z) Test$Close[z:(z+10)])
A few things to note in the loop you provided though:
You are not iterating: Your loop is from i in nrow(Test) which is effectively nrow(Test)
You would be overwriting Sub with each iteration
If you are still in search for doing it with a for loop here is the answer:
#### results list #####
results <- list()
for (i in rows.test){
if (test[i,8] > 0)
{
results[[i]] = test$Close[i:(i+10)]
}
else {results[[i]] = "no value"}
}
This could also be further parallelisable if your dataset is huge with a package called foreach. A good intro here: http://www.vikparuchuri.com/blog/parallel-r-loops-for-windows-and-linux/. You could also change "no value" to next if you want a list with only three named elements

Create label column in dataframe according to an existing date column

I am new to R and struggling with the fact that functions are able to operate on whole vectors without having to explicitly specify this.
My goal
I have a data frame calls with multiple columns, one of which is a “date” column. Now I want to add a new column, “daytime”, that labels the daytime the particular entry’s date falls into:
> calls
call_id length date direction daytime
1 258 531 1400594572974 outgoing afternoon
2 259 0 1375555528144 unanswered evening
3 260 778 1385922648396 incoming evening
What I have done so far
I have already implemented methods that return a vector of booleans like that:
# Operates on POSIXlt timestamps
is.earlymorning <- function(date) {
hour(floor_date(date, "hour")) >= 5 & hour(floor_date(date, "hour")) < 9
}
The call is.earlymorning(“2014-05-20 16:02:52”, “2013-08-03 20:45:28”, “2013-12-01 19:30:48”) would thus return (“FALSE”, “FALSE”, “FALSE”). What I am currently struggling with is to implement a function that actually returns labels. What I would like the function to do is the following:
# rawDate is a long value of the date as ms since 1970
Daytime <- function(rawDate) {
date <- as.POSIXlt(as.numeric(rawDate) / 1000, origin = "1970-01-01")
if (is.earlymorning(date)) {
"earlymorning"
} else if (is.morning(date)) {
"morning"
} else if (is.afternoon(date)) {
"afternoon"
} else if (is.evening(date)) {
"evening"
} else if (is.earlynight(date)) {
"earlynight"
} else if (is.latenight(date)) {
"latenight"
}
}
The problem
Obviously, my above approach does not work since the if-conditions would operate on whole vectors in my example. Is there an elegant way to solve this problem? I am sure I am confusing or missing some important points, but as I mentioned I am pretty new to R.
In short, what I want to implement is a function that returns a vector of labels according to a vector of date values:
# Insert new column with daytime labels
calls$daytime <- Daytime(df$date)
# or something like that:
calls$daytime <- sapply(df$date, Daytime)
# Daytime(1400594572974, 1375555528144, 1385922648396) => (“afternoon”, “evening”, “evening”)
One approach would be to use cut rather than ifelse. I am not entirely sure how you want to label hours, but this will give you the idea. foo is your data (i.e., calls).
library(dplyr)
# Following your idea
ana <- transform(foo, date = as.POSIXlt(as.numeric(date) / 1000, origin = "1970-01-01"))
ana %>%
mutate(hour = cut(as.numeric(format(date, "%H")),
breaks = c(00,04,08,12,16,20,24),
label = c("late night", "early morning",
"morning", "afternoon",
"evening", "early night")
)
)
# call_id length date direction daytime hour
#1 258 531 2014-05-20 23:02:52 outgoing afternoon early night
#2 259 0 2013-08-04 03:45:28 unanswered evening late night
#3 260 778 2013-12-02 03:30:48 incoming evening late night
There is no need to have 6 different functions to establish which period of the day a given date is. It suffices to define a vector which matches the hour with the daytime. For instance:
Daytime<-function(rawDate) {
#change the vector according to your definition of the daytime.
#the first value corresponds to hour 0 and the last to hour 23
hours<-c(rep("latenight",5),rep("earlymorning",4),rep("morning",4),rep("afternoon",4),rep("evening",4),rep("earlynight",3))
hours[as.POSIXlt(as.numeric(rawDate) / 1000, origin = "1970-01-01")$hour+1]
}
Given Thomas' hint, I solved my problem in the following (addmittedly unelegant) way:
Daytime <- function(rawDates) {
dates <- as.POSIXlt(as.numeric(rawDates) / 1000, origin = "1970-01-01")
ifelse(is.earlymorning(dates), "earlymorning",
ifelse(is.morning(dates), "morning",
ifelse(is.afternoon(dates), "afternoon",
ifelse(is.evening(dates), "evening",
ifelse(is.earlynight(dates), "earlynight",
ifelse(is.latenight(dates), "latenight",
"N/A")
)
)
)
)
)
}
Considering a case with more labels this approach will get unmaintainable soon. Right now it serves my purposes and I will leave it at that since I must focus on analysing the data as soon as possible. But I will let you know if I had time left and found a less complicated solution! Thank you for your quick response, Thomas.

Resources