[R|读书笔记]:高效R语言编程

作者: drlee_fc74 | 来源:发表于2020-03-09 21:59 被阅读0次

    这个是对于《高效R语言编程》的读书记录

    性能和机能测试

    基准测试

    我们在写代码的时候,要去考虑代码的运行速度的。目前有两种方法来评价我们的运行速度。

    1. system.time()可以来对代码运行一次的时间进行评价。
    2. microbenchmark::microbenchmark来进行运行多次时间评选。同时也可以比较多个代码之间的优劣
    library(microbenchmark)
    df <- data.frame(v = 1:4, name = letters[1:4])
    ## 三种取第三行第二列的方式
    microbenchmark(df[3,2], df[3,"name"], df$name[3])
    
    ## Unit: nanoseconds
    ##           expr  min     lq     mean median     uq   max neval cld
    ##       df[3, 2] 9309 9575.5  9752.87 9687.0 9817.5 12468   100   b
    ##  df[3, "name"] 9192 9585.5 10217.35 9707.5 9875.5 50222   100   b
    ##     df$name[3]  973 1158.5  1317.04 1327.0 1384.5  4715   100  a
    

    性能测试

    基准测试通常是测试一个函数和其他函数比较的运行时间。而性能测试则是测试一大块代码当中哪个行代码更占用

    时间。我们通过profvis::profvis来进行性能测试。

    # Load the data set
    data(movies, package = "ggplot2movies") 
    
    # Load the profvis package
    library(profvis)
    
    # Profile the following code with the profvis function
    profvis({
      # Load and select data
      comedies <- movies[movies$Comedy == 1, ]
    
      # Plot data of interest
      plot(comedies$year, comedies$rating)
    
      # Loess regression line
      model <- loess(rating ~ year, data = comedies)
      j <- order(comedies$year)
      
      # Add fitted line to the plot
      lines(comedies$year[j], model$fitted[j], col = "red")
    })     ## Remember the closing brackets!
    
    image

    通过上图我们就可以发现我们在构建模型的时候。花的时间是最多的。

    R高效配置

    R启动文件

    每次启动R的时候都将读入两个文件。.Renviron以及.Rprofile

    • .Renviron:
      首要目的是设置环境变量。这些变量通知R去何处查找程序等等的。
    • .Rprofile: 用于每次启动R前简单的运行几行R代码。

    两个文件一般来说是先查找.Renviron然后在查找.Rprofile

    启动文件位置

    启动文件可以放到三个位置上:

    1. R_HOME: 可以通过R.home()来查看
    2. HOME: 可以通过Sys.getenv("HOME)来查看
    3. R当前的工作目录: 可以通过getwd()来查看

    R寻找启动文件的顺序是3>2>1的。如果前一个有相关文件就不会在后面进行查找了

    .Rprofile文件

    这个文件其实类似于在启动的时候。加载一个简单的脚本。这样的脚本可以放置一些我们的常规的设置。比如定义一些常规的函数以及对于options()的修改。

    一些有用的功能。

    ##一些options的设置
    options(stringsAsFactors = F) ## 把文本转让为因子去掉
    options(prompt = "R>") ## 把代码的提示符从">"变为"R>"
    options(continue = " ") ## 把相同代码的不同行之间的"+"设置为空白
    ## 定义一些简单的函数
    hf <- function(dat) dat[1:5,1:5] ### 查看文件的前五行
    ## 缩写一些函数
    v = utils::View
    

    R启动参数

    在shell下运行R的时候,我们可以设置一些简单的参数

    • --no-environ/--no-init: 通知R尽在当前目录下查找启动文件。
    • --no-restore: 通知R不加载可能存在于当前目录下的.RData文件
    • --no-save:
      通知R,当用户使用q()的退出的时候,不询问是否保存文件。

    高效编程

    1. 尽量不要增加向量的大小

    只要有可能,提前分配好向量的大小然后填入数据

    ### 比较两个不同函数的时间
    method1 <- function(x){  ###逐渐增加向量
        vec <- NULL
        for(i in seq_len(x)){
            vec <- c(vec, i)
        }
        vec
    }
    method2 <- function(x){ ### 提前制定好变量长度
        vec <- numeric(x)
        for(i in seq_len(x)){
            vec[i] <- i
        }
        vec
    }
    
    microbenchmark(method1(10000), method2(10000), unit = "s")
    
    ## Unit: seconds
    ##            expr       min          lq         mean       median           uq
    ##  method1(10000) 0.1388324 0.147609943 0.1567819665 0.1530471155 0.1606227850
    ##  method2(10000) 0.0005931 0.000636882 0.0007747023 0.0006527875 0.0006813865
    ##          max neval cld
    ##  0.239146280   100   b
    ##  0.008395814   100  a
    

    2.尽可能向量化代码

    尽可能的访问底层的C/Fortran代码。基于这个原则,大量的R函数被向量化,这意味着,函数的输入和

    /输出通常是向量。从而降低了函数调用请求的数量。

    3.适当时机下使用因子(factor).

    因子字符的编码方式上。其实因子本质是数字。只是赋予了不同的字符。而字符就单纯的是字符。所以因子稍微的节约空间

    FactorVec <- iris$Species
    ChaVec <- as.character(iris$Species)
    sapply(list(FactorVec, ChaVec), pryr::object_size)
    
    ## Registered S3 method overwritten by 'pryr':
    ##   method      from
    ##   print.bytes Rcpp
    
    ## [1] 1248 1432
    

    4.通过缓存变量避免不必要的计算

    只计算对象一次的并在重用结果是提高代码速度的简单直接的方法。例如👇两个代码:

    ### 方法一:
    apply(x, 2, function(i) mean(i)/sd(x))
    ### 方法二:
    sd_X <- sd(x)
    apply(x, 2, function(i) mean(i)/sd_X)
    

    相较于方法一:方法二当中的sd只计算了一次。所以相对来说会一些。

    缓存更高级的形式是使用memoise包。如果一个函数在相同输入下被调用多次,将已知结果存入可检索的缓存中可以加快运行速度。这是一种通过内存消耗来换取速度的方法。

    plot_mpg <- function(x){
        data(mpg, package = "ggplot2")
        mpg <- mpg[-x,]
        plot(mpg$cty, mpg$hwy)
        lines(lowess(mpg$cty, mpg$hwy),col = 2)
    }
    m_plot_mpg <- memoise::memoise(plot_mpg)
    microbenchmark(unit = "ms", plot_mpg(10), m_plot_mpg(10))
    
    image

    5. 字节编译包可使性能轻而易举的提升

    通过compiler包可以将函数编译成字节代码。这样可以增加函数运行的速度。

    mean_r <- function(x){
        m = 0
        n = length(x)
        for(i in seq_len(n))
            m = m + x[i]/n
        m
    }
    cpm_mean_r <- compiler::cmpfun(mean_r)
    x = rnorm(1000)
    microbenchmark(times = 10, unit = "ms", mean_r(x), cpm_mean_r(x), mean(x))
    
    ## Unit: milliseconds
    ##           expr      min       lq      mean    median       uq      max neval
    ##      mean_r(x) 0.050395 0.050956 0.3490756 0.0516625 0.094729 2.926259    10
    ##  cpm_mean_r(x) 0.050556 0.051660 0.0608802 0.0526610 0.068143 0.101249    10
    ##        mean(x) 0.004334 0.004560 0.0099136 0.0047280 0.010270 0.042967    10
    ##  cld
    ##    a
    ##    a
    ##    a
    

    高效工作流

    5条建议

    1. 编程前要有清晰头脑,或许你可以准备好纸和笔,以保证你牢记自己的目标,而不是沉溺于技术中。
    2. 制定计划,计划的规模和类型取决于项目。
    3. 今早选择项目中要用到的包。
    4. 记录你的每个阶段的工作。
    5. 尽可能使你的整个工作流可复制。

    R包的查找

    1. 我们可以在谷歌上进行直接的搜索
    2. rdocumentation.org提供了可以检索R包的地方。我们可以在这里进行搜索
    3. 在R当中使用RSiteSearch()也是进行搜索R包的一个方法

    高效输入/输出(I/O)

    关于数据I/O的5条技巧

    1. 如果可能,不管文件是从互联网下载还是拷贝导你的电脑上,保留本地文件名不变。这将有助于将来跟踪数据来源。
    2. R语言有自己的数据格式.Rds。可使用readRDS()saveRDS()导入和导出该格式文件。是一种速度雨空间储存都非常高效的数据格式
    3. 使用rio包当中的import()可导入各种可是的数据。从而避免了特定格式的代码选择
    4. 大文件的导入推荐使用data.tablereadr。现在还有vroom可以使用
    5. 使用file.size()object.size()跟踪文件与R对象大小,以便在他们过大的时候提前预防

    高效优化

    1.在开始优化代码之前,使用代码分析器

    通过profivs来对代码进行性能分析

    2. 知道一些高效的R基础

    if()函数和ifelse()函数

    虽然ifelse()在代码上保持了简洁性。但是在运行速度上却要比if()慢很多。另外在dplyr::if_else的运行速度要比ifelse()快但是仍是if()快一些。至于基于if_else上创建的case_when速度就又要稍微慢一些了。

    ifelseFunc <- function(x){
      ifelse(x > 40, "pass", "fail")
    }
    IfFunc <- function(x){
      result <- rep("fail", length(x))
      result[x > 40] <- "pass"
    }
    if_elseFunc <- function(x){
      dplyr::if_else(x > 40, "pass", "fail")
    }
    caseWhenFunc <- function(x){
      dplyr::case_when(
        x > 40 ~ "pass",
        TRUE ~ "fail"
          )
    }
    marks <- runif(10e4, min = 30, max = 99)
    microbenchmark(unit = "ms",ifelseFunc(marks), IfFunc(marks), if_elseFunc(marks),caseWhenFunc(marks))
    
    ## Unit: milliseconds
    ##                 expr       min        lq      mean    median        uq
    ##    ifelseFunc(marks) 13.071040 15.093970 16.171436 15.558156 16.974654
    ##        IfFunc(marks)  1.177236  1.825465  2.162302  1.959760  2.069466
    ##   if_elseFunc(marks)  4.173324  5.412398  6.378833  5.799150  6.325605
    ##  caseWhenFunc(marks)  7.194478  8.988347 14.369881  9.676258 10.968151
    ##         max neval cld
    ##   21.610459   100   c
    ##    6.518785   100 a  
    ##   15.240395   100  b 
    ##  172.603551   100   c
    

    使用&&代替&,以及使用||代替|

    x < 0.4 | x > 0.6

    一般而言逻辑判断是&或者|,在R进行比较的时候。会先计算x >

    0.6的结果而不管x <

    0.4的结果。想比较而言,非向量的版本&&,只有在非要情况下执行第二个条件。

    但是仍需要担心的时候,不要使用&&或者||操作向量,因为它只会计算向量的第一个然后返回一个结果

    x <- c(0.8,0.5,0.6)
    x < 0.4 | x > 0.6
    
    ## [1]  TRUE FALSE FALSE
    
    x < 0.4 || x > 0.6
    
    ## [1] TRUE
    

    如果数据类型是一个类型的话,可以考虑将数据框转换为矩阵

    对于data.frame每一列可以是同一个数据格式。但是matrix这个矩阵只有一个数据格式。由于有不同的数据格式,所以data.frame在保存数据的时候每一列的是分开的。这就导致,我们在提取一行的时候的时候,其实是要从不同的保存位置提取数据。这样速度就慢了。相比较而言matrix由于数据类型一直,所以保存的都在一起。这样提取的时候就方便很多。另外数据的大小而言,matrix也要比data.frame小一些。

    需要注意的是,数据要一致。如果有数字还有字符。则会都转换为字符。

    3.尽快能的使用专用的行列函数

    对于行列的统计计算。我们可以通过apply来进行操作。但是会降低速度。可以使用一些专用的函数类似rowSums()colSums()rowMean()以及colMean()这些的。其中matrixStats包含有很多类似的优化的函数。

    4.为了性能优化,考虑用C++重写代码中的关键部分。

    这个很好理解的,毕竟底层语言嘛。还是要快的。

    相关文章

      网友评论

        本文标题:[R|读书笔记]:高效R语言编程

        本文链接:https://www.haomeiwen.com/subject/kfmsoctx.html