Как воспользоваться возможностями R в C++

в 15:20, , рубрики: c++, data mining, метки: ,

R — язык программирования для статистической обработки данных и работы с графикой, а также свободная программная среда вычислений с открытым исходным кодом в рамках проекта GNU © Wikipedia.

В R собрано огромное число статистических алгоритмов на все случаи жизни и их можно использовать не только из родной програмной среды: его поддерживают такие известные математические пакеты, как SPSS, Statistica, SAS, Wolfram Mathematica и некоторые другие. Как же можно интегрировать R в свои приложения?
На хабрахабре уже была статья про использование R в С++, однако она осветила всего один из способов, который возможно использовать только если ваш код распространяется под GPL-совместимой лицензией. Но и в противном случае есть выход — использование пакета Rserve.

Rserve

Rserve — это TCP/IP / unix socket сервер, который позволяет использовать возможности R без необходимости линковаться с его библиотеками. Клиенты реализованы под различные популярные языки, такие как C/C++, Java, PHP. Код клиентов распространяется под LGPL.

Установим R и Rserve:

# sudo apt-get instal r-base
# sudo R
> install.package("Rserve");

Теперь осталось запустить Rserve:

# R CMD Rserve

Существует также отладочный режим

# R CMD Rserve.dbg

При запуске без параметров Rserve работает в режиме TCP/IP сервера на дефолтном порту (6311). Чтобы использовать unix sockets следует запускать Rserve с флагом --RS-socket

# R CMD Rserve --RS-socket "/home/zoberg/mysocket"

Список доступных флагов можно посмотреть, набрав

# R CMD Rserve --help

C++ клиент

C++ клиент состоит из 4 заголовочных файлов: Rconnection.h (тут лежат все объявление, которые могут понадобиться в работе), Rsrv.h, config.h (нужен только в Windows), sisocks.h (кроссплатформенные сокеты) и одного Rconnection.cc. Взять их все можно из официального репозитория на guthub.

Итак, разберём теперь демо, охватывающее большинство основных операций

/// main.cpp
#include <iostream>
using std::cout;

#define MAIN                      // коcтыль из sisocks.h
#define SOCK_ERRORS  // verbose socket errors

#include "sisocks.h"
#include "Rconnection.h"

// бесхитростный пример работы с C++ API
int main(int argc, char **argv) {
    initsocks(); // нужно только под Win32 - ничего не делает в unix

    // создаем объект подключения к Rserve. 
    // Параметры конструктора по умолчанию - "127.0.0.1", 6311.
    // если хотим использовать unix sockets, то следует передавать такие параметры: 
    // Rconnection *rc = new Rconnection("/home/zoberg/mysocket", -1);
    Rconnection *rc = new Rconnection();
    
    int i=rc->connect();
    if (i) {
        char msg[128];
        sockerrorchecks(msg, 128, -1);
        printf("unable to connect (result=%d, socket:%s).n", i, msg); return i;
    }
   
    double d[6] = { 1.5, 2.4, 5.6, -1.2, 0.6, 1.7 };
    
    // присвоим определенный выше массив переменной "a" в R
    Rdouble *rd = new Rdouble(d, 6);
    rc->assign("a", rd);
    delete rd;

    // создаем матрицу "b" размером 2 x 3 и считаем произведение матриц b * t(b)
    Rdouble *x = (Rdouble*) rc->eval("b<-matrix(a,2); b%*%t(b)");
    
    if (x) { // если всё прошло хорошо, то получаем результат
        cout << x << "n";
        
        // получим размерность получившейся в результате матрицы
        Rinteger *dim = (Rinteger*) x->attribute("dim");
        if (dim)
            cout << dim->intAt(0) << " by " << dim->intAt(1) << " matrixn";
        
        // выведем матрицу результата (без форматирования)
        double *d = x->doubleArray();
        int i=0, ct = x->length();
        while (i < ct) {
            cout << d[i++] << " "; 
        }
        cout << "n";
        
        // освободим память
        delete x;
    }

    // присваивание массива целых чисел переменной i в R
    int ia[6] = { 1, 4, 6, 3, 5 , 2 };
    Rinteger *ri = new Rinteger(ia, 6);
    rc->assign("i", ri);
    delete ri;

    // Ирисы Фишера — это набор данных для задачи классификации, 
    // на примере которого Рональд Фишер в 1936 году продемонстрировал
    // работу разработанного им метода дискриминантного анализа.
    // Этот набор данных есть в R, получим его
    Rvector *iris = (Rvector*) rc->eval("data(iris); iris");
    if (!iris) {
        cout << "oops! couldn't get iris datan"; delete rc; return 0;
    }

    // теперь из этого набора данных выберем ширину чашелистиков (sepal width) -
    // дешевая операция без обращения к Rserve, так как мы уже имеем все необходимые данные
    Rdouble *sw = (Rdouble*) iris->byName("Sepal.Width");
    double *swd = sw->doubleArray();
    
    // and print it ...
    { 
        int i=0, ct=sw->length(); 
        while (i<ct) {
            cout << swd[i++] << " ";
        }
        cout << "n";
     }
    
    // Следует освобождать только память, выделенную под iris,
    // деструктор sw вызывается автоматически.
    delete iris;
    
    // освободим объект соединения. Соединение будет разорвано.
    delete rc;
}

