在R中将数字格式化为有效数字

42

我想要对报告中的数字进行 有效数字 格式化,但保留尾部的有效零,并正确格式化大数值。

例如,数字 c(10.00001,12345,1234.5,123.45,1.2345,0.12345) 保留3个有效数字后应为 10.0, 12300, 1230, 123, 1.23, 0.123,但不同方法得到不同结果(且似乎没有一种通用方法可行)。

> numbers<-c(10.00001,12345,1234.5,123.45,1.2345,0.12345)
> for(n in seq(numbers)){
+   print(signif(numbers[n],digits=3))
+   print(format(numbers[n],digits=3))
+   print(formatC(numbers[n], digits=3,format="fg"))
+   print(formatC(numbers[n], digits=3,format="fg", flag="#"))
+   }
[1] 10
[1] "10"
[1] "  10"
[1] "10.0"
[1] 12300
[1] "12345"
[1] "12345"
[1] "12345."
[1] 1230
[1] "1234"
[1] "1234"
[1] "1234."
[1] 123
[1] "123"
[1] " 123"
[1] "123."
[1] 12.3
[1] "12.3"
[1] "12.3"
[1] "12.3"
[1] 1.23
[1] "1.23"
[1] "1.23"
[1] "1.23"
[1] 0.123
[1] "0.123"
[1] "0.123"
[1] "0.123"

这里,signif和format函数将10.00001结果四舍五入。使用flag="#"的formatC可以正确处理小数但无法处理大数字。

有更好的方法吗?


尝试使用apply、lapply和sapply代替循环... - Matt Bannert
我可能存在一个概念上的错误,但是 apply 函数族仍然使用循环吗?但是它抽象了循环,使代码更清晰、更易读,减少了繁琐的操作。对吗? - Richard Herron
抱歉,循环只是为了展示结果,我最初使用的是signif(numbers, digits=3),但由于某种原因它默认使用科学计数法。 - PaulHurleyuk
这取决于你对语句的理解。在某些情况下,一些apply家族函数在C中是循环而不是R中的循环。lapply具有优化功能,使其比for循环更快。aggregate()和tapply()在R中比循环快得多。此外,您还可以为进一步优化留下代码。关于您最初的问题,请参阅help(format)。 - John
只是想留言指出你的四位有效数字示例有点问题...它们应该进行四舍五入,例如 1235(因为 5 将 4 进位)...就像 round(.123456,digits=4) 给出 [1] 0.1235。不过还是感谢你的提问!+1 - Alexis
8个回答

45
抱歉我当时没有更新这个。我的问题中的任何陈述或 prettynum 都没有起作用。最后我使用了...
print(formatC(signif(numbers[n], digits=3), digits=3, format="fg", flag="#"))

正确处理尾随的零和大数字的。

10
看到使用 formatC() 能够解决问题我感到很高兴(我将该函数移植到 R 中,特别是自己引入了 format="fg" 参数)。通常现在人们更倾向于使用 sprintf() 而不是 formatC(),但确实,我没有找到一个像 formatC() 那样漂亮的方式来用 sprintf() 实现该解决方案。顺便说一下:根据您提供的 numbers,我发现使用 sapply 比使用 for 循环更方便: sapply(numbers, function(N) formatC(signif(N, digits=3), digits=3,format="fg", flag="#")) - Martin Mächler
formatC带有fg参数难道不能找到有效数字,因此无需使用signif吗? - Aaron left Stack Overflow
formatC 帮助我满足了上次的格式化需求。我发现,为了获得我需要的结果,我必须为输入值 >=1 使用 "digits=0, format="f"",对于小于1的值使用 " digits=1, format="fg"" 进行 ifelse 处理。 - masher
感谢@MartinMächler提供的sappy解决方案。 - Stefan Jelkovich

22

您是否了解 prettyNum() 函数及其所有选项?


有什么技巧可以将数字格式化为字符:1000 -> 1k10000 -> 10k1000000 -> 1M1e7 -> 10M - jangorecki
6
@Jan,有一个相当新的软件包可以实现这一点:prettyunits - Dirk Eddelbuettel

10

对Paul的回答进行了修改,但似乎还留下了一个小数点。我用gsub删除了它:

sigfig <- function(vec, digits){
  return(gsub("\\.$", "", formatC(signif(vec,digits=digits), digits=digits, format="fg", flag="#")))
}

8
一个更简洁的选项是options(),它只进行舍入。如果你计划经常这样做,建议使用Sweave。
> a <- 1.23456789
> options(digits=2)
> a
[1] 1.2
> options(digits=6)
> a
[1] 1.23457

6

如果您喜欢科学计数法

