Como formatar um número como porcentagem em R?

135

Uma das coisas que costumava me deixar perplexo como um novato em R era como formatar um número como uma porcentagem para impressão.

Por exemplo, exiba 0.12345como 12.345%. Eu tenho várias soluções alternativas para isso, mas nenhuma delas parece ser "newby friendly". Por exemplo:

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

Pergunta: Existe uma função R básica para fazer isso? Como alternativa, existe um pacote amplamente usado que fornece um invólucro conveniente?


Apesar de procurar algo assim em ?format, ?formatCe ?prettyNum, ainda não encontrei um invólucro adequadamente conveniente na base R. ??"percent"não produziu nada de útil. library(sos); findFn("format percent")retorna 1250 hits - mais uma vez não é útil. ggplot2tem uma função, percentmas isso não dá controle sobre a precisão do arredondamento.

Andrie
fonte
5
sprintfparece ser a solução favorita nas listas de discussão e não vi nenhuma solução melhor. Qualquer função embutida não será muito mais simples de chamar, certo?
Michel-slm
1
Na minha opinião, sprintfé perfeitamente adequado para esse subconjunto de codificadores R que também são programadores. Eu codifiquei muito na minha vida, incluindo COBOL (arrepio) e fortran (mostra minha idade). Mas não considero as sprintfregras de formatação óbvias (tradução: WTF?). E, claro, um invólucro dedicado deve ser mais fácil de chamar do que sprintf, por exemplo:format_percent(x=0.12345, digits=2)
Andrie
@ hircus Eu acho que é comum o suficiente merecer sua própria função de curry curta. É particularmente um problema com o Sweave, onde \ Sexpr {sprintf (% 1.2f %% ", myvar)} é muito mais feio que \ Sexpr {pct (myvar)} ou seja qual for a função mais curta.
Ari B. Friedman
2
Aprender a usar as ferramentas apropriadas não é algo que devemos esperar que os usuários busquem? Quero dizer, aprendendo a usar sprintf()dificilmente é mais demorado do que descobrir que pacote foo contém format_percent(). O que acontece se o usuário não quiser formatar como porcentagem, mas outra coisa semelhante? Eles precisam encontrar outro invólucro. A longo prazo, o aprendizado das ferramentas básicas será benéfico.
Gavin Simpson
1
Há um pequeno problema, pois %é o caractere de comentário no LaTeX, que é o formato de relatório "padrão" para R. Portanto, embora possa ser útil para rotular gráficos, é preciso ter cuidado para que o número formatado seja Sweaved.
James

Respostas:

118

Mais tarde:

Como apontado por @DzimitryM, percent()foi "aposentado" em favor de label_percent(), que é um sinônimo para a percent_format()função antiga .

label_percent() retorna uma função; portanto, para usá-la, você precisa de um par extra de parênteses.

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

Personalize isso adicionando argumentos dentro do primeiro conjunto de parênteses.

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

Uma atualização, vários anos depois:

Atualmente, há uma percentfunção no scalespacote, conforme documentado na resposta do krlmlr. Use isso em vez da minha solução enrolada à mão.


Tente algo como

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

Com o uso, por exemplo,

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

(Se preferir, altere o formato de "f"para "g".)

Richie Cotton
fonte
2
Sim, isso funciona e é uma versão um pouco mais geral da solução alternativa fornecida na pergunta. Mas minha verdadeira pergunta é se isso existe na base R ou não.
Andrie
Funciona para mim na listagem de porcentagens, mas substituir "x" por "porcentagem (x)" em um comando estatístico ou gráfico produz uma mensagem de erro.
Roland2
@ rolando2 Tanto a resposta como a resposta do krlmlr retornam vetores de caracteres como saída, não números. Eles são para formatar rótulos de eixos e similares. Talvez você queira apenas multiplicar por 100?
Richie Cotton
A partir de 2020 scalesver. 1.1.0 manual diz: percent()está aposentado; use em label_percent()vez disso, o que não é adequado para a formatação de números . Para que a solução laminada à mão ainda seja relevante
DzimitryM
74

Confira o scalespacote. Costumava fazer parte ggplot2, eu acho.

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

A lógica interna para detectar a precisão deve funcionar bem o suficiente para a maioria dos casos.

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%"
krlmlr
fonte
2
Não funciona para números negativos. percent(-0.1)produzNaN%
akhmed
1
@akhmed: isso já foi relatado, uma correção está disponível, mas a revisão está pendente: github.com/hadley/scales/issues/50 . Note-se que ela parece funcionar por mais de um número negativo:scales::percent(c(-0.1, -0.2))
krlmlr
Obrigado pelo link! Eu não tinha certeza se é um recurso ou um bug. Para vários números, às vezes funciona e às vezes não. Digamos, scales::percent(c(-0.1,-0.1,-0.1))produz , "NaN%" "NaN%" "NaN%"mas o seu exemplo funciona. Para referência de outras pessoas, o bug ainda não foi corrigido a partir de scales_0.2.4. Além disso, a partir de hoje, a solicitação pull correspondente que a corrige ainda não foi mesclada na ramificação principal.
akhmed
34

Confira a percentfunção do formattablepacote:

library(formattable)
x <- c(0.23, 0.95, 0.3)
percent(x)
[1] 23.00% 95.00% 30.00%
Liliana Pacheco
fonte
4
+1, isso permite especificar quantos dígitos incluir, o que scales::percentnas duas primeiras respostas não.
Sam Firke
3
+1, mesmo que seja muito fácil rolar sua própria função, permitir a escolha do número de dígitos é realmente útil.
Gang Su
10

Fiz alguns testes comparativos para acelerar essas respostas e fiquei surpreso ao ver percento scalespacote tão elogiado, dada a sua lentidão. Imagino que a vantagem seja seu detector automático para uma formatação adequada, mas se você souber como seus dados parecem, parece claro que deve ser evitado.

Aqui estão os resultados da tentativa de formatar uma lista de 100.000 porcentagens em (0,1) para uma porcentagem em 2 dígitos:

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

Assim, sprintfsurge como um vencedor claro quando queremos adicionar um sinal de porcentagem. Por outro lado, se apenas queremos multiplicar o número e a ronda (passe de proporção para porcentagem sem "%", então round()é mais rápido:

# 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()
MichaelChirico
fonte
8

Você pode usar o pacote de escalas apenas para esta operação (sem carregá-lo com require ou library)

scales::percent(m)
בנימן הגלילי
fonte
1
Como fornecer a precisão do número de dígitos?
Elmex80s 25/03
6

Aqui está minha solução para definir uma nova função (principalmente para que eu possa brincar com Curry e Compose :-)):

library(roxygen)
printpct <- Compose(function(x) x*100, Curry(sprintf,fmt="%1.2f%%"))
Ari B. Friedman
fonte
3

Vendo como scalable::percentjá havia se mostrado mais lento e Liliana Pacheco oferecendo outra solução, fui em frente e tentei compará-lo com algumas das outras opções com base no exemplo de Michael:

library(microbenchmark)
library(scales)
library(formattable)

x<-runif(1e5)

lilip <- function() formattable::percent(x,2)
krlmlr <- function() scales::percent(x)
andrie1 <- function() paste0(round(x,4) * 100, '%')

microbenchmark(times=100L,lilip(), krlmlr(), andrie1())

Estes são os resultados que obtive:

Unit: microseconds
      expr        min          lq        mean      median          uq        max neval
   lilip()    194.562    373.7335    772.5663    889.7045    950.4035   1611.537   100
  krlmlr() 226270.845 237985.6560 260194.9269 251581.0235 280704.2320 373022.180   100
 andrie1()  87916.021  90437.4820  92791.8923  92636.8420  94448.7040 102543.252   100

Eu não tenho idéia, porém, por que o meu krlmlr()e andrie1()realizada de modo muito pior do que no exemplo de MichaelChirico. Alguma pista?

matt_jay
fonte
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)
}
voz leve
fonte
0

Esta função pode transformar os dados em porcentagens por colunas

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)
}
Edwin Torres
fonte
A aritmética básica é vetorizada - o loop for interno é ineficiente e desnecessário. Pode ser substituído por base2[, j] = base[ , j] * 100 / suma.c. Também vale a pena notar que essa não é exatamente uma resposta para a pergunta ... a questão é sobre formatar algo como 0.5"50,0%", não é sobre fazer um cálculo ...
Gregor Thomas
0

A tidyverseversão é esta:

> library(tidyverse)

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

> dt %>% mutate(perc=scales::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%

Parece arrumado, como de costume.

Giacomo
fonte