1

Back in 2015, I asked a similar question on this, but I would like to find a tidy way of doing this.

This is the best that I could come up with so far. It works, but having to change column types just for sorting seems "wrong" somehow. However, so does resorting to dplyr::*_join() and match() comes with its own catches (plus it's hard to use it in tidy contexts).

So is there a good/recommended way of doing this in the tidyverse?

Define function

library(magrittr)

arrange_by_target <- function(
  x,
  targets
) {
  x %>%
    # Transform arrange-by columns to factors so we can leverage the order of
    # the levels:
    dplyr::mutate_at(
      names(targets),
      function(.x, .targets = targets) {
        .col <- deparse(substitute(.x))
        factor(.x, levels = .targets[[.col]])
      }
    ) %>%
    # Actual arranging:
    dplyr::arrange_at(
      names(targets)
    ) %>%
    # Clean up by recasting factor columns to their original type:
    dplyr::mutate_at(
      .vars = names(targets),
      function(.x, .targets = targets) {
        .col <- deparse(substitute(.x))
        vctrs::vec_cast(.x, to = class(.targets[[.col]]))
      }
    )
}

Test

x <- tibble::tribble(
  ~group, ~name, ~value,
  "A", "B", 1,
  "A", "C", 2,
  "A", "A", 3,
  "B", "B", 4,
  "B", "A", 5
)

x %>%
  arrange_by_target(
    targets = list(
      group = c("B", "A"),
      name = c("A", "B", "C")
    )
  )
#> # A tibble: 5 x 3
#>   group name  value
#>   <chr> <chr> <dbl>
#> 1 B     A         5
#> 2 B     B         4
#> 3 A     A         3
#> 4 A     B         1
#> 5 A     C         2

x %>%
  arrange_by_target(
    targets = list(
      group = c("B", "A"),
      name = c("A", "B", "C") %>% rev()
    )
  )
#> # A tibble: 5 x 3
#>   group name  value
#>   <chr> <chr> <dbl>
#> 1 B     B         4
#> 2 B     A         5
#> 3 A     C         2
#> 4 A     B         1
#> 5 A     A         3

Created on 2019-11-06 by the reprex package (v0.3.0)

Rappster
  • 12,762
  • 7
  • 71
  • 120

1 Answers1

0

The easiest way to accomplish this is to convert your character columns to factors, like so:

x %>% 
  mutate(
      group = factor(group, c("A", "B")), 
      name = factor(name, c("C", "B", "A"))
  ) %>% 
  arrange(group, name)

Another option that I frequently use is to utilize joins. For example:

x <- tibble::tribble(
  ~group, ~name, ~value,
  "A", "B", 1,
  "A", "C", 2,
  "A", "A", 3,
  "B", "B", 4,
  "B", "A", 5,
  "A", "A", 6,
  "B", "C", 7,
  "A", "B", 8,
  "B", "B", 9
)

custom_sort <- tibble::tribble(
  ~group, ~name,
  "A", "C",
  "A", "B",
  "A", "A",
  "B", "B",
  "B", "A"
)

x %>% right_join(custom_sort)
vectorson
  • 31
  • 3
  • Thanks for taking the time to address this, but using factors is literally what I prototyped/proposed myself ;-) Might be giving the join variant another polish, though. That does seem like a solid way to go with. – Rappster Nov 06 '19 at 19:07