13

I have a recurrent situation where I set a value at the top of a long set of R code that's used in subsetting one or more data frames. Something like this:

city_code <- "202"

At the end of the whole process I'd like to save the results in a data frame that's named appropriately, say, based on appending "city_code" to a common stub.

city_results <- paste("city_stats", city_code, sep = "")

My problem is that I can't figure out how to rename the resulting data frame as the value of 'city_results'. Lots of info out there on how to rename the columns of a data frame, but not on how to rename the data frame itself. Based on a proposed answer, here's a clarification:

Thanks, @mike-wise. Helpful to study Hadley's Advanced R with a concrete problem in hand.

library(dplyr)
gear_code <- 4
gear_subset <- paste("mtcars_", gear_code, sep = "")
mtcars_subset <- mtcars %>% filter(gear == gear_code)
head(mtcars_subset)
write.csv(mtcars_subset, file = paste(gear_subset, ".csv", sep = ""))

That lets me write the subset to an appropriately named csv file. However, your suggestion kind of works, but I can't, for example, reference the data.frame with the new name:

assign(gear_subset, mtcars_subset)
head(gear_subset)
Mike Wise
  • 22,131
  • 8
  • 81
  • 104
John David Smith
  • 573
  • 1
  • 6
  • 15
  • Hi John. I added a comment in the reply that answers your question. – Mike Wise Jul 07 '15 at 09:23
  • 1
    I know this is old, but for whoever may need it that last line just needs to be `head(get(gear_subset))`. The opposite of `assign` is `get`. – Brian Stamper Jan 11 '17 at 19:37
  • Minor point: paste("mtcars_", gear_code, sep = "") can be shortened ever so slightly to paste0("mtcars_", gear_code). Shortcut for sep="" option. – Bradford Nov 22 '19 at 15:57

2 Answers2

25

The truth is that objects in R don't have names per-se. There exists different kinds of environments, including a global one for every process. These environments have lists of names, that point to various objects. Two different names can point to the same object. This is best explained to my knowledge in the environments chapter of Hadley Wickhams Advanced R book http://adv-r.had.co.nz/Environments.html

So there is no way to change a name of a data frame, because there is nothing to change.

But you can make a new name (like newname) point to the same object (in your case a data frame object) as an given name (like oldname) simply by doing:

   newname <- oldname

Note that if you change one of these variables a new copy will be made and the internal references will no longer be the same. This is due to R's "Copy on modify" semantics. See this post for an explanation: What exactly is copy-on-modify semantics in R, and where is the canonical source?

Hope that helps. I know the pain. Dynamic and functional languages are different than static and procedural languages...

Of course it is possible to calculate a new name for a dataframe and register it in the environment with the assign command - and perhaps you are looking for this. However referring to it afterwards would be rather convoluted.

Example (assuming df is the dataframe in question):

   assign(  paste("city_stats", city_code, sep = ""), df )

As always see the help for assign for more information http://stat.ethz.ch/R-manual/R-devel/library/base/html/assign.html

