作为 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
但这无法控制舍入精度。
甚至后来:
正如 @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"
。)
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
包中的 formattable
函数:
library(formattable)
x <- c(0.23, 0.95, 0.3)
percent(x)
[1] 23.00% 95.00% 30.00%
我更喜欢使用
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"
我对这些答案进行了一些速度基准测试,并惊讶地发现
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()
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 包来执行此操作(无需使用 require 或库加载它)
scales::percent(m)
这是我定义新函数的解决方案(主要是为了我可以使用 Curry 和 Compose :-) ):
library(roxygen)
printpct <- Compose(function(x) x*100, Curry(sprintf,fmt="%1.2f%%"))
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)
}
此函数可以将数据按列转换为百分比
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)
}
这是一个轻量级的百分比类对象和所有关联的方法。
它与比例的不同之处在于,
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>