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 :

functionName <- function(argumentA, argumentB) {
  
  # performs an operation
  
  # return a result
  return(variablec)
  
  }

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
chart1

This 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,…