Skip to contents

Recursively flattens an R object. Unlike base::unlist(), it

  • always returns a regular list, i.e. wraps x in a list if necessary, and will never remove the last list level. Thus it is type-safe.

  • won't treat any of the list leafs specially (like unlist() does with factors). Thus leaf values will never be modified.

Usage

as_flat_list(
  x,
  is_node = NULL,
  name_spec = "{outer}.{inner}",
  name_repair = c("minimal", "unique", "check_unique", "universal")
)

Arguments

x

An R object.

is_node

A predicate function that determines whether an element is a node (by returning TRUE) or a leaf (by returning FALSE). The default value, NULL, treats simple lists as nodes and everything else (including richer objects like data frames and linear models) as leaves, using vctrs::obj_is_list(). To recurse into all objects built on lists use is.list().

name_spec

If both inner and outer names are present, control how they are combined. Should be a glue specification that uses variables inner and outer.

name_repair

One of "minimal", "unique", "universal", or "check_unique". See vctrs::vec_as_names() for the meaning of these options.

Value

A list.

Examples

library(magrittr)

nested_list <- list(1:3, list("foo", list("bar"))) %T>% str()
#> List of 2
#>  $ : int [1:3] 1 2 3
#>  $ :List of 2
#>   ..$ : chr "foo"
#>   ..$ :List of 1
#>   .. ..$ : chr "bar"
lm_obj <- lm(mpg ~ hp, mtcars)