Замечание насчет управления памятью: вся необходимая память аллоцируется объектом Rmessage, который неявно создается при вызове «eval». Собственником этого объекта является Rexp, возвращаемый eval'ом (назовём его главным). Нельзя освобождать никакие другие Rexp, кроме главного! Любой другой Rexp, ассоциированный с главным, становится недействительным после освобождения главного Rexp. Также после освобождения главного Rexp недействительными становятся все указатели (например, возвращаемые методом doubleArray()).

Скомпилируем

# g++ -c Rconnection.cc
# g++ Rconnection.o main.cpp -o RExample -lcrypt

и запустим

# ./RExample
Rdouble[4]
2 by 2 matrix
33.97 -2.1 -2.1 10.09 
3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 3.7 3.4 3 3 4 4.4 3.9 3.5 3.8 3.8 3.4 3.7 3.6 3.3 3.4 3 3.4 3.5 3.4 3.2 3.1 3.4 4.1 4.2 3.1 3.2 3.5 3.6 3 3.4 3.5 2.3 3.2 3.5 3.8 3 3.8 3.2 3.7 3.3 3.2 3.2 3.1 2.3 2.8 2.8 3.3 2.4 2.9 2.7 2 3 2.2 2.9 2.9 3.1 3 2.7 2.2 2.5 3.2 2.8 2.5 2.8 2.9 3 2.8 3 2.9 2.6 2.4 2.4 2.7 2.7 3 3.4 3.1 2.3 3 2.5 2.6 3 2.6 2.3 2.7 3 2.9 2.9 2.5 2.8 3.3 2.7 3 2.9 3 3 2.5 2.9 2.5 3.6 3.2 2.7 3 2.5 2.8 3.2 3 3.8 2.6 2.2 3.2 2.8 2.8 2.7 3.3 3.2 2.8 3 2.8 3 2.8 3.8 2.8 2.8 2.6 3 3.4 3.1 3 3.1 3.1 3.1 2.7 3.2 3.3 3 2.5 3 3.4 3 

Готово! Более подробные сведения об интерфейсе взаимодействия с R можно получить, исследовав Rconnection.h.

Rserve.conf

Скажу пару слов о файле конфигураций Rserve.conf. Он предоставляет интересную возможность выполнять некоторый код на R сразу при старте Rserve. Файл настроек по умолчанию ищется в /etc/Rserve.conf, но можно задавать и другой путь, воспользовавшись флагом --RS-conf при запуске Rserve. Rserve.conf может содержать следующие опции:


workdir <path> [/tmp/Rserv]


pwdfile <file> [none=disabled]

remote enable|disable [disable]
auth required|disable [disable]
plaintext enable|disable [disable]
fileio enable|disable [enable]
interactive yes|no [yes] (since 0.6-2)

(с версии 0.1-9):
socket <socket> [none=disabled]
port <port> [6311]
maxinbuf <size in kb> [262144]

(с версии 0.3):
maxsendbuf <size in kb> [0=unlimited]
uid <uid> [none]
gid <gid> [none]
su now|server|client [none] (since 0.6-1)

(с версии 0.3-16):
source <file>
eval <expressions>

(с версии 0.5 и только в unix):
chroot <directory> [none]
sockmod <mode> [0=default]
umask <mask> [0]

(с версии 0.5-3):
encoding native|utf8|latin1 [native]

Пакету Rserve и C++ клиенту для него уже много лет, однако никаких русскоязычных мануалов по ним я не нашёл. Надеюсь, что данная статья сэкономит кому-то немного времени.

P.S. Существует load-balancer для Rserve: RSproxy.

Автор: Zoberg

Источник

Поделиться

* - обязательные к заполнению поля