Model Selection

 


In programming languages, a function is a block of code lines that performs a specific task when it is called. Functions encapsulate related statements, allowing for task-specific code organization and modularization. The basic syntax to define a function in R programming language is:

my_function = function(parameters) {
  executions
}


For example, we can make a function that can calculate the monthly payments as follows. When defining the function, you can specify a set of parameters, which in this example house_value, loan_amount, loan_term_years, and interest_rate_calculator. They are place holders in the function signature, representing the input it expects.

get_monthly_payment <- function(house_value, loan_amount, loan_term_years, interest_rate_calculator) {
  # Use the provided interest_rate_calculator function to get the interest rate
  interest_rate <- interest_rate_calculator(house_value, loan_amount)
  
  # Convert annual interest rate to monthly rate
  monthly_interest_rate <- interest_rate / 12 / 100
  
  # Calculate the number of monthly payments
  num_payments <- loan_term_years * 12
  
  # Calculate monthly payment for a fixed-rate mortgage
  monthly_payment <- round((loan_amount * monthly_interest_rate) / (1 - (1 + monthly_interest_rate)^-num_payments), 2)
  
  return(monthly_payment)
}


When is called, arguments are provided to the function. They are actual values passed to the function, filling the placeholders defined by the parameters. An argument can be constants, variables, expressions, or even other functions. For example:

# Define the interest_rate_calculator function
get_interest_rate <- function(house_value, loan_amount) {

  base_interest_rate <- 3.5
  
  # Calculate Loan-to-Value (LTV) ratio to adjust the base interest rate
  ltv_ratio <- loan_amount / house_value
  ltv_adjustment <- ifelse(ltv_ratio > 0.8, 0.2, 0) 

  interest_rate <- base_interest_rate + ltv_adjustment

  return(interest_rate)
}

# Set constant argument values
HOUSE_VALUE <- 400000
LOAN_AMOUNT <- 300000
LOAN_TERM_YEARS <- 30

# Call get_monthly_payment function with 
monthly_payment <- get_monthly_payment(HOUSE_VALUE, LOAN_AMOUNT, LOAN_TERM_YEARS, get_interest_rate)
cat(paste("Your monthly payment is", monthly_payment, sep = ": $"))
## Your monthly payment is: $1347.13


In the example above, I used three constant arguments, HOUSE_VALUE, LOAN_AMOUNT, LOAN_TERM_YEARS, as well as a function get_interest_rate. When passing in the four arguments, the get_montly_payment function performs some calculations and returned the montly_payment value.

You can see the parameters and body of a function using formals and body to check how the function works. This is helpful to maintain your functions and program:

formals(get_monthly_payment)
## $house_value
## 
## 
## $loan_amount
## 
## 
## $loan_term_years
## 
## 
## $interest_rate_calculator


body(get_monthly_payment)
## {
##     interest_rate <- interest_rate_calculator(house_value, loan_amount)
##     monthly_interest_rate <- interest_rate/12/100
##     num_payments <- loan_term_years * 12
##     monthly_payment <- round((loan_amount * monthly_interest_rate)/(1 - 
##         (1 + monthly_interest_rate)^-num_payments), 2)
##     return(monthly_payment)
## }


You can pass arguments either by position or its name. For example, when we call the get_monthly_payment function mentioned earlier, we can obtain the same result with the code lines below:

# Set constant argument values
HOUSE_VALUE <- 400000
LOAN_AMOUNT <- 300000
LOAN_TERM_YEARS <- 30

# Call get_monthly_payment function with 
monthly_payment <- get_monthly_payment(house_value = HOUSE_VALUE, LOAN_AMOUNT, loan_term_years = LOAN_TERM_YEARS, get_interest_rate)
cat(paste("Your monthly payment is", monthly_payment, sep = ": $"))
## Your monthly payment is: $1347.13


When you define a function, you can also set default values for any (or all) of the arguments. For example:

# Define the interest_rate_calculator function
get_interest_rate <- function(house_value = 400000, loan_amount = 300000) {

  base_interest_rate <- 3.5
  
  # Calculate Loan-to-Value (LTV) ratio to adjust the base interest rate
  ltv_ratio <- loan_amount / house_value
  ltv_adjustment <- ifelse(ltv_ratio > 0.8, 0.2, 0) 

  interest_rate <- base_interest_rate + ltv_adjustment

  return(interest_rate)
}

interest_rate <- get_interest_rate()
cat(paste0("Your interest rate is ", interest_rate, "%"))
## Your interest rate is 3.5%


The Invisibles

The invisible is a function in R that can suppress the printing of an expression’s evaluation. While not as common as explicit return, it can be used in user-defined functions to control printing and manage side effects. For example:

square_invisible <- function(x) invisible(x^2)
square <- function(x) x^2


In the code lines above, I defined two different functions; one is squaring an argument with invisible and the other is without it. When we call the square function by passing in 4, we can obtain the resulting value:

square(4)
## [1] 16


On the other hand, when we call square_invisible with the same argument, it doesn’t return any output:

square_invisible(4)


However, the invisible does not truly discard the evaluated value. So, if we assign a variable to the invisible function:

xsquared <- square_invisible(4)
xsquared
## [1] 16



Function Environments and Scope

When you call a function, it creates its own environment separate from the global environment. This environment holds not only the function definition and arguments, but also the variables and temporary objects created during execution.

While commands generally run sequentially, the function’s logic can change the order. R searches for variables within the function’s environment first. If it doesn’t find the needed variable, it then looks in the parent environment and continues this search until it finds the variable or exhaust all parent environments. This mechanism ensures local variables take precedence and prevents inadvertent modification of global variables from within the function.

When a function is called, its body is evaluated in an execution environment whose parent is the function’s environment. For example:

w <- 12
f <- function(y) {
  d <- 8
  h <- function() {
    return(d * (w + y))
  }
  cat("h's environment: ", "\n")
  print(environment(h))
  cat("h's parent environment:", "\n")
  print(parent.env(environment(h)))
  return(h())
}

f(1)
## h's environment:  
## <environment: 0x0000026b2a5bcbc8>
## h's parent environment: 
## <environment: R_GlobalEnv>
## [1] 104
environment(f)
## <environment: R_GlobalEnv>


Compare the above code chunks with the followings. The code chunks below will throw an error, because when it execute the body of the function h nested in f, R cannot find the object d in the execution environment of h. So, R tries to find d in its parent environment, which is global. But, since d is defined in f, not global or above, R throws an error.

f <- function(y) {
  d <- 8
  return(h())
}

h <- function() {
  cat("h's environment:", "\n")
  print(environment(h))
  cat("h's parent environment:", "\n")
  print(parent.env(environment(h)))
  return(d * (w + y))
}

f(5)
## h's environment: 
## <environment: R_GlobalEnv>
## h's parent environment: 
## <environment: package:stats>
## attr(,"name")
## [1] "package:stats"
## attr(,"path")
## [1] "C:/Program Files/R/R-4.3.1/library/stats"
## Error in h(): object 'd' not found
environment(f)
## <environment: R_GlobalEnv>

Post a Comment

0 Comments