【问题标题】:How to format a number as percentage in R?如何在R中将数字格式化为百分比?
【发布时间】:2011-11-01 00:42:00
【问题描述】:

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

【问题讨论】:

  • sprintf 似乎是邮件列表中最受欢迎的解决方案,我还没有看到更好的解决方案。无论如何调用任何内置函数都不会简单得多,对吧?
  • 在我看来,sprintf 非常适合同时也是程序员的 R 编码器子集。我一生中写过很多代码,包括 COBOL(不寒而栗)和 fortran(显示我的年龄)。但我不认为sprintf 格式化规则很明显(翻译:WTF?)。当然,专用包装器必须比 sprintf 更容易调用,例如:format_percent(x=0.12345, digits=2)
  • @hircus 我认为这很常见,值得拥有自己的短咖喱函数。这在 Sweave 中尤为突出,其中 \Sexpr{sprintf(%1.2f%%",myvar)} 比 \Sexpr{pct(myvar)} 或任何更短的函数都丑陋得多。
  • 学习使用适当的工具不是我们应该期望用户努力的吗?我的意思是,学习使用sprintf() 并不比找出包foo 包含format_percent() 更费时间。如果用户不想格式化为百分比而是其他类似的格式会发生什么?他们需要找到另一个包装器。从长远来看,学习基础工具将是有益的。
  • 有一个小问题,% 是 LaTeX 中的注释字符,这是 R 的“默认”报告格式。因此,虽然它可能对标记图形有用,但必须小心如果格式化的数字要被编织。

标签: r formatting


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    甚至以后:

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

    【讨论】:

    • 是的,这可行,并且是我在问题中提供的解决方法的更通用版本。但我真正的问题是这是否存在于基础 R 中。
    • 列出百分比中对我有用,但在统计或绘图命令中将“x”替换为“percent(x)”会产生错误消息。
    • @rolando2 我的答案和 krlmlr 的答案都返回字符向量作为输出,而不是数字。它们用于格式化轴标签等。也许您只想乘以 100?
    • 截至 2020 年 scales 版本。 1.1.0 手册告诉:percent() 已退休;请改用label_percent()不适合数字格式。所以手卷解决方案仍然是相关的
    • @DzimitryM 为什么label_percent() 不适合数字格式?
    【解决方案3】:

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

    【讨论】:

    • 不适用于负数。 percent(-0.1) 产生 NaN%
    • @akhmed:这已经报告过了,有一个修复程序,但有待审查:github.com/hadley/scales/issues/50。请注意,它似乎适用于多个负数:scales::percent(c(-0.1, -0.2))
    • 感谢您的链接!我不确定这是功能还是错误。对于多个数字,它有时有效,有时无效。说,scales::percent(c(-0.1,-0.1,-0.1)) 产生 "NaN%" "NaN%" "NaN%" 但你的例子确实有效。供其他人参考,截至scales_0.2.4,该错误尚未修复。此外,截至今天,修复它的相应拉取请求尚未合并到主分支中。
    【解决方案4】:

    我对这些答案的速度进行了一些基准测试,并且很惊讶地看到 percentscales 包中受到如此吹捧,因为它的迟缓。我想优点是它的自动检测器可以正确格式化,但是如果你知道你的数据是什么样的,那么显然可以避免。

    以下是尝试将 (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()
    

    【讨论】:

      【解决方案5】:
      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)
      }
      

      【讨论】:

        【解决方案6】:

        查看formattable 包中的percent 函数:

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

        【讨论】:

        • +1,这允许指定要包含多少位数字,前两个答案中的scales::percent 没有。
        • +1,尽管滚动你自己的函数很容易,但允许选择位数真的很有用。
        【解决方案7】:

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

        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%”,而不是进行计算......
        【解决方案8】:

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

        scales::percent(m)
        

        【讨论】:

        • 如何给出位数的准确性?
        【解决方案9】:

        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%
        

        看起来像往常一样整洁。

        【讨论】:

        • 确实很整洁。但是考虑到我们重视整洁,我认为可以将库称为“scales”(就像您对“tidyverse”所做的那样)并省略“::”运算符,这会让像我这样的新手感到困惑。
        • 是的,我认为你是对的,我已经更新了答案。
        【解决方案10】:

        基础 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%
        

        最后,为了我自己的自私参考,既然我们在谈论格式化,这就是我用base R做逗号的方式:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-06-19
          • 1970-01-01
          • 2019-12-28
          • 2015-07-17
          • 2018-06-12
          • 1970-01-01
          • 2013-08-26
          • 2020-10-29
          相关资源
          最近更新 更多