What Are Replacement Functions in R

What are Replacement Functions in R?

When you call

cutoff(x) <- 65

you are in effect calling

x <- "cutoff<-"(x = x, value = 65)

The name of the function has to be quoted as it is a syntactically valid but non-standard name and the parser would interpret <- as the operator not as part of the function name if it weren't quoted.

"cutoff<-"() is just like any other function (albeit with a weird name); it makes a change to its input argument on the basis of value (in this case it is setting any value in x greater than 65 to Inf (infinite)).

The magic is really being done when you call the function like this

cutoff(x) <- 65

because R is parsing that and pulling out the various bits to make the real call shown above.

More generically we have

FUN(obj) <- value

R finds function "FUN<-"() and sets up the call by passing obj and value into "FUN<-"() and arranges for the result of "FUN<-"() to be assigned back to obj, hence it calls:

obj <- "FUN<-"(obj, value)

A useful reference for this information is the R Language Definition Section 3.4.4: Subset assignment ; the discussion is a bit oblique, but seems to be the most official reference there is (replacement functions are mentioned in passing in the R FAQ (differences between R and S-PLUS), and in the R language reference (various technical issues), but I haven't found any further discussion in official documentation).

Replacement functions in R

There was a change in how R 3.5 stores values in the form a:b. If you try the same example with

library(pryr)
x <- c(1,2,3,4,5,6,7,8,9,10)
address(x)
x[2] <- 7L
address(x)

You should get the same address. Now the 1:10 isn't full expanded until it has to be. And changing an element inside the vector will cause it to expand.

Replacement functions in R that don't take input

After further information from the OP, it looks as if what is needed is a way to write to the existing variable in the environment that calls the function. This can be done with non-standard evaluation:

check_result <- function(process_list) 
{
# Capture the name of the passed object as a string
list_name <- deparse(substitute(process_list))

# Check the object exists in the calling environment
if(!exists(list_name, envir = parent.frame()))
stop("Object '", list_name, "' not found")

# Create a local copy of the passed object in function scope
copy_of_process_list <- get(list_name, envir = parent.frame())

# If the process has completed, write its output to the copy
# and assign the copy to the name of the object in the calling frame
if(length(copy_of_process_list$process$get_exit_status()) > 0)
{
copy_of_process_list$output <- copy_of_process_list$process$read_all_output_lines()
assign(list_name, copy_of_process_list, envir = parent.frame())
}
print(copy_of_process_list)
}

This will update res if the process has completed; otherwise it leaves it alone. In either case it prints out the current contents. If this is client-facing code you will want further type-checking logic on the object passed in.

So I can do

res <- run_sh(c("naw.sh", "hello"))

and check the contents of res I have:

res
#> $`process`
#> PROCESS 'sh', running, pid 1112.
#>
#> $orig_args
#> [1] "naw.sh" "hello"
#>
#> $output
#> NULL

and if I immediately run:

check_result(res)
#> $`process`
#> PROCESS 'sh', running, pid 1112.
#>
#> $orig_args
#> [1] "naw.sh" "hello"
#>
#> $output
#> NULL

we can see that the process hasn't completed yet. However, if I wait a few seconds and call check_result again, I get:

check_result(res)
#> $`process`
#> PROCESS 'sh', finished.
#>
#> $orig_args
#> [1] "naw.sh" "hello"
#>
#> $output
#> [1] "hello" "naw 1" "naw 2" "naw 3" "naw 4" "naw 5"
#> [7] "All done."

and without explicitly writing to res, it has updated via the function:

res
#> $`process`
#> PROCESS 'sh', finished.
#>
#> $orig_args
#> [1] "naw.sh" "hello"
#>
#> $output
#> [1] "hello" "naw 1" "naw 2" "naw 3" "naw 4" "naw 5"
#> [7] "All done."

replace function examples

If you look at the function (by typing it's name at the console) you will see that it is just a simple functionalized version of the [<- function which is described at ?"[". [ is a rather basic function to R so you would be well-advised to look at that page for further details. Especially important is learning that the index argument (the second argument in replace can be logical, numeric or character classed values. Recycling will occur when there are differing lengths of the second and third arguments:

You should "read" the function call as" "within the first argument, use the second argument as an index for placing the values of the third argument into the first":

> replace( 1:20, 10:15, 1:2)
[1] 1 2 3 4 5 6 7 8 9 1 2 1 2 1 2 16 17 18 19 20

Character indexing for a named vector:

> replace(c(a=1, b=2, c=3, d=4), "b", 10)
a b c d
1 10 3 4

Logical indexing:

> replace(x <- c(a=1, b=2, c=3, d=4), x>2, 10)
a b c d
1 2 10 10

Concerning R, when defining a Replacement Function, do the arguments have to be named as/like x and value?

There are two things going on here. First, the only real rule of replacement functions is that the new value will be passed as a parameter named value and it will be the last parameter. That's why when you specify the signature function(somex, somevalue), you get the error unused argument (value = 9) and the assignment doesn't work.

Secondly, things work with the signature function(x11, value11) thanks to partial matching of parameter names in R. Consider this example

f<-function(a, value1234=5) {
print(value1234)
}
f(value=5)
# [1] 5

Note that 5 is returned. This behavior is defined under argument matching in the language definition.

Another way to see what's going on is to print the call signature of what's actually being called.

'first0<-' <- function(x, value){
print(sys.call())
x[1] <- value
x
}
a <- c(1,2,3)
first0(a) <- 5
# `first0<-`(`*tmp*`, value = 5)

So the first parameter is actually passed as an unnamed positional parameter, and the new value is passed as the named parameter value=. This is the only parameter name that matters.

Using get() with replacement functions

To understand why this doesn't work, you need to understand what colnames<- does. Like every function in that looks like it's modifying an object, it's actually modifying a copy, so conceptually colnames(x) <- y gets expanded to:

copy <- x
colnames(copy) <- y
x <- copy

which can be written a little more compactly if you call the replacement operator in the usual way:

x <- `colnames<-`(x, y)

So your example becomes

get("x") <- `colnames<-`(get("x"), y)

The right side is valid R, but the command as a whole is not, because you can't assign something to the result of a function:

x <- 1
get("x") <- 2
# Error in get("x") <- 2 :
# target of assignment expands to non-language object

R: How to write a function that replaces a function call with another function call?

The following function, when passed mean(x) and some fn such as sqrt as its two arguments returns the call object fn(x), i.e. sqrt(x), replacing occurrences of mean with fn.

replace_mean <- function(code, fn) {
do.call("substitute", list(substitute(code), list(mean = substitute(fn))))
}

Examples

1) Basic example

e <- replace_mean(mean(x), sqrt)
e
## sqrt(x)

x <- 4
eval(e)
## [1] 2

2) more complex expression

