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

问题描述 投票:0回答:11

作为 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
但这无法控制舍入精度。

r formatting
11个回答
168
投票

甚至后来:

正如 @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"

几年后的更新:

这些天,在

percent
包中有一个
scales
函数,如 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"
。)


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%"

40
投票

查看

percent
包中的
formattable
函数:

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

30
投票

基础R

我更喜欢使用

sprintf
,它在基础 R 中可用。

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"

12
投票

我对这些答案进行了一些速度基准测试,并惊讶地发现

percent
在如此吹捧的
scales
包中,因为它的速度缓慢。我想它的优点是它的自动检测器可以正确格式化,但如果您知道您的数据是什么样子,那么似乎显然可以避免。

以下是尝试将 (0,1) 中 100,000 个百分比的列表格式化为 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%

看起来像平常一样整洁。


8
投票

您可以仅使用 scales 包来执行此操作(无需使用 require 或库加载它)

scales::percent(m)

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

0
投票

这是一个轻量级的百分比类对象和所有关联的方法。

它与比例的不同之处在于,

percent(1)
将返回“1%”,而
scales::percent(1)
将返回“100%”。如果需要的话,可以通过在
percent()
中删除除以 100 来轻松修改。

new_percent <- function(x){
  class(x) <- "percent"
  x
}
#' @export
percent <- function(x){
  if (!is.numeric(x)){
    stop("x must be a numeric vector")
  }
  new_percent(x / 100)
}
#' @export
as.character.percent <- function(x, ...){
  paste0(unclass(x) * 100, "%")
}
#' @export
format.percent <- function(x, symbol = "%", ...){
  paste0(format(unclass(x) * 100, ...), symbol)
}
#' @export
print.percent <- function(x, max = NULL, ...){
  out <- x
  N <- length(out)
  if (N == 0){
    print("percent(numeric(0))")
    return(invisible(x))
  }
  if (is.null(max)) {
    max <- getOption("max.print", 9999L)
  }
  suffix <- character()
  max <- min(max, N)
  if (max < N) {
    out <- out[seq_len(max)]
    suffix <- paste(" [ reached 'max' / getOption(\"max.print\") -- omitted",
                    N - max, "entries ]\n")
  }
  print(as.character(out), ...)
  cat(suffix)
  invisible(x)
}
#' @export
`[.percent` <- function(x, ..., drop = TRUE){
  cl <- oldClass(x)
  class(x) <- NULL
  out <- NextMethod("[")
  class(out) <- cl
  out
}
#' @export
unique.percent <- function(x, incomparables = FALSE,
                           fromLast = FALSE, nmax = NA, ...){
  cl <- oldClass(x)
  class(x) <- NULL
  out <- NextMethod("[")
  class(out) <- cl
  out
}
#' @export
rep.percent <- function(x, ...){
  x[rep(seq_along(x), ...)]
}
#' @export
rep_len.percent <- function(x, length.out){
  x[rep_len(seq_along(x), length.out)]
}
#' @export
Ops.percent <- function(e1, e2){
  math <- switch(.Generic,
                 `+` =,
                 `-` =,
                 `*` =,
                 `/` =,
                 `^` =,
                 `%%` =,
                 `%/%` = TRUE, FALSE)
  both_percent <- inherits(e1, "percent") && inherits(e2, "percent")
  if (!both_percent){
    e1 <- unclass(e1)
    e2 <- unclass(e2)
  }
  NextMethod(.Generic)
}
x <- seq(0, 1, 1e-6)
print_default <- getOption("max.print")
options(max.print = 20)
percent(x)
options(max.print = print_default)
 [1] "0%"       "1e-06%"   "2e-06%"   "3e-06%"   "4e-06%"   "5e-06%"   "6e-06%"   "7e-06%"  
 [9] "8e-06%"   "9e-06%"   "1e-05%"   "1.1e-05%" "1.2e-05%" "1.3e-05%" "1.4e-05%" "1.5e-05%"
[17] "1.6e-05%" "1.7e-05%" "1.8e-05%" "1.9e-05%"
 [ reached 'max' / getOption("max.print") -- omitted 999981 entries ]

通过这门课,我们可以做基本的数学,而

scales::percent

无法完成
10 * percent(50)
[1] 5
percent(10) + percent(20)
[1] "30%"

针对秤包的基准

bench::mark(percent(x), 
            scales::percent(x), 
            check = FALSE)
# A tibble: 2 × 13
  expression      min median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time result memory    
  <bch:expr>   <bch:> <bch:>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm> <list> <list>    
1 percent(x)   1.13ms 1.28ms   454.       7.63MB     2.00   227     1   500.13ms <NULL> <Rprofmem>
2 scales::per…  3.67s  3.67s     0.272  305.55MB     1.36     1     5      3.67s <NULL> <Rprofmem>
# ℹ 2 more variables: time <list>, gc <list>
© www.soinside.com 2019 - 2024. All rights reserved.