准备工作(安装程序包并配置环境变量):
R软件:https://www.r-project.org/
对应的Rtools:https://cran.r-project.org/bin/windows/Rtools/
Miktex:https://miktex.org/
R需要安装 Rcpp 和 RcppArmadillo程序包:
https://cran.r-project.org/web/packages/Rcpp/index.html
https://cran.r-project.org/web/packages/RcppArmadillo/index.html
(也可通过install.packages('Rcpp') 和 install.packages('RcppArmadillo')安装)
建立骨架包
在R中运行如下命令生成基于Rcpp的R包骨架(不支持并行)
library(Rcpp)
Rcpp.package.skeleton( "Test" )
getwd()
Test保存在getwd()所示的文件夹下, 有 man, R, src三个文件夹, 以及DESCRIPTION以及 NAMESPACE 以及 Read-and-delete-me 三个文本文件,这个三个文件没有任何扩展名。
man下面, 保存的是所有R函数以及R程序包的帮助文件文件, 遵循是Latex格式。需要逐项填写
R下面, 保存的是R函数的文件, 每个R函数单独一个文件
src下面, 放的是cpp文件, 即c++的源文件
默认的情况下,
man文件夹下, 会有 rcpp_hello_world.Rd 文件,
R文件夹下, 会有RcppExports.R文件
src 文件夹下, 会有 rcpp_hello_world.cpp 和 RcppExports.cpp两个C++源文件
这些文件在本例中均可以删除。
或者在R中运行如下命令生成基于RcppArmadillo的R包骨架(支持并行)
library(RcppArmadillo)
RcppArmadillo.package.skeleton("Test")
getwd()
默认情况下,与Rcpp相比,RcppArmadillo会在src文件夹下多生成Makevars和Makevars.win,这两个文件不需要修改,其他操作与Rcpp相同。
改写C++代码
源代码:实现从1到n的加法
#include <iostream>
#include <omp.h>
using namespace std;
int get_sum(int n) {
int sum = 0;
#pragma omp parallel for
for(int i = 0; i < n; i++) {
sum += i;
}
return sum;
}
改写代码(getsum.cpp,放于src文件夹下):
#include <Rcpparmadillo.h>
#include <omp.h>
// [[Rcpp::depends(RcppArmadillo)]]
using namespace Rcpp;
using namespace std;
// [[Rcpp::export]]
RcppExport SEXP get_sum(SEXP n_s) {
int n = Rcpp::as<int> (n_s);
int sum = 0;
#pragma omp parallel for
for(int i = 0; i < n; i++) {
sum += i;
}
return (wrap(sum));
}
注:1)在返回类型前要加上RcppExport,返回值则用wrap函数(记得加括号),可直接返回SEXP数据类型。
2)同时返回多个值的情况:
#include <RcppArmadillo.h>
// [[Rcpp::depends(RcppArmadillo)]]
// [[Rcpp::export]]
Rcpp::List fastLm(const arma::mat& X, const arma::colvec& y) {
int n = X.n_rows, k = X.n_cols;
arma::colvec coef = arma::solve(X, y); // fit model y ~ X
arma::colvec res = y - X*coef; // residuals
// std.errors of coefficients
double s2 = std::inner_product(res.begin(), res.end(), res.begin(), 0.0)/(n - k);
arma::colvec std_err = arma::sqrt(s2 * arma::diagvec(arma::pinv(arma::trans(X)*X)));
return Rcpp::List::create(Rcpp::Named("coefficients") = coef,
Rcpp::Named("stderr") = std_err,
Rcpp::Named("df.residual") = n - k);
}
则可以返回result.coefficients, result.stderr, result.df.residual三个结果。
写调用函数
为了能够在R中调用编译后的C++函数,可以使用R中的.Call()函数(get_sum.R,置于R文件夹下)。
get_sum_R <- function(n_s) {
.Call('get_sum', n_s, PACKAGE = 'Test')
}
#其中get_sum_R是R包中的方法名
#get_sum是C++中的函数名
#n_s是函数要接收参数
#PACKAGE是R包的名字
为了生成该get_sum_R函数的帮助文件, 需要将该函数粘贴到R console中,之后运行R命令 prompt(get_sum_R),所形成的get_sum_R.Rd 文件需要进一步根据参数的意义填写和编辑,之后放到man文件夹下。
建议为R包提供的每一个方法单独创建一个.Rd文件,其格式如下:
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/get_sum.R
\name{get_sum_R}
\alias{get_sum_R}
\title{get the sum of 0 to (n_s - 1)}
\usage{
get_sum_R(n_s)
}
\arguments{
\item{n}{numeric variable}
}
\value{
the sum of 0 to (n_s - 1)
}
\description{
get sum
}
\examples{
get_sum_R(10)
}
编辑和填写 DESCRIPTION以及NAMESPACE 两个文本文件。删除 Read-and-delete-me文件。这样,程序包的源文件就做好了。
DESCRIPTION文件格式如下,修改Package名,Title和Version等信息。
Package: Test
Type: Package
Title: Test how to change the C++ code to R package
Version: 1.0
Date: 2020-11-18
Author: Zhang Yf
Maintainer: qdu-bioinfo <qdu_bioinfo@163.com>
Description: XXX.
License: GPL-2
Imports: Rcpp (>= 1.0.4.6)
LinkingTo: Rcpp, RcppArmadillo, RcppEigen
Depends:
R (>= 2.10)
RoxygenNote: 7.1.1
编译或安装R程序包的命令
package='Test'
#check skycalc package
R CMD check ${package}
#build skycalc package Windows Binary.bat
#INSTALL --build ${package}
#build skycalc package Linux Source Code.bat
R CMD build ${package}
#install skycalc package.bat
R CMD INSTALL ${package}
编写.bat文件,将其置于Test文件夹所在目录,并添加可执行权限,直接执行该文件, 即可完成程序包的检查、编译和安装。
准备数据文件
一般来说,为了复用性与测试,需要包中包含一些测试文件。
添加数据文件的方法如下,在R console中按如下方式生成数据文件,然后通过use_data
函数添加到Test库中,这样当加载库的时候,相应的数据文件也被加载进来了,可以直接使用。
library(devtools)
test_num = 10
use_data(test_num, Test)
调用函数
library(Test)
get_sum_R(10)