3

I'm sorry for the confusion but eventually, the first example I posted (at the bottom of the page), did not help me to figure out how tidyeval works with mutate, so I'm adding a new example.

I would like to create a function that takes three args:

  • a dataframe
  • the column(s) to mutate
  • a variable (from the dataframe) to replace the values that are being mutated

For instance, to replace the values in mpg with the values from carb I tried this:

I tried this
colToX <- function(dt, ..., repl) {
  cols <- quos(...)
  repl <- quo(repl)
  mutate_at(dt, vars(!!!cols), funs(function(x) !!repl))
}

colToX(mtcars, mpg, repl = carb)

which doesn't work since:

Error in mutate_impl(.data, dots) : Column mpg is of unsupported type function

My first example (@MrFlick's and @UseR's both work fine for this):

For instance, the one below should mutate() assigning all 1 to the variable passed in ...

colTo1 <- function(dt, ...) {
col <- quo(...)
mutate(mtcars, !!col := 1)
}

colTo1(mtcars, mpg)

Error: LHS must be a name or string

Eventually, the output should be the same as mutate(mtcars, mpg = 1)

Dambo
  • 3,318
  • 5
  • 30
  • 79

2 Answers2

4

@MrFlick's solution works for the one column case, but since OP used ... as an argument, I assume OP would also want the function to be able to take in multiple columns. For example, the following would not work:

colTo1 <- function(dt, ...) {
  col <- quo_name(quo(...))
  mutate(dt, !!col := 1)
}

colTo1(mtcars, mpg, cyl)

Error in inherits(x, "quosure") : object 'cyl' not found

What we can do is to use quos instead of quo and mutate_at instead of mutate:

colTo1 <- function(dt, ...) {
  cols <- quos(...)
  mutate_at(dt, vars(!!!cols), function(x) x=1)
}

quos converts each argument from ... into vector of quosures. Using mutate_at's vars syntax and explicit splicing with !!! from rlang, we can unquote each quosure in cols, and mutate on those specified columns.

Now this works as intended:

colTo1(mtcars, mpg, cyl)

Result:

   mpg cyl  disp  hp drat    wt  qsec vs am gear carb
1    1   1 160.0 110 3.90 2.620 16.46  0  1    4    4
2    1   1 160.0 110 3.90 2.875 17.02  0  1    4    4
3    1   1 108.0  93 3.85 2.320 18.61  1  1    4    1
4    1   1 258.0 110 3.08 3.215 19.44  1  0    3    1
5    1   1 360.0 175 3.15 3.440 17.02  0  0    3    2
6    1   1 225.0 105 2.76 3.460 20.22  1  0    3    1
7    1   1 360.0 245 3.21 3.570 15.84  0  0    3    4
8    1   1 146.7  62 3.69 3.190 20.00  1  0    4    2
9    1   1 140.8  95 3.92 3.150 22.90  1  0    4    2
10   1   1 167.6 123 3.92 3.440 18.30  1  0    4    4
...

It's also easy enough to let "1" be another argument to be passed into the function:

colToX <- function(dt, ..., X) {
  cols <- quos(...)
  mutate_at(dt, vars(!!!cols), function(x) x=X)
}

colToX(mtcars, mpg, cyl, X = 2)

Edit: OP changed the question to require that X should be another column in the same dataframe. Below is my new solution:

colToX <- function(dt, ..., X) {
  cols <- quos(...)
  X_quo <- enquo(X)
  mutate_at(dt, vars(!!!cols), funs(.data$`!!`(X_quo)))
}

colToX(mtcars, mpg, cyl, X = hp)

Here, I am using the funs function to create a list of function calls to each column referenced from vars. .data refers to the input dataframe into mutate_at (in this case dt). I used enquo to convert what's called from X into a quosure and unquote it using !!.

Result:

   mpg cyl  disp  hp drat    wt  qsec vs am gear carb
1  110 110 160.0 110 3.90 2.620 16.46  0  1    4    4
2  110 110 160.0 110 3.90 2.875 17.02  0  1    4    4
3   93  93 108.0  93 3.85 2.320 18.61  1  1    4    1
4  110 110 258.0 110 3.08 3.215 19.44  1  0    3    1
5  175 175 360.0 175 3.15 3.440 17.02  0  0    3    2
6  105 105 225.0 105 2.76 3.460 20.22  1  0    3    1
7  245 245 360.0 245 3.21 3.570 15.84  0  0    3    4
8   62  62 146.7  62 3.69 3.190 20.00  1  0    4    2
9   95  95 140.8  95 3.92 3.150 22.90  1  0    4    2
10 123 123 167.6 123 3.92 3.440 18.30  1  0    4    4
...
acylam
  • 18,231
  • 5
  • 36
  • 45
  • I realized that my example is a bit limited for my original problem, where I need to pass a variable instead of 1 after `:=`. Could you generalize to that case? Or maybe I should post a new question... – Dambo Feb 20 '18 at 20:11
  • 1
    @Dambo Updated my answer. See if this is what you want. – acylam Feb 20 '18 at 20:16
  • I meant to be able to pass something like `colToX(mtcars, mpg, cyl, X = mpg)`, where `mpg` is a variable from `mtcars` – Dambo Feb 21 '18 at 01:13
  • 1
    @Dambo I've edited my answer to solve your "new" requirement. In general, it's frowned upon in the SO community to have an initial question, accepting an answer, changing the original question, then unaccepting the accepted answer. Therefore, I suggest next time when asking a question, provide all the necessary details an answerer would need to solve your "real" problem, and try not to change your question dramatically – acylam Feb 21 '18 at 16:34
  • I accepted the asnwer since I did it initially, but I get an error when I run your new code `Error in mutate_impl(.data, dots) : Evaluation error: invalid subscript type 'language'. ` – Dambo Feb 21 '18 at 17:02
  • @Dambo Did you get the error running my code exactly or did you use another table? From my understanding, `data.table`s gets buggy with `mutate_at` and `mutate_all`. Not sure if it is fixed now. See: (https://github.com/tidyverse/dplyr/issues/2937) – acylam Feb 21 '18 at 17:07
  • I ran that exact snippet of code. Maybe there is a way to do the same by passing the dataset as an environment in funs()? But I guess I should open a new question for this, – Dambo Feb 21 '18 at 17:23
  • @Dambo Might be some versioning issues with `dplyr` and `rlang`, try reinstalling `tidyverse`. – acylam Feb 21 '18 at 18:13
2

As the error says "Error: LHS must be a name or string" where LHS means left-hand side and it's specifically referring to the !!col := 1 part. You need to turn the quosure you made into a string or symbol. It's probably easiest to get the string version of the quosure with quo_name

colTo1 <- function(dt, ...) {
    col <- quo_name(quo(...))
    mutate(mtcars, !!col := 1)
}
MrFlick
  • 195,160
  • 17
  • 277
  • 295