我正在处理某种结构的数据框。举例来说,它们都应该具有一组相同的列和“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/