functionName <- function(argumentA, argumentB) {
# performs an operation
# return a result
return(variablec)
}24 Create a function
25 Introduction
25.1 What is a function ?
A function is somehow like a macro in Excel :
a script of instructions that we execute
and to execute it, we call the name of the macro, or here, the name of the function.
It contains several lines of code enclosed within a body.
A function has :
an input : a dataset, some variables
an output : the result of the calculation, which could be different types of objects, for example a dataframe, a value, a list, a chart,…
So, functions are user defined pieces of code that perform a desired operation on given input(s) and return the output back to the user.
Some interesting features :
the variables defined and declared inside the body are not available outside the body
the function definition defines one or more variables that can be passed by the caller of the function
25.2 Why should we create a function?
“if you do the same thing more than 2 times, then you should create a function” Hadley Wickham (probably)
Imagine 2 (quite common) situations :
you execute more than 2 times the same data transformation during your data processing.
you work on different projects, and each project requires the same data transformation.
Each time this transformation takes several (similar) lines of codes.
By creating and using a function, you will :
keep your ETL script lighter : calling only the name of the function instead of writing each time the different steps
make your code reusable and easier to maintain : you simply need to update the function, and not all the different scripts
The basic format of writing a function is :
This contains :
the name of the function : here, functionName
some inputs : here argumentA and argumentB
an output : here defined as “variableC”
Then, to apply the function we will use the following syntax : functionName(argumentA, argumentB)
And get the results.
Writing a function in R is pretty simple; it’s like encapsulating some transformations between {}.
Let’s have a look at it through 4 examples.
26 Part 1 : Create demo dataset
In the alcohol and spirits industry, bottles of liquor come in different sizes (10cl, 70cl, 1Liter,…) and are packaged into different outer cases.
Those outer cases can contain different quantities of bottles, depending on their size (weight), for example: 6 bottles, 12 bottles, 24 bottles,…
A common Unit of Measure (UoM) to compare all those different types of stocks is called a “9 Liter case”.
Historically, it was a standard for a 12 bottles case of 750 ml bottles (the standard bottle size), which contains 9 liters of product.
This 9LC UoM acts like a standard way to measure the common volume, whether they are stocks, supply or demand among different SKUs.
Let’s create a demo dataset with different products, 6 items, coming in various sizes and number of bottles per case.
# create vectors
product <- c("item 1", "item 2", "item 3", "item 4", "item 5", "item 6")
size <- c(0.7, 0.2, 0.375, 0.5, 1.5, 3)
nb_bottles_case <- c(12, 24, 12, 12, 6, 4)
opening_stocks_in_case <- c(1268, 432, 2560, 836, 513, 720)
# assemble
stocks_data <- data.frame(product,
size,
nb_bottles_case,
opening_stocks_in_case)
glimpse(stocks_data)Rows: 6
Columns: 4
$ product <chr> "item 1", "item 2", "item 3", "item 4", "item 5…
$ size <dbl> 0.700, 0.200, 0.375, 0.500, 1.500, 3.000
$ nb_bottles_case <dbl> 12, 24, 12, 12, 6, 4
$ opening_stocks_in_case <dbl> 1268, 432, 2560, 836, 513, 720
27 Part 2 : First function
27.1 Create function
Now, let’s create a simple function to convert the stocks which are expressed in cases, into a common unit of measure (UoM) : 9L cases.
To do this conversion we need to express first the opening stocks in liters, and then divide by 9.
So : [opening_stocks_9LC] = ([opening_stocks_in_case] x [nb_bottles_case] x [size]) / 9
convert_actual_cases_into_9LC <- function(data,
size,
nb_bottles_case,
opening_stock){
# set a working df
df1 <- data
# calculate opening_stocks_9LC
df1$opening_stocks_9LC <- (df1$opening_stocks_in_case * df1$nb_bottles_case * df1$size) / 9
# keep results
return(df1)
}How does it work?
we give a name to our function : “convert_actual_cases_into_9LC”
we define some inputs arguments :
a dataset : data
some variables of this dataset : size, nb_bottles_case, opening_stock
inside the “body” of the function we perform a calculation
we get the result through the function return() : a dataframe
27.2 Apply function
Now, let’s apply this function on the demo dataset stocks_datastocks_data.
# apply on stocks_data
df1 <- convert_actual_cases_into_9LC(data = stocks_data,
size = size,
nb_bottles_case = nb_bottles_case,
opening_stock = opening_stocks_in_case
)
# see results
df1 product size nb_bottles_case opening_stocks_in_case opening_stocks_9LC
1 item 1 0.700 12 1268 1183.4667
2 item 2 0.200 24 432 230.4000
3 item 3 0.375 12 2560 1280.0000
4 item 4 0.500 12 836 557.3333
5 item 5 1.500 6 513 513.0000
6 item 6 3.000 4 720 960.0000
We can see that on top of the original dataframe we now have an additional variable [opening_stocks_9LC], which corresponds to the conversion of the initial variable [opening_stocks_in_case] expressed in actual cases into 9 Liters cases.
28 Part 3 : Second function
28.1 Create function
Now, let’s add a few more features to our function :
sort by decreasing opening stocks
calculate the percentage of the total stock that each item represents
share_total_stocks_9LC <- function(data,
size,
nb_bottles_case,
opening_stock){
# set a working df
df1 <- data
# calculate opening_stocks_9LC
df1$opening_stocks_9LC <- (df1$opening_stocks_in_case * df1$nb_bottles_case * df1$size) / 9
# sort by decreasing opening stocks
df1 <- df1 |> arrange(desc(opening_stocks_9LC))
# calculate the percentage of the total stock that each item represents
df1$opening_stocks_9LC_pc <- df1$opening_stocks_9LC / sum(df1$opening_stocks_9LC)
# keep results
return(df1)
}28.2 Apply function
Now, let’s apply this function on the demo dataset stocks_datastocks_data.
# apply on stocks_data
df1 <- share_total_stocks_9LC(data = stocks_data,
size = size,
nb_bottles_case = nb_bottles_case,
opening_stock = opening_stocks_in_case
)
# see results
df1 product size nb_bottles_case opening_stocks_in_case opening_stocks_9LC
1 item 3 0.375 12 2560 1280.0000
2 item 1 0.700 12 1268 1183.4667
3 item 6 3.000 4 720 960.0000
4 item 4 0.500 12 836 557.3333
5 item 5 1.500 6 513 513.0000
6 item 2 0.200 24 432 230.4000
opening_stocks_9LC_pc
1 0.27094535
2 0.25051155
3 0.20320901
4 0.11797412
5 0.10858981
6 0.04877016
We now get 2 additional variables to the initial dataframe :
opening_stocks_9LC
opening_stocks_9LC_pc
Also, the data frame is sorted by [opening_stocks_9LC] descending.
29 Part 4 : Third function
We can create other outputs, for example here a chart.
29.1 Create function
Let’s add the code to create a chart within the function.
We aim to :
convert the stocks in 9 Liter cases
sort by decreasing opening stocks
create a column chart to display the results, using the R package highcharter
chart_stocks_9LC <- function(data,
product,
size,
nb_bottles_case,
opening_stock){
# set a working df
df1 <- data
#-----------------------
# Transform
# calculate opening_stocks_9LC
df1$opening_stocks_9LC <- (df1$opening_stocks_in_case * df1$nb_bottles_case * df1$size) / 9
# sort by decreasing opening stocks
df1 <- df1 |> arrange(desc(opening_stocks_9LC))
# formatting for a better display
df1$opening_stocks_9LC <- as.integer(df1$opening_stocks_9LC)
#-----------------------
# Chart
highchart() |>
hc_title(text = "Stocks by Items") |>
hc_subtitle(text = "in 9LC") |>
hc_add_theme(hc_theme_google()) |>
hc_xAxis(categories = df1$product) |>
hc_add_series(name = "Stocks",
color = "steelblue",
dataLabels = list(align = "center", enabled = TRUE),
data = df1$opening_stocks_9LC) |>
hc_chart(type = "column")
}29.2 Apply function
Now, let’s apply this function on the demo dataset stocks_datastocks_data.
# apply on stocks_data
chart1 <- chart_stocks_9LC(data = stocks_data,
product = product,
size = size,
nb_bottles_case = nb_bottles_case,
opening_stock = opening_stocks_in_case
)
# see results
chart1This function returns a chart as an output.
30 Part 5 : Fourth function
Now, let’s imagine that we are calculating some statistical forecasts, and during the step of “Train | Test | Validate” we aim to calculate the MAPE (Moving Average Percentage of Error).
For this, over a selected horizon of time, we compare the actuals vs the calculated forecasts.
Let’s take the example of the MLR (Multi Linear Regression) calculation.
30.1 Create demo dataset
Let’s create a simple demo dataset with only 3 variables :
a period of time (a selected horizon)
some actual (historical) sales quantities
some calculated statistical forecasts quantities
# create vectors
period <- c("2019-01-01", "2019-02-01", "2019-03-01", "2019-04-01", "2019-05-01", "2019-06-01")
actuals_qty <- c(740, 934, 603, 468, 903, 901)
forecasts_qty <- c(802, 1016, 572, 405, 871, 938)
# assemble
mape_data <- data.frame(period,
actuals_qty,
forecasts_qty)
glimpse(mape_data)Rows: 6
Columns: 3
$ period <chr> "2019-01-01", "2019-02-01", "2019-03-01", "2019-04-01", …
$ actuals_qty <dbl> 740, 934, 603, 468, 903, 901
$ forecasts_qty <dbl> 802, 1016, 572, 405, 871, 938
30.2 Create function
Now let’s create the function to calculate the MAPE :
[absolute difference] between [actuals_qty] and [forecasts_qty]
mean of the [absolute difference] over a selected horizon of time
Then, display of the results as a percentage value.
calculate_mape <- function(data,
actuals_qty,
forecasts_qty){
# set a working df
df1 <- data
# calculate monthly difference
df1$delta <- df1$forecasts_qty - df1$actuals_qty
df1$delta_pc <- df1$delta / df1$actuals_qty
# get the absolute percentage of error
df1$abs_delta_pc <- abs(df1$delta_pc)
# calculate the mean
MAPE <- mean(df1$abs_delta_pc)
MAPE <- percent(MAPE)
return(MAPE)
}30.3 Apply function
# apply on stocks_data
mape1 <- calculate_mape(data = mape_data,
actuals_qty = actuals_qty,
forecasts_qty = forecasts_qty
)
# see results
mape1[1] "7%"
This time the function returns a value.
We find a MAPE of 7%, same value that we calculated during the Statistical Forecast MLR calculation.
We now can reuse this function to calculate the MAPE for other methods, such as Holts-Winter, ARIMA,…