17

Is there a R function (or any package) allowing to format numbers (integer) using standard unit prefix (Kilo, Mega etc ...), so

10 -> 10
1000 -> 1K
0.01 - > 10m

etc ... I can do it myself but I would prefer to not reinvent the wheel.

IRTFM
  • 258,963
  • 21
  • 364
  • 487
mb14
  • 22,276
  • 7
  • 60
  • 102
  • `utils:::print.object_size` implements it for some binary units – James Jul 05 '12 at 08:58
  • I would just be careful about doing this w/o proofreading. For example, while things like 3.5 Mm are allowed by ISO, in standard usage everyone goes with 3.5e3 km . Since one never uses prefixes for dimensionless values, how are you planning to append the physical units themselves? – Carl Witthoft Jul 05 '12 at 11:52
  • I see your point. However what I'm doing is not "serious" and I don't have any unit. I'm just displaying value in a plot/table and I need every to fit it 3/4 characters. 10k is more compact than 1000 or 1e+03. – mb14 Jul 05 '12 at 13:46
  • 1
    `?scales::label_number_si`. – Axeman Aug 22 '20 at 18:03

5 Answers5

16
require(sitools)
f2si(80000)
 [1] "80 k"
f2si(8E12)
 [1] "8 T"

It seems to be very simplistic as it appends two spaces if no SI prefix is used:

f2si(80)
[1] "80  "

The function is easy to modify to include rounding. I also fixed the issue with appended spaces.

f2si2<-function (number,rounding=F) 
{
    lut <- c(1e-24, 1e-21, 1e-18, 1e-15, 1e-12, 1e-09, 1e-06, 
        0.001, 1, 1000, 1e+06, 1e+09, 1e+12, 1e+15, 1e+18, 1e+21, 
        1e+24)
    pre <- c("y", "z", "a", "f", "p", "n", "u", "m", "", "k", 
        "M", "G", "T", "P", "E", "Z", "Y")
    ix <- findInterval(number, lut)
    if (lut[ix]!=1) {
        if (rounding==T) {
         sistring <- paste(round(number/lut[ix]), pre[ix])
        }
        else {
         sistring <- paste(number/lut[ix], pre[ix])
        } 
    }
    else {
        sistring <- as.character(number)
    }
    return(sistring)
}

f2si2(12345)
 [1] "12.345 k"
f2si2(12345,T)
 [1] "12 k"
Roland
  • 127,288
  • 10
  • 191
  • 288
13

I came here with the same question. Thanks to Roland for his answer; I built on his code with a few changes:

  • Allows significant figures to be specified when rounding=FALSE (defaults to 6 just like the 'signif' builtin function)
  • Doesn't throw an error with values below 1e-24
  • Outputs scientific notation (no units) for values above 1e27

Hope this is helpful.

f2si<-function (number, rounding=F, digits=ifelse(rounding, NA, 6)) 
{
    lut <- c(1e-24, 1e-21, 1e-18, 1e-15, 1e-12, 1e-09, 1e-06, 
        0.001, 1, 1000, 1e+06, 1e+09, 1e+12, 1e+15, 1e+18, 1e+21, 
        1e+24, 1e+27)
    pre <- c("y", "z", "a", "f", "p", "n", "u", "m", "", "k", 
        "M", "G", "T", "P", "E", "Z", "Y", NA)
    ix <- findInterval(number, lut)
    if (ix>0 && ix<length(lut) && lut[ix]!=1) {
        if (rounding==T && !is.numeric(digits)) {
            sistring <- paste(round(number/lut[ix]), pre[ix])
        }
        else if (rounding == T || is.numeric(digits)) {
            sistring <- paste(signif(number/lut[ix], digits), pre[ix])
        }
        else {
            sistring <- paste(number/lut[ix], pre[ix])
        } 
    }
    else {
        sistring <- as.character(number)
    }
    return(sistring)
}

f2si(12345)
 [1] "12.345 k"
f2si(12345, T)
 [1] "12 k"
f2si(10^31)
 [1] "1e+31" # (previous version would output "1e+07 Y"
f2si(10^-25)
 [1] "1e-25" # (previous version would throw error)
f2si(123456789)
 [1] "123.457 M" # (previous version would output ""123.456789 M"
f2si(123456789, digits=4)
 [1] "123.5 M" # (note .456 is rounded up to .5)

From this code it's pretty easy to write a similar function for commonly used financial units (K, MM, Bn, Tr), too.

