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

Support all vectors in enframe() #740

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Imports:
pkgconfig,
rlang (>= 0.4.3),
utils,
vctrs (>= 0.3.2)
vctrs (>= 0.3.4)
Suggests:
bench,
bit64,
Expand Down
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,11 @@ importFrom(vctrs,vec_as_names_legacy)
importFrom(vctrs,vec_as_subscript2)
importFrom(vctrs,vec_c)
importFrom(vctrs,vec_is)
importFrom(vctrs,vec_names)
importFrom(vctrs,vec_names2)
importFrom(vctrs,vec_rbind)
importFrom(vctrs,vec_recycle)
importFrom(vctrs,vec_set_names)
importFrom(vctrs,vec_size)
importFrom(vctrs,vec_slice)
useDynLib(tibble, .registration = TRUE)
54 changes: 34 additions & 20 deletions R/enframe.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,74 +3,88 @@
#' @description
#' `r lifecycle::badge("maturing")`
#'
#' `enframe()` converts named atomic vectors or lists to one- or two-column
#' data frames.
#' `enframe()` converts named atomic vectors or lists to two-column data frames.
#' For a list, the result will be a nested tibble with a column of type `list`.
#' For unnamed vectors, the natural sequence is used as name column.
#'
#' @param x An atomic vector (for `enframe()`) or a data frame with one or two columns
#' (for `deframe()`).
#' @param x
#' For `enframe()`, a vector.
#' For `deframe()`, a data frame with two columns.
#' Passing a one-column data frame is deprecated, use `x[[1]]` instead.
#' @param name,value Names of the columns that store the names and values.
#' If `name` is `NULL`, a one-column tibble is returned; `value` cannot be `NULL`.
#' Passing `NULL` is deprecated, use [as_tibble_col()] instead.
#'
#' @return A [tibble] with two columns (if `name` is not `NULL`, the default)
#' or one column (otherwise).
#' @return
#' For `enframe()`, a [tibble] with two columns.
#' For `deframe()`, a named vector.
#' @export
#'
#' @examples
#' enframe(1:3)
#' enframe(c(a = 5, b = 7))
#' enframe(list(one = 1, two = 2:3, three = 4:6))
#' enframe(trees)
#' enframe(matrix(1:6, ncol = 2))
enframe <- function(x, name = "name", value = "value") {
if (is.null(value)) {
cnd_signal(error_enframe_value_null())
}

if (length(dim(x)) > 1) {
cnd_signal(error_enframe_has_dim(x))
if (is_null(x)) {
x <- logical()
}

if (is.null(x)) x <- logical()
if (!vec_is(x)) {
cnd_signal(error_enframe_must_be_vector(x))
}

if (is.null(name)) {
df <- list(unname(x))
} else if (is.null(names(x))) {
df <- list(seq_along(x), x)
deprecate_warn("3.0.4", what = "tibble::enframe(name = 'can\\'t be NULL')",
with = "as_tibble_col()")

df <- list(vec_set_names(x, NULL))
} else if (is.null(vec_names(x))) {
df <- list(seq_len(vec_size(x)), x)
} else {
df <- list(names(x), unname(x))
df <- list(vec_names2(x), vec_set_names(x, NULL))
}

names(df) <- c(name, value)
new_tibble(df, nrow = length(x))
new_tibble(df, nrow = vec_size(x))
}

#' @rdname enframe
#' @description
#' `deframe()` converts two-column data frames to a named vector or list,
#' using the first column as name and the second column as value.
#' If the input has only one column, an unnamed vector is returned.
#' @export
#' @examples
#' deframe(enframe(3:1))
#' deframe(tibble(a = 1:3))
#' deframe(tibble(a = as.list(1:3)))
deframe <- function(x) {
if (length(x) == 1) {
deprecate_soft("3.0.4", "tibble::deframe(x = 'can\\'t be a one-column data frame')",
details = "Please use `x[[1]]` instead.")

return(x[[1]])
} else if (length(x) != 2) {
warn("`x` must be a one- or two-column data frame in `deframe()`.")
}

value <- x[[2L]]
name <- x[[1L]]
names(value) <- name
value
vec_set_names(value, as.character(name))
}

# Errors ------------------------------------------------------------------

error_enframe_value_null <- function() {
tibble_error("`value` can't be NULL.")
}

error_enframe_has_dim <- function(x) {
tibble_error(paste0("`x` must not have more than one dimension. `length(dim(x))` must be zero or one, not ", length(dim(x)), "."))
error_enframe_must_be_vector <- function(x) {
tibble_error(paste0(
"The `x` argument to `enframe()` must be a vector, not ", class(x)[[1]], "."
))
}
4 changes: 3 additions & 1 deletion R/tibble-package.R
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
#' @import ellipsis
#' @importFrom vctrs vec_as_location vec_as_location2 vec_as_names vec_as_names_legacy vec_c
#' @importFrom vctrs vec_is vec_rbind vec_recycle vec_size vec_slice vec_slice<-
#' @importFrom vctrs unspecified vec_as_subscript2 num_as_location
#' @importFrom vctrs unspecified vec_as_subscript2
#' @importFrom vctrs vec_names vec_names2 vec_set_names
#' @importFrom vctrs num_as_location
#' @aliases NULL tibble-package
#' @details
#' `r lifecycle::badge("stable")`
Expand Down
17 changes: 9 additions & 8 deletions man/enframe.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 40 additions & 2 deletions tests/testthat/enframe.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,44 @@
> enframe(1:3, value = NULL)
Error: `value` can't be NULL.

> enframe(Titanic)
Error: `x` must not have more than one dimension. `length(dim(x))` must be zero or one, not 4.
> nrow(enframe(Titanic))
[1] 4

> enframe(Titanic)$value
, , Age = Child, Survived = No

Sex
Class Male Female
[1,] 0 0
[2,] 0 0
[3,] 35 17
[4,] 0 0

, , Age = Adult, Survived = No

Sex
Class Male Female
[1,] 118 4
[2,] 154 13
[3,] 387 89
[4,] 670 3

, , Age = Child, Survived = Yes

Sex
Class Male Female
[1,] 5 1
[2,] 11 13
[3,] 13 14
[4,] 0 0

, , Age = Adult, Survived = Yes

Sex
Class Male Female
[1,] 57 140
[2,] 14 80
[3,] 75 76
[4,] 192 20


5 changes: 5 additions & 0 deletions tests/testthat/helper-zzz.R
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ skip_enh_bullets_format <- function() {
skip_legacy()
}

skip_enh_enframe_vector <- function() {
# ENH: enframe() supports all vectors (#730)
skip_legacy()
}

skip_int_error_unknown_names <- function() {
# INT: error_unknown_names() no longer implemented
skip_legacy()
Expand Down
6 changes: 3 additions & 3 deletions tests/testthat/msg.txt
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ Use .name_repair to specify repair.
<error/tibble_error_enframe_value_null>
`value` can't be NULL.

> error_enframe_has_dim(Titanic)
<error/tibble_error_enframe_has_dim>
`x` must not have more than one dimension. `length(dim(x))` must be zero or one, not 4.
> error_enframe_must_be_vector(lm(speed ~ ., cars))
<error/tibble_error_enframe_must_be_vector>
The `x` argument to `enframe()` must be a vector, not lm.


glimpse
Expand Down
42 changes: 36 additions & 6 deletions tests/testthat/test-enframe.R
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ test_that("can use custom names", {

test_that("can enframe without names", {
expect_identical(
enframe(letters, name = NULL, value = "letter"),
expect_deprecated(enframe(letters, name = NULL, value = "letter")),
tibble(letter = letters)
)
})
Expand All @@ -62,10 +62,34 @@ test_that("can't use value = NULL", {
)
})

test_that("can't pass objects with dimensions", {
test_that("can pass vectors (#730)", {
a <- set_names(1:5, letters[2:6])
au <- 1:5
expect_identical(enframe(a), tibble(name = letters[2:6], value = au))

b <- data.frame(bb = 1:3, row.names = letters[4:6])
bu <- data.frame(bb = 1:3)
expect_identical(enframe(b), tibble(name = letters[4:6], value = bu))

cu <- matrix(1:6, nrow = 3, ncol = 2)
colnames(cu) <- letters[1:2]
c <- cu
rownames(c) <- letters[24:26]
expect_identical(enframe(c), tibble(name = letters[24:26], value = cu))

d <- array(1:120, dim = 5:2, dimnames = list(
d1 = letters[1:5], d2 = letters[6:9],
d3 = LETTERS[10:12], d4 = LETTERS[13:14]
))
du <- array(1:120, dim = 5:2, dimnames = list(
d1 = NULL, d2 = letters[6:9],
d3 = LETTERS[10:12], d4 = LETTERS[13:14]
))
expect_identical(enframe(d), tibble(name = letters[1:5], value = du))

expect_tibble_error(
enframe(iris),
error_enframe_has_dim(iris)
enframe(lm(speed ~ ., cars)),
error_enframe_must_be_vector(lm(speed ~ ., cars))
)
})

Expand All @@ -77,11 +101,15 @@ test_that("can deframe two-column data frame", {
deframe(tibble(name = letters[1:3], value = 3:1)),
c(a = 3L, b = 2L, c = 1L)
)
expect_identical(
deframe(tibble(name = 1:3, value = 3:1)),
c("1" = 3L, "2" = 2L, "3" = 1L)
)
})

test_that("can deframe one-column data frame", {
expect_identical(
deframe(tibble(value = 3:1)),
expect_deprecated(deframe(tibble(value = 3:1))),
3:1
)
})
Expand All @@ -107,5 +135,7 @@ test_that("can deframe three-column data frame with warning", {
verify_output("enframe.txt", {
enframe(1:3, value = NULL)

enframe(Titanic)
# Work around https://github.com/r-lib/pillar/issues/142
nrow(enframe(Titanic))
enframe(Titanic)$value
})
2 changes: 1 addition & 1 deletion tests/testthat/test-msg.R
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ verify_output("msg.txt", {
"#enframe"
error_enframe_value_null()

error_enframe_has_dim(Titanic)
error_enframe_must_be_vector(lm(speed ~ ., cars))

"# glimpse"
error_glimpse_infinite_width()
Expand Down
2 changes: 2 additions & 0 deletions tests/testthat/test-zzz-enframe.R
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ test_that("can't use value = NULL", {
})

test_that("can't pass objects with dimensions", {
skip_enh_enframe_vector()

expect_legacy_error(
enframe(iris),
error_enframe_has_dim(iris),
Expand Down