# unlike `unlist()` which also removes the last list tier from regular lists and many list-based
# objects...
unlist("foobar")
#> [1] "foobar"
unlist(nested_list) |> str()
#>  chr [1:5] "1" "2" "3" "foo" "bar"
unlist(lm_obj) |> str()
#> List of 238
#>  $ coefficients.(Intercept)         : num 30.1
#>  $ coefficients.hp                  : num -0.0682
#>  $ residuals.Mazda RX4              : num -1.59
#>  $ residuals.Mazda RX4 Wag          : num -1.59
#>  $ residuals.Datsun 710             : num -0.954
#>  $ residuals.Hornet 4 Drive         : num -1.19
#>  $ residuals.Hornet Sportabout      : num 0.541
#>  $ residuals.Valiant                : num -4.83
#>  $ residuals.Duster 360             : num 0.917
#>  $ residuals.Merc 240D              : num -1.47
#>  $ residuals.Merc 230               : num -0.817
#>  $ residuals.Merc 280               : num -2.51
#>  $ residuals.Merc 280C              : num -3.91
#>  $ residuals.Merc 450SE             : num -1.42
#>  $ residuals.Merc 450SL             : num -0.518
#>  $ residuals.Merc 450SLC            : num -2.62
#>  $ residuals.Cadillac Fleetwood     : num -5.71
#>  $ residuals.Lincoln Continental    : num -5.03
#>  $ residuals.Chrysler Imperial      : num 0.294
#>  $ residuals.Fiat 128               : num 6.8
#>  $ residuals.Honda Civic            : num 3.85
#>  $ residuals.Toyota Corolla         : num 8.24
#>  $ residuals.Toyota Corona          : num -1.98
#>  $ residuals.Dodge Challenger       : num -4.36
#>  $ residuals.AMC Javelin            : num -4.66
#>  $ residuals.Camaro Z28             : num -0.0829
#>  $ residuals.Pontiac Firebird       : num 1.04
#>  $ residuals.Fiat X1-9              : num 1.7
#>  $ residuals.Porsche 914-2          : num 2.11
#>  $ residuals.Lotus Europa           : num 8.01
#>  $ residuals.Ford Pantera L         : num 3.71
#>  $ residuals.Ferrari Dino           : num 1.54
#>  $ residuals.Maserati Bora          : num 7.76
#>  $ residuals.Volvo 142E             : num -1.26
#>  $ effects.(Intercept)              : num -114
#>  $ effects.hp                       : num -26
#>  $ effects3                         : num -0.556
#>  $ effects4                         : num -0.852
#>  $ effects5                         : num 0.67
#>  $ effects6                         : num -4.48
#>  $ effects7                         : num 0.816
#>  $ effects8                         : num -0.97
#>  $ effects9                         : num -0.426
#>  $ effects10                        : num -2.21
#>  $ effects11                        : num -3.61
#>  $ effects12                        : num -1.31
#>  $ effects13                        : num -0.406
#>  $ effects14                        : num -2.51
#>  $ effects15                        : num -5.68
#>  $ effects16                        : num -5.03
#>  $ effects17                        : num 0.242
#>  $ effects18                        : num 7.29
#>  $ effects19                        : num 4.38
#>  $ effects20                        : num 8.73
#>  $ effects21                        : num -1.6
#>  $ effects22                        : num -4.15
#>  $ effects23                        : num -4.45
#>  $ effects24                        : num -0.184
#>  $ effects25                        : num 1.17
#>  $ effects26                        : num 2.19
#>  $ effects27                        : num 2.51
#>  $ effects28                        : num 8.34
#>  $ effects29                        : num 3.55
#>  $ effects30                        : num 1.67
#>  $ effects31                        : num 7.36
#>  $ effects32                        : num -0.917
#>  $ rank                             : int 2
#>  $ fitted.values.Mazda RX4          : num 22.6
#>  $ fitted.values.Mazda RX4 Wag      : num 22.6
#>  $ fitted.values.Datsun 710         : num 23.8
#>  $ fitted.values.Hornet 4 Drive     : num 22.6
#>  $ fitted.values.Hornet Sportabout  : num 18.2
#>  $ fitted.values.Valiant            : num 22.9
#>  $ fitted.values.Duster 360         : num 13.4
#>  $ fitted.values.Merc 240D          : num 25.9
#>  $ fitted.values.Merc 230           : num 23.6
#>  $ fitted.values.Merc 280           : num 21.7
#>  $ fitted.values.Merc 280C          : num 21.7
#>  $ fitted.values.Merc 450SE         : num 17.8
#>  $ fitted.values.Merc 450SL         : num 17.8
#>  $ fitted.values.Merc 450SLC        : num 17.8
#>  $ fitted.values.Cadillac Fleetwood : num 16.1
#>  $ fitted.values.Lincoln Continental: num 15.4
#>  $ fitted.values.Chrysler Imperial  : num 14.4
#>  $ fitted.values.Fiat 128           : num 25.6
#>  $ fitted.values.Honda Civic        : num 26.6
#>  $ fitted.values.Toyota Corolla     : num 25.7
#>  $ fitted.values.Toyota Corona      : num 23.5
#>  $ fitted.values.Dodge Challenger   : num 19.9
#>  $ fitted.values.AMC Javelin        : num 19.9
#>  $ fitted.values.Camaro Z28         : num 13.4
#>  $ fitted.values.Pontiac Firebird   : num 18.2
#>  $ fitted.values.Fiat X1-9          : num 25.6
#>  $ fitted.values.Porsche 914-2      : num 23.9
#>  $ fitted.values.Lotus Europa       : num 22.4
#>  $ fitted.values.Ford Pantera L     : num 12.1
#>  $ fitted.values.Ferrari Dino       : num 18.2
#>  $ fitted.values.Maserati Bora      : num 7.24
#>  $ fitted.values.Volvo 142E         : num 22.7
#>   [list output truncated]

