14

Can anyone explain to me why the following example occurs?

#Create simple dataframe
assign( "df" , data.frame( P = runif(5) , Q = runif(5) , R = runif(5) ) ) 

#Return the dataframe from the given character vector
get( "df" ) 
            P          Q          R
1  0.17396222 0.90994676 0.90590685
2  0.33860092 0.98078739 0.38058921
3  0.80751402 0.93229290 0.82853094
4  0.05460417 0.55448507 0.01605027
5  0.04250316 0.03808318 0.40678270

#Return the column names of df
colnames( get( "df" ) )
[1] "P" "Q" "R"

#But using a replacement function...
colnames( get( "df" ) ) <- c( "S" , "T" , "U" ) 
    Error in colnames(get("df")) <- c("S", "T", "U") : 
    target of assignment expands to non-language object

I'd A) like to know why the replacement functions won't work in this way with get()?

And b) if there is some way to work around this, given my problem which I outline below;

My problem is that I have many objects, created (using a toy example) in a loop, something like this: assign( paste( "Object" , i , sep = "." ) , rnorm(1000 , i) ), where i is a vector, say i <- 1:1000 and then I would like to be able to assign names (for instance from a different vector) to each object in the loop, but names( get( paste( "Object" , i , sep = "." ) ) <- someNewName doesn't work as in the example above.

But get( paste( "Object" , i , sep = "." ) ) does return the names (or NULL) of those objects.

Thanks!

Simon O'Hanlon
  • 58,647
  • 14
  • 142
  • 184
  • 1
    For your specific question, try `setNames`. In general, I don't know why it doesn't work: I look forward to reading an edifying answer. – Blue Magister Jan 22 '13 at 17:28
  • Thanks. I see that works using `assign()` and `setNames( get( "df" ) )`, however I am using a mcmc object from the coda package, which has a replacement function `varnames()` defined for changing variable names, which I need to use (unless I want to try and make my own - which is possible, I was just hoping to avoid it). I am still curious as to how the expression is evaluated though! Thanks. – Simon O'Hanlon Jan 22 '13 at 17:34
  • 2
    Don't use `assign`. Put all objects created in your loop into a list instead. There is a good chance that you don't even need a `for` loop. – Roland Jan 22 '13 at 17:34
  • 1
    I know I probably don't need a `for` loop, it was just sheer convenience. Honestly, the overhead of calling the loop to create objects is minimal, < 0.1 seconds, so I'm not rigidly determined to vectorise everything in this case. It's a useful convenience construct that has it's place for the lazy coder (plus I'm doing a few other renaming assignments in the loop on different object, so it really is just convenient). – Simon O'Hanlon Jan 22 '13 at 17:37
  • 1
    :hand waving: evaulation, blah blah blah, :more hand waving: Try `'colnames<-'(get('df'),c('A','B','C'))` and `colnames(data.frame( P = runif(5) , Q = runif(5) , R = runif(5) )) <- c('A','B','C')` – joran Jan 22 '13 at 17:39
  • 1
    @SimonO101 I think you missed my point. I can't remember ever using `assign`. The way to go is a list holding the objects created in the loop. – Roland Jan 22 '13 at 17:40
  • I searched your error line "target of replacement expands to non-language object" and found some useful questions on Stack Overflow. Particularly [this link to the R FAQ](http://cran.r-project.org/doc/FAQ/R-FAQ.html#How-can-I-turn-a-string-into-a-variable_003f). Maybe `eval` and `substitute` will do the trick. – Blue Magister Jan 22 '13 at 17:41
  • 1
    @Roland just because you never used it, does not mean it does not ever have it's place! – Simon O'Hanlon Jan 22 '13 at 17:44
  • 2
    @SimonO101 I take your point to Roland, but in my view there has to be a really compelling reason to use `assign` and `get` rather than the more R-like paradigm of `lapply` and working with lists. I'd be keen to understand why you want to avoid working with lists. – Andrie Jan 22 '13 at 17:45
  • 1
    While `assign` is sometimes useful, Roland is correct, you should almost certainly not be using it in the manner you've described. – joran Jan 22 '13 at 17:45
  • @joran Thanks, your comment above is what I am looking for. I should remember in future, to Read-The-Manual. Specifically, pages 10 and 48 of S Programming! Thank you – Simon O'Hanlon Jan 22 '13 at 17:50
  • Related: http://stackoverflow.com/q/5365482/271616 – Joshua Ulrich Jan 22 '13 at 17:52
  • @joran @Andrie @Roland I do agree with all of you! However, as I have previously stated, in this case should I really be making myself, HAVE to use lapply for something which has a tiny runtime and memory footprint anyway. The annoyance is in having many objects which I dont want to rename manually (the naming is just to do with displaying of variable names on plots), not in finding the most computer-efficient (CPU time, RAM) way to do it. It was more time-efficient for me to just use `for()`. I know I shouldn't, but I did. So shoot me! :-) – Simon O'Hanlon Jan 22 '13 at 17:56
  • @SimonO101 `for` loops have their place, but once you understand `lapply` you realize that it is actually more convenient. The loop construct is not the most important lesson here, using a list is. – Roland Jan 22 '13 at 17:59
  • @joran @Andrie Can I delete the second half of my question? Because the what I am most interested in, is not how to side-step that error (but thanks and kudos for solving that for me), but someone stepping me through how `colnames( get( "df" ) ) <- (c1,2,3)` gets evaluated leading to the error. Thank you all. – Simon O'Hanlon Jan 22 '13 at 18:01
  • @Roland ok, thanks, point taken. No more `for()` loops when I post on SO. But side-stepping the issue of using `for()` loops or `lists` (it's not really not what I am asking about) I'd love to understand how/why the last evaluation fails. – Simon O'Hanlon Jan 22 '13 at 18:06

2 Answers2

20

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
sds
  • 58,617
  • 29
  • 161
  • 278
hadley
  • 102,019
  • 32
  • 183
  • 245
10

Using assign in the way you demonstrate in the question is at least uncommon in R. Normally you would just put all objects in a list.

So, instead of

for (i in 1:10) {
assign( paste( "Object" , i , sep = "." ) , rnorm(1000 , i) )}

you would do

objects <- list()
for (i in 1:10) {
objects[[i]] <- rnorm(1000 , i) }

In fact, this construct is so common that there is a (optimized) function (lapply), which does something similar:

objects <- lapply(1:10, function(x) rnorm(1000,x))

You can then access, e.g., the first object as objects[[1]] and there are several functions for working with lists.

Roland
  • 127,288
  • 10
  • 191
  • 288
  • 2
    Almost +1, and I will upvote if you also add the `lapply` version. – Andrie Jan 22 '13 at 17:46
  • +1 `lapply()` is the way to go here. It will simplify code, and may even be faster than the for loop, simply because `<-` is a relatively slow operation. – Andrie Jan 22 '13 at 17:50
  • @Andrie `<-` is not relatively slow: repeatedly modifying a complex data structure is slow. (And I don't think you'll see much difference here since IIRC `[[<-` is special-cased so it doesn't make a copy) – hadley Jan 22 '13 at 19:10
  • @hadley Sorry, your're correct. I should have written `[<-` is slow. Some time ago I was able to create a much faster version of `read.fwf()` by replacing `[<-` with `lapply`. I may be wrong in this analysis, but at the time this was my interpretation of my `rprof()` results. – Andrie Jan 22 '13 at 20:04
  • @Andrie If you want to be even more precise, it's really `[<-.data.frame` that's slow. Also, `read.fwf` is a disaster that just about any change can make better ;) – hadley Jan 22 '13 at 20:29
  • @Andrie @hadley Considering the chastening discussion in my question above I have been looking at replacing `for` with `lapply`, however once I have constructed the `objects` list in the example above, how would I then go about replacing the names of each element in each vector, for each element in the list (considering the `names` vector for each list element is different), e.g. `names( objects[[1]] ) <- c(1:1000)` but `names( objects[[2]]) <- sample(letters , 1000 , replacement = TRUE)`. How do I lapply this? Maybe this is a seperate question with better example? – Simon O'Hanlon Jan 22 '13 at 20:57