如何在我的 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()
结果分配给一个变量,并且 print
s/ 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 及其运作方式:
- Class包含数字,
- 要打印的 class 值,如 1k、2k、100k、1M,
- 可以对数值进行操作。
-- 让我们调用 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
许多 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 包中创建的对象执行此操作?我已经阅读了几个相关的问题和答案(例如,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()
结果分配给一个变量,并且 print
s/ 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 及其运作方式:
- Class包含数字,
- 要打印的 class 值,如 1k、2k、100k、1M,
- 可以对数值进行操作。
-- 让我们调用 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)
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