【问题标题】:how to return number of decimal places in R如何在R中返回小数位数
【发布时间】:2011-03-02 21:25:57
【问题描述】:

我在 R 中工作。我有一系列以十进制度为单位的坐标,我想按这些数字的小数位数对这些坐标进行排序(即我想丢弃小数位数太少的坐标) .
R 中是否有一个函数可以返回一个数字的小数位数,我可以将其合并到函数编写中?
输入示例:

AniSom4     -17.23300000        -65.81700

AniSom5     -18.15000000        -63.86700

AniSom6       1.42444444        -75.86972

AniSom7       2.41700000        -76.81700

AniLac9       8.6000000        -71.15000

AniLac5      -0.4000000        -78.00000

理想情况下,我会编写一个脚本来丢弃 AniLac9 和 AniLac 5,因为这些坐标的记录精度不够高。我想丢弃经度和纬度的非零十进制值少于 3 个的坐标。

【问题讨论】:

  • 您的数据文件中如何记录小数位?例如,它可能同时具有 34.4 和 34.400,它们会被认为是不同的吗?示例输入和所需输出会有所帮助。
  • 警告;小数十进制数在 x86 和其他主流处理器中无法准确表示。除了文本表示之外,您将得到虚假结果。

标签: r


【解决方案1】:

您可以轻松地为该任务编写一个小函数,例如:

decimalplaces <- function(x) {
    if ((x %% 1) != 0) {
        nchar(strsplit(sub('0+$', '', as.character(x)), ".", fixed=TRUE)[[1]][[2]])
    } else {
        return(0)
    }
}

然后运行:

> decimalplaces(23.43234525)
[1] 8
> decimalplaces(334.3410000000000000)
[1] 3
> decimalplaces(2.000)
[1] 0

更新(2018 年 4 月 3 日)以解决 @owen88 报告由于舍入双精度浮点数而导致的错误 - 替换 x %% 1 检查:

decimalplaces <- function(x) {
    if (abs(x - round(x)) > .Machine$double.eps^0.5) {
        nchar(strsplit(sub('0+$', '', as.character(x)), ".", fixed = TRUE)[[1]][[2]])
    } else {
        return(0)
    }
}

【讨论】:

  • 我喜欢这个外观。非常感谢您的帮助!
  • 谢谢@Pascal!我刚刚意识到我在函数中有错字(在as.character 函数中写了“num”而不是“x”),我已经更正了。我还添加了正则表达式部分,因此数字/字符串末尾的零将被自动删除。
  • 这个函数很棒,但是当给定一个数字比如 63.0000 时,它会返回一个错误。有没有办法修改它,在这些情况下它会返回一个 0?
  • @schnee 感谢您的反馈。或者,您可以将options(scipen = 999) 设置为不使用科学格式。
  • as.character 不好——改用sprintfas.character 对于像 1e6、1e-6 这样的小/大“整数”数字将失败。
【解决方案2】:

这是一种方法。它会检查小数点后的前 20 位,但如果您有其他想法,可以调整数字 20。

x <- pi
match(TRUE, round(x, 1:20) == x)

这是另一种方式。

nchar(strsplit(as.character(x), "\\.")[[1]][2])

