C言語でRの書く拡張の中で data.frame を新しく作る例です。ソースコードはgithub/junkoda/Rext-examples。
この例では x,y,z という3つのベクトルをもつ data.frame を返す。Rでいうところの:
> x <- c(0,0,0,0); y <- c(0,0,0,0); z <- c(0,0,0,0)
> data.frame(x,y,z)
x y z
1 0 0 0
2 0 0 0
3 0 0 0
4 0 0 0
以下、C側での関数 return_data_frame の説明が続きます。
# include <R.h>
# include <Rinternals.h>
SEXP return_data_frame(void)
{
まずはベクトル x, y, z
を作っておく。
const int nrow= 4;
SEXP x;
PROTECT(x = allocVector(REALSXP, nrow));
double* rx= REAL(x);
for(int i=0; i<nrow; i++)
rx[i]= 0.0;
y,z も同様。
data.frame とは class="data.frame"
属性をもつリストで、printで表示するためには行番号も必要。リストも内部的にはベクトル。
const int ncol= 3; // Column are x,y,z
SEXP list;
PROTECT(list = allocVector(VECSXP, ncol));
setAttrib(list, R_ClassSymbol, ScalarString(mkChar("data.frame")));
列の名前 x y z:
SEXP col_names;
PROTECT(col_names = allocVector(STRSXP, ncol));
SET_STRING_ELT(col_names, 0, mkChar("x"));
SET_STRING_ELT(col_names, 1, mkChar("y"));
SET_STRING_ELT(col_names, 2, mkChar("z"));
setAttrib(list, R_NamesSymbol, col_names);
同様に列番号 1 2 3 4 をセット:
SEXP row_names;
PROTECT(row_names = allocVector(STRSXP, nrow));
char rname[10];
for(int i=0; i<nrow; i++) {
sprintf(rname, "%d", i+1);
SET_STRING_ELT(row_names, i, mkChar(rname));
}
setAttrib(list, R_RowNamesSymbol, row_names);
ベクトルを data.frame にセット:
SET_VECTOR_ELT(list, 0, x);
SET_VECTOR_ELT(list, 1, y);
SET_VECTOR_ELT(list, 2, z);
最後に PROTECT
した回数だけ UNPROTECT
:
UNPROTECT(6);
ここでは x,y,z,list,row_names, col_names の6回なので 6。
こうして作った list が SEXP return_data_frame(void)
の戻り値:
return list;
}
R から使う。
コンパイル
R CMD SHLIB dataframe.c
dyn.load("dataframe.so")
return.data.frame <- function() {
.Call("return_data_frame")
}
d <- return.data.frame()
print(d)
# x y z
# 1 0 0 0
# 2 0 0 0
# 3 0 0 0
# 4 0 0 0
print(is.data.frame(d)) #=> TRUE
参考
data.frame() のソースをみると
attr(value, "row.names") <- row.names
attr(value, "class") <- "data.frame"
と書いてある。"class" を設定しないと data.frame とは認識されない。"row.names" がないと print()
で表示してくれない。
Cからリストをつくる方法は R のソース builtin.c do_makelist() 関数をみるとよい。