如何在我的 R 包中自定义 print/show 变量(使用自定义 class)

How to custom print/show variables (with custom class) in my R package

许多 R 函数 return 以特殊方式打印到控制台的对象。例如,t_results = t.test(c(1,2,3), c(1,2,4)) 会将 list 分配给 t_results 变量,但是当我在控制台中输入此变量时,或将其称为 print(t_results)show(t_results) , 它会打印一些纯文本信息(例如 Welch Two Sample t-test... 等)而不是 returning 实际的 list。 (这是一个基本的 R 函数,但我也看到它在许多自定义用户 R 包中实现。)

我的问题是:如何为在我自己的自定义 R 包中创建的对象执行此操作?我已经阅读了几个相关的问题和答案(例如,, , and this),它们确实给出了一个总体思路(使用 setMethod 作为我的自定义 classes),但是 none他们让我清楚地知道我需要做什么才能让它在自定义 R 包中正常工作。我也找不到关于此事的任何官方文档或教程。

举一个我想做的例子,这是我假设的 R 包中的一个非常简单的函数,它只是 return 一个小的 data.frame(带有任意 class 我给它加的名字,这里 'my_df_class'):

my_main_function = function() {
    my_df = data.frame(a = c('x1', 'y2', 'z2'),
                       b = c('x2', 'y2', 'z2'))
    class(my_df) = c(class(my_df), 'my_df_class')
    return(my_df)
}

我想要这个 printed/shown 例如像这样:

my_print_function = function(df) {
    cat('My results:', df$a[2], df$a[3])
}
# see my_print_function(my_main_function())

为了让我的 R 包工作,究竟需要做什么(即,当有人安装我的 R 包时,将 my_main_function() 结果分配给一个变量,并且 prints/ show是那个变量,可以通过my_print_function())?

为 class 使用特定函数的最简单方法是将其设置为 S3 泛型。

print.my_df_class = function(df) {
    cat('My results:', df$a[2], df$a[3])
}

请注意,因为您在行 class(my_df) = c(class(my_df), 'my_df_class') 上保留了 data.frame class,所以 print() 将显示 data.frame.[=20 的打印=]

print(my_main_function())
#    a  b
# 1 x1 x2
# 2 y2 y2
# 3 z2 z2

您可以使用 print.my_df_class(),或修改 my_main_function() class 赋值。

my_main_function = function() {
    my_df = data.frame(a = c('x1', 'y2', 'z2'),
                       b = c('x2', 'y2', 'z2'))
    class(my_df) = 'my_df_class'
    return(my_df)
}

然后你可以使用 print 而没有最后的 class 规范来获得 class-specific 响应。

print(my_main_function())
# My results: y2 z2

这里稍微解释一下。添加到@nya 发布的惊人答案:

首先,您正在处理 S3 classes。使用这些 classes,我们可以使用一种方法根据对象所属的 class 以不同方式操作对象。

下面是一个简单的 class 及其运作方式:

  1. Class包含数字,
  2. 要打印的 class 值,如 1k、2k、100k、1M,
  3. 可以对数值进行操作。

-- 让我们调用 class my_numbers

现在我们将定义 class 构造函数:

 my_numbers = function(x) structure(x, class = c('my_numbers', 'numeric'))

请注意,我们添加了 class 'numeric'。即 class my_numbers 从数字 class

继承

我们可以创建上述class的对象如下:

b <- my_numbers(c(100, 2000, 23455, 24567654, 2345323))
b 
[1]      100     2000    23455 24567654  2345323
attr(,"class")
[1] "my_numbers" "numeric" 

没有发生什么特别的事情。仅向矢量添加了 class 的属性。您可以通过调用 c(b)

轻松 remove/strip 关闭属性
c(b)
[1]      100     2000    23455 24567654  2345323

向量 b 只是一个普通的数字向量。

请注意,class 属性可以通过以下任何方式添加(任何更多方式):

 class(b) <- c('my_numbers', 'numeric')
 attr(b, 'class') <- c('my_numbers', 'numeric')
 attributes(b) <- list(class = c('my_numbers', 'numeric'))

神奇在哪里?

我会用递归写一个简单的函数。不用担心功能实现。我们将仅以它为例。

my_numbers_print = function(x, ..., digs=2,  d = 1,  L =   c('', 'K', 'M', 'B', 'T')){
  ifelse(abs(x) >= 1000, Recall(x/1000, d = d + 1),
         sprintf(paste0('%.',digs,'f%s'), x, L[d]))
}

my_numbers_print(b)
[1] "100.00" "2.00K"  "23.45K" "24.57M" "2.35M" 

仍然没有魔法。那是在 b.

上调用的正常函数

我们可以编写另一个名为 print.my_numbers 的函数,即 method.class_name(注意我添加了参数 quote = FALSE

,而不是调用函数 my_numbers_print
print.my_numbers = function(x, ..., quote = FALSE){
   print(my_numbers_print(x), quote = quote)
 }
   
 b
[1] 100.00 2.00K  23.45K 24.57M 2.35M 

现在b已经打印好了。我们仍然可以在 b

上做数学运算
 b^2
 [1] 10.00K  4.00M   550.14M 603.57T 5.50T 

我们可以将 b 添加到数据框吗?

data.frame(b)
         b
1      100
2     2000
3    23455
4 24567654
5  2345323 

b 恢复为数字而不是保持其 class。那是因为我们需要改变另一个功能。即 formats 函数。

理想情况下,执行此操作的正确方法是先创建格式函数,然后再创建打印函数。 (变得太长了)


总结:一切都放在一起

# Create a my_numbers class definition function
my_numbers = function(x) structure(x, class = c('my_numbers', 'numeric'))

# format the numbers
format.my_numbers =  function(x,...,digs =1, d = 1,  L =   c('', 'K', 'M', 'B', 'T')){
      ifelse(abs(x) >= 1000, Recall(x/1000, d = d + 1),
         sprintf(paste0('%.',digs,'f%s'), x, L[d]))
}

#printing the numbers
print.my_numbers = function(x, ...) print(format(x), quote = FALSE)

# ensure class is maintained after extraction to allow for sort/order etc
'[.my_numbers' = function(x, ..., drop = FALSE)  my_numbers(NextMethod('['))


b <- my_numbers(c(2000, 100, 20, 23455, 24567654, 2345323))

data.frame(x = sort(-b) / 2)                     
   
       x
1 -12.3M
2  -1.2M
3 -11.7K
4  -1.0K
5  -50.0
6  -10.0