На днях в официальном блоге проекта RStudio было объявлено о выходе их нового продукта - пакета Shiny ("сияющий"), предназначенного для создания интерактивных веб-приложений на базе R. Это не первая разработка такого рода - существует несколько пакетов для создания пользовательских интерфейсов и веб-приложений на основе R (например, tcltk, RGtk2rpanel, gWidgets, gWidgetsWWW2, и другие). Однако, как утверждают разработчики, Shiny отличается особой простотой, с которой пользователи могут создавать свои приложения (в частности, не требуется знания HTML и/или Java). В этом сообщении я приведу небольшой пример, демонстрирующий возможности Shiny.

В настоящее время пакет Shiny не опубликован на сайте CRAN и поэтому для его инсталляции рекомендуется выполнить следующие команды (предполагается, что во время инсталляции Ваш компьютер подключен к Internet):

options(repos = c(RStudio = "http://rstudio.org/_packages", getOption("repos")))
install.packages("shiny")

В простейшем виде приложение, созданное при помощи Shiny, состоит из двух файлов, которые хранятся в одной папке. Один из этих обязательных файлов (должен иметь имя ui.R) содержит определения элементов пользовательского интерфейса; второй обязательный файл - server.R - содержит скрипт, определяющий работу сервера.

"Скелет" файла ui.R выглядит следующим образом:

library(shiny)
 
# Функция shinyUI() задает структуру пользовательского интерфейса:
 
shinyUI(pageWithSidebar(
 
  # Название приложения
  headerPanel("My application"),
 
  # Функция, определяющая структуру боковой панели приложения:
  sidebarPanel(),
 
  # Функция, определяющая структуру основного окна приложения:
  mainPanel()
 
))

"Скелет" файла, контролирующего работу сервера (server.R), выглядит так:

library(shiny)
 
 # Функция shinyServer() определяет логику отображения элементов
 # графического интерфейса и обновления значений различных переменных:
 shinyServer(function(input, output) {
 
})

В качестве примера я приведу код приложения, которое будет строить диаграмму размахов по данным о длине раковины моллюска Dreissena polymorpha в трех озерах Беларуси, обследованных в мае, июле и сентябре 2003 г. (Mastitsky 2012; файл с данными, полученными в ходе этого исследования, хранится на сайте figshare). У пользователя будет возможность выбирать озеро, для которого он/она желает отобразить сезонную динамику размеров раковины моллюска. (Примечание: интерфейс приложения будет выполнен на английском языке. Я пока не проверял, "дружит" ли Shiny с кириллицей, но в принципе проблем быть не должно).

У себя на компьютере я создал папку с названием dreissena_app (по адресу D:\dreissena_app) и поместил в нее два упомянутых выше файла, содержащих "скелет" будущего приложения - ui.R и server.R. Уже сейчас это приложение может быть запущено из консоли при помощи следующей команды (пакет Shiny должен быть перед этим активирован командой library(shiny)):

runApp("D:/dreissena_app")

В результате выполнения этой команды откроется окно браузера (на моей машине это по умолчанию Google Chrome) с запущенным приложением:


Как видим, приложение запускается на локальном сервере по адресу localhost:8100. На данном этапе в браузере не отображается ничего, кроме названия приложения и залитой серым цветом области, зарезервированной для элементов графического интерфейса. Начнем добавлять эти элементы.

Таблица с данными, которые будут использованы для построения графиков, содержит переменные:
  • Lake: фактор с тремя уровнями, соответствующими названиям исследованных озер (Batorino, Naroch и Myastro).
  • Month: фактор с тремя уровнями, соответствующими датам отбора проб дрейссены (May, July, September).
  • Site: фактор с 9-ю уровнями, обозначающими станции, на которых проводился отбор проб (три станции в каждом озере). В рассматриваемом примере эта переменная никак не используется.
  • ZMlength: измеренная длина раковины дрейссены (в мм). Всего было измерено 5068 моллюсков.
Подробнее с этими данными можно ознакомиться, выполнив, например, следующие команды:


dat = read.delim("https://ndownloader.figshare.com/files/97988")
 
summary(dat)
 
      Lake            Month           Site         ZMlength    
 Batorino:1219   July     :1777   S5     : 733   Min.   : 2.00  
 Myastro :1973   May      :1662   S6     : 645   1st Qu.:12.00  
 Naroch  :1876   September:1629   S7     : 645   Median :16.00  
                                  S8     : 627   Mean   :15.85  
                                  S9     : 604   3rd Qu.:20.00  
                                  S4     : 595   Max.   :35.00  
                                  (Other):1219                

Сначала добавим в боковую панель приложения выпадающее меню, позволяющее пользователю выбрать озеро, для которого необходимо построить график. Для создания такого меню служит функция selectInput(). Заменим код в файле ui.R на следующий:

library(shiny)
 
shinyUI(pageWithSidebar(
 
  # Название приложения:
  headerPanel("Dreissena shell length"),
 
  # Боковая панель с выпадающим меню:
  sidebarPanel(
    selectInput("lake_to_plot", "Select lake:",
                list("Batorino" = "Batorino", 
                     "Myastro" = "Myastro", 
                     "Naroch" = "Naroch"))
  ),
 
  mainPanel()
))

Сохранив изменения и запустив приложение, увидим боковую панель с только что созданным выпадающим меню:



