Pass function arguments by column position to mutate_at










9















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) :
Column life_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)










share|improve this question

















  • 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 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





    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
















9















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) :
Column life_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)










share|improve this question

















  • 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 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





    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














9












9








9


1






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) :
Column life_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)










share|improve this question














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) :
Column life_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






share|improve this question













share|improve this question











share|improve this question




share|improve this question










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. 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





    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













  • 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 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





    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








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













2 Answers
2






active

oldest

votes


















4














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)






share|improve this answer

























  • 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





    FYI : github.com/tidyverse/purrr/issues/546

    – Moody_Mudskipper
    Nov 14 '18 at 19:08






  • 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






  • 1





    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


















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)






share|improve this answer
























    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
    );



    );













    draft saved

    draft discarded


















    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









    4














    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)






    share|improve this answer

























    • 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





      FYI : github.com/tidyverse/purrr/issues/546

      – Moody_Mudskipper
      Nov 14 '18 at 19:08






    • 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






    • 1





      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















    4














    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)






    share|improve this answer

























    • 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





      FYI : github.com/tidyverse/purrr/issues/546

      – Moody_Mudskipper
      Nov 14 '18 at 19:08






    • 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






    • 1





      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













    4












    4








    4







    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)






    share|improve this answer















    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)







    share|improve this answer














    share|improve this answer



    share|improve this answer








    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, running apply(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! 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





      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

















    • 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





      FYI : github.com/tidyverse/purrr/issues/546

      – Moody_Mudskipper
      Nov 14 '18 at 19:08






    • 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






    • 1





      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
















    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













    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)






    share|improve this answer





























      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)






      share|improve this answer



























        1












        1








        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)






        share|improve this answer















        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)







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Nov 13 '18 at 20:03

























        answered Nov 13 '18 at 19:56









        Calum YouCalum You

        7,0701829




        7,0701829



























            draft saved

            draft discarded
















































            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.




            draft saved


            draft discarded














            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





















































            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







            Popular posts from this blog

            Top Tejano songwriter Luis Silva dead of heart attack at 64

            ReactJS Fetched API data displays live - need Data displayed static

            政党