【讨论】:

    【解决方案3】:

    总结 Roman 的建议:

    num.decimals <- function(x) {
        stopifnot(class(x)=="numeric")
        x <- sub("0+$","",x)
        x <- sub("^.+[.]","",x)
        nchar(x)
    }
    x <- "5.2300000"
    num.decimals(x)
    

    如果不能保证您的数据格式正确,则应进行更多检查以确保其他字符不会潜入。

    【讨论】:

      【解决方案4】:

      我已经测试了一些解决方案,我发现这个解决方案对其他解决方案中报告的错误非常有效。

      countDecimalPlaces <- function(x) {
        if ((x %% 1) != 0) {
          strs <- strsplit(as.character(format(x, scientific = F)), "\\.")
          n <- nchar(strs[[1]][2])
        } else {
          n <- 0
        }
        return(n) 
      }
      
      # example to prove the function with some values
      xs <- c(1000.0, 100.0, 10.0, 1.0, 0, 0.1, 0.01, 0.001, 0.0001)
      sapply(xs, FUN = countDecimalPlaces)
      

      【讨论】:

        【解决方案5】:

        在 [R] 中,2.30000 和 2.3 之间没有区别,两者都四舍五入到 2.3,因此如果您要检查的话,一个并不比另一个更精确。另一方面,如果这不是你的意思:如果你真的想这样做,你可以使用 1)乘以 10,2)使用 floor() 函数 3)除以 10 4)检查与原始的相等性。 (但请注意,比较浮点数是否相等是不好的做法,请确保这确实是您想要的)

        【讨论】:

        • 或者将数据作为字符导入,统计“点”后面的字符个数,作为排序标准。
        【解决方案6】:

        对于常见的应用,这里修改daroczig的代码来处理向量:

        decimalplaces <- function(x) {
            y = x[!is.na(x)]
            if (length(y) == 0) {
              return(0)
            }
            if (any((y %% 1) != 0)) {
              info = strsplit(sub('0+$', '', as.character(y)), ".", fixed=TRUE)
              info = info[sapply(info, FUN=length) == 2]
              dec = nchar(unlist(info))[seq(2, length(info), 2)]
              return(max(dec, na.rm=T))
            } else {
              return(0)
            }
        }
        

        一般来说,浮点数如何存储为二进制可能存在问题。试试这个:

        > sprintf("%1.128f", 0.00000000001)
        [1] "0.00000000000999999999999999939458150688409432405023835599422454833984375000000000000000000000000000000000000000000000000000000000"
        

        我们现在有多少位小数?

        【讨论】:

        • 好主意!我认为仍然必须有一个错误:decimalplaces2(c(1.2, 2.34, 3)) 返回 1 - 还有:传递少于 3 个数字会导致错误。
        • 我收到Error in seq.default(2, length(info), 2) : wrong sign in 'by' argument
        【解决方案7】:

        有趣的问题。这是对上述受访者工作的另一个调整,矢量化和扩展以处理小数点左侧的数字。针对负数进行了测试,这对于之前的 strsplit() 方法会给出不正确的结果。

        如果只想计算右边的,trailingonly 参数可以设置为TRUE

        nd1 <- function(xx,places=15,trailingonly=F) {
          xx<-abs(xx); 
          if(length(xx)>1) {
            fn<-sys.function();
            return(sapply(xx,fn,places=places,trailingonly=trailingonly))};
          if(xx %in% 0:9) return(!trailingonly+0); 
          mtch0<-round(xx,nds <- 0:places); 
          out <- nds[match(TRUE,mtch0==xx)]; 
          if(trailingonly) return(out); 
          mtch1 <- floor(xx*10^-nds); 
          out + nds[match(TRUE,mtch1==0)]
        }
        

        这里是strsplit() 版本。

        nd2 <- function(xx,trailingonly=F,...) if(length(xx)>1) {
          fn<-sys.function();
          return(sapply(xx,fn,trailingonly=trailingonly))
          } else {
            sum(c(nchar(strsplit(as.character(abs(xx)),'\\.')[[1]][ifelse(trailingonly, 2, T)]),0),na.rm=T);
          }
        

        字符串版本在 15 位数处截断(实际上,不知道为什么其他人的 places 参数偏离了 1...如果数量足够大,则为大小)。 as.character() 可能有一些格式化选项,可以为 nd2() 提供与 nd1()places 参数等效的选项。

        nd1(c(1.1,-8.5,-5,145,5,10.15,pi,44532456.345243627,0));
        # 2  2  1  3  1  4 16 17  1
        nd2(c(1.1,-8.5,-5,145,5,10.15,pi,44532456.345243627,0));
        # 2  2  1  3  1  4 15 15  1
        

        nd1() 更快。

        rowSums(replicate(10,system.time(replicate(100,nd1(c(1.1,-8.5,-5,145,5,10.15,pi,44532456.345243627,0))))));
        rowSums(replicate(10,system.time(replicate(100,nd2(c(1.1,-8.5,-5,145,5,10.15,pi,44532456.345243627,0))))));
        

        【讨论】:

          【解决方案8】:

          并不是要劫持线程,只是将其发布在这里,因为它可能会帮助某人处理我尝试使用提议的代码完成的任务。

          不幸的是,即使是the updated@daroczig 的解决方案也无法让我检查一个数字是否少于 8 位小数。

          @daroczig 的代码:

          decimalplaces <- function(x) {
              if (abs(x - round(x)) > .Machine$double.eps^0.5) {
                  nchar(strsplit(sub('0+$', '', as.character(x)), ".", fixed = TRUE)[[1]][[2]])
              } else {
                  return(0)
              }
          }
          

          在我的情况下产生了以下结果

          NUMBER / NUMBER OF DECIMAL DIGITS AS PRODUCED BY THE CODE ABOVE
          [1] "0.0000437 7"
          [1] "0.000195 6"
          [1] "0.00025 20"
          [1] "0.000193 6"
          [1] "0.000115 6"
          [1] "0.00012501 8"
          [1] "0.00012701 20"
          

          等等

          到目前为止,能够使用以下笨拙的代码完成所需的测试:

          if (abs(x*10^8 - floor(as.numeric(as.character(x*10^8)))) > .Machine$double.eps*10^8) 
             {
             print("The number has more than 8 decimal digits")
             }
          

          PS:我可能会遗漏一些关于不使用.Machine$double.eps 的内容,所以请小心

          【讨论】:

            【解决方案9】:

            另一个贡献,完全保持为数字表示而不转换为字符:

            countdecimals <- function(x) 
            {
              n <- 0
              while (!isTRUE(all.equal(floor(x),x)) & n <= 1e6) { x <- x*10; n <- n+1 }
              return (n)
            }
            

            【讨论】:

            • 这遇到了我认为是浮点精度问题。例如,countdecimals(4.56) 返回 8。
            【解决方案10】:

            不知道为什么上面没有使用这种简单的方法(从 tidyverse/ma​​grittr 加载管道)。

            count_decimals = function(x) {
              #length zero input
              if (length(x) == 0) return(numeric())
            
              #count decimals
              x_nchr = x %>% abs() %>% as.character() %>% nchar() %>% as.numeric()
              x_int = floor(x) %>% abs() %>% nchar()
              x_nchr = x_nchr - 1 - x_int
              x_nchr[x_nchr < 0] = 0
            
              x_nchr
            }
            
            > #tests
            > c(1, 1.1, 1.12, 1.123, 1.1234, 1.1, 1.10, 1.100, 1.1000) %>% count_decimals()
            [1] 0 1 2 3 4 1 1 1 1
            > c(1.1, 12.1, 123.1, 1234.1, 1234.12, 1234.123, 1234.1234) %>% count_decimals()
            [1] 1 1 1 1 2 3 4
            > seq(0, 1000, by = 100) %>% count_decimals()
             [1] 0 0 0 0 0 0 0 0 0 0 0
            > c(100.1234, -100.1234) %>% count_decimals()
            [1] 4 4
            > c() %>% count_decimals()
            numeric(0)
            

            因此,R 似乎并没有在内部区分最初获得1.0001。因此,如果有一个包含各种十进制数的向量输入,则可以通过取小数位数的最大值来查看它最初有多少位(至少)。

            已编辑:修复错误

            【讨论】:

            • - 2 假设您在小数点前只有一个数字。不适用于数字&gt;= 10&lt; 0(因为负号也会被计算在内)。一种可能的解决方案是使用(abs(x) %% 1) %&gt;% ...。但即便如此,我也遇到了浮点问题。 abs(-23342.2) %% 1,在我的机器上,打印为0.2,但as.character(abs(-23342.2) %% 1) 给出"0.200000000000728"
            • (虽然现在我查了一下,似乎最佳答案与%% 1 存在问题并找到了解决方法。)比我的第一条评论更简单的解决方法是x_nchr - nchar(round(x)) 而不是x_nchr - 2 .这应该可以处理负数和多个前导数字就好了。
            • 好点格雷戈尔。我为此添加了一个简单的修复程序。还添加了对负值的简单修复。
            【解决方案11】:

            如果这里有人需要上面 Gergely Daróczi 提供的函数的矢量化版本:

            decimalplaces <- function(x) {
              ifelse(abs(x - round(x)) > .Machine$double.eps^0.5,
                     nchar(sub('^\\d+\\.', '', sub('0+$', '', as.character(x)))),
                     0)
            }
            
            decimalplaces(c(234.1, 3.7500, 1.345, 3e-15))
            #> 1 2 3 0
            

            【讨论】:

              【解决方案12】:

              基于daroczig函数的向量解(也可以处理包含字符串和数字的脏列):

              decimalplaces_vec <- function(x) {
              
                vector <- c()
                for (i in 1:length(x)){
              
                  if(!is.na(as.numeric(x[i]))){
              
                    if ((as.numeric(x[i]) %% 1) != 0) {
                      vector <- c(vector, nchar(strsplit(sub('0+$', '', as.character(x[i])), ".", fixed=TRUE)[[1]][[2]]))
              
              
                    }else{
                      vector <- c(vector, 0)
                    }
                  }else{
                    vector <- c(vector, NA)
                  }
                }
                return(max(vector))
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-11-18
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多