Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: swap_if(), conditional swapping of values between columns #2149

Closed
bhive01 opened this issue Sep 26, 2016 · 8 comments
Closed

Comments

@bhive01
Copy link

bhive01 commented Sep 26, 2016

Made a mistake in a partial dataset of a very manual experiment (costly to redo) and realized I needed a conditional swap between two columns.

I envision it looking something like this in practice. I wish I could figure out how to include it in a mutate(), but it requires returning two columns at once.

dt %>%
    swap_if(., condition, Column1, Column2)

My attempt at implementation leaves a lot to be desired and is hacked entirely from if_else/replace_with in dplyr.

library(tidyverse)

dt <- tibble(V1=c(1,2,4), V2=c("a","a","b"), V3=c(2,3,1))

swap_if<- function (condition, val1, val2, missing = NA) 
{
    if (!is.logical(condition)) {
        stop("`condition` must be logical", call. = FALSE)
    }
    out1 <- val1[rep(NA_integer_, length(condition))]
    out2 <- val2[rep(NA_integer_, length(condition))]

    out1[condition & !is.na(condition)] <- val2[condition & !is.na(condition)]
    out2[condition & !is.na(condition)] <- val1[condition & !is.na(condition)]

    out1[!condition & !is.na(condition)] <- val1[!condition & !is.na(condition)]
    out2[!condition & !is.na(condition)] <- val2[!condition & !is.na(condition)]

    tibble(out1, out2)
}

#swaps, but want to use in a mutate call
swap_if(dt$V2=="b", dt$V1, dt$V3)
@bhive01
Copy link
Author

bhive01 commented Sep 26, 2016

Current workaround is the following as suggested by @bpbond
Clear, but not concise. I have to swap many columns sadly so this explodes quickly.

df %>% 
    mutate(newV1=ifelse(V2=="b",V3,V1),
                 newV3=ifelse(V2=="b",V1,V3),
                 V1=newV1,
                 V3=newV3) %>%
    select(-newV1, -newV3)

@krlmlr
Copy link
Member

krlmlr commented Nov 7, 2016

@hadley: Is this a particularly common use case we want to support?

@hadley
Copy link
Member

hadley commented Nov 7, 2016

No, I don't think so.

However, there is a general pattern that would help resolve this issue: if summarise() and mutate() could accept data.frames/tibbles as return values, you could do this in a single step:

swap_if <- function(cond, x, y) {
  out_x <- if_else(cond, y, x)
  out_y <- if_else(!cond, x, y)

  setNames(tibble(out_x, out_y), c(expr_name(x), expr_name(y))
}

df %>% mutate(swap_if(V2 == "b", V1, V3))

I don't think the code for getting the names is quite right, but it should be something along those lines.

That would also simplify the implementation of tidy::separate(), tidyr::unite() etc because you'd be able to make use of mutate().

@krlmlr
Copy link
Member

krlmlr commented Nov 7, 2016

Returning a tibble is related with #2171.

@krlmlr
Copy link
Member

krlmlr commented Nov 7, 2016

But I was confused I think. @hadley: Wouldn't your code work with do()?

@hadley
Copy link
Member

hadley commented Nov 7, 2016

Yes, but do() is very inefficient.

@krlmlr
Copy link
Member

krlmlr commented Nov 7, 2016

Groupwise do(), yes. But isn't do() over the entire data frame just a single vectorized function call?

The syntax with the dot is a bit awkward, though.

@hadley
Copy link
Member

hadley commented Nov 7, 2016

Oh indeed, it's just that with mutate() you get the nicer implicit scoping.

@hadley hadley closed this as completed Feb 2, 2017
@lock lock bot locked as resolved and limited conversation to collaborators Jun 8, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants