359

I'm curious to know if R can use its eval() function to perform calculations provided by e.g. a string.

This is a common case:

eval("5+5")

However, instead of 10 I get:

[1] "5+5"

Any solution?

zx8754
  • 52,746
  • 12
  • 114
  • 209
Federico Giorgi
  • 10,495
  • 9
  • 42
  • 56
  • 8
    Despite all the answers showing how to solve that with parse ... Why do you need to store language types in a character `string` ? Martin Mächler's answer should deserve much more upvotes. – Petr Matousu Nov 29 '16 at 10:48
  • 8
    Thank you @PetrMatousu. Yes, I'm shocked to see how mis-information is spread on SO now.. by people upvoting `eval(parse(text = *))` fake solutions. – Martin Mächler Apr 01 '17 at 16:22
  • 2
    I want to run scrips of the form: `QQ = c('11','12','13','21','22','23')`, i.e.: QQ =c(...,'ij',..) with i,j varying on a range that is may vary from run to run. For this and similar examples, I can write the script as `paste( "QQ = c('", paste(rep(1:2,each=3),1:3, sep="", collapse="','"), "')",sep="")`, and the option `eval(parse(text=...))` creates the vector QQ in the working environment as per the script. What would be the proper R coder way to do this, if not with "text=..."? – VictorZurkowski Sep 18 '18 at 15:04
  • 3
    @MartinMächler how is `eval(parse(text = "5+5"))` a "fake solution"? It seems to work fine for all the cases I have tried. There are reasons that one might need to evaluate something read-in as a string. I am finding your answer more confusing and less useful to evaluating a string (OP) than the others, which might be why the other answers have more upvotes? – Dylan_Gomes Jan 11 '22 at 19:56
  • @VictorZurkowski: With your example, written in a "generalizable way", `i <- rep(1:2, each=3) ; j <- 1:3` the proper solution is (many times faster and more readable) `QQ <- paste0(i, j)` -- voilà, that's all. – Martin Mächler Jan 18 '22 at 16:22

8 Answers8

510

The eval() function evaluates an expression, but "5+5" is a string, not an expression. Use parse() with text=<string> to change the string into an expression:

> eval(parse(text="5+5"))
[1] 10
> class("5+5")
[1] "character"
> class(parse(text="5+5"))
[1] "expression"

Calling eval() invokes many behaviours, some are not immediately obvious:

> class(eval(parse(text="5+5")))
[1] "numeric"
> class(eval(parse(text="gray")))
[1] "function"
> class(eval(parse(text="blue")))
Error in eval(expr, envir, enclos) : object 'blue' not found

See also tryCatch.

Community
  • 1
  • 1
