Turn character strings into named function arguments - r

I have an R script I intend to call from the command line, which includes a function which may take option ... arguments. I'd like to parse any arguments given at the command line as arguments in .... How might I do this?
I've tried the rlang package. I thought something like this would work:
z <- c('x=1', 'y=2') #but actually, z <- commandArgs(T)
c(UQS(z))
Here I would expect to get a vector as if I had called c(x=1, y=2). Instead I get a list of names x=1 &c.

I agree with the previous answer that this is a bit unsafe. That said, one hacky way to achieve this somewhat safely is to take advantage of environments.
First, create a blank environment:
args_env <- env()
Parse-eval your arguments inside that environment.
z <- c("x=1", "y=2")
eval(parse(text = z), envir = args_env)
Convert the environment to a list:
args_list <- as.list(args_env)
And now, you should be able to use do.call to pass arguments in.
do.call(f, args_list)

I wouldn't necessary recommend your approach. It would be safer to use a package like optparse to properly parse your command line parameters.
But that said, if you want to treat arbitrary strings as R code, you can just eval(parse(test=)) it. Of if you really want to use rlang, you can use eval_tidy/parse_expr.
args <- c('x=1', 'y=2')
z <- eval_tidy(parse_expr(paste0("c(", paste(args, collapse=","), ")")))
# or
z <- eval(parse(text=paste0("c(", paste(args, collapse=","), ")")))
You need this because things in the form a=b can't exist independently outside of an expression otherwise it's interpreted as assignment. If you needed to play with just the name on the left or the value on the right, there might be smarter ways to do this, but if you pass it as one character chunk, you'll need to parse it.

Related

class of expr and exprs are different in rlang in R ! Why?

I am not sure if this has been asked here, But I am very confused here. I am reading this awesome book called Advanced R by Hadley Wickham from here.
There is function called cement that has been described here, I have modified it little bit and trying to understand it.
library(rlang)
cement1 <- function(x) {
dots <- expr(x)
print(class(dots))
#paste(expr_name(x))
}
cement2 <- function(y,z) {
dots <- exprs(y,z)
print(class(dots))
#paste(purrr::map(dots, expr_name), collapse = " ")
}
Running the above cement1 without any parameter returns me the class of dots as "name".
However, when I run the cement2 function with additional parameter, the class returns "list", {simply putting class(expr(x)) returns "name" whereas class(exprs(x)) returns "list"}.
I am not getting my head around this as why it is printing different class returned by expr and exprs. The only difference I thought I knew about them was, one deals with one parameter, other one deals with multiple parameters, but I may be wrong, I might have missed some details.
Original Problem: So, it all started by running these two functions separately by removing the comments section in the code for both cement1 and cement2, when I run the functions Below are the output returned by them:
cement1(Hello) #Returns , Error in type_of(.x) : object 'Hello' not found
cement2(Hello) #Works very well and returns, [1] "y z"
So I tried to find the reason why cement1 failed and then printed their classes and that is when I realized , expr and exprs return different classes.
My question is:
1) Are they by design, if yes then why? Or, I am doing some horrible mistake, which I am currently unable to see.
2) Does cement1 can't work this if not , what is the correct way?
I am sorry for too long sentences, My first language is not English, hence If anything silly is there, Please let me know I shall correct it. I hope this is not a duplicate, I tried to find the answer but could not found by my own.
Thanks for any help.
R Version: 3.4.2
rlang: 0.2.0
1) Yes, the return values of expr and exprs differ by design. From the ?expr help page:
enexpr() and expr() capture a single raw expression.
enexprs() and exprs() capture a list of raw expressions including expressions contained in ....
2) expr_name() expects a quoted expression, such as what's produced by expr(). So, you need to modify your cement1 to call expr_name() on dots, not x. You can also remove paste because you are not concatenating anything.
cement1 <- function(x) {
dots <- expr(x)
# print(class(dots)) ## Commented out for clarity
expr_name(dots) ## The input to expr_name is now effectively expr(x)
}
cement1( Hello )
# "x"
Your function cement2 basically calls expr_name() on every element of the list returned by exprs(), then concatenates the results into a single string.
2a) Now that we got your cement1 working, we can improve it further. Currently, the function doesn't make any use of its input argument x. expr() simply captures the unevaluated expression, and this expression will always be x, regardless of what you name your argument:
cement1.1 <- function( completelyIgnoredName ) {
dots <- expr(x)
expr_name(dots)
}
cement1.1( Hello )
# "x"
However, if you replace expr() with enexpr(), the function will substitute the expression provided as the function's argument and capture that instead:
cement1.2 <- function(x) {
dots <- enexpr(x)
expr_name(dots)
}
cement1.2( Hello )
# "Hello"

Trouble interpolating column name for subset in R function