Далее нам необходимо внести определенные изменения в файл, определяющий работу сервера. В частности, нужно описать, как на приложение будут подаваться входные данные и как будут выполняться и выводится на экран результаты вычислений. Приведенный ниже код для файла server.R демонстрирует несколько важных концепций, реализованных в пакете Shiny:
  • Входные данные хранятся в объекте input, выходные - в объекте output. Извлечение необходимых данных выполняется обычным для R способом - при помощи оператора $ (подробнее о работе с таблицами данных см. здесь).
  • Объект с данными, над которыми выполняются вычисления, инициализируется в момент запуска приложения.
  • Для обновления выводимой на экран информации в реальном времени используются т.н. "реактивные" функции (т.е., функции, мгновенно реагирующие на выполненные пользователем изменения тех или иных параметров).
Таким образом, назначение файла server.R состоит в том, чтобы описать правила взаимодействий между входными и выходными параметрами. Комментарии в приведенном ниже коде для файла server.R помогут лучше понять эту идею:

library(shiny)
 
# Загрузка данных из внешнего источника:
dat = read.delim("https://ndownloader.figshare.com/files/97988")
 
shinyServer(function(input, output) {
 
  # Реактивная функция, которая будет возвращать значение,
  # соответствующее выбранному пользователем названию озера:
  lake = reactive(function(){
    input$lake_to_plot
  }
  )
 
  # Заголовок графика (хранится в слоте caption объекта output);
  # обновляется в зависимости от выбора, сделанного
  # пользователем в выпадающем меню боковой панели приложения:
 
  output$caption = reactiveText(function(){
    title = lake() # актуальный в текущий момент выбор пользователя
    if(title == "Batorino") title = "Changes in lake Batorino"
    else {title = ifelse(title == "Myastro", "Changes in lake Myastro",
                         "Changes in lake Naroch")
    }
  }
  )
 
  # Построение диаграммы размахов:
  output$lakePlot = reactivePlot(function(){
    LAKE = lake()
    boxplot(ZMlength ~ Month, data = dat[dat$Lake == LAKE, ],
            main = "", xlab = "Month", ylab = "Shell length (mm)")
  }
 
  )
 
})

Таким образом, интерактивность приложения возникает благодаря использованию функций reactiveText() и reactivePlot(), которые отслеживают текущий выбор пользователя и производят обновление соответствующих переменных при изменении выбора.

В приведенном скрипте server.R мы создали две новые переменные в объекте output, а именно output$caption (заголовок графика) и output$lakePlot (собственно сам график). Чтобы эти новые объекты отображались в графическом интерфейсе приложения, необходимо внести соответствующие изменения в скрипте ui.R. В частности, заголовок графика вызывается функцией textOutput() и форматируется как h3-заголовок. График прорисовывается в основной панели (mainPanel) при помощи функции plotOutput():

library(shiny)
 
shinyUI(pageWithSidebar(
 
  # Название приложения:
  headerPanel("Dreissena shell length"),
 
  # Боковая панель с выпадающим меню:
  sidebarPanel(
    selectInput("lake_to_plot", "Select lake:",
                list("Batorino" = "Batorino", 
                     "Myastro" = "Myastro", 
                     "Naroch" = "Naroch"))
  ),
 
  mainPanel(
    h3(textOutput("caption")),
    plotOutput("lakePlot", height = "500px", width = "500px")
  )
))

Теперь наше приложение готово. Запустив его (команда runApp("D:/dreissena_app")), мы увидим страницу браузера с выпадающим меню слева и графиком для выбранного пользователем озера справа:



Созданным и хорошо отлаженным приложеним можно поделится с другими пользователями тремя разными способами:
  • Публикация кода на Gist. Оба обязательных файла (ui.R и server.R) должны быть частью опубликованного сниппета, как, например, здесь: https://gist.github.com/3239667. Для запуска приложения человек, с которым Вы им делитесь, должен иметь на своем компьютере установленные R и Shiny. Пользователю достаточно выполнить команду shiny::runGist("3239667"), где "3239667" - Gist-идентификатор сниппета. При вызове этой команды можно также указать прямую ссылку на сниппет: shiny::runGist("https://gist.github.com/3239667")
  • Директорию с файлами приложения можно заархивировать и просто послать человеку, с которым Вы им хотите поделиться. После распаковывания архива, приложение запускается как обычно, т.е. при помощи команды runApp() (см. выше). Архив с рассмотренным в этом сообщении приложением Вы может скачать здесь.
  • Наконец, приложение можно конвертировать в отдельный R-пакет.
Безусловно, наиболее удобным способом использования shiny-приложений пользователями, которые не знакомы с R, был бы их непосредственный запуск в режиме онлайн. В планах компании RStudio запустить соответствующий сервер в недалеком будущем. Стоит отметить, однако, что эта услуга будет платной (подробнее здесь).

Пакет Shiny позволяет создавать гораздо более гораздо более сложные приложения, чем рассмотренное в этом сообщении. Подробности - на сайте проекта (англ. яз.).

2 Комментарии

Анонимный написал(а)…
runApp("dreissena_app")

Listening on http://127.0.0.1:4511
Предупреждение в file(file, "rt") :
cannot open URL 'https://figshare.com/media/download/98923/97988': HTTP status was '404 Not Found'
Предупреждение: Error in file: cannot open the connection to 'http://figshare.com/media/download/98923/97988'
54: file
53: read.table
52: read.delim
Error in file(file, "rt") :
cannot open the connection to 'http://figshare.com/media/download/98923/97988'

Ссылочка подохла ((((
Sergey Mastitsky написал(а)…
Проблемная ссылка теперь заменена на рабочую.
Новые Старые