3
votes

I am pretty familiar with SAS. I am a beginner in R and am trying to figure out what the R equivalent of macro variables is. Specifically I have 6 datasets with a common variable name, Price. I want to create a loop that changes Price in each data set to DatasetNamePrice. This would be simple in SAS using macro variables for text replacement. So far I have created vector with the names of each data set.

v=c("SP","SPF","SPP","NQ","RTY","NYA")

I usually use this code to rename variables:

names(SP)[names(SP)=="Price"]="SPPrice"

My attempt so far is:

for(i in 1:6) 
{ 
  names(v[[i]])[names(v[[i]])=="Price"]="v[[i]]Price"
}

R does not give me any errors when this runs but does not appear to do anything. Any help is appreciated.

2
What would be your expected output?user3710546
Are you simply looking for paste0(v, "Price")? It gives [1] "SPPrice" "SPFPrice" "SPPPrice" "NQPrice" "RTYPrice" "NYAPrice". So you can grep/match/whatever names first.user3710546
SAS and R are very different languages. Trying to do literal translations will get you into trouble quickly. R doesn't use a MACRO preprocessor so trying to dynamically create variable names isn't the best strategy, rather you would apply over vectors and lists.MrFlick

2 Answers

3
votes

Here are some alternatives.

1) Base R Set e to be the environment of the data frames. Here we assume they are in the current environment. With this in place e[[nm]] refers to the data frame whose name is the character string held in variable nm so the following works modifying the names in place:

e <- environment()
for(nm in v) {
   is.price <- names(e[[nm]]) == "Price"
   names(e[[nm]])[ is.price ] <- paste0(nm, "Price")
}

1a) Base R Function passing name and environment Here we define a function which takes the name of the data frame and the environment and modifies the names of the data frame in place. We use match instead of == so that from and to can optionally be vectors of names. The in place modification in this solution is not really in the spirit of R's functional nature but we show it as an alternative:

rename1a <- function(DFname, from, to, envir = parent.frame()) {
    ix <- match(from, names(envir[[DFname]]))
    names(envir[[DFname]])[ ix ] <- to
}

for(nm in v) rename1a(nm, "Price", paste0(nm, "Price"))

1b) Base R Function returning copy Here we define a function which takes the data frame itself and returns a copy with the name changed. The function itself does not need to deal with environments and is more funtional in nature (i.e. it does not modify its inputs) -- the caller is responsible for assigning the result back.

rename1b <- function(DF, from, to) {
    names(DF)[match(from, names(DF))] <- to
    DF
}

e <- environment()
for(nm in v) e[[nm]] <- rename1b(e[[nm]], "Price", paste0(nm, "Price"))

2) doBy::renameCol renameCol in the doBy package is plug compatible with rename1b in (1b) so:

library(doBy)
e <- environment()
for(nm in v) e[[nm]] <- renameCol(e[[nm]], "Price", paste0(nm, "Price"))

3) plyr::rename The plyr package has a rename function. Note that like (1b) it produces a copy of the data frame with the renamed columns so we assign it back:

e <- environment()
for(nm in v) e[[nm]] <- plyr::rename(e[[nm]], list(Price = paste0(nm, "Price")))

The reshape package has a similar function also called rename and the above works if we replace plyr::rename with reshape::rename.

4) gtools::defmacro It would also be possible to use defmacro in gtools to create a macro which alters the names in place. Although not typical of processing in R this does allow one to pass the data frame itself rather than separate name and environment as in (1a).

library(gtools)
rename4 <- defmacro(DF, from, to, expr = { names(DF)[ match(from, names(DF)) ] <- to })

e <- environment()
for(nm in v) rename4(e[[nm]], "Price", paste0(nm, "Price"))

Also see Thomas Lumley's Programmer's Niche article in R News 2001/3.

Note 1: You may wish to examine why you want to make these name changes in first place. There is also the question of whether the data frames should be freely defined in the global environment or combined into a list given that we want to deal with them en masse. The first Map creates a named list L such that, for example, L$SP or L[["SP"]] refers to the SP component in L. The second Map outputs a new named list whose components have the new column names:

L <- Map(get, v) # create named list of input data frames
Map(rename1b, L, "Price", paste0(names(L), "Price"))

Note 2: Here we create some input to test with using the builtin data frame BOD. This creates the objects SP, SPF, etc. that are the same as data frame BOD except that the second column is named "Price" :

# create SP, SPF, ... to test, each with a Price column
v <- c("SP","SPF","SPP","NQ","RTY","NYA")
for(nm in v) assign(nm, setNames(BOD, c("Time", "Price")))
2
votes

For your needs, you will need the get() and assign() functions since you are attempting to pass a string literal in the names() attribute which expects a data frame object. Also, to concatenate variables with strings you need to use paste().

Consider the following that uses lapply() (a recursive method to apply a function to list or vector and returns a list); it renames the fields and returns each data frame into a list of data frames. Then, a for loop re-writes the original data frames from this created list using assign():

v=c("SP","SPF","SPP","NQ","RTY","NYA")

dfList <- lapply(v, function(x) {
                      df <- get(x)
                      names(df)[grep("Price", names(df))] <- paste0(x, "Price")              
                      return(df)    
                })

for (i in 1:length(v)) {  
     assign(v[[i]], as.data.frame(dfList[[i]]))  
}

rm(dfList)