I have (fbodata) 'data.frame': 6181090 obs. of 41 variables:
I want to subset it and save the portion that pertains to a specific subset (like a zip). My approach seems to work when it is not in a function, but I ultimately want to use sapply.
nmakedir <- function(item, ccol) {
snipped a bunch of code that works
trim<- fbodata[ which(paste(ccol)==item),]
trim%>% drop_na(paste(ccol))
trim<- droplevels(trim
save(trim, file = paste(item, "rda", sep="."))
}
The line that doesn't work is one where I creating the subset with which. If i hardcode the line using fbodata$zip instead of paste(ccol) it works fine. Eventually, I plan to call it with something like:
sapply(unique(fbodata$zip),zip, FUN = nmakedir)
I appreciate any clues, I have been on this for a good long while.
A few things going on:
ccol is a string. paste(ccol) is the same string. You never need to call paste with only one argument. (You can use paste to coerce non-strings to strings, but in that case you should use as.character() to be clear.)
Keeping in mind that ccol is a string, what is fbodata$zip? It's a column! What is the equivalent using ccol and brackets? fbodata[[ccol]] or fbodata[, ccol]. You can use either of those interchangeably with fbodata$zip. So, this bad line
fbodata[ which(paste(ccol)==item),]
# should be this:
fbodata[which(fbodata[[ccol]] == item), ]
drop_na, like most dplyr functions, expects (quoting from the help) "bare variable names", not strings. Also from the help, "See Also: drop_na_ for a version that uses regular evaluation and is suitable for programming with". In this case, I don't think you need to do anything more than replace drop_na with drop_na_.
You are missing a right parenthesis on your droplevels command.
There might be more, but this is much as I can see without any sample data. Your sapply call looks funny to me because I thought zip is supposed to be a column name, but when you call sapply(unique(fbodata$zip),zip, FUN = nmakedir) it needs to be an object in your global environment. I would think sapply(unique(fbodata$zip), 'zip', FUN = nmakedir) makes more sense, but without a reproducile example there's no way to know.
It also seems like you're coding your own version of split. I would probably start this off with fbo_split = split(fbodata, fbodata$zip) and then use lapply to drop_na_, droplevels, and save, but maybe your snipped code makes that a less good idea.

Pass non-string argument to r function and use it inside this function as is

I am a bit confused with the way arguments are transmitted to r function, and the associated syntax (quoting, substituting, evaluating, calling, expressions, "...", ...) .
Basically, what I need to do is to pass arguments in a function using only their name, but without using the type "character".
This is a (not working) illustration of what I would like to do
require(dplyr)
test <- function(x) select(iris, DesiredFunction(x))
test(Species)
I am also interested in general resources about the possibilities to pass arguments to functions.
Thank you,
François
UPDATE
The following is working
require(dplyr)
test <- function(x) select_(iris, substitute(x))
test(Species)
Is there a way to do this but with "select" instead of "select_" ?
Or in other words, what is the inverse operation for quoting ?

Automatically using the object name as file name with write.table or write.csv

Is there a way to have the object name become the file name character string when using write.table or write.csv?
In the following, a and b are vectors. I will be doing similar comparisons for many other pairs of vectors, and would like to not write out the object name as many times as I have been doing.
unique_downa<-a[!(a%in%b)]
write.csv(unique_downa,file="unique_downa.csv")
Or if anyone has a suggestion for a better way to do this whole process, I'd be happy to hear it.
The idiomatic approach is to use deparse(substitute(blah))
eg
write.csv.named <- function(x, ...){
fname <- sprintf('%s.csv',deparse(substitute(x)))
write.csv(x=x, file = fname, ...)
}
It might be easiest to use the names of elements of a list instead of trying to use object names:
mycomparisons <-list (unique_downa = a[!(a%in%b)], unique_downb = b[!(b%in%a)])
mapply (write.csv, mycomparisons, paste (names (mycomparisons), ".csv", sep =""))
The best thing to do is probably put your vectors in a list, and then do the comparisons, the naming, and the writing out all inside the same loop, but that depends on how similar these similar comparisons are...

How to use variable in xpath in R?

when i parse a web file, it works fine ,
tdata=xpathApply(data,"//table[#id='PL']")
i want to use variable in xpathApply,
x="PL"
tdata=xpathApply(data,"//table[#id=x]")
it can not work,how to write the xpath expression in xpathApply with variable?
think for Dason's suggestion,
x="PL"
y=paste0("//table[#id='",x,"']")
tdata=xpathApply(data,y)
it is ok,but i feel it is ugly,how can i write it more beautiful?
The gsubfn package can do string interpolation somewhat along the lines of Perl if we preface the function whose arguments are to contain substitutions with fn$. Here $x means substitute in the value of x . See ?fn and the gsubfn home page.
library(gsubfn)
x <- "PL"
tdata <- fn$xpathApply(data, "//table[#id='$x']")
#Dason's suggestion of using paste or one alike is most likely the only way to go. If you find it ugly, you can sweep it under the rug by creating your own function:
my.xpathApply <- function(data, x) xpathApply(data, paste0("//table[#id='",x,"']"))
tdata <- my.xpathApply(data, "PL")
After all, you must use a lot of package functions that use paste somewhere, so you should be ok with having one of your own :-)

Resources