Перейти к основному содержимому

Интеграция Java-R

· 5 мин. чтения

1. Обзор

R — популярный язык программирования, используемый для статистики. Поскольку он имеет широкий спектр доступных функций и пакетов, нередко требуется встраивать код R в другие языки.

В этой статье мы рассмотрим некоторые наиболее распространенные способы интеграции кода R в Java.

2. R-скрипт

Для нашего проекта мы начнем с реализации очень простой функции R, которая принимает вектор в качестве входных данных и возвращает среднее значение его значений. Мы определим это в специальном файле:

customMean <- function(vector) {
mean(vector)
}

В этом руководстве мы будем использовать вспомогательный метод Java для чтения этого файла и возврата его содержимого в виде строки :

String getMeanScriptContent() throws IOException, URISyntaxException {
URI rScriptUri = RUtils.class.getClassLoader().getResource("script.R").toURI();
Path inputScript = Paths.get(rScriptUri);
return Files.lines(inputScript).collect(Collectors.joining());
}

Теперь давайте посмотрим на различные варианты вызова этой функции из Java.

3. RCaller

Первая библиотека, которую мы собираемся рассмотреть, — это RCaller , которая может выполнять код, порождая выделенный процесс R на локальном компьютере.

Поскольку RCaller доступен в Maven Central , мы можем просто включить его в наш pom.xml :

<dependency>
<groupId>com.github.jbytecode</groupId>
<artifactId>RCaller</artifactId>
<version>3.0</version>
</dependency>

Затем давайте напишем собственный метод, который возвращает среднее значение наших значений, используя наш оригинальный R-скрипт:

public double mean(int[] values) throws IOException, URISyntaxException {
String fileContent = RUtils.getMeanScriptContent();
RCode code = RCode.create();
code.addRCode(fileContent);
code.addIntArray("input", values);
code.addRCode("result <- customMean(input)");
RCaller caller = RCaller.create(code, RCallerOptions.create());
caller.runAndReturnResult("result");
return caller.getParser().getAsDoubleArray("result")[0];
}

В этом методе мы в основном используем два объекта:

  • RCode , который представляет контекст нашего кода, включая нашу функцию, ее ввод и оператор вызова.
  • RCaller , который позволяет нам запустить наш код и получить результат обратно

Важно отметить, что RCaller не подходит для небольших и частых вычислений из-за времени, которое требуется для запуска процесса R. Это заметный недостаток.

Также RCaller работает только с R, установленным на локальной машине .

4. Ренджин

Renjin — еще одно популярное решение, доступное в сфере интеграции R. Он получил более широкое распространение, а также предлагает корпоративную поддержку .

Добавление Renjin в наш проект немного менее тривиально, поскольку нам нужно добавить репозиторий Mulesoft вместе с зависимостью Maven:

<repositories>
<repository>
<id>mulesoft</id>
<name>Mulesoft Repository</name>
<url>https://repository.mulesoft.org/nexus/content/repositories/public/</url>
</repository>
</repositories>

<dependencies>
<dependency>
<groupId>org.renjin</groupId>
<artifactId>renjin-script-engine</artifactId>
<version>RELEASE</version>
</dependency>
</dependencies>

Давайте еще раз создадим Java-оболочку для нашей функции R:

public double mean(int[] values) throws IOException, URISyntaxException, ScriptException {
RenjinScriptEngine engine = new RenjinScriptEngine();
String meanScriptContent = RUtils.getMeanScriptContent();
engine.put("input", values);
engine.eval(meanScriptContent);
DoubleArrayVector result = (DoubleArrayVector) engine.eval("customMean(input)");
return result.asReal();
}

Как мы видим, концепция очень похожа на RCaller, хотя и менее многословна , поскольку мы можем вызывать функции напрямую по имени с помощью метода eval .

Основное преимущество Renjin заключается в том, что он не требует установки R, поскольку использует интерпретатор на основе JVM. Однако в настоящее время Renjin не на 100% совместим с GNU R.

5. Резерв

Библиотеки, которые мы рассмотрели до сих пор, являются хорошим выбором для локального запуска кода. Но что, если мы хотим, чтобы наш R-скрипт вызывали несколько клиентов? Вот где Rserve вступает в игру, позволяя нам запускать R-код на удаленной машине через TCP-сервер .

Настройка Rserve включает в себя установку соответствующего пакета и запуск сервера, загружающего наш скрипт, через консоль R:

> install.packages("Rserve")
...
> library("Rserve")
> Rserve(args = "--RS-source ~/script.R")
Starting Rserve...

Теперь мы можем включить Rserve в наш проект, как обычно, добавив зависимость Maven :

<dependency>
<groupId>org.rosuda.REngine</groupId>
<artifactId>Rserve</artifactId>
<version>1.8.1</version>
</dependency>

Наконец, давайте завернем наш сценарий R в метод Java. Здесь мы будем использовать объект RConnection с адресом нашего сервера, по умолчанию 127.0.0.1:6311, если он не указан:

public double mean(int[] values) throws REngineException, REXPMismatchException {
RConnection c = new RConnection();
c.assign("input", values);
return c.eval("customMean(input)").asDouble();
}

6. ФастР

Последняя библиотека, о которой мы собираемся поговорить, — это FastR . высокопроизводительная реализация R, построенная на GraalVM . На момент написания этой статьи FastR доступен только в системах Linux и Darwin x64 .

Чтобы использовать его, нам сначала нужно установить GraalVM с официального сайта. После этого нам нужно установить сам FastR с помощью Graal Component Updater, а затем запустить конфигурационный скрипт, который идет вместе с ним:

$ bin/gu install R
...
$ languages/R/bin/configure_fastr

На этот раз наш код будет зависеть от Polyglot , внутреннего API GraalVM для встраивания различных гостевых языков в Java. Поскольку Polyglot — это общий API, мы указываем язык кода, который хотим запустить. Кроме того, мы будем использовать функцию c R для преобразования нашего ввода в вектор:

public double mean(int[] values) {
Context polyglot = Context.newBuilder().allowAllAccess(true).build();
String meanScriptContent = RUtils.getMeanScriptContent();
polyglot.eval("R", meanScriptContent);
Value rBindings = polyglot.getBindings("R");
Value rInput = rBindings.getMember("c").execute(values);
return rBindings.getMember("customMean").execute(rInput).asDouble();
}

Следуя этому подходу, имейте в виду, что он делает наш код тесно связанным с JVM . Чтобы узнать больше о GraalVM, ознакомьтесь с нашей статьей о Graal Java JIT Compiler .

7. Заключение

В этой статье мы рассмотрели некоторые из самых популярных технологий для интеграции R в Java. Подводить итоги:

  • RCaller легче интегрировать, поскольку он доступен на Maven Central.
  • Renjin предлагает корпоративную поддержку и не требует установки R на локальном компьютере, но он не на 100% совместим с GNU R.
  • Rserve можно использовать для выполнения кода R на удаленном сервере.
  • FastR обеспечивает бесшовную интеграцию с Java, но делает наш код зависимым от виртуальной машины и доступен не для каждой ОС.

Как всегда, весь код, используемый в этом руководстве, доступен на GitHub .