Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

R Version of SAS macro variable?

Tags:

for-loop

r

macros

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.

like image 857
BNT Avatar asked Oct 12 '25 15:10

BNT


2 Answers

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")))
like image 139
G. Grothendieck Avatar answered Oct 14 '25 07:10

G. Grothendieck


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)
like image 29
Parfait Avatar answered Oct 14 '25 09:10

Parfait