Pass function arguments by column position to mutate_at
I'm trying to tighten up a %>%
piped workflow where I need to apply the same function to several columns but with one argument changed each time. I feel like purrr
's map
or invoke
functions should help, but I can't wrap my head around it.
My data frame has columns for life expectancy, poverty rate, and median household income. I can pass all these column names to vars
in mutate_at
, use round
as the function to apply to each, and optionally supply a digits
argument. But I can't figure out a way, if one exists, to pass different values for digits
associated with each column. I'd like life expectancy rounded to 1 digit, poverty rounded to 2, and income rounded to 0.
I can call mutate
on each column, but given that I might have more columns all receiving the same function with only an additional argument changed, I'd like something more concise.
library(tidyverse)
df <- tibble::tribble(
~name, ~life_expectancy, ~poverty, ~household_income,
"New Haven", 78.0580437642378, 0.264221051111753, 42588.7592521085
)
In my imagination, I could do something like this:
df %>%
mutate_at(vars(life_expectancy, poverty, household_income),
round, digits = c(1, 2, 0))
But get the error
Error in mutate_impl(.data, dots) :
Columnlife_expectancy
must be length 1 (the number of rows), not 3
Using mutate_at
instead of mutate
just to have the same syntax as in my ideal case:
df %>%
mutate_at(vars(life_expectancy), round, digits = 1) %>%
mutate_at(vars(poverty), round, digits = 2) %>%
mutate_at(vars(household_income), round, digits = 0)
#> # A tibble: 1 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.26 42589
Mapping over the digits uses each of the digits
options for each column, not by position, giving me 3 rows each rounded to a different number of digits.
df %>%
mutate_at(vars(life_expectancy, poverty, household_income),
function(x) map(x, round, digits = c(1, 2, 0))) %>%
unnest()
#> # A tibble: 3 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.3 42589.
#> 2 New Haven 78.1 0.26 42589.
#> 3 New Haven 78 0 42589
Created on 2018-11-13 by the reprex package (v0.2.1)
r dplyr purrr
add a comment |
I'm trying to tighten up a %>%
piped workflow where I need to apply the same function to several columns but with one argument changed each time. I feel like purrr
's map
or invoke
functions should help, but I can't wrap my head around it.
My data frame has columns for life expectancy, poverty rate, and median household income. I can pass all these column names to vars
in mutate_at
, use round
as the function to apply to each, and optionally supply a digits
argument. But I can't figure out a way, if one exists, to pass different values for digits
associated with each column. I'd like life expectancy rounded to 1 digit, poverty rounded to 2, and income rounded to 0.
I can call mutate
on each column, but given that I might have more columns all receiving the same function with only an additional argument changed, I'd like something more concise.
library(tidyverse)
df <- tibble::tribble(
~name, ~life_expectancy, ~poverty, ~household_income,
"New Haven", 78.0580437642378, 0.264221051111753, 42588.7592521085
)
In my imagination, I could do something like this:
df %>%
mutate_at(vars(life_expectancy, poverty, household_income),
round, digits = c(1, 2, 0))
But get the error
Error in mutate_impl(.data, dots) :
Columnlife_expectancy
must be length 1 (the number of rows), not 3
Using mutate_at
instead of mutate
just to have the same syntax as in my ideal case:
df %>%
mutate_at(vars(life_expectancy), round, digits = 1) %>%
mutate_at(vars(poverty), round, digits = 2) %>%
mutate_at(vars(household_income), round, digits = 0)
#> # A tibble: 1 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.26 42589
Mapping over the digits uses each of the digits
options for each column, not by position, giving me 3 rows each rounded to a different number of digits.
df %>%
mutate_at(vars(life_expectancy, poverty, household_income),
function(x) map(x, round, digits = c(1, 2, 0))) %>%
unnest()
#> # A tibble: 3 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.3 42589.
#> 2 New Haven 78.1 0.26 42589.
#> 3 New Haven 78 0 42589
Created on 2018-11-13 by the reprex package (v0.2.1)
r dplyr purrr
1
In the past when faced with this problem I ended up gathering my columns, grouping them, mutating them, and spreading them back out. See also How do I sweep specific columns with dplyr?
– Konrad Rudolph
Nov 13 '18 at 19:38
@KonradRudolph thanks, I was thinking about that too, and that's an approach I've used before, but I'm trying to figure out whether a super simple, one-line version is possible
– camille
Nov 13 '18 at 19:50
@Henrik you might be on to something. Usingmap2_dfc
could work, but that requires dropping thename
column and then maybe joining it back on. I'm trying to imagine amap2_dfc
/map_at
hybrid
– camille
Nov 13 '18 at 19:56
1
Seems like it might be easier when you will be able to pass a list of functions to summarize_at/mutate_at: github.com/tidyverse/dplyr/issues/3433. That doesn't seem to work yet.
– MrFlick
Nov 13 '18 at 20:06
mutate
supports!!!
so the easiest in my opinion is to recreate the verbosemutate
call (notmutate_at
) programmatically throughmap2
or (cleaner to me)imap
– Moody_Mudskipper
Nov 14 '18 at 14:21
add a comment |
I'm trying to tighten up a %>%
piped workflow where I need to apply the same function to several columns but with one argument changed each time. I feel like purrr
's map
or invoke
functions should help, but I can't wrap my head around it.
My data frame has columns for life expectancy, poverty rate, and median household income. I can pass all these column names to vars
in mutate_at
, use round
as the function to apply to each, and optionally supply a digits
argument. But I can't figure out a way, if one exists, to pass different values for digits
associated with each column. I'd like life expectancy rounded to 1 digit, poverty rounded to 2, and income rounded to 0.
I can call mutate
on each column, but given that I might have more columns all receiving the same function with only an additional argument changed, I'd like something more concise.
library(tidyverse)
df <- tibble::tribble(
~name, ~life_expectancy, ~poverty, ~household_income,
"New Haven", 78.0580437642378, 0.264221051111753, 42588.7592521085
)
In my imagination, I could do something like this:
df %>%
mutate_at(vars(life_expectancy, poverty, household_income),
round, digits = c(1, 2, 0))
But get the error
Error in mutate_impl(.data, dots) :
Columnlife_expectancy
must be length 1 (the number of rows), not 3
Using mutate_at
instead of mutate
just to have the same syntax as in my ideal case:
df %>%
mutate_at(vars(life_expectancy), round, digits = 1) %>%
mutate_at(vars(poverty), round, digits = 2) %>%
mutate_at(vars(household_income), round, digits = 0)
#> # A tibble: 1 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.26 42589
Mapping over the digits uses each of the digits
options for each column, not by position, giving me 3 rows each rounded to a different number of digits.
df %>%
mutate_at(vars(life_expectancy, poverty, household_income),
function(x) map(x, round, digits = c(1, 2, 0))) %>%
unnest()
#> # A tibble: 3 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.3 42589.
#> 2 New Haven 78.1 0.26 42589.
#> 3 New Haven 78 0 42589
Created on 2018-11-13 by the reprex package (v0.2.1)
r dplyr purrr
I'm trying to tighten up a %>%
piped workflow where I need to apply the same function to several columns but with one argument changed each time. I feel like purrr
's map
or invoke
functions should help, but I can't wrap my head around it.
My data frame has columns for life expectancy, poverty rate, and median household income. I can pass all these column names to vars
in mutate_at
, use round
as the function to apply to each, and optionally supply a digits
argument. But I can't figure out a way, if one exists, to pass different values for digits
associated with each column. I'd like life expectancy rounded to 1 digit, poverty rounded to 2, and income rounded to 0.
I can call mutate
on each column, but given that I might have more columns all receiving the same function with only an additional argument changed, I'd like something more concise.
library(tidyverse)
df <- tibble::tribble(
~name, ~life_expectancy, ~poverty, ~household_income,
"New Haven", 78.0580437642378, 0.264221051111753, 42588.7592521085
)
In my imagination, I could do something like this:
df %>%
mutate_at(vars(life_expectancy, poverty, household_income),
round, digits = c(1, 2, 0))
But get the error
Error in mutate_impl(.data, dots) :
Columnlife_expectancy
must be length 1 (the number of rows), not 3
Using mutate_at
instead of mutate
just to have the same syntax as in my ideal case:
df %>%
mutate_at(vars(life_expectancy), round, digits = 1) %>%
mutate_at(vars(poverty), round, digits = 2) %>%
mutate_at(vars(household_income), round, digits = 0)
#> # A tibble: 1 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.26 42589
Mapping over the digits uses each of the digits
options for each column, not by position, giving me 3 rows each rounded to a different number of digits.
df %>%
mutate_at(vars(life_expectancy, poverty, household_income),
function(x) map(x, round, digits = c(1, 2, 0))) %>%
unnest()
#> # A tibble: 3 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.3 42589.
#> 2 New Haven 78.1 0.26 42589.
#> 3 New Haven 78 0 42589
Created on 2018-11-13 by the reprex package (v0.2.1)
r dplyr purrr
r dplyr purrr
asked Nov 13 '18 at 19:20
camillecamille
7,01031427
7,01031427
1
In the past when faced with this problem I ended up gathering my columns, grouping them, mutating them, and spreading them back out. See also How do I sweep specific columns with dplyr?
– Konrad Rudolph
Nov 13 '18 at 19:38
@KonradRudolph thanks, I was thinking about that too, and that's an approach I've used before, but I'm trying to figure out whether a super simple, one-line version is possible
– camille
Nov 13 '18 at 19:50
@Henrik you might be on to something. Usingmap2_dfc
could work, but that requires dropping thename
column and then maybe joining it back on. I'm trying to imagine amap2_dfc
/map_at
hybrid
– camille
Nov 13 '18 at 19:56
1
Seems like it might be easier when you will be able to pass a list of functions to summarize_at/mutate_at: github.com/tidyverse/dplyr/issues/3433. That doesn't seem to work yet.
– MrFlick
Nov 13 '18 at 20:06
mutate
supports!!!
so the easiest in my opinion is to recreate the verbosemutate
call (notmutate_at
) programmatically throughmap2
or (cleaner to me)imap
– Moody_Mudskipper
Nov 14 '18 at 14:21
add a comment |
1
In the past when faced with this problem I ended up gathering my columns, grouping them, mutating them, and spreading them back out. See also How do I sweep specific columns with dplyr?
– Konrad Rudolph
Nov 13 '18 at 19:38
@KonradRudolph thanks, I was thinking about that too, and that's an approach I've used before, but I'm trying to figure out whether a super simple, one-line version is possible
– camille
Nov 13 '18 at 19:50
@Henrik you might be on to something. Usingmap2_dfc
could work, but that requires dropping thename
column and then maybe joining it back on. I'm trying to imagine amap2_dfc
/map_at
hybrid
– camille
Nov 13 '18 at 19:56
1
Seems like it might be easier when you will be able to pass a list of functions to summarize_at/mutate_at: github.com/tidyverse/dplyr/issues/3433. That doesn't seem to work yet.
– MrFlick
Nov 13 '18 at 20:06
mutate
supports!!!
so the easiest in my opinion is to recreate the verbosemutate
call (notmutate_at
) programmatically throughmap2
or (cleaner to me)imap
– Moody_Mudskipper
Nov 14 '18 at 14:21
1
1
In the past when faced with this problem I ended up gathering my columns, grouping them, mutating them, and spreading them back out. See also How do I sweep specific columns with dplyr?
– Konrad Rudolph
Nov 13 '18 at 19:38
In the past when faced with this problem I ended up gathering my columns, grouping them, mutating them, and spreading them back out. See also How do I sweep specific columns with dplyr?
– Konrad Rudolph
Nov 13 '18 at 19:38
@KonradRudolph thanks, I was thinking about that too, and that's an approach I've used before, but I'm trying to figure out whether a super simple, one-line version is possible
– camille
Nov 13 '18 at 19:50
@KonradRudolph thanks, I was thinking about that too, and that's an approach I've used before, but I'm trying to figure out whether a super simple, one-line version is possible
– camille
Nov 13 '18 at 19:50
@Henrik you might be on to something. Using
map2_dfc
could work, but that requires dropping the name
column and then maybe joining it back on. I'm trying to imagine a map2_dfc
/ map_at
hybrid– camille
Nov 13 '18 at 19:56
@Henrik you might be on to something. Using
map2_dfc
could work, but that requires dropping the name
column and then maybe joining it back on. I'm trying to imagine a map2_dfc
/ map_at
hybrid– camille
Nov 13 '18 at 19:56
1
1
Seems like it might be easier when you will be able to pass a list of functions to summarize_at/mutate_at: github.com/tidyverse/dplyr/issues/3433. That doesn't seem to work yet.
– MrFlick
Nov 13 '18 at 20:06
Seems like it might be easier when you will be able to pass a list of functions to summarize_at/mutate_at: github.com/tidyverse/dplyr/issues/3433. That doesn't seem to work yet.
– MrFlick
Nov 13 '18 at 20:06
mutate
supports !!!
so the easiest in my opinion is to recreate the verbose mutate
call (not mutate_at
) programmatically through map2
or (cleaner to me) imap
– Moody_Mudskipper
Nov 14 '18 at 14:21
mutate
supports !!!
so the easiest in my opinion is to recreate the verbose mutate
call (not mutate_at
) programmatically through map2
or (cleaner to me) imap
– Moody_Mudskipper
Nov 14 '18 at 14:21
add a comment |
2 Answers
2
active
oldest
votes
2 solutions
mutate
with !!!
invoke
was a good idea but you need it less now that most tidyverse
functions support the !!!
operator, here's what you can do :
digits <- c(life_expectancy = 1, poverty = 2, household_income = 0)
df %>% mutate(!!!imap(digits, ~round(..3[[.y]], .x),.))
# # A tibble: 1 x 4
# name life_expectancy poverty household_income
# <chr> <dbl> <dbl> <dbl>
# 1 New Haven 78.1 0.26 42589
..3
is the initial data frame, passed to the function as a third argument, through the dot at the end of the call.
Written more explicitly :
df %>% mutate(!!!imap(
digits,
function(digit, name, data) round(data[[name]], digit),
data = .))
If you need to start from your old interface (though the one I propose will be more flexible), first do:
digits <- setNames(c(1, 2, 0), c("life_expectancy", "poverty", "household_income"))
mutate_at
and <<-
Here we bend a bit the good practice of avoiding <<-
whenever possible, but readability matters and this one is really easy to read.
digits <- c(1, 2, 0)
i <- 0
df %>%
mutate_at(vars(life_expectancy, poverty, household_income), ~round(., digits[i<<- i+1]))
# A tibble: 1 x 4
# name life_expectancy poverty household_income
# <chr> <dbl> <dbl> <dbl>
# 1 New Haven 78.1 0.26 42589
(or just df %>% mutate_at(names(digits), ~round(., digits[i<<- i+1]))
if you use a named vector as in my first solution)
This is the correct way to do it. I've deleted my answer because while the output in the console matched OPs result, runningapply(df, 1, print)
showed that the values were each rounded to two decimals.
– Mako212
Nov 14 '18 at 17:27
3
FYI : github.com/tidyverse/purrr/issues/546
– Moody_Mudskipper
Nov 14 '18 at 19:08
1
This is wild! Soimap
is mapping overdigits
and its names, then applying theround
function, but also taking the original data frame in...
? Am I getting that right?
– camille
Nov 14 '18 at 20:04
1
Also, I appreciate the GH comment!
– camille
Nov 14 '18 at 20:05
Yes you got it perfectly, passing thelhs
to the...
is a trick I like a lot, I added a more explicit version for clarity.
– Moody_Mudskipper
Nov 14 '18 at 20:11
add a comment |
Here's a map2
solution along the lines of Henrik's comment. You can then wrap this inside a custom function. I provided an rough first attempt but I have done minimal tests, so it probably breaks under all sorts of situations if evaluation is strange. It also doesn't use tidyselect for .at
, but neither does modify_at
...
library(tidyverse)
df <- tibble::tribble(
~name, ~life_expectancy, ~poverty, ~household_income,
"New Haven", 78.0580437642378, 0.264221051111753, 42588.7592521085,
"New York", 12.349685329, 0.324067934, 32156.230974623
)
rounded <- df %>%
select(life_expectancy, poverty, household_income) %>%
map2_dfc(
.y = c(1, 2, 0),
.f = ~ round(.x, digits = .y)
)
df %>%
select(-life_expectancy, -poverty, -household_income) %>%
bind_cols(rounded)
#> # A tibble: 2 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.26 42589
#> 2 New York 12.3 0.32 32156
modify2_at <- function(.x, .y, .at, .f)
modified <- .x[.at] %>%
map2(.y, .f)
.x[.at] <- modified
return(.x)
df %>%
modify2_at(
.y = c(1, 2, 0),
.at = c("life_expectancy", "poverty", "household_income"),
.f = ~ round(.x, digits = .y)
)
#> # A tibble: 2 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.26 42589
#> 2 New York 12.3 0.32 32156
Created on 2018-11-13 by the reprex package (v0.2.1)
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53288100%2fpass-function-arguments-by-column-position-to-mutate-at%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
2 solutions
mutate
with !!!
invoke
was a good idea but you need it less now that most tidyverse
functions support the !!!
operator, here's what you can do :
digits <- c(life_expectancy = 1, poverty = 2, household_income = 0)
df %>% mutate(!!!imap(digits, ~round(..3[[.y]], .x),.))
# # A tibble: 1 x 4
# name life_expectancy poverty household_income
# <chr> <dbl> <dbl> <dbl>
# 1 New Haven 78.1 0.26 42589
..3
is the initial data frame, passed to the function as a third argument, through the dot at the end of the call.
Written more explicitly :
df %>% mutate(!!!imap(
digits,
function(digit, name, data) round(data[[name]], digit),
data = .))
If you need to start from your old interface (though the one I propose will be more flexible), first do:
digits <- setNames(c(1, 2, 0), c("life_expectancy", "poverty", "household_income"))
mutate_at
and <<-
Here we bend a bit the good practice of avoiding <<-
whenever possible, but readability matters and this one is really easy to read.
digits <- c(1, 2, 0)
i <- 0
df %>%
mutate_at(vars(life_expectancy, poverty, household_income), ~round(., digits[i<<- i+1]))
# A tibble: 1 x 4
# name life_expectancy poverty household_income
# <chr> <dbl> <dbl> <dbl>
# 1 New Haven 78.1 0.26 42589
(or just df %>% mutate_at(names(digits), ~round(., digits[i<<- i+1]))
if you use a named vector as in my first solution)
This is the correct way to do it. I've deleted my answer because while the output in the console matched OPs result, runningapply(df, 1, print)
showed that the values were each rounded to two decimals.
– Mako212
Nov 14 '18 at 17:27
3
FYI : github.com/tidyverse/purrr/issues/546
– Moody_Mudskipper
Nov 14 '18 at 19:08
1
This is wild! Soimap
is mapping overdigits
and its names, then applying theround
function, but also taking the original data frame in...
? Am I getting that right?
– camille
Nov 14 '18 at 20:04
1
Also, I appreciate the GH comment!
– camille
Nov 14 '18 at 20:05
Yes you got it perfectly, passing thelhs
to the...
is a trick I like a lot, I added a more explicit version for clarity.
– Moody_Mudskipper
Nov 14 '18 at 20:11
add a comment |
2 solutions
mutate
with !!!
invoke
was a good idea but you need it less now that most tidyverse
functions support the !!!
operator, here's what you can do :
digits <- c(life_expectancy = 1, poverty = 2, household_income = 0)
df %>% mutate(!!!imap(digits, ~round(..3[[.y]], .x),.))
# # A tibble: 1 x 4
# name life_expectancy poverty household_income
# <chr> <dbl> <dbl> <dbl>
# 1 New Haven 78.1 0.26 42589
..3
is the initial data frame, passed to the function as a third argument, through the dot at the end of the call.
Written more explicitly :
df %>% mutate(!!!imap(
digits,
function(digit, name, data) round(data[[name]], digit),
data = .))
If you need to start from your old interface (though the one I propose will be more flexible), first do:
digits <- setNames(c(1, 2, 0), c("life_expectancy", "poverty", "household_income"))
mutate_at
and <<-
Here we bend a bit the good practice of avoiding <<-
whenever possible, but readability matters and this one is really easy to read.
digits <- c(1, 2, 0)
i <- 0
df %>%
mutate_at(vars(life_expectancy, poverty, household_income), ~round(., digits[i<<- i+1]))
# A tibble: 1 x 4
# name life_expectancy poverty household_income
# <chr> <dbl> <dbl> <dbl>
# 1 New Haven 78.1 0.26 42589
(or just df %>% mutate_at(names(digits), ~round(., digits[i<<- i+1]))
if you use a named vector as in my first solution)
This is the correct way to do it. I've deleted my answer because while the output in the console matched OPs result, runningapply(df, 1, print)
showed that the values were each rounded to two decimals.
– Mako212
Nov 14 '18 at 17:27
3
FYI : github.com/tidyverse/purrr/issues/546
– Moody_Mudskipper
Nov 14 '18 at 19:08
1
This is wild! Soimap
is mapping overdigits
and its names, then applying theround
function, but also taking the original data frame in...
? Am I getting that right?
– camille
Nov 14 '18 at 20:04
1
Also, I appreciate the GH comment!
– camille
Nov 14 '18 at 20:05
Yes you got it perfectly, passing thelhs
to the...
is a trick I like a lot, I added a more explicit version for clarity.
– Moody_Mudskipper
Nov 14 '18 at 20:11
add a comment |
2 solutions
mutate
with !!!
invoke
was a good idea but you need it less now that most tidyverse
functions support the !!!
operator, here's what you can do :
digits <- c(life_expectancy = 1, poverty = 2, household_income = 0)
df %>% mutate(!!!imap(digits, ~round(..3[[.y]], .x),.))
# # A tibble: 1 x 4
# name life_expectancy poverty household_income
# <chr> <dbl> <dbl> <dbl>
# 1 New Haven 78.1 0.26 42589
..3
is the initial data frame, passed to the function as a third argument, through the dot at the end of the call.
Written more explicitly :
df %>% mutate(!!!imap(
digits,
function(digit, name, data) round(data[[name]], digit),
data = .))
If you need to start from your old interface (though the one I propose will be more flexible), first do:
digits <- setNames(c(1, 2, 0), c("life_expectancy", "poverty", "household_income"))
mutate_at
and <<-
Here we bend a bit the good practice of avoiding <<-
whenever possible, but readability matters and this one is really easy to read.
digits <- c(1, 2, 0)
i <- 0
df %>%
mutate_at(vars(life_expectancy, poverty, household_income), ~round(., digits[i<<- i+1]))
# A tibble: 1 x 4
# name life_expectancy poverty household_income
# <chr> <dbl> <dbl> <dbl>
# 1 New Haven 78.1 0.26 42589
(or just df %>% mutate_at(names(digits), ~round(., digits[i<<- i+1]))
if you use a named vector as in my first solution)
2 solutions
mutate
with !!!
invoke
was a good idea but you need it less now that most tidyverse
functions support the !!!
operator, here's what you can do :
digits <- c(life_expectancy = 1, poverty = 2, household_income = 0)
df %>% mutate(!!!imap(digits, ~round(..3[[.y]], .x),.))
# # A tibble: 1 x 4
# name life_expectancy poverty household_income
# <chr> <dbl> <dbl> <dbl>
# 1 New Haven 78.1 0.26 42589
..3
is the initial data frame, passed to the function as a third argument, through the dot at the end of the call.
Written more explicitly :
df %>% mutate(!!!imap(
digits,
function(digit, name, data) round(data[[name]], digit),
data = .))
If you need to start from your old interface (though the one I propose will be more flexible), first do:
digits <- setNames(c(1, 2, 0), c("life_expectancy", "poverty", "household_income"))
mutate_at
and <<-
Here we bend a bit the good practice of avoiding <<-
whenever possible, but readability matters and this one is really easy to read.
digits <- c(1, 2, 0)
i <- 0
df %>%
mutate_at(vars(life_expectancy, poverty, household_income), ~round(., digits[i<<- i+1]))
# A tibble: 1 x 4
# name life_expectancy poverty household_income
# <chr> <dbl> <dbl> <dbl>
# 1 New Haven 78.1 0.26 42589
(or just df %>% mutate_at(names(digits), ~round(., digits[i<<- i+1]))
if you use a named vector as in my first solution)
edited Nov 14 '18 at 20:10
answered Nov 14 '18 at 14:08
Moody_MudskipperMoody_Mudskipper
21.8k32864
21.8k32864
This is the correct way to do it. I've deleted my answer because while the output in the console matched OPs result, runningapply(df, 1, print)
showed that the values were each rounded to two decimals.
– Mako212
Nov 14 '18 at 17:27
3
FYI : github.com/tidyverse/purrr/issues/546
– Moody_Mudskipper
Nov 14 '18 at 19:08
1
This is wild! Soimap
is mapping overdigits
and its names, then applying theround
function, but also taking the original data frame in...
? Am I getting that right?
– camille
Nov 14 '18 at 20:04
1
Also, I appreciate the GH comment!
– camille
Nov 14 '18 at 20:05
Yes you got it perfectly, passing thelhs
to the...
is a trick I like a lot, I added a more explicit version for clarity.
– Moody_Mudskipper
Nov 14 '18 at 20:11
add a comment |
This is the correct way to do it. I've deleted my answer because while the output in the console matched OPs result, runningapply(df, 1, print)
showed that the values were each rounded to two decimals.
– Mako212
Nov 14 '18 at 17:27
3
FYI : github.com/tidyverse/purrr/issues/546
– Moody_Mudskipper
Nov 14 '18 at 19:08
1
This is wild! Soimap
is mapping overdigits
and its names, then applying theround
function, but also taking the original data frame in...
? Am I getting that right?
– camille
Nov 14 '18 at 20:04
1
Also, I appreciate the GH comment!
– camille
Nov 14 '18 at 20:05
Yes you got it perfectly, passing thelhs
to the...
is a trick I like a lot, I added a more explicit version for clarity.
– Moody_Mudskipper
Nov 14 '18 at 20:11
This is the correct way to do it. I've deleted my answer because while the output in the console matched OPs result, running
apply(df, 1, print)
showed that the values were each rounded to two decimals.– Mako212
Nov 14 '18 at 17:27
This is the correct way to do it. I've deleted my answer because while the output in the console matched OPs result, running
apply(df, 1, print)
showed that the values were each rounded to two decimals.– Mako212
Nov 14 '18 at 17:27
3
3
FYI : github.com/tidyverse/purrr/issues/546
– Moody_Mudskipper
Nov 14 '18 at 19:08
FYI : github.com/tidyverse/purrr/issues/546
– Moody_Mudskipper
Nov 14 '18 at 19:08
1
1
This is wild! So
imap
is mapping over digits
and its names, then applying the round
function, but also taking the original data frame in ...
? Am I getting that right?– camille
Nov 14 '18 at 20:04
This is wild! So
imap
is mapping over digits
and its names, then applying the round
function, but also taking the original data frame in ...
? Am I getting that right?– camille
Nov 14 '18 at 20:04
1
1
Also, I appreciate the GH comment!
– camille
Nov 14 '18 at 20:05
Also, I appreciate the GH comment!
– camille
Nov 14 '18 at 20:05
Yes you got it perfectly, passing the
lhs
to the ...
is a trick I like a lot, I added a more explicit version for clarity.– Moody_Mudskipper
Nov 14 '18 at 20:11
Yes you got it perfectly, passing the
lhs
to the ...
is a trick I like a lot, I added a more explicit version for clarity.– Moody_Mudskipper
Nov 14 '18 at 20:11
add a comment |
Here's a map2
solution along the lines of Henrik's comment. You can then wrap this inside a custom function. I provided an rough first attempt but I have done minimal tests, so it probably breaks under all sorts of situations if evaluation is strange. It also doesn't use tidyselect for .at
, but neither does modify_at
...
library(tidyverse)
df <- tibble::tribble(
~name, ~life_expectancy, ~poverty, ~household_income,
"New Haven", 78.0580437642378, 0.264221051111753, 42588.7592521085,
"New York", 12.349685329, 0.324067934, 32156.230974623
)
rounded <- df %>%
select(life_expectancy, poverty, household_income) %>%
map2_dfc(
.y = c(1, 2, 0),
.f = ~ round(.x, digits = .y)
)
df %>%
select(-life_expectancy, -poverty, -household_income) %>%
bind_cols(rounded)
#> # A tibble: 2 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.26 42589
#> 2 New York 12.3 0.32 32156
modify2_at <- function(.x, .y, .at, .f)
modified <- .x[.at] %>%
map2(.y, .f)
.x[.at] <- modified
return(.x)
df %>%
modify2_at(
.y = c(1, 2, 0),
.at = c("life_expectancy", "poverty", "household_income"),
.f = ~ round(.x, digits = .y)
)
#> # A tibble: 2 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.26 42589
#> 2 New York 12.3 0.32 32156
Created on 2018-11-13 by the reprex package (v0.2.1)
add a comment |
Here's a map2
solution along the lines of Henrik's comment. You can then wrap this inside a custom function. I provided an rough first attempt but I have done minimal tests, so it probably breaks under all sorts of situations if evaluation is strange. It also doesn't use tidyselect for .at
, but neither does modify_at
...
library(tidyverse)
df <- tibble::tribble(
~name, ~life_expectancy, ~poverty, ~household_income,
"New Haven", 78.0580437642378, 0.264221051111753, 42588.7592521085,
"New York", 12.349685329, 0.324067934, 32156.230974623
)
rounded <- df %>%
select(life_expectancy, poverty, household_income) %>%
map2_dfc(
.y = c(1, 2, 0),
.f = ~ round(.x, digits = .y)
)
df %>%
select(-life_expectancy, -poverty, -household_income) %>%
bind_cols(rounded)
#> # A tibble: 2 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.26 42589
#> 2 New York 12.3 0.32 32156
modify2_at <- function(.x, .y, .at, .f)
modified <- .x[.at] %>%
map2(.y, .f)
.x[.at] <- modified
return(.x)
df %>%
modify2_at(
.y = c(1, 2, 0),
.at = c("life_expectancy", "poverty", "household_income"),
.f = ~ round(.x, digits = .y)
)
#> # A tibble: 2 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.26 42589
#> 2 New York 12.3 0.32 32156
Created on 2018-11-13 by the reprex package (v0.2.1)
add a comment |
Here's a map2
solution along the lines of Henrik's comment. You can then wrap this inside a custom function. I provided an rough first attempt but I have done minimal tests, so it probably breaks under all sorts of situations if evaluation is strange. It also doesn't use tidyselect for .at
, but neither does modify_at
...
library(tidyverse)
df <- tibble::tribble(
~name, ~life_expectancy, ~poverty, ~household_income,
"New Haven", 78.0580437642378, 0.264221051111753, 42588.7592521085,
"New York", 12.349685329, 0.324067934, 32156.230974623
)
rounded <- df %>%
select(life_expectancy, poverty, household_income) %>%
map2_dfc(
.y = c(1, 2, 0),
.f = ~ round(.x, digits = .y)
)
df %>%
select(-life_expectancy, -poverty, -household_income) %>%
bind_cols(rounded)
#> # A tibble: 2 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.26 42589
#> 2 New York 12.3 0.32 32156
modify2_at <- function(.x, .y, .at, .f)
modified <- .x[.at] %>%
map2(.y, .f)
.x[.at] <- modified
return(.x)
df %>%
modify2_at(
.y = c(1, 2, 0),
.at = c("life_expectancy", "poverty", "household_income"),
.f = ~ round(.x, digits = .y)
)
#> # A tibble: 2 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.26 42589
#> 2 New York 12.3 0.32 32156
Created on 2018-11-13 by the reprex package (v0.2.1)
Here's a map2
solution along the lines of Henrik's comment. You can then wrap this inside a custom function. I provided an rough first attempt but I have done minimal tests, so it probably breaks under all sorts of situations if evaluation is strange. It also doesn't use tidyselect for .at
, but neither does modify_at
...
library(tidyverse)
df <- tibble::tribble(
~name, ~life_expectancy, ~poverty, ~household_income,
"New Haven", 78.0580437642378, 0.264221051111753, 42588.7592521085,
"New York", 12.349685329, 0.324067934, 32156.230974623
)
rounded <- df %>%
select(life_expectancy, poverty, household_income) %>%
map2_dfc(
.y = c(1, 2, 0),
.f = ~ round(.x, digits = .y)
)
df %>%
select(-life_expectancy, -poverty, -household_income) %>%
bind_cols(rounded)
#> # A tibble: 2 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.26 42589
#> 2 New York 12.3 0.32 32156
modify2_at <- function(.x, .y, .at, .f)
modified <- .x[.at] %>%
map2(.y, .f)
.x[.at] <- modified
return(.x)
df %>%
modify2_at(
.y = c(1, 2, 0),
.at = c("life_expectancy", "poverty", "household_income"),
.f = ~ round(.x, digits = .y)
)
#> # A tibble: 2 x 4
#> name life_expectancy poverty household_income
#> <chr> <dbl> <dbl> <dbl>
#> 1 New Haven 78.1 0.26 42589
#> 2 New York 12.3 0.32 32156
Created on 2018-11-13 by the reprex package (v0.2.1)
edited Nov 13 '18 at 20:03
answered Nov 13 '18 at 19:56
Calum YouCalum You
7,0701829
7,0701829
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53288100%2fpass-function-arguments-by-column-position-to-mutate-at%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
In the past when faced with this problem I ended up gathering my columns, grouping them, mutating them, and spreading them back out. See also How do I sweep specific columns with dplyr?
– Konrad Rudolph
Nov 13 '18 at 19:38
@KonradRudolph thanks, I was thinking about that too, and that's an approach I've used before, but I'm trying to figure out whether a super simple, one-line version is possible
– camille
Nov 13 '18 at 19:50
@Henrik you might be on to something. Using
map2_dfc
could work, but that requires dropping thename
column and then maybe joining it back on. I'm trying to imagine amap2_dfc
/map_at
hybrid– camille
Nov 13 '18 at 19:56
1
Seems like it might be easier when you will be able to pass a list of functions to summarize_at/mutate_at: github.com/tidyverse/dplyr/issues/3433. That doesn't seem to work yet.
– MrFlick
Nov 13 '18 at 20:06
mutate
supports!!!
so the easiest in my opinion is to recreate the verbosemutate
call (notmutate_at
) programmatically throughmap2
or (cleaner to me)imap
– Moody_Mudskipper
Nov 14 '18 at 14:21