Edit: In reply to your edit, and various comments around the problems with using eval(parse(...) you could parse the name like this:

head(get(gear_subset))
Mike Wise
  • 22,131
  • 8
  • 81
  • 104
  • 2
    It may be necessary to specify the environment to assign into: `assign(city_results, df, pos=1)` or `assign(city_results, df, envir=.GlobalEnv)` – Jota Jul 05 '15 at 00:24
  • the default is `environment()` I believe (from experimentation, the documentation not clear on this) – Mike Wise Jul 07 '15 at 09:49
  • 1
    Thanks @mike-wise. You've more than answered my question. (I had used parse in a cookbook fashion before but now I'm studying the help file. Very helpful. :-) – John David Smith Jul 07 '15 at 15:22
  • The line about `newname <- oldname` making `newname` point to the same object is not correct. Try it, then modify the object called `oldname`, then look at `newname` - the change will not appear in `newname`. See also http://stackoverflow.com/questions/2603184/r-pass-by-reference. – Brian Stamper Jan 11 '17 at 19:52
  • I guess you have not heard of R's "copy-on-modify" semantics. I made an addition to the post so that this kind of confusion will not happen again. And I did not appreciate the down-vote. – Mike Wise Jan 11 '17 at 21:07
  • 4
    `eval(parse(...))` is useful when you want to parse code that *does something*. For simply accessing an object whose name is in a string, `get` is a nice shortcut, e.g., `head(get(gear_subset))` instead of `head(eval(parse(text = gear_subset))) – Gregor Thomas Jan 11 '17 at 21:21
  • True, but then you have to remember two commands. But yeah, it is an option, and does just what you need. – Mike Wise Jan 11 '17 at 21:23
  • 1
    Most of the time you don't *need* `get` and if you make frequent use of `eval(parse(text = "..."))` then you're really doing it wrong. – Gregor Thomas Jan 11 '17 at 21:25
  • I don't use it very often at all in fact. But okay, if it bothers that bad, I will edit it and suggest a `get`. I don't really see why it is much of a big deal though. Unless you see a danger. – Mike Wise Jan 11 '17 at 21:27
  • 1
    `eval(parse(...))` is generally acknowledged to be bad practice in most cases, so I dislike pointing newbies that way. See, e.g., `fortunes::fortune(106)` and `fortunes::fortune(181)`, also [What exactly are the dangers of `eval(parse())`?](http://stackoverflow.com/q/13649979/903061), though that discussion focuses more on the cases where it's use is *actually warranted*, not so much like here when there is an easy alternative. – Gregor Thomas Jan 11 '17 at 21:34
  • Thanks for that. Will certainly remember it :) – Mike Wise Jan 11 '17 at 21:36
  • The edit does help, thank you. To a person searching for exactly this question title, following the 'same object' bit can lead to unexpected behavior without this clarification. – Brian Stamper Jan 12 '17 at 14:53
3

Generally, you shouldn't be programmatically generating names for data frames in your global environment. This is a good indication that you should be using list to make your life simpler. See the FAQ How to make a list of data frames? for many examples and more discussion.

Using your concrete example, I would rewrite it in one of a few different ways.

library(dplyr)
gear_code <- 4
gear_subset <- paste("mtcars_", gear_code, sep = "")
mtcars_subset <- mtcars %>% filter(gear == gear_code)
head(mtcars_subset)
write.csv(mtcars_subset, file = paste(gear_subset, ".csv", sep = ""))

The goal seems to be to write a CSV called gear_X.csv that has the mtcars subset with gear == X. You don't to keep an intermediate data frame around, this should be fine:

gear_code <- 4
mtcars %>% filter(gear == gear_code) %>%
    write.csv(file = paste0('mtcars_', gear_code, '.csv'))

But probably you're coding it this way because you want to do it for each value of gear, and this is where dplyr's group_by helps:

CSVs for all the gears

mtcars %>% group_by(gear) %>%
  do(csv = write.csv(file = sprintf("mt_gear_%s.csv", .[1, "gear"]), x = .)

Data frames for each gear level:

If you really want individual data frame objects for each gear level, keeping them in a list is the way to go.

gear_df = split(mtcars, mtcars$gear)

This gives you a list of three data frames, one for each level of gear. And they are named with the levels already, so to see the data frame with all the gear == 4 rows, do

gear_df[["4"]]

Generally, this easier to work with than three data frames floating around. Anything you want to do to all of the data frames you can do at the same time with a single lapply, and even if you want to use a for loop it's simpler than eval(parse()) or get().

Community
  • 1
  • 1
Gregor Thomas
  • 136,190
  • 20
  • 167
  • 294
  • Kind of depends on exactly what the intent is, but yeah, this does feel like what it probably was. However I can't remember what I was programming in July 2015, and I kind of wonder if JDS will be able to either. – Mike Wise Jan 11 '17 at 22:02
  • 2
    Who knows, but SO is as much or more a reference for people browsing the site as it is for the original askers. I saw the question get bumped from your earlier edit and wanted to throw my two cents in. – Gregor Thomas Jan 11 '17 at 22:09