如何在R中将数字格式化为百分比?

188
作为一个刚接触 R 语言的新手,有一件事情曾经令我感到困惑:如何将数字格式化为百分数并进行打印。
例如,将 0.12345 显示为 12.345%。我知道一些解决方法,但似乎都不太适合新手。例如:
set.seed(1)
m <- runif(5)

paste(round(100*m, 2), "%", sep="")
[1] "26.55%" "37.21%" "57.29%" "90.82%" "20.17%"

sprintf("%1.2f%%", 100*m)
[1] "26.55%" "37.21%" "57.29%" "90.82%" "20.17%"
问题: 是否有一个基本的R函数可以做到这一点?或者,是否有一个广泛使用的软件包提供了一个方便的包装器?
尽管在?format?formatC?prettyNum中搜索了类似这样的内容,但我仍然没有在基本的R中找到一个合适的便捷包装器。 ??"percent"没有产生任何有用的结果。 library(sos); findFn("format percent")返回1250个命中结果 - 因此也不是有用的。 ggplot2有一个函数percent,但它不能控制四舍五入的精度。

7
在邮件列表中,“sprintf”似乎是最受欢迎的解决方案,我也没有看到更好的解决方案。无论如何,任何内置的函数调用起来都不会更简单,对吧? - michel-slm
1
在我看来,sprintf 对于那些同时也是程序员的 R 程序员而言完全可以胜任。我一生中写过很多代码,包括 COBOL(令人不寒而栗)和 FORTRAN(显露出我的年龄)。但我不认为 sprintf 的格式规则是显而易见的(译者:WTF?)。当然,相对于 sprintf,一个专用的包装器更容易调用,例如:format_percent(x=0.12345, digits=2) - Andrie
@hircus 我认为这是很常见的,值得拥有自己的短柄函数。这在 Sweave 中尤其成问题,其中 \Sexpr{sprintf(%1.2f%%",myvar)} 比 \Sexpr{pct(myvar)} 或其他更短的函数难看得多。 - Ari B. Friedman
2
学习使用适当的工具难道不是我们应该期望用户努力追求的吗?我的意思是,学习使用sprintf()几乎不比找出包foo中是否包含format_percent()更耗时。如果用户不想将格式设置为百分比,而是想要类似的其他格式,那么会发生什么?他们需要找到另一个封装器。从长远来看,学习基本工具将是有益的。 - Gavin Simpson
1
在LaTeX中,“%”是注释字符,这是R的“默认”报告格式,因此存在一个小问题。因此,尽管它可能对标记图表有用,但如果要Sweaved格式化数字,则必须小心处理。 - James
显示剩余3条评论
10个回答

165

更晚一点:

正如@DzimitryM指出的那样,percent()已经被“废弃”,支持使用同义词label_percent(),它是旧的percent_format()函数。

label_percent()返回一个函数,因此要使用它,您需要一个额外的括号。

library(scales)
x <- c(-1, 0, 0.1, 0.555555, 1, 100)
label_percent()(x)
## [1] "-100%"   "0%"      "10%"     "56%"     "100%"    "10 000%"

在第一组括号内添加参数以自定义此内容。

label_percent(big.mark = ",", suffix = " percent")(x)
## [1] "-100 percent"   "0 percent"      "10 percent"    
## [4] "56 percent"     "100 percent"    "10,000 percent"

数年后的更新:

现在,scales软件包中有一个percent函数,如krlmlr的答案所述。请使用该函数,而不是我的手写解决方案。


尝试类似以下代码:

percent <- function(x, digits = 2, format = "f", ...) {
  paste0(formatC(100 * x, format = format, digits = digits, ...), "%")
}

例如使用:

x <- c(-1, 0, 0.1, 0.555555, 1, 100)
percent(x)

(如果您喜欢,可以将格式从 "f" 更改为 "g"。)


2
是的,这个方法可行,并且是我在问题中提供的解决方法的一个稍微更通用的版本。但我的真正问题是,这个方法是否存在于基本的R语言中。 - Andrie
列出百分比方面对我有效,但是在统计或绘图命令中用"percent(x)"替换"x"会产生错误消息。 - rolando2
@rolando2 我的回答和krlmlr的回答都返回字符向量作为输出,而不是数字。它们用于格式化轴标签等内容。也许你只想乘以100? - Richie Cotton
截至2020年,scales ver. 1.1.0手册指出:percent()已经被弃用,请使用label_percent()代替,但它不适用于数字格式化。因此,手动解决方案仍然是相关的。 - DzimitryM
@DzimitryM 为什么label_percent()不适合数字格式化? - Michael A

82

看看 scales 包。我想它曾经是 ggplot2 的一部分。

library('scales')
percent((1:10) / 100)
#  [1] "1%"  "2%"  "3%"  "4%"  "5%"  "6%"  "7%"  "8%"  "9%"  "10%"

内置的精度检测逻辑对于大多数情况来说应该已经足够好了。

percent((1:10) / 1000)
#  [1] "0.1%" "0.2%" "0.3%" "0.4%" "0.5%" "0.6%" "0.7%" "0.8%" "0.9%" "1.0%"
percent((1:10) / 100000)
#  [1] "0.001%" "0.002%" "0.003%" "0.004%" "0.005%" "0.006%" "0.007%" "0.008%"
#  [9] "0.009%" "0.010%"
percent(sqrt(seq(0, 1, by=0.1)))
#  [1] "0%"   "32%"  "45%"  "55%"  "63%"  "71%"  "77%"  "84%"  "89%"  "95%" 
# [11] "100%"
percent(seq(0, 0.1, by=0.01) ** 2)
#  [1] "0.00%" "0.01%" "0.04%" "0.09%" "0.16%" "0.25%" "0.36%" "0.49%" "0.64%"
# [10] "0.81%" "1.00%"

2
不适用于负数。percent(-0.1) 会产生 NaN% - akhmed
1
@akhmed:这个问题已经被报告了,有一个修复方案可用,但正在等待审核:https://github.com/hadley/scales/issues/50。请注意,它似乎适用于多个负数:`scales::percent(c(-0.1, -0.2))`。 - krlmlr
谢谢提供这个链接!我不确定它是一个功能还是一个错误。对于多个数字,有时它可以工作,有时则不能。例如,scales::percent(c(-0.1,-0.1,-0.1))会产生 "NaN%" "NaN%" "NaN%" 但是你的示例确实有效。为了其他人的参考,该漏洞截至scales_0.2.4仍未修复。另外,截至今天,相应的拉取请求尚未合并到主分支中。 - akhmed

40

查看 formattable 包中的 percent 函数:

library(formattable)
x <- c(0.23, 0.95, 0.3)
percent(x)
[1] 23.00% 95.00% 30.00%

6
+1表示可以指定要包含的数字位数,而前两个答案中的scales::percent不能。 - Sam Firke
3
即使自己编写函数相当容易,但允许选择数字位数确实非常有用。 - Gang Su

29

基本R语言

我更喜欢使用基本R语言中提供的sprintf函数。

sprintf("%0.1f%%", .7293827 * 100)
[1] "72.9%"

我特别喜欢sprintf,因为你还可以插入字符串。

sprintf("People who prefer %s over %s: %0.4f%%", 
        "Coke Classic", 
        "New Coke",
        .999999 * 100)
[1] "People who prefer Coke Classic over New Coke: 99.9999%"

使用sprintf在处理数据库配置等内容时非常有用;只需读取yaml文件,然后使用sprintf填充模板,而不需要大量使用paste0

更长的示例

当您有大量文本和许多值需要聚合时,此模式对于rmarkdown报告特别有用。

设置/聚合:

library(data.table) ## for aggregate

approval <- data.table(year = trunc(time(presidents)), 
                       pct = as.numeric(presidents) / 100,
                       president = c(rep("Truman", 32),
                                     rep("Eisenhower", 32),
                                     rep("Kennedy", 12),
                                     rep("Johnson", 20),
                                     rep("Nixon", 24)))
approval_agg <- approval[i = TRUE,
                         j = .(ave_approval = mean(pct, na.rm=T)), 
                         by = president]
approval_agg
#     president ave_approval
# 1:     Truman    0.4700000
# 2: Eisenhower    0.6484375
# 3:    Kennedy    0.7075000
# 4:    Johnson    0.5550000
# 5:      Nixon    0.4859091

使用 sprintf 与文本和数字向量,仅输出换行符到 cat
approval_agg[, sprintf("%s approval rating: %0.1f%%",
                       president,
                       ave_approval * 100)] %>% 
  cat(., sep = "\n")
# 
# Truman approval rating: 47.0%
# Eisenhower approval rating: 64.8%
# Kennedy approval rating: 70.8%
# Johnson approval rating: 55.5%
# Nixon approval rating: 48.6%

最后,为了方便我自己的参考,既然我们在讨论格式,这是我用基本R处理逗号的方法:

30298.78 %>% round %>% prettyNum(big.mark = ",")
[1] "30,299"

11

我对这些答案进行了一些速度基准测试,惊讶地发现比例尺中如此被吹捧的百分比表现如此低劣。我想优点在于其自动检测适当格式的探测器,但如果你知道你的数据长什么样,显然应该避免使用它。

以下是将100,000个介于0和1之间的百分比格式化为2位数百分比的结果:

library(microbenchmark)
x = runif(1e5)
microbenchmark(times = 100L, andrie1(), andrie2(), richie(), krlmlr())
# Unit: milliseconds
#   expr       min        lq      mean    median        uq       max
# 1 andrie1()  91.08811  95.51952  99.54368  97.39548 102.75665 126.54918 #paste(round())
# 2 andrie2()  43.75678  45.56284  49.20919  47.42042  51.23483  69.10444 #sprintf()
# 3  richie()  79.35606  82.30379  87.29905  84.47743  90.38425 112.22889 #paste(formatC())
# 4  krlmlr() 243.19699 267.74435 304.16202 280.28878 311.41978 534.55904 #scales::percent()

如果我们想要添加百分号,那么sprintf显然是最好的选择。另一方面,如果我们只想要将数字乘以一个因子并四舍五入(从比例转换为百分比但不需要“%”符号),那么round()是最快的:

# Unit: milliseconds
#        expr      min        lq      mean    median        uq       max
# 1 andrie1()  4.43576  4.514349  4.583014  4.547911  4.640199  4.939159 # round()
# 2 andrie2() 42.26545 42.462963 43.229595 42.960719 43.642912 47.344517 # sprintf()
# 3  richie() 64.99420 65.872592 67.480730 66.731730 67.950658 96.722691 # formatC()

10

tidyverse 的版本如下:

> library(dplyr)
> library(scales)

> set.seed(1)
> m <- runif(5)
> dt <- as.data.frame(m)

> dt %>% mutate(perc=percent(m,accuracy=0.001))
          m    perc
1 0.2655087 26.551%
2 0.3721239 37.212%
3 0.5728534 57.285%
4 0.9082078 90.821%
5 0.2016819 20.168%

看起来照常整洁。


2
确实整洁。但是考虑到我们重视整洁,我认为可以将该库称为“scales”(就像您使用“tidyverse”一样),并省略“::”运算符,因为这对像我这样的新手来说很容易混淆。 - W Barker
是的,我认为你是正确的,我已经更新了答案。 - Giacomo

8
您可以仅使用scales包进行此操作(无需使用require或library加载它)。
scales::percent(m)

5
如何准确地给出数字的位数? - Elmex80s

6

这是我定义新函数的解决方案(主要是为了使用Curry和Compose :-)):

library(roxygen)
printpct <- Compose(function(x) x*100, Curry(sprintf,fmt="%1.2f%%"))

0
try this~

data_format <- function(data,digit=2,type='%'){
if(type=='d') {
    type = 'f';
    digit = 0;
}
switch(type,
    '%' = {format <- paste("%.", digit, "f%", type, sep='');num <- 100},
    'f' = {format <- paste("%.", digit, type, sep='');num <- 1},
    cat(type, "is not a recognized type\n")
)
sprintf(format, num * data)
}

0

这个函数可以按列将数据转换为百分比

percent.colmns = function(base, columnas = 1:ncol(base), filas = 1:nrow(base)){
    base2 = base
    for(j in columnas){
        suma.c = sum(base[,j])
        for(i in filas){
            base2[i,j] = base[i,j]*100/suma.c
        }
    }
    return(base2)
}

基本算术是矢量化的——内部for循环效率低下且不必要。可以用base2[, j] = base[ , j] * 100 / suma.c替换。值得注意的是,这并不完全是问题的答案...问题是如何将类似于“0.5”的格式转换为“50.0%”,而不是进行计算... - Gregor Thomas

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