I'm interested in an elegant way of using immutable variables from parent functions.
Let's see an example.
I have the dataframe with number of the week and its corresponding value.
df <- tribble(
~week, ~count,
1, 99.6,
2, 116,
3, 107,
4, 125,
5, 126,
6, 131,
7, 149,
8, 130,
9, 111,
48, 43.4,
49, 136,
50, 133,
51, 115,
52, 93.3
)
The following functions add to the dataframe columns with the first and the last date of every week.
add_week_limit_dates <- function(df){
year_change <- ifelse(any(df$week < 25) && any(df$week > 38), TRUE, FALSE)
df %>%
mutate(week_start = week_to_date(week, 1, year_change),
week_end = week_to_date(week, 7, year_change))
}
week_to_date <- function(week, day_of_week, year_change){
paste0(get_year(week, year_change),
"-W", ifelse(week < 10, paste0("0", as.character(week)), week),
"-", day_of_week) %>% ISOweek2date()
}
get_year <- function(week, year_change){
if(isTRUE(year_change)){
ifelse(week < 38, year(Sys.Date()), year(Sys.Date()) - 1)
} else{
year(Sys.Date())
}
}
> add_week_limit_dates(df)
# A tibble: 14 × 4
week count week_start week_end
<dbl> <dbl> <date> <date>
1 1 99.6 2022-01-03 2022-01-09
2 2 116. 2022-01-10 2022-01-16
3 3 107. 2022-01-17 2022-01-23
4 4 125. 2022-01-24 2022-01-30
5 5 126. 2022-01-31 2022-02-06
6 6 131. 2022-02-07 2022-02-13
7 7 149. 2022-02-14 2022-02-20
8 8 130. 2022-02-21 2022-02-27
9 9 111. 2022-02-28 2022-03-06
10 48 43.4 2021-11-29 2021-12-05
11 49 136. 2021-12-06 2021-12-12
12 50 133. 2021-12-13 2021-12-19
13 51 115. 2021-12-20 2021-12-26
14 52 93.3 2021-12-27 2022-01-02
So, my question: is it possible not to set explicitly the common variable year_change and force the children function search for it in the parent one? I dream about the next way
df %>%
mutate(week_start = week_to_date(week, 1),
week_end = week_to_date(week, 7))
UPD - Solution
As recommended I accessed the parent.frame(), but notice that it isn't expected one when you use it inside dplyr's methods
add_week_limit_dates <- function(df){
year_change <- ifelse(any(df$week < 25) && any(df$week > 38), TRUE, FALSE)
df$week_start <- week_to_date(df$week, 1)
df$week_end <- week_to_date(df$week, 7)
df
}
week_to_date <- function(week, day_of_week){
paste0(get_year(week, parent.frame()$year_change),
"-W", ifelse(week < 10, paste0("0", as.character(week)), week),
"-", day_of_week) %>% ISOweek2date()
}
...
CodePudding user response:
you can access the scope of the calling function through the called function's parent frame:
mother <- function(){
year_change = 8
child()
}
child <- function(){
print(parent.frame()$year_change)
}
... or define them in one of the function's immediate parent environments:
mother <- function(){ ## now the immediate parent environment
year_change = 8
child <- function(){
print(year_change)
}
child()
}
... or in the global environment:
year_change = 8 ## the global environment
mother <- function(){
print(paste("mom's idea of year_change is:", year_change))
child <- function(){
print(paste("kid's idea of year_change is:", year_change))
}
child()
}
Note that the immediate parent environment outmatches the farther ancestors if variables have the same name.
Details of scoping e.g. in H. Wickham: Advanced R
Edit
Shamefully forgot the possibility to pass variables through by taking advantage of the ellipsis ...
argument.
example
mother_says <- function(...){
tell_child(...)
}
tell_child <- function(...){
args <- list(...)
print(paste("Mom says:", args$message_to_daughter))
tell_grandchild(...)
}
tell_grandchild <- function(...){
args <- list(...)
print(paste("Granny says:", args$message_to_grandchild))
}
example's output:
## > mother_says(message_to_daughter = "Hi, daughter, how're you?",
## message_to_grandchild = "Grandchild, sweetie!"
## )
##
## [1] "Mom says: Hi, daughter, how're you?"
## [1] "Granny says: Grandchild, sweetie!"