Harlan
  • 18,883
  • 8
  • 47
  • 56
  • 40
    As Shane notes below, "You need to specify that the input is text, because parse expects a file by default" – PatrickT Jan 15 '14 at 08:39
  • 1
    the side-effects of using eval(parse) should be specified. For example, if you have a pre-defined variable **name** equal to "David" and you reassign using eval(parse(text = "name") == "Alexander", you will get an error because eval & parse do not return an R expression that can be evaluated. – kgui Aug 01 '16 at 22:51
  • 1
    @NelsonGon: Unevaluated expressions constructed using `quote()`, `bquote()`, or the more sophisticated tools provided by the `rlang` package. – Artem Sokolov Aug 13 '19 at 14:32
  • @ArtemSokolov Thanks, I somehow keep coming back to this question looking for an alternative. I've looked at `rlang` but the closest I found was `parse_expr` which calls `parse_exprs` which in turn is the same as using `parse` and wrapping it in `eval` which seems to be the same thing as done here. I am unsure what the advantage would be of using `rlang`. – NelsonGon Aug 20 '19 at 18:19
  • 3
    @NelsonGon: with `rlang`, you would work directly with expressions, not strings. No parse step necessary. It has two advantages. 1. Expression manipulations will always produce valid expressions. String manipulations will only produce valid strings. You won't know if they are valid expressions until you parse them. 2. There's no equivalent to the `substitute()` class of functions in the string world, which severely limits your ability to manipulate function calls. Consider [this glm wrapper](https://stackoverflow.com/a/57528229/300187). What would a string equivalent look like? – Artem Sokolov Aug 21 '19 at 15:28
118

You can use the parse() function to convert the characters into an expression. You need to specify that the input is text, because parse expects a file by default:

eval(parse(text="5+5"))
Shane
  • 98,550
  • 35
  • 224
  • 217
  • 8
    > fortunes::fortune("answer is parse") If the answer is parse() you should usually rethink the question. -- Thomas Lumley R-help (February 2005) > – Martin Mächler Oct 20 '16 at 20:40
  • 23
    @MartinMächler That's ironic, because the core R packages use `parse` all the time! https://github.com/wch/r-source/search?utf8=%E2%9C%93&q=extension%3AR+parse&type= – geneorama Mar 30 '17 at 22:10
66

Sorry but I don't understand why too many people even think a string was something that could be evaluated. You must change your mindset, really. Forget all connections between strings on one side and expressions, calls, evaluation on the other side.

The (possibly) only connection is via parse(text = ....) and all good R programmers should know that this is rarely an efficient or safe means to construct expressions (or calls). Rather learn more about substitute(), quote(), and possibly the power of using do.call(substitute, ......).

fortunes::fortune("answer is parse")
# If the answer is parse() you should usually rethink the question.
#    -- Thomas Lumley
#       R-help (February 2005)

Dec.2017: Ok, here is an example (in comments, there's no nice formatting):

q5 <- quote(5+5)
str(q5)
# language 5 + 5

e5 <- expression(5+5)
str(e5)
# expression(5 + 5)

and if you get more experienced you'll learn that q5 is a "call" whereas e5 is an "expression", and even that e5[[1]] is identical to q5:

identical(q5, e5[[1]])
# [1] TRUE
Scarabee
  • 5,437
  • 5
  • 29
  • 55
Martin Mächler
  • 4,619
  • 27
  • 27
  • 6
    could you give an example? maybe you could show us how to "hold on" to 5+5 in an r object, then evaluate it later, using quote and substitute rather than a character and eval(parse(text=)? – Richard DiSalvo Dec 19 '17 at 03:30
  • 16
    I may be a little lost. At what point do you get 10? Or is that not the point? – Nick S Feb 20 '18 at 13:12
  • @RichardDiSalvo: yes, `q5 <- quote(5+5)` above *is* the expression (actually the "call") `5+5` and it is an R object, but not a string. You can evaluate it any time. Again: using, quote(), substitute(), ... **instead** parse creates calls or expressions directly and more efficiently than via parse(text= . ). Using `eval()` is fine, using `parse(text=*)` is error prone and sometimes quite inefficient in comparison to construction calls and manipulating them.. @Nick S: It's `eval(q5)` or `eval(e5)` in our running example – Martin Mächler Feb 25 '18 at 21:05
  • 1
    @NickS : To get 10, you evaluate the call/expression, i.e., call `eval(.)` on it. My point was that people should not use `parse(text=.)` but rather `quote(.)` etc, to construct the call which later will be `eval()`ed. – Martin Mächler Aug 06 '19 at 07:43
  • 4
    `eval(quote())` does work in a few cases but will fail for some cases where `eval(parse())` would work well. – NelsonGon Aug 20 '19 at 18:22
  • I don't understand this solution still. I have a dataframe where one of the columns is a fraction (ie, "73/873") that's been stored as a string. I need to turn this into a decimal so I can sort/graph the data. When I try to use the included solutions, I can't get reference the variables correctly. So df[[1,1]] becomes "73/873", but quote(df[[1,1]]) simply produces "df[1,1]" – Max F Jan 13 '21 at 16:16
  • Let me give you a use case. If I want to join 80 data frames, whose names I get from vcdExtra::datasets as strings, then what is a better way than this: ``` data <- vcdExtra::datasets("dbdataset") data$Item %>% lapply( function(i) i %>% parse(text = .) %>% eval()) %>% Reduce(function(x, y) merge(x, y), .)``` – Frank May 17 '21 at 15:27
  • 3
    `eval(quote("5+10*2"))` returns `"5+10*2"` whereas `eval(parse(text="5+10*2"))` returns `25`. In this case, `quote` doesn't seem to be the better function to use here... – Dylan_Gomes Jan 11 '22 at 20:04
  • 1
    @Dylan_Gomes, @NelsonGon: I did *not* say, that `quote()` works when you give it a string. You should *not* have strings at all, when you need expressions or calls. Yes, sometimes your input does consist of strings, and you must parse these one way or the other, to *turn* them into expressions or calls which *then* you can evaluate. Using packages such as `rlang` or `lazy_eval` do the same thing: Always two steps: 1. parse (which does "string" -> expression) 2. eval (expression -> "value"). – Martin Mächler Jan 18 '22 at 16:13
  • 1
    @MartinMächler thanks for the clarification. It seems that this doesn't exactly answer the OP then. I understand that you are saying not to use strings at all, but one doesn't always have a choice. If you are handed a dataset that has character strings of equations, how does one evaluate them? There must be an acceptable way to do this, no? – Dylan_Gomes Jan 18 '22 at 16:21
  • @Dylan_Gomes: Yes, as I say above, if you really get string data, you are "out of luck" (because parsing is necessarily somewhat error prone), and you need 1. parse, 2. eval, which indeed you can combine in one `eval(parse(text= * ))` (or any other variants of that). – Martin Mächler Jan 18 '22 at 16:30
31

Not sure why no one has mentioned two Base R functions specifically to do this: str2lang() and str2expression(). These are variants of parse(), but seem to return the expression more cleanly:

eval(str2lang("5+5"))

# > 10
  
eval(str2expression("5+5"))

# > 10

Also want to push back against the posters saying that anyone trying to do this is wrong. I'm reading in R expressions stored as text in a file and trying to evaluate them. These functions are perfect for this use case.

David J. Bosak
  • 1,386
  • 12
  • 22
  • 2
    It's not that it's *always* wrong, it's just that there are many, many cases where it's safer and better to do things in a different way. – Ben Bolker Sep 20 '20 at 00:52
22

Nowadays you can also use lazy_eval function from lazyeval package.

> lazyeval::lazy_eval("5+5")
[1] 10
20

Alternatively, you can use evals from my pander package to capture output and all warnings, errors and other messages along with the raw results:

> pander::evals("5+5")
[[1]]
$src
[1] "5 + 5"

$result
[1] 10

$output
[1] "[1] 10"

$type
[1] "numeric"

$msg
$msg$messages
NULL

$msg$warnings
NULL

$msg$errors
NULL


$stdout
NULL

attr(,"class")
[1] "evals"
daroczig
  • 28,004
  • 7
  • 90
  • 124
  • 2
    Nice function; fills a hole left by `evaluate::evaluate` by actually returning the result object; that leaves your function suitable for use for calling via mclapply. I hope that feature remains! – russellpierce Dec 02 '15 at 09:56
  • Thank you, @rpierce. This function was originally written in 2011 as part of our `rapport` package, and have been actively maintained since then as being heavily used in our [rapporter.net](http://rapporter.net) service besides a few other projects as well -- so I'm sure it will remain maintained for a while :) I'm glad you find it useful, thanks for your kind feedback. – daroczig Dec 03 '15 at 07:22
11

Similarly using rlang:

eval(parse_expr("5+5"))
c1au61o_HH
  • 867
  • 7
  • 14
  • 4
    Came here looking for an `rlang` answer but what if any is the advantage of this over base alternatives? Actually, close examination of the code used shows that it is in fact using `eval(parse(....))` which I wanted to avoid. – NelsonGon Aug 20 '19 at 18:16
  • 6
    Not only those negatives, but its name is also misleading. It is NOT evaluating an expression. Should be called parse_to_expr ot something else to indicate that the user will know that it intended for character arguments. – IRTFM Sep 11 '19 at 03:00
  • 1
    It's just a wrapper that handles seamlessly any type of input (character, list, connection) – deeenes Jan 05 '22 at 03:17
2

I agree there are concerns around eval and parse, but if the expression is in a string, there appears nothing much that can be done. This eval parse is also used in the glue package by the tidyverse experts, see https://github.com/tidyverse/glue/blob/d47d6c7701f738f8647b951df418cfd5e6a7bf76/R/transformer.R#L1-L12

Perhaps the accepted answer of eval(parse(text="5+5")) has security concerns if the text string is from an untrusted source, eg imagine user_input = "list.files()" or worse file.remove...

One potential work around is below.

The idea is to set the R environment in which the expression is to be evaluated. In R, most functions that 'comes with R' are actually in packages that gets autoloaded at R start up, eg 'list.files', 'library' and 'attach' functions come from the 'base' package. By setting the evaluation environment to empty environment, these functions are no longer available to the expression to be evaluated, preventing malicious code from executing. In the code below, by default I include only arithmetic related functions, otherwise user can provide the evaluation environment with explicitly allowed functions.

eval_text_expression <- function(text_expression, data_list, eval_envir = NULL) {
  # argument checks
  stopifnot(is.character(text_expression) && length(text_expression) == 1)
  stopifnot(is.list(data_list))
  stopifnot(length(data_list) == 0 || (!is.null(names(data_list)) && all(names(data_list) != "")))
  stopifnot(all(!(lapply(data_list, typeof) %in% c('closure', 'builtin'))))
  stopifnot(is.null(eval_envir) || is.environment(eval_envir))
  # default environment for convenience 
  if (is.null(eval_envir)) {
    arithmetic_funcs <- list("+" = `+`, "-" = `-`, "*" = `*`, "/" = `/`, "^" = `^`, "(" = `(`)
    eval_envir = rlang::new_environment(data = arithmetic_funcs, parent = rlang::empty_env())
  }
  # load data objects into evaluation environment, then evaluate expression
  eval_envir <- list2env(data_list, envir = eval_envir)
  eval(parse(text = text_expression, keep.source = FALSE), eval_envir)
}

eval_text_expression("(a+b)^c - d", list(a = 1, b = 2, c = 3, d = 4))
# [1] 23
eval_text_expression("list.files()", list())
# Error in list.files() : could not find function "list.files"
eval_text_expression("list.files()", list(), eval_envir = rlang::new_environment(list("list.files" = list.files)))
# succeeds in listing my files if i explicitly allow it

  • That looks great, but I have a little trouble understanding how it works. Could you please explain it a bit how the solution addresses the security concerns? That would make the answer more meaningful. Thanks. – Jan Jul 09 '22 at 09:06
  • 1
    edited with explanations – user3408956 Jul 11 '22 at 23:20