In a tibble with list-columns, how could I replace <NULL> entries with nested NA (which will take the nested form of <lgl [1]>)?
library(tibble)
tbl_with_null <-
tibble(letter = letters[1:10],
value_1 = list(1, 2, 4, data.frame(a = 1, 2, 3), NULL, 6, 7, c(8, 11, 25), NULL, 10),
value_2 = list("A", "B", "C", "D", NULL, NULL, NULL, list("H", "B", list(data.frame(id = 1:3))), "I", "J"))
> tbl_with_null
## # A tibble: 10 x 3
## letter value_1 value_2
## <chr> <list> <list>
## 1 a <dbl [1]> <chr [1]>
## 2 b <dbl [1]> <chr [1]>
## 3 c <dbl [1]> <chr [1]>
## 4 d <df[,3] [1 x 3]> <chr [1]>
## 5 e <NULL> <NULL>
## 6 f <dbl [1]> <NULL>
## 7 g <dbl [1]> <NULL>
## 8 h <dbl [3]> <list [3]>
## 9 i <NULL> <chr [1]>
## 10 j <dbl [1]> <chr [1]>
Is there a way to act on the entire tbl_with_null to replace <NULL> with NA to get:
## # A tibble: 10 x 3
## letter value_1 value_2
## <chr> <list> <list>
## 1 a <dbl [1]> <chr [1]>
## 2 b <dbl [1]> <chr [1]>
## 3 c <dbl [1]> <chr [1]>
## 4 d <df[,3] [1 x 3]> <chr [1]>
## 5 e <lgl [1]> <- NA <lgl [1]> # <- NA
## 6 f <dbl [1]> <lgl [1]> # <- NA
## 7 g <dbl [1]> <lgl [1]> # <- NA
## 8 h <dbl [3]> <list [3]>
## 9 i <lgl [1]> <- NA <chr [1]>
## 10 j <dbl [1]> <chr [1]>
UPDATE
I made some progress based on this solution:
tbl_with_null %>%
mutate(across(c(value_1, value_2), ~replace(., !lengths(.), list(NA))))
## # A tibble: 10 x 3
## letter value_1 value_2
## <chr> <list> <list>
## 1 a <dbl [1]> <chr [1]>
## 2 b <dbl [1]> <chr [1]>
## 3 c <dbl [1]> <chr [1]>
## 4 d <df[,3] [1 x 3]> <chr [1]>
## 5 e <lgl [1]> <lgl [1]>
## 6 f <dbl [1]> <lgl [1]>
## 7 g <dbl [1]> <lgl [1]>
## 8 h <dbl [3]> <list [3]>
## 9 i <lgl [1]> <chr [1]>
## 10 j <dbl [1]> <chr [1]>
However, this is insufficient because I'm looking for a solution that would blindly replace NULL with NA across the entire dataframe. And if we go with mutate(across(everything(), ~replace(., !lengths(.), list(NA)))) we get that the letters column became a list-column too, which is unintended.
## # A tibble: 10 x 3
## letter value_1 value_2
## <list> <list> <list>
## 1 <chr [1]> <dbl [1]> <chr [1]>
## 2 <chr [1]> <dbl [1]> <chr [1]>
## 3 <chr [1]> <dbl [1]> <chr [1]>
## 4 <chr [1]> <df[,3] [1 x 3]> <chr [1]>
## 5 <chr [1]> <lgl [1]> <lgl [1]>
## 6 <chr [1]> <dbl [1]> <lgl [1]>
## 7 <chr [1]> <dbl [1]> <lgl [1]>
## 8 <chr [1]> <dbl [3]> <list [3]>
## 9 <chr [1]> <lgl [1]> <chr [1]>
## 10 <chr [1]> <dbl [1]> <chr [1]>
UPDATE 2
I thought that I got it done with
mutate(across(everything(), ~simplify(replace(., !lengths(.), list(NA)))))
But unfortunately this fails in some cases such as this data:
tbl_with_no_null <-
tbl_with_null %>%
slice(8) %>%
select(letter, value_1)
## # A tibble: 1 x 2
## letter value_1
## <chr> <list>
## 1 h <dbl [3]>
While I was expecting that
tbl_with_no_null %>%
mutate(across(everything(), ~simplify(replace(., !lengths(.), list(NA)))))
would return just the same tbl_with_no_null (because no <NULL> to replace):
## # A tibble: 1 x 2
## letter value_1
## <chr> <list>
## 1 h <dbl [3]>
But instead I got the error:
Error: Problem with `mutate()` input `..1`.
x Input `..1` can't be recycled to size 1.
i Input `..1` is `(function (.cols = everything(), .fns = NULL, ..., .names = NULL) ...`.
i Input `..1` must be size 1, not 3.
Bottom line
I'm looking for a way to replace <NULL> with NA in list columns, and naturally, if there's no <NULL> to replace, then return the input as-is.