# ...this function is able to return consistent results, i.e. an unnested list
pal::as_flat_list("foobar", is_node = is.list) |> str()
#> List of 1
#>  $ : chr "foobar"
pal::as_flat_list(nested_list, is_node = is.list) |> str()
#> List of 3
#>  $ : int [1:3] 1 2 3
#>  $ : chr "foo"
#>  $ : chr "bar"
pal::as_flat_list(lm_obj, is_node = is.list) |> str()
#> List of 16
#>  $ coefficients : Named num [1:2] 30.0989 -0.0682
#>   ..- attr(*, "names")= chr [1:2] "(Intercept)" "hp"
#>  $ residuals    : Named num [1:32] -1.594 -1.594 -0.954 -1.194 0.541 ...
#>   ..- attr(*, "names")= chr [1:32] "Mazda RX4" "Mazda RX4 Wag" "Datsun 710" "Hornet 4 Drive" ...
#>  $ effects      : Named num [1:32] -113.65 -26.046 -0.556 -0.852 0.67 ...
#>   ..- attr(*, "names")= chr [1:32] "(Intercept)" "hp" "" "" ...
#>  $ rank         : int 2
#>  $ fitted.values: Named num [1:32] 22.6 22.6 23.8 22.6 18.2 ...
#>   ..- attr(*, "names")= chr [1:32] "Mazda RX4" "Mazda RX4 Wag" "Datsun 710" "Hornet 4 Drive" ...
#>  $ assign       : int [1:2] 0 1
#>  $ qr.qr        : num [1:32, 1:2] -5.657 0.177 0.177 0.177 0.177 ...
#>   ..- attr(*, "dimnames")=List of 2
#>   .. ..$ : chr [1:32] "Mazda RX4" "Mazda RX4 Wag" "Datsun 710" "Hornet 4 Drive" ...
#>   .. ..$ : chr [1:2] "(Intercept)" "hp"
#>   ..- attr(*, "assign")= int [1:2] 0 1
#>  $ qr.qraux     : num [1:2] 1.18 1.08
#>  $ qr.pivot     : int [1:2] 1 2
#>  $ qr.tol       : num 1e-07
#>  $ qr.rank      : int 2
#>  $ df.residual  : int 30
#>  $ call         : language lm(formula = mpg ~ hp, data = mtcars)
#>  $ terms        :Classes 'terms', 'formula'  language mpg ~ hp
#>   .. ..- attr(*, "variables")= language list(mpg, hp)
#>   .. ..- attr(*, "factors")= int [1:2, 1] 0 1
#>   .. .. ..- attr(*, "dimnames")=List of 2
#>   .. .. .. ..$ : chr [1:2] "mpg" "hp"
#>   .. .. .. ..$ : chr "hp"
#>   .. ..- attr(*, "term.labels")= chr "hp"
#>   .. ..- attr(*, "order")= int 1
#>   .. ..- attr(*, "intercept")= int 1
#>   .. ..- attr(*, "response")= int 1
#>   .. ..- attr(*, ".Environment")=<environment: 0x55a5f2b65180> 
#>   .. ..- attr(*, "predvars")= language list(mpg, hp)
#>   .. ..- attr(*, "dataClasses")= Named chr [1:2] "numeric" "numeric"
#>   .. .. ..- attr(*, "names")= chr [1:2] "mpg" "hp"
#>  $ model.mpg    : num [1:32] 21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
#>  $ model.hp     : num [1:32] 110 110 93 110 175 105 245 62 95 123 ...
#>  - attr(*, "class")= chr "lm"

nested_list <- list(list(factor("a"), factor("b")), factor("c")) %T>% str()
#> List of 2
#>  $ :List of 2
#>   ..$ : Factor w/ 1 level "a": 1
#>   ..$ : Factor w/ 1 level "b": 1
#>  $ : Factor w/ 1 level "c": 1

# unlike `unlist()` which combines factors...
unlist(nested_list) |> str()
#>  Factor w/ 3 levels "a","b","c": 1 2 3
# ...this function does not modify the list elements
pal::as_flat_list(nested_list) |> str()
#> List of 3
#>  $ : Factor w/ 1 level "a": 1
#>  $ : Factor w/ 1 level "b": 1
#>  $ : Factor w/ 1 level "c": 1

nested_list <-
  list(c(list(1L), list(tibble::tibble(a = list(1.1, "2")))),
       list(tibble::as_tibble(mtcars[1:2, ]))) %T>%
  str()
