如何在R中编写一个包装函数或类来将数字格式化为百分比、货币等形式?

11
在一个之前的问题中,我问是否存在基于base R的便捷包装器可以将数字格式化为百分比。
这引发了三个回复:
  1. 可能没有。
  2. 这样的包装器太窄而无法使用。更好的方法是让使用者学习如何使用现有工具,例如 sprintf,可以高度灵活地格式化数字。
  3. 无论如何,这样的包装器都有问题,因为你失去了对对象执行计算的能力。
尽管如此,在我看来,sprintf函数对于R初学者来说略微过于模糊(除非他们来自C背景)。也许更好的解决方案是修改formatprettyNum以具有添加前缀和后缀的选项,这样您就可以轻松创建百分比、货币、角度等。
问题: 您会如何设计一个函数、类或一组函数,以优雅地处理格式化数字为百分比、货币、角度等?
4个回答

10

我会保持简单。对于大多数基本格式需求,format()通常很有用。我会使用一个简单的包装器扩展它,允许任意的前缀后缀字符串。以下是一个简单的版本:

formatVal <- function(x, prefix = "", suffix = "", sep = "", collapse = NULL,
                      ...) {
    x <- format(x, ...)
    x <- paste(prefix, x, suffix, sep = sep, collapse = collapse)
    x
}
如果我真正做这件事,我可能不会在formatVal()的定义中包含collapse参数,而是将其从...中处理出来,但为了说明,我保持以上函数简单。
使用:
set.seed(1)
m <- runif(5)

一些使用的简单示例

> formatVal(m*100, suffix = "%")
[1] "26.55087%" "37.21239%" "57.28534%" "90.82078%" "20.16819%"
> formatVal(m*100, suffix = "%", digits = 2)
[1] "27%" "37%" "57%" "91%" "20%"
> formatVal(m*100, suffix = "%", digits = 2, nsmall = 2)
[1] "26.55%" "37.21%" "57.29%" "90.82%" "20.17%"
> formatVal(m, prefix = "£")
[1] "£0.2655087" "£0.3721239" "£0.5728534" "£0.9082078" "£0.2016819"
> formatVal(m, prefix = "£", digits = 1)
[1] "£0.3" "£0.4" "£0.6" "£0.9" "£0.2"
> formatVal(m, prefix = "£", digits = 1, nsmall = 2)
[1] "£0.27" "£0.37" "£0.57" "£0.91" "£0.20"

我认为设置 sep=="" 是合适的,也许是必要的,因为否则默认值将是 sep="_",而我想不出这种情况何时适用。此外,在实际操作中,最好为前缀和后缀分别设置一个单独的 sep(前缀通常为空格,后缀通常为空字符串)。 - Andrie

8
print.formatted <- function(x)
{
   print(paste(attr(x,"prefix"), sprintf(x*attr(x,"scaleFactor"),fmt=paste("%.",attr(x,"precision"),"f",sep="")), attr(x,"suffix"), sep=""))
}

as.percent <- function(x,precision=3)
{
  class(x) <- c(class(x),"formatted")
  attr(x,"scaleFactor")<-100
  attr(x,"prefix")<-""
  attr(x,"suffix")<-"%"
  attr(x,"precision")<-precision
  return(x)
}

as.currency <- function(x,prefix="£")
{
  class(x) <- c(class(x),"formatted")
  attr(x,"scaleFactor")<-1
  attr(x,"prefix")<-prefix
  attr(x,"suffix")<-""
  attr(x,"precision")<-2
  return(x)
}

as.percent(runif(3))
[1] "21.585%" "12.396%" "37.744%"

x <- as.currency(rnorm(3,500,100))
x
[1] "£381.93" "£339.49" "£521.74"
2*x
[1] "£763.86"  "£678.98"  "£1043.48"

一个不足之处是你在 as.percent() 中硬编码了比例因子。如果我已经有了百分数,但只想要添加 "%",该怎么办?另一个问题是你没有得到格式化的字符串,它们只是被打印出来而已。 - Gavin Simpson
@Gavin Simpson 最好将数字内部存储为基本数字,以便可以进行计算。在这种情况下,只需使用 as.percent(yourPercs/100) 即可。您可以使用 y <- print(x) 存储打印的字符串。我同意,使用具有设置合理参数的便利函数的通用 as.formatted 方法可能是更好的方法。 - James
我不同意,如果目的是格式化数字,那么如果函数仅按照要求格式化给定的输入,那么代码更加清晰。为什么我必须将我的完全可以接受的百分比除以1来适应您将百分比存储为0、1比例的想法呢?;-) 从技术上讲,有人可能会争论您的函数不符合@Andrie所设定的任务要求,因为它没有在任何地方格式化数字并返回它们。print()真正(在R的精神上)应该给出一个打印表示;您的函数确实这样做了,但它在print()时才格式化输入。 - Gavin Simpson
如果没有存储百分比的标准,那么你怎么知道输入是10%还是1000%?我想我对问题的解释不同,但我认为为了完全灵活的方法,用户可能也需要学习如何使用pastesprintfprettyNum等工具。 - James

4

我认为可以通过属性来完成这个操作,例如让 v <- 3.4。如果是英镑,我们可以使用以下内容:

attributes(v)<-list(style = "descriptor", type = "currency", category = "pound")

如果是百分比:

attributes(v)<-list(style = "descriptor", type = "proportion", category = "percentage")

然后需要一个特殊的打印方法。还可以加入翻译方法,例如将英镑转换为美元(磅到美元),厘米到英寸等。

descriptor 实质上是指示给定数字进行特殊处理的标志。以后可能会扩展到文本字符串,如地址和名称。对于其他数字,例如电话号码,可能会有特殊的分解方式,例如国家代码、区域代码,一直到扩展。

这样的软件包可能类似于数据类型的ggplot - 用于在类型内部存储、转换和打印事物的特殊方法?

这样的系统可以确保在乘以值时维度是正确的。在许多应用中,这具有实际效用。

据我所知,R 中唯一广泛处理单位的方式是字节(字节、KB、MB 等)和时间(小时、秒等)。即使如此,在处理时,虽然简单,但并不明显 - 我仍然必须告诉print要使用的单位。例如,如果我想将对象的大小以KB打印出来,我不能简单地计算object.size(v)/1024 - 输出会报告字节的一小部分,而不是KB;我必须使用print(object.size(v), units = "K")


2
ggplot2拥有一堆函数来格式化常见的特定情况。虽然这些函数很理想,但有两个问题:它们并不够通用,而且您不应该必须加载ggplot2(以及它的所有依赖项)才能使用此类函数。您可以尝试联系Hadley,将签名更改为传递更多内容以进行格式化,并将它们移动到较低级别的软件包中(例如plyr,或者他们自己的软件包ggtools?)。

好的建议。希望这已经成为“ggplot2”重写的一部分。例如,我知道意图是将“ggplot2”分成多个包,以便更轻松地单独重用它们。 - Andrie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接