ee <- replace_mean(mean(x) + mean(x*x), sqrt)
ee
## sqrt(x) + sqrt(x * x)

x <- 4
eval(ee)
## [1] 6

3) apply replace_mean to body of f creating g

f <- function(x) mean(x) + mean(x*x)
g <- f
body(g) <- do.call("replace_mean", list(body(f), quote(sqrt)))

g
## function (x)
## sqrt(x) + sqrt(x * x)

x <- 4
g(x)
## [1] 6

R replace list elements from within a function

The problem you're having is that the first argument of assign is:

x - a variable name, given as a character string.

But even outside the function, this doesn't work.

assign(mylist$a,0)
#Error in assign(mylist$a, 0) : invalid first argument
assign("mylist$a",0)
mylist
#$a
#[1] 1 2 3

However, you can use $<-, like this:

> mylist$a <- 0
> mylist$a
[1] 0

One approach, then is to create that expression and evaluate it:

mylist = list("a"=1:3)
myexpression <- deparse(substitute(mylist$a))
myexpression
#[1] "mylist$a"

library(rlang)
expr(!!parse_expr(myexpression) <- 0)
#mylist$a <- 0
eval(expr(!!parse_expr(myexpression) <- 0))
mylist$a
#[1] 0

Obviously use <<- inside the function.

Replace multiple similar values in a column in R

Perhaps adding 3 and pasting " years old" will satisfy your needs?

 data$txtAge <- paste(data$Age, "years old")

There is no need for an iterative command. R's functions often iterate automagically. In this case the paste command is designed to return character results of the same length as the longest input argument but it "recycles" (repeats) the shorter argument. You would get a column of the same length as there were rows in the data object.



Related Topics



Leave a reply



Submit