#> List of 2
#>  $ :List of 2
#>   ..$ : int 1
#>   ..$ : tibble [2 × 1] (S3: tbl_df/tbl/data.frame)
#>   .. ..$ a:List of 2
#>   .. .. ..$ : num 1.1
#>   .. .. ..$ : chr "2"
#>  $ :List of 1
#>   ..$ : tibble [2 × 11] (S3: tbl_df/tbl/data.frame)
#>   .. ..$ mpg : num [1:2] 21 21
#>   .. ..$ cyl : num [1:2] 6 6
#>   .. ..$ disp: num [1:2] 160 160
#>   .. ..$ hp  : num [1:2] 110 110
#>   .. ..$ drat: num [1:2] 3.9 3.9
#>   .. ..$ wt  : num [1:2] 2.62 2.88
#>   .. ..$ qsec: num [1:2] 16.5 17
#>   .. ..$ vs  : num [1:2] 0 0
#>   .. ..$ am  : num [1:2] 1 1
#>   .. ..$ gear: num [1:2] 4 4
#>   .. ..$ carb: num [1:2] 4 4
nested_list_2 <- list(1:3, xfun::strict_list(list(list("buried deep")))) %T>% str()
#> List of 2
#>  $ : int [1:3] 1 2 3
#>  $ :List of 1
#>   ..$ :List of 1
#>   .. ..$ :List of 1
#>   .. .. ..$ : chr "buried deep"
#>   ..- attr(*, "class")= chr "xfun_strict_list"

# by default, classed lists like data frames, tibbles or `xfun_strict_list`s are retained, i.e.
# not flattened...
pal::as_flat_list(nested_list) |> str()
#> List of 3
#>  $ : int 1
#>  $ : tibble [2 × 1] (S3: tbl_df/tbl/data.frame)
#>   ..$ a:List of 2
#>   .. ..$ : num 1.1
#>   .. ..$ : chr "2"
#>  $ : tibble [2 × 11] (S3: tbl_df/tbl/data.frame)
#>   ..$ mpg : num [1:2] 21 21
#>   ..$ cyl : num [1:2] 6 6
#>   ..$ disp: num [1:2] 160 160
#>   ..$ hp  : num [1:2] 110 110
#>   ..$ drat: num [1:2] 3.9 3.9
#>   ..$ wt  : num [1:2] 2.62 2.88
#>   ..$ qsec: num [1:2] 16.5 17
#>   ..$ vs  : num [1:2] 0 0
#>   ..$ am  : num [1:2] 1 1
#>   ..$ gear: num [1:2] 4 4
#>   ..$ carb: num [1:2] 4 4
pal::as_flat_list(nested_list_2) |> str()
#> List of 2
#>  $ : int [1:3] 1 2 3
#>  $ :List of 1
#>   ..$ :List of 1
#>   .. ..$ :List of 1
#>   .. .. ..$ : chr "buried deep"
#>   ..- attr(*, "class")= chr "xfun_strict_list"
# ...but you can drop them and thereby flatten custom objects if needed
pal::as_flat_list(nested_list, is_node = is.list) |> str()
#> List of 14
#>  $     : int 1
#>  $ a.1 : num 1.1
#>  $ a.2 : chr "2"
#>  $ mpg : num [1:2] 21 21
#>  $ cyl : num [1:2] 6 6
#>  $ disp: num [1:2] 160 160
#>  $ hp  : num [1:2] 110 110
#>  $ drat: num [1:2] 3.9 3.9
#>  $ wt  : num [1:2] 2.62 2.88
#>  $ qsec: num [1:2] 16.5 17
#>  $ vs  : num [1:2] 0 0
#>  $ am  : num [1:2] 1 1
#>  $ gear: num [1:2] 4 4
#>  $ carb: num [1:2] 4 4
pal::as_flat_list(nested_list_2, is_node = is.list) |> str()
#> List of 2
#>  $ : int [1:3] 1 2 3
#>  $ : chr "buried deep"