What is the difference between (un)packing and (un)nesting a tibble?

nest
unnest
pack
unpack
tidyr
json
constructive
Author
Affiliations

Layal Christine Lettry

cynkra GmbH

University of Fribourg, Dept. of Informatics, ASAM Group

Published

May 30, 2024

Does a nested tibble have the same structure as a packed tibble?

Initial object

Let’s assume that we have the object my_tib which is a nested tibble containing a list, namely my_values, with another tibble where the variables are my_ints and my_chars.

my_tib <-
  tibble::tibble(
    my_values = list(tibble::tibble(
      my_ints = 1L:5L,
      my_chars = LETTERS[my_ints]
    ))
  )
constructive::construct(my_tib)
tibble::tibble(
  my_values = list(tibble::tibble(my_ints = 1:5, my_chars = c("A", "B", "C", "D", "E"))),
)

We could also use tidyr::nest() to create my_tib (please refer to this article for more info).

my_nested_tib <-
  tibble::tribble(
    ~my_ints, ~my_chars,
    1L, "A",
    2L, "B",
    3L, "C",
    4L, "D",
    5L, "E"
  ) |>
  tidyr::nest(my_values = c(my_ints, my_chars))

constructive::construct(my_nested_tib)
tibble::tibble(
  my_values = list(tibble::tibble(my_ints = 1:5, my_chars = c("A", "B", "C", "D", "E"))),
)

As you can see, there is no difference between my_tib and my_nested_tib.

waldo::compare(my_tib, my_nested_tib)
✔ No differences

What is the difference between a nested and a packed tibble?

To obtain a packed tibble, we should pack the variables my_ints and my_chars together so that we have a tibble in another tibble instead of a list with an element that is a tibble.

my_packed_tib <-
  tibble::tribble(
    ~my_ints, ~my_chars,
    1L, "A",
    2L, "B",
    3L, "C",
    4L, "D",
    5L, "E"
  ) |>
  tidyr::pack(my_values = c(my_ints, my_chars))
constructive::construct(my_packed_tib)
tibble::tibble(
  my_values = tibble::tibble(my_ints = 1:5, my_chars = c("A", "B", "C", "D", "E")),
)

We can assess the difference between my_nested_tib and my_packed_tib with waldo::compare().

waldo::compare(my_nested_tib, my_packed_tib)
`attr(old, 'row.names')`: 1        
`attr(new, 'row.names')`: 1 2 3 4 5

`old$my_values` is a list
`new$my_values` is an S3 object of class <tbl_df/tbl/data.frame>, a list

This tells us that my_nested_tib has only one row and contains the variable my_values that is a list, whereas my_packed_tib has 5 rows and is constituted by the variable my_values that has, in this case, the class data.frame.

class(my_packed_tib$my_values)
[1] "tbl_df"     "tbl"        "data.frame"

For the record, a data frame is a special list where every element has the same length.

typeof(my_packed_tib$my_values)
[1] "list"

How to unnest or unpack a tibble?

To get a tibble without any variable that is a list or a tibble, we should unnest and, respectively, unpack our nested/packed tibble.

my_unnested_tib <-
  my_nested_tib |>
  tidyr::unnest(my_values)

constructive::construct(my_unnested_tib)
tibble::tibble(my_ints = 1:5, my_chars = c("A", "B", "C", "D", "E"))

Now, we have a simple tibble with two variables instead of one single variable that is a list.

my_unpacked_tib <-
  my_packed_tib |>
  tidyr::unpack(my_values)

constructive::construct(my_unpacked_tib)
tibble::tibble(my_ints = 1:5, my_chars = c("A", "B", "C", "D", "E"))

Here again, we obtain a simple tibble with two variables instead of one single variable that has the class data.frame.

What do the packed tibble and nested tibble look like in a JSON format?

The main difference is that the instances of the variable my_values of the nested tibble will be written between extra square brackets to represent the list class of my_values. On the contrary, each row of the variable my_values of the packed tibble will be displayed separately between curly brackets given that my_values has the class data.frame in the packed case.

jsonlite::toJSON(my_nested_tib, pretty = TRUE)
[
  {
    "my_values": [
      {
        "my_ints": 1,
        "my_chars": "A"
      },
      {
        "my_ints": 2,
        "my_chars": "B"
      },
      {
        "my_ints": 3,
        "my_chars": "C"
      },
      {
        "my_ints": 4,
        "my_chars": "D"
      },
      {
        "my_ints": 5,
        "my_chars": "E"
      }
    ]
  }
] 
jsonlite::toJSON(my_packed_tib, pretty = TRUE)
[
  {
    "my_values": {
      "my_ints": 1,
      "my_chars": "A"
    }
  },
  {
    "my_values": {
      "my_ints": 2,
      "my_chars": "B"
    }
  },
  {
    "my_values": {
      "my_ints": 3,
      "my_chars": "C"
    }
  },
  {
    "my_values": {
      "my_ints": 4,
      "my_chars": "D"
    }
  },
  {
    "my_values": {
      "my_ints": 5,
      "my_chars": "E"
    }
  }
] 

Citation

BibTeX citation:
@online{lettry2024,
  author = {Lettry, Layal Christine},
  title = {What Is the Difference Between (Un)packing and (Un)nesting a
    Tibble?},
  date = {2024-05-30},
  url = {https://rdiscovery.netlify.app/posts/2024-05-30_pack-nest/},
  langid = {en}
}
For attribution, please cite this work as:
Lettry, Layal Christine. 2024. “What Is the Difference Between (Un)packing and (Un)nesting a Tibble?” May 30, 2024. https://rdiscovery.netlify.app/posts/2024-05-30_pack-nest/.