10.4 Functions

Like algorithms, functions take an input and return an output.Functions, however, refer to the implementation in a particular programming language, rather than the ‘recipe’ itself.In R, functions are objects in their own right, that can be created and joined together in a modular fashion.We can, for example, create a function that undertakes step 2 of our centroid generation algorithm as follows:

  1. t_centroid = function(x) {
  2. (x[1, ] + x[2, ] + x[3, ]) / 3
  3. }

The above example demonstrates two key components of functions:1) the function body, the code inside the curly brackets that define what the function does with the inputs; and 2) the formals, the list of arguments the function works with — x in this case (the third key component, the environment, is beyond the scope of this section).By default, functions return the last object that has been calculated (the coordinates of the centroid in the case of t_centroid()).58

The function now works on any inputs you pass it, as illustrated in the below command which calculates the area of the 1st triangle from the example polygon in the previous section (see Figure 10.3):

  1. t_centroid(T1)
  2. #> x_coords y_coords
  3. #> 3.33 3.33

We can also create a function to calculate a triangle’s area, which we will name t_area():

  1. t_area = function(x) {
  2. abs(
  3. x[1, 1] * (x[2, 2] - x[3, 2]) +
  4. x[2, 1] * (x[3, 2] - x[1, 2]) +
  5. x[3, 1] * (x[1, 2] - x[2, 2])
  6. ) / 2
  7. }

Note that after the function’s creation, a triangle’s area can be calculated in a single line of code, avoiding duplication of verbose code:functions are a mechanism for generalizing code.The newly created function t_area() takes any object x, assumed to have the same dimensions as the ‘triangle matrix’ data structure we’ve been using, and returns its area, as illustrated on T1 as follows:

  1. t_area(T1)
  2. #> [1] 50

We can test the generalizability of the function by using it to find the area of a new triangle matrix, which has a height of 1 and a base of 3:

  1. t_new = cbind(x = c(0, 3, 3, 0),
  2. y = c(0, 0, 1, 0))
  3. t_area(t_new)
  4. #> x
  5. #> 1.5

A useful feature of functions is that they are modular.Provided that you know what the output will be, one function can be used as the building block of another.Thus, the functions t_centroid() and t_area() can be used as sub-components of a larger function to do the work of the script 10-centroid-alg.R: calculate the area of any convex polygon.The code chunk below creates the function poly_centroid() to mimic the behavior of sf::st_centroid() for convex polygons:59

  1. poly_centroid = function(x) {
  2. i = 2:(nrow(x) - 2)
  3. T_all = lapply(i, function(x) {
  4. rbind(Origin, poly_mat[x:(x + 1), ], Origin)
  5. })
  6. C_list = lapply(T_all, t_centroid)
  7. C = do.call(rbind, C_list)
  8. A = vapply(T_all, t_area, FUN.VALUE = double(1))
  9. c(weighted.mean(C[, 1], A), weighted.mean(C[, 2], A))
  10. }
  1. poly_centroid(poly_mat)
  2. #> [1] 8.83 9.22

Functions such as poly_centroid() can further be extended to provide different types of output.To return the result as an object of class sfg, for example, a ‘wrapper’ function can be used to modify the output of poly_centroid() before returning the result:

  1. poly_centroid_sfg = function(x) {
  2. centroid_coords = poly_centroid(x)
  3. sf::st_point(centroid_coords)
  4. }

We can verify that the output is the same as the output from sf::st_centroid() as follows:

  1. poly_sfc = sf::st_polygon(list(poly_mat))
  2. identical(poly_centroid_sfg(poly_mat), sf::st_centroid(poly_sfc))
  3. #> [1] TRUE