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

flatten_names() #525

Closed
lionel- opened this issue Jul 31, 2018 · 4 comments
Closed

flatten_names() #525

lionel- opened this issue Jul 31, 2018 · 4 comments
Labels
feature a feature request or enhancement flatten 🌎
Milestone

Comments

@lionel-
Copy link
Member

lionel- commented Jul 31, 2018

To provide a way of combining names prior to flattening:

x <- list(A = list(foo = 1, bar = 2), B = list(baz = 3)

x %>%
  flatten_names(~paste(.x, .y, sep = " :: ")) %>%
  flatten()
#> $`A :: foo`
#> [1] 1
#>
#> $`A :: bar`
#> [1] 2
#>
#> $`B :: baz`
#> [1] 3

The definition would be

flatten_names <- function(.x, .f = paste, ...) { ... }

So this would work as well:

x %>% flatten_names(sep = " :: ")
@lionel- lionel- added the feature a feature request or enhancement label Nov 29, 2018
@lionel- lionel- added this to the vctrs milestone Dec 4, 2018
@hadley
Copy link
Member

hadley commented Dec 4, 2018

Or should this be an argument to flatten()? (Like .name_repair in tibble())

@ColinFay
Copy link
Contributor

ColinFay commented Jan 10, 2019

Hey,

I was about to open an issue after needing something related, but I can see it's already been thought about :) Feel free to tell if this is not related, I can open another issue.

I think an extra argument to flatten would be nice (maybe keep_names and sep ?)

Here is a minimal reprex of my use case :

I'm requesting an API, which return nested JSON. Let's say the result is:

{
  "object": {
    "colors": {
      "blue": 1,
      "green": 2,
      "brown": 3
    }, 
    "animals": {
      "cat": 4,
      "mouse": 4,
      "dog": 6
    }
  }
}

Which would be the R equivalent of

res <- list(
  object = list(
    colors = list(
      blue = 1, 
      green = 2,
      brown = 3
    ), 
    animals = list(
      cat = 4, 
      mouse = 5,
      dog = 6
    )
  )
)

I want to turn this into a data.frame, but I want to keep the information that "blue" is in the "color" sublist (in my case it has meaning to keep the middle names), and this is lost when using flatten :

map_df(res, flatten_df)
# A tibble: 2 x 6
   blue green brown   cat mouse   dog
  <int> <int> <int> <int> <int> <int>
1     1     2     3     4     4     6

While what I would want is to have something like

flatten_df(res[[1]], keep_names = TRUE, sep = "_") %>% names()
[1] "colors_blue"  "colors_green" "colors_brown" "animals_cat"   "animals_mouse" "animals_dog"

So now I'm handling is with :

map_df(res, ~ tibble(
  colors_blue = .x$colors$blue, 
  colors_green = .x$colors$green, 
  colors_brown = .x$colors$brown, 
  animals_cat = .x$animals$cat, 
  animals_mouse = .x$animals$mouse, 
  animals_dog = .x$animals$dog, 
))

But it's unefficient and would fail if at some point the JSON structure changes.

And this would also make sense on this kind of cases :

l <- list(
  a = list(
    b = 12
  ), 
  c = list(
    b = 13
  )
)

flatten(l)
$b
[1] 12

$b
[1] 13

@ColinFay
Copy link
Contributor

I don't know if you would prefer a C implementation or handling this in R, but here is a suggestion :

# Calling devtools loal all so I can .Call
devtools::load_all("~/Seafile/documents_colin/R/purrr/")
#> Loading purrr
flatten <- function(.x, keep_names = FALSE, sep = "_") {
  
  # Handling unnamed list & keep_names = TRUE
  if (keep_names & all(is.null(names(.x)))) {
    abort("You can't keep names of an unnamed list")
  }
  
  res <- .Call(flatten_impl, .x)
  
  if (keep_names)  {
    nm <- paste(names(.x), map_chr(.x, names), sep = sep)
    return(set_names(res, nm))
  }
  res
}


l <- list(
  a = list(
    b = 12
  ), 
  c = list(
    b = 13
  )
)

flatten(l)
#> $b
#> [1] 12
#> 
#> $b
#> [1] 13
flatten(l, keep_names = TRUE)
#> $a_b
#> [1] 12
#> 
#> $c_b
#> [1] 13

flatten(l, keep_names = TRUE, sep = "@")
#> $`a@b`
#> [1] 12
#> 
#> $`c@b`
#> [1] 13

x <- rerun(2, sample(4))

flatten(x)
#> [[1]]
#> [1] 2
#> 
#> [[2]]
#> [1] 3
#> 
#> [[3]]
#> [1] 4
#> 
#> [[4]]
#> [1] 1
#> 
#> [[5]]
#> [1] 4
#> 
#> [[6]]
#> [1] 2
#> 
#> [[7]]
#> [1] 3
#> 
#> [[8]]
#> [1] 1
flatten(x, keep_names = TRUE)
#> Error: You can't keep names of an unnamed list

(I removed the backtrack from the output)

Created on 2019-01-10 by the reprex package (v0.2.1)

Would be happy to work on this and PR if you think this is a good idea.

@hadley
Copy link
Member

hadley commented Aug 27, 2022

Closing because we're moving towards an approach using .name_spec arguments.

@hadley hadley closed this as completed Aug 27, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature a feature request or enhancement flatten 🌎
Projects
None yet
Development

No branches or pull requests

3 participants