首页 > 解决方案 > data.frame/tibble/data.table 中的自定义 S4 类

问题描述

我希望在data.frames (和/或 tibbles,data.tables,...)内部使用由我定义的 S4 类,类似于lubridate::period()提供的内容。

例如,以下代码创建一个句点向量,将它们存储在 data.frame(在本例中为 tibble)中,提取一个值并再次访问该类...


tibble::tibble(
  x = c(lubridate::period(120), 
        lubridate::period(2))
)
#> # A tibble: 2 x 1
#>   x       
#>   <Period>
#> 1 120S    
#> 2 2S 

str(
  tibble::tibble(
    x = c(lubridate::period(120), 
          lubridate::period(2))
  )$x[1]
)
#> Formal class 'Period' [package "lubridate"] with 6 slots
#>   ..@ .Data : num 120
#>   ..@ year  : num 0
#>   ..@ month : num 0
#>   ..@ day   : num 0
#>   ..@ hour  : num 0
#>   ..@ minute: num 0

我想用典型的Person课程来复制这个。

第一次(失败的)尝试如下所示:

.person <- setClass("Person", 
                   slots = list(name = "character",
                                age = "numeric"))
person <- function(name, age) {
  .person(name = name, age = age)
}

format.Person <- function(x, ...) {
  paste0("<Person: ", x@name, " ", x@age, ">")
}


# create some instances
person("Alice", 123)
#> An object of class "Person"
#> Slot "name":
#>   [1] "Alice"
#> 
#> Slot "age":
#>   [1] 123

## Fair enough, no proper "show" method implemented yet...

format(person("Alice", 123))
#> [1] "<Person: Alice 123>"


ppl <- c(person("Alice", 123),
  person("Bob", 42))
ppl
#> [[1]]
#> An object of class "Person"
#> Slot "name":
#>   [1] "Alice"
#> 
#> Slot "age":
#>   [1] 123
#> 
#> 
#> [[2]]
#> An object of class "Person"
#> Slot "name":
#>   [1] "Bob"
#> 
#> Slot "age":
#>   [1] 42

## Now converting to data.frames

data.frame(x = ppl)
#> Error in unique.default(x, nmax = nmax) : 
#>   unique() applies only to vectors

tibble::tibble(x = ppl)
#> # A tibble: 2 x 1
#> x       
#> <list>  
#>   1 <Person>
#>   2 <Person>

data.table::data.table(x = ppl)
#> x
#> 1: <Person[2]>
#> 2: <Person[2]>

我最终想要的是有一些类似的东西:

tibble::tibble(x = ppl)
#> # A tibble: 2 x 1
#>   x     
#>   <Person>
#> 1 <Alice, 123>    
#> 2 <Bob, 42>
#>

标签: rs4

解决方案


这不起作用的原因是因为data.frames(和类似的对象,如tibbles)只能在它们的列中包含原子类型(即可以通过调用创建的东西vector)。原子类型是“逻辑”、“整数”、“双”、“复杂”、“字符”、“原始”和“列表”。

实际上,这意味着您的类将必须包含这些原子类型之一(可能是“列表”),类似于setClass("onePerson",contains="list"). 让每个插槽都是原子类型是不够的。如果您想强制列表具有正确类的正确组件,那么您可以使用validObject方法来强制执行该操作。

可能会坚持使用不扩展原子类型的类定义,但您需要为类似的函数编写方法cbind2[允许它与数据帧交互。

最后,您可以使您的person类扩展data.frame或扩展tibble自身,但对列名和类型有限制。这与列表解决方案基本相同,因为数据框列表。


推荐阅读