r - 修改后保留 R 数据帧的结构(在整洁的管道中)

标签 r tidyverse r-s4 r6

我正在处理某种结构的数据框。举例来说,它们都应该具有一组相同的列和“id”列的唯一值。此类数据框还有一组 S3 泛型和方法,如“plot”、“write”等。

但是,如果用户修改数据框的结构,那么从语义上讲,它不再属于初始类,尽管该对象可能仍然正式具有类标签。因此,S3 方法都不能安全地应用于该对象。

是否可以在每次修改数据框时以某种方式检查数据框的有效性,如果结构发生变化,则删除类标签?修改数据框(或任何对象)并保留其结构的正确方法是什么?

UPD:我所说的修改不仅指的是 base-R [ setter ,还指应用 tidyverse 管道的结果,例如select'ing、mutate'ing、...——这些动词不会修改原始对象,而是返回修改后的对象,复制 原始属性。

我能想到的一个选择是将数据框包装在 R6 类中:

ZDf <- R6::R6Class(
    'ZDf',
    private = list(
        .Data = NULL
    ),
    public = list(
        initialize = function(z) {
            # Do checks here
            # This duplicates set(), but in general may implement completely
            # different logic
            private$.Data <- z
            invisible(private$.Data)
        },
        set = function(z) {
            # Do checks here
            private$.Data <- z
            invisible(private$.Data)
        },
        get = function() {
            private$.Data
        }
    )
)

像这样使用它:

z <- ZDf$new(mtcars[1:10,])
z$get() %>%
    group_by(cyl) %>%
    summarise(mean(mpg)) %>%
    z$set()

我读到 S4 对 S3 类有一些支持,并且 S4 包装的对象看起来几乎像普通的 S4 对象:

> setClass('XDf', contains = 'data.frame')
> x <- new('XDf', mtcars[1:3,1:3]) 
> x
Object of class "XDf"
               mpg cyl disp
Mazda RX4     21.0   6  160
Mazda RX4 Wag 21.0   6  160
Datsun 710    22.8   4  108
> x[1,]
          mpg cyl disp
Mazda RX4  21   6  160

但是,不可能在hadleyverse管道中使用它:

> x %>% slice(1)
Error in `stop_vctrs()`:
! Input must be a vector, not a <XDf> object.
Run `rlang::last_error()` to see where the error occurred.

最佳答案

有一种不同的方法可以实现这一目标。为了说明这一点,我们假设约束是转换必须保留与 iris 相同的列。此约束在 check_ZDf 函数中进行编码:

check_ZDf <- function(x, ...) {
  all(colnames(iris) %in% colnames(x))
}

然后我们可以使用函数来检查对象是否属于 ZDf 类:

is.ZDf <- function(x) {
  inherits(x, "ZDf")
}

让我们创建一个函数,它接受任何 tibble/data.frame 并修改 ZDf 类(如果它确实满足的话):

create_ZDf_from <- function(x, ...) {
  stopifnot("`x` is already instance of `ZDf`" = is.ZDf(x),
            check_ZDf(x))
  structure(x, class = c("ZDf", class(x)))
}

现在,我们可以确保在数据变量的每次突变之后满足约束。这意味着我们在访问 [$,当然还有 [[

时必须在赋值后进行检查。
`[[<-.ZDf` <- function(x, ...) {
  result <- NextMethod()
  stopifnot(check_ZDf(result))
  result
}

`[<-.ZDf` <- function(x,...) {
  result <- NextMethod()
  stopifnot(check_ZDf(result))
  result
}

`$<-.ZDf` <- function(x, ...) {
  result <- NextMethod()
  stopifnot(check_ZDf(result))
  result
}

因此您可以尝试以下操作:

example_df <- as_tibble(iris[sample.int(ncol(iris), 
  size = 100, replace = TRUE),])
ZDf_example_df <- create_ZDf_from(example_df)

ZDf_example_df[["Sepal.Length"]] <- NULL # doesn't work
ZDf_example_df["Sepal.Length"] <- rnorm(nrow(ZDf_example_df)) # works!
ZDf_example_df["Sepal.Length"] <- NULL # doesn't work
ZDf_example_df$Sepal.Length <- NULL    # doesn't work

ZDf_example_df$New <- rnorm(1) # works

关于r - 修改后保留 R 数据帧的结构(在整洁的管道中),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72380166/

相关文章:

arrays - 有选择地使用 drop [R] - 仅删除指定的 length-1 尺寸

R:通过 *list* 列连接两个表(tibbles)

r - 具有因子水平的双色热图

r - 使用具有多个键或值的 Tidyr 扩展表

r - 数据表 : Remove all horizontal borders

r - 在\include 图形之后编织空行

r - 如何比较7个数字列,获取它们之间的最小数字并从R中的字符变量插入值?

R为 "Matrix"和NULL创建虚拟类

r - 如何使用trace将S4通用函数单步执行到实际调度的方法?