> format(2^31-1, scientific = TRUE, digits = 3)
[1] "2.15e+09"

6

保罗·赫利的方法对我来说在正数和负数方面都很有效。下面是一些代码,将保罗的解决方案修改为一个函数,可以指定所需的有效数字。

sigfig <- function(vec, n=3){ 
### function to round values to N significant digits
# input:   vec       vector of numeric
#          n         integer is the required sigfig  
# output:  outvec    vector of numeric rounded to N sigfig

formatC(signif(vec,digits=n), digits=n,format="fg", flag="#") 

}      # end of function   sigfig

验证它是否正常工作

numbers <- c(50000.01, 1000.001, 10.00001, 12345, 1234.5, 123.45, 1.2345, 0.12345, 0.0000123456, -50000.01, -1000.001,-10.00001, -12345, -1234.5, -123.45, -1.2345, -0.12345, -0.0000123456)
sigfig(numbers)   # defaults to 3
sigfig(numbers, 3)
sigfig(numbers, 1)
sigfig(numbers, 6)

0
我发现了PaulHurleyuk给出的答案可能存在不必要的行为: 测试 测试1:
numbers <- c(0.0001, 0.001, 0.01, 0.1, 1, 10, 100, 1000)
print(formatC(signif(numbers, digits = 3), digits = 3, format = "fg", flag = "#"))

返回:

[1] "0.000100" "0.00100"  "0.0100"   "0.100"    "1.00"     "10.0"     "100."     "1000."

测试2:

numbers <- c(1.0001, 1.001, 1.01, 1.1, 11, 101, 1001, 10001)
print(formatC(signif(numbers,digits=3), digits=3,format="fg", flag="#"))

返回:
[1] "1.00"   "1.00"   "1.01"   "1.10"   "11.0"   "101."   "1000."  "10000."

注意两个示例中的尾部小数分隔符以及测试1中引入的尾部零。

解决方案

要删除尾部小数分隔符:

gsub("\\.$", "", formatC(signif(numbers, digits = 3), digits = 3,format = "fg", flag = "#"))

如果经常使用,将其封装成一个函数:
sigfill <- function(x, sigfigs = 3){
  out <- gsub("\\.$", "",
              formatC(signif(x, digits = sigfigs),
                      digits = sigfigs, format = "fg", flag = "#"))
  return(out)
}

为了同时移除前面代码引入的尾随零:

sigfill <- function(x, sigfigs = 3){
  out <- gsub("\\.$", "",
              formatC(signif(x, digits = sigfigs),
                      digits = sigfigs, format = "fg", flag = "#"))
  out[grepl(".", out, fixed = TRUE)] <- strtrim(out[grepl(".", out, fixed = TRUE)],
                                                sigfigs + c(1, 2)[grepl("-", out, fixed = TRUE) + 1])
  return(out)
}

再次测试

正数:

numbers <- c(0.0001, 0.001, 0.01, 0.1, 1, 10, 100, 1000)
sigfill(numbers)

返回

[1] "0.00" "0.00" "0.01" "0.10" "1.00" "10.0" "100"  "1000"

“展开”数字:

numbers <- c(1.0001, 1.001, 1.01, 1.1, 11, 101, 1001, 10001)
sigfill(numbers)

返回

[1] "1.00"  "1.00"  "1.01"  "1.10"  "11.0"  "101"   "1000"  "10000"

负数:

numbers <- c(-0.0001, -0.001, -0.01, -0.1, -1, -10, -100, -1000)
sigfill(numbers)

返回

[1] "-0.00" "-0.00" "-0.01" "-0.10" "-1.00" "-10.0" "-100"  "-1000"

结果:没有尾随的小数点分隔符或额外的尾随零。


0

以下选项复制了formatC(format="fg",flag="#")的格式(fgf的特殊版本,其中数字指定有效数字而不是小数点后的数字,#标志使fg不会删除尾随零):

> f=2;x=c(10000.0001,1111,111.11,11.1,1.1,1.99,.01,.001,0,-.11,-.9,-.000011)
> dig=abs(pmin(0,floor(log10(abs(x)))-f+1))
> sprintf(paste0("%.",ifelse(is.infinite(dig),0,dig),"f"),x)
 [1] "10000"     "1111"      "111"       "11"        "1.1"       "2.0"
 [7] "0.010"     "0.0010"    "0"         "-0.11"     "-0.90"     "-0.000011"
> sub("\\.$","",formatC(x,f,,"fg","#"))
 [1] "10000"     "1111"      "111"       "11"        "1.1"       "2.0"
 [7] "0.010"     "0.0010"    "0"         "-0.11"     "-0.90"     "-0.000011"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接