如何在我的R软件包中自定义打印/显示变量(带有自定义类)

发布于 2025-01-24 08:55:36 字数 1772 浏览 0 评论 0 原文

许多R功能以特殊方式返回将对象打印到控制台的对象。例如, t_results = t.t.test(c(1,2,3),c(1,2,4))将把 list 分配给 t_results 变量,但是当我在控制台中输入此变量,或称其为 print(t_results) show(t_results),它会打印一些普通文本信息(例如 welch两个示例t检验... 等),而不是返回实际的 list 。 (这是一个基本r函数,但我也看到了许多自定义用户r软件包中实现的。)

我的问题是:我该如何处理我自己的自定义R软件包中创建的对象?我已经阅读了几个相关的问题和答案(例如, this this this ),哪个确实给出了一个一般的想法(使用 setMethod 我的自定义类),但没有一个使我明确我需要什么以自定义的R软件包使其正常工作。我也找不到有关此事的任何官方文件或教程。

为了举一个我想做的示例,这是我的假设R软件包中的一个非常简单的功能,该功能只需返回一个小 data.frame (我添加了任意类名称, '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)
}

我想打印/显示的(例如

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

:将 my_main_function()分配给变量, print s/ show show s show 该变量,它将通过 my_print_function( ))?

Many R functions return objects that are printed to the console in a special manner. For instance, t_results = t.test(c(1,2,3), c(1,2,4)) will assign a list to the t_results variable, but when I enter this variable in the console, or call it as print(t_results) or show(t_results), it prints some plain text information (such as Welch Two Sample t-test... etc.) instead of returning the actual list. (This is a base R function, but I've seen this implemented in many custom user R packages just as well.)

My question is: how do I do this for objects created in my own custom R package? I've read several related questions and answers (e.g., this, this, and this), which do give a general idea (using setMethod for my custom classes), but none of them makes it clear to me what exactly I need to do to make it work properly in a custom R package. I also cannot find any official documentation or tutorial on the matter.

To give an example of what I want to do, here is a very simple function from my hypothetical R package, which simply return a small data.frame (with an arbitrary class name I add to it, here '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)
}

I would like to have this printed/shown e.g. like this:

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

What exactly has to be done to make this work for my R package (i.e., that when someone installs my R package, assigns the my_main_function() results to a variable, and prints/shows that variable, it would be done via my_print_function())?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

旧人九事 2025-01-31 08:55:36

这是一个小解释。添加@NYA发布的惊人答案:

首先,您正在处理S3课程。有了这些类,我们可以使用一种方法来操纵对象,具体取决于对象所属的类。

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

  1. 类包含数字,
  2. 类似于1K,2K,100K,1M的类值,
  3. 可以通过数值操纵值。

- 让我们调用类 my_numbers

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

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

请注意,我们添加了类“数字”。即类 my_numbers 从数字类继承

我们可以创建上述类的对象,如下所示:

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

没有什么特别的。仅将类的属性添加到向量。 来轻松删除/剥离属性

c(b)
[1]      100     2000    23455 24567654  2345323

您可以通过调用 c(b) vector 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 上称为正常功能。

而不是调用函数 my_numbers_print 我们可以用名称 print.my_numbers ie method.class_name (注意我添加了parameter method> method> method> print.my_numbers IE ie ie ie > quote = false

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^2
 [1] 10.00K  4.00M   550.14M 603.57T 5.50T 

添加数学

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

现在 功能。

另一个


因为我们需要更改

# 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

Here is a small explanation. Adding to the amazing answer posted by @nya:

First, you are dealing with S3 classes. With these classes, we can have one method manipulating the objects differently depending on the class the object belongs to.

Below is a simple class and how it operates:

  1. Class contains numbers,
  2. The class values to be printed like 1k, 2k, 100k, 1M,
  3. The values can be manipulated numerically.

-- Lets call the class my_numbers

Now we will define the class constructor:

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

Note that we added the class 'numeric'. ie the class my_numbers INHERITS from numeric class

We can create an object of the said class as follows:

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

Nothing special has happened. Only an attribute of class has been added to the vector. You can easily remove/strip off the attribute by calling c(b)

c(b)
[1]      100     2000    23455 24567654  2345323

vector b is just a normal vector of numbers.

Note that the class attribute could have been added by any of the following (any many more ways):

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

Where is the magic?

I will write a simple function with recursion. Don't worry about the function implementation. We will just use it as an example.

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" 

There is no magic still. Thats the normal function called on b.

Instead of calling the function my_numbers_print we could write another function with the name print.my_numbers ie method.class_name (Note I added the parameter quote = FALSE

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 

Now b has been printed nicely. We can still do math on b

 b^2
 [1] 10.00K  4.00M   550.14M 603.57T 5.50T 

Can we add b to a dataframe?

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

b reverts back to numeric instead of maintaining its class. That is because we need to change another function. ie the formats function.

Ideally, the correct way to do this is to create a format function and then the print function. (Becoming too long)


Summary : Everything Put Together

# 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
遗弃M 2025-01-31 08:55:36

使用特定功能的最简单方法是将其设置为S3通用。

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

请注意,由于您保留 data.frame line class(my_df)= c(class(my_df),'my_df_class') print() 将显示data.frame的打印。

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

您可以使用 print.my_df_class(),也可以修改 my_main_function()类分配。

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 ,而无需在末尾进行类规范,以获得特定于类的响应。

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

The easiest way to use a specific function for a class is to set it as an S3 generic.

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

Note that because you retain the data.frame class on line class(my_df) = c(class(my_df), 'my_df_class'), the print() will show the printing of the data.frame.

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

You can either use print.my_df_class(), or modify the my_main_function() class assignment.

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)
}

Then you can use print without the class specification at the end to get a class-specific response.

print(my_main_function())
# My results: y2 z2
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文