tomelgin
  • 141
  • 1
  • 3
3

This is simple to vectorise using case_when from dplyr, and it's much easier on the eyes:

library(dplyr)

si_number = function(x, digits) {
    
    compress = function(x, n) {
        signif(x * 10^(-n), digits)
    }
    
    case_when(
        x >= 1e6   ~ paste0(compress(x, 6), "M"),
        x >= 1000  ~ paste0(compress(x, 3), "k"),
        x >= 1     ~ as.character(compress(x, 0)),
        x >= 0.001 ~ paste0(compress(x, -3), "m"),
        x >= 1e-6  ~ paste0(compress(x, -6), "u")
    )
}

Waldi
  • 39,242
  • 6
  • 30
  • 78
Tarquinnn
  • 501
  • 3
  • 8
  • 2
    Hi, this edit has completely broken the function for all numbers less than 1, and the author appears to have misunderstood the point of the code (0.33 should be displayed as 330m as per the spec). An edit to handle zero and negative numbers would be useful but please could this be reverted. – Tarquinnn Jun 15 '22 at 12:38
2

I was looking for Thousand(K), million(M) and Billion(B) number converter. I modified this routine to take a numeric vector/single number spitting out the required output.

CurrencyFormat <-function (number,rounding=F) 
{
    first <- TRUE
    lut <- c( 1, 1000, 1000000, 1000000000,1000000000000 )
    pre <- c("", "K", "M", "B", "T")
    if (length(number) > 1) {
        for (cnt in 1:length(number)){        
            ix <- findInterval(number[cnt], lut)
            if (ix != 0 | ix != 1){
                if (rounding==T) {
                    sistring <- paste(round(number[cnt]/lut[ix]), pre[ix])
                }
                else {
                    sistring <- paste(signif(number[cnt]/lut[ix],digits=5), pre[ix])
                }
                if (first){
                    tnumber <- sistring
                    fnumber <- tnumber
                    first <- FALSE
                }
                else
                    fnumber <- append(fnumber, sistring)
            }
            else {
                sistring <- number[cnt]
                if (first){
                    tnumber <- sistring
                    fnumber <- tnumber
                    first <- FALSE
                }
                else
                    fnumber <- append(fnumber, sistring)
            }
        }
        return(fnumber)
    }
    else{
        ix <- findInterval(number, lut)
        if (ix != 0 | ix != 1){
            if (rounding==T) {
                sistring <- paste(round(number/lut[ix]), pre[ix])
            }
            else {
                sistring <- paste(signif(number/lut[ix],digits=5), pre[ix])
            }
            return(sistring)
        }    
        else
            return(number)
    }
}

Examples:

CurrencyFormat(1.25,F)
[1] "1.25 "

CurrencyFormat(1000.25,F)
[1] "1.0002 K"

CurrencyFormat(c( 1,45,1234, 4.36e+06, 2.84e+04, 2.01e+06),F)
[1] "1 "      "45 "     "1.234 K" "4.36 M"  "28.4 K"  "2.01 M" 
Jaap
  • 81,064
  • 34
  • 182
  • 193
1

Slightly modified version to account for negative numbers :

f2si<-function (number, rounding=F, digits=ifelse(rounding, NA, 6)) 
{
mysign <- ""
if (number<0) {
    mysign <- "-"
}
number <- abs(number)
lut <- c(1e-24, 1e-21, 1e-18, 1e-15, 1e-12, 1e-09, 1e-06, 
    0.001, 1, 1000, 1e+06, 1e+09, 1e+12, 1e+15, 1e+18, 1e+21, 
    1e+24, 1e+27)
pre <- c("y", "z", "a", "f", "p", "n", "u", "m", "", "k", 
    "M", "G", "T", "P", "E", "Z", "Y", NA)
ix <- findInterval(number, lut)
if (ix>0 && ix<length(lut) && lut[ix]!=1) {
    if (rounding==T && !is.numeric(digits)) {
        sistring <- paste(mysign,mysign,round(number/lut[ix]), pre[ix])
    }
    else if (rounding == T || is.numeric(digits)) {
        sistring <- paste(mysign,signif(number/lut[ix], digits), pre[ix],sep="")
    }
    else {
        sistring <- paste(mysign,number/lut[ix], pre[ix],sep="")
    } 
} else {
    sistring <- paste(mysign,as.character(number),sep="")
}
return(sistring)

}

Chapo
  • 2,563
  • 3
  • 30
  • 60