R学习R语言编程进阶计算机@linux_python_R 技术帖

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

2020-03-09  本文已影响0人  drlee_fc74

这个是对于《高效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然后在查找.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的时候,我们可以设置一些简单的参数

高效编程

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++重写代码中的关键部分。

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

上一篇下一篇

猜你喜欢

热点阅读