Date Редакция Категория comp Теги R / Shiny / учебник

Перевод-пересказ руководства.

Этот урок покажет вам как загружать данные, а также использовать скрипты и пакеты R в ваших приложениях на Shiny. На этом пути мы создадим довольно продвинутое приложение, отображающее данные переписи населения в США (US Census).

views.png

counties.rds

counties.rds — это набор (dataset) демографических данных по каждому округу США, собранный в пакете расширения R UScensus2010. Вы можете скачать этот файл здесь.

После того как скачаете файл:

  • создайте новую папку с именем data в каталоге вашего приложения census-app;
  • переместите counties.rds в папку data.

Когда всё будет готово, каталог вашего приложения census-app будет выглядеть так:

example2-folder.png

Набор данных в counties.rds включает в себя:

  • имя (name) каждого округа (county) в США;
  • общую численность населения округа (total.pop);
  • процент жителей округа, являющихся белыми (white), чёрными (black), латиноамериканцами (hispanic) или азиатами (asian).
counties <- readRDS("census-app/data/counties.rds")
head(counties)
             name total.pop white black hispanic asian
1 alabama,autauga     54571  77.2  19.3      2.4   0.9
2 alabama,baldwin    182265  83.5  10.9      4.4   0.7
3 alabama,barbour     27457  46.8  47.8      5.1   0.4
4    alabama,bibb     22915  75.0  22.9      1.8   0.1
5  alabama,blount     57322  88.9   2.5      8.1   0.2
6 alabama,bullock     10914  21.9  71.0      7.1   0.2

helpers.R

helpers.R — это скрипт на R, который поможет вам создать фоновую картограмму (или хороплет, по англ.: choropleth map), вроде той, что показана выше. Фоновая картограмма — это карта, данные на которую наносятся при помощи окраски разной степени насыщенности. В нашем случае helpers.R создаёт percent_map — функцию, отображающую на карте данные из counties.rds. Здесь вы можете скачать helpers.R.

helpers.R использует пакеты R maps и mapproj. Если вы ещё не установили эти пакеты, сделайте это перед тем как приступить к созданию приложения. Выполните

> install.packages(c("maps", "mapproj"))

Сохраните helpers.R в каталоге вашего приложения census-app, как показано ниже.

example2-folder2.png

Функция percent_map из helpers.R принимает на вход пять аргументов:

Аргумент Значение
var вектор-столбец из набора данных counties.rds
color строка, задающая название цвета (см. функцию colors())
legend.title строка заголовка подписи к легенде на графике
max максимальное значение насыщенности (по умолчанию, 100)
min минимальное значение насыщенности (по умолчанию, 0)

Чтобы построить картограмму по данным округов, вы можете запустить percent_map в командной строке:

library(maps)
library(mapproj)
source("census-app/helpers.R")
counties <- readRDS("census-app/data/counties.rds")
percent_map(counties$white, "darkgreen", "% white")

Примечание. В приведенном выше коде предполагается, что census-app является подкаталогом вашего рабочего каталога. Удостоверьтесь, что это действительно так. Чтобы изменить расположение рабочего каталога, выберите в меню RStudio последовательно Session > Set Working Directory > Choose Directory… или используйте функцию setwd() в командной строке.

percent_map строит картограмму по данным для округов. Вот как выглядит картограмма, отображающая процент белого населения округа, выполненная в оттенках зелёного цвета:

census4.png

Загрузка файлов и пути к ним

Взгляните на размещенный выше код. Чтобы использовать percent_map, мы сначала запускаем helpers.R с помощью функции source, а затем загружаем counties.rds функцией readRDS. Кроме того, мы загружаем библиотеки library(maps) и library(mapproj).

Чтобы использовать percent_map в вашем приложении нужно, чтобы Shiny вызвал те же самые функции, но с другими значениями аргументов. source и readRDS используют в качестве аргументов путь к файлу и эти пути в приложении Shiny отличаются от тех, что используются в командной строке.

Когда Shiny выполняет команды из скрипта server.R, он трактует все пути к файлам так, словно бы они все начинаются от каталога, в котором находится server.R. Другими словами, каталог где вы сохранили server.R становится рабочим каталогом вашего Shiny-приложения.

Так как вы сохранили helpers.R в том же каталоге, что и server.R, то указать Shiny загрузить его можно командой

source("helpers.R")

Поскольку вы сохранили counties.rds в подкаталоге (с именем data) каталога с server.R, то загрузить counties.rds можно так:

counties <- readRDS("data/counties.rds")

Пакеты maps и mapproj загружаются обычным способом

library(maps)
library(mapproj)

не требующим указания пути к файлу.

Выполнение

Shiny выполнит все команды, если вы поместите их в скрипт server.R. Но то как часто будет выполняться команда зависит от места в скрипте, куда вы её поместите. Соотвественно, расположение команды скажется на производительности вашего приложения.

Shiny выполняет некоторые разделы server.R чаще, чем остальные.

Shiny выполнит скрипт целиком как только вы первый раз вызовите runApp. Это заставит Shiny запустить shinyServer. shinyServer в свою очередь в своём первом аргументе передаёт Shiny безымянную функцию.

run-once.png

Shiny сохраняет безымянную функцию до тех пор, пока ваше приложение не посетит новый пользователь. С его приходом Shiny запустит безымянную функцию снова, всего один раз — и так до следующего посетителя. Такое поведение безымянной функции помогает Shiny строить различные наборы реагирующих объектов для каждого из пользователей приложения.

run-once-per-user.png

Как только пользователь изменит состояние виджета, Shiny перезапустит команды на языке R, описывающие поведение объекта, который отвечает за реакцию виджета. Если пользователь окажется слишком активными, эти команды будут выполнятся очень часто, много раз в секунду.

run-many-times.png

Итак, вот что мы установили:

  • скрипт server.R выполняется один раз, когда вы запускаете ваше приложение;
  • безымянная функция внутри shinyServer запускается один раз за каждое посещение пользователем вашего приложения;
  • команды R внутри функций render* выполняются много раз. Shiny запускает их всякий раз как только пользователь изменяет виджет.

Как можно использовать эту информацию?

Скрипты с источниками функций (загружаемые с помощью source), загрузка библиотек и чтение наборов данных должны помещаться в начале файла server.R, вне функции shinyServer. Shiny запустит этот код всего один раз — ровно столько, сколько нужно для настройки параметров вашего сервера, позволяющих выполнить команды R, что находятся внутри shinyServer.

Определять пользовательские объекты нужно внутри безымянной функции из shinyServer, но за пределами вызовов функций render*. Это касается тех объектов, для которых предполагается, что каждый пользователь должен располагать своим личным экземпляром такого объекта. Например, это может быть объект, который записывает информацию о сеансе работы конкретного пользователя. Этот код будет выполняться один раз для каждого пользователя.

Внутри функций render* должен размещаться только код, который Shiny будет выполнять всякий раз, чтобы заново перестроить объект. Shiny будет повторно выполнять весь код в render* каждый раз, как только пользователь изменяет виджет, за состояние которого этот фрагмент кода отвечает. Это может происходить довольно часто.

В целом следует избегать размещения внутри функций render любого кода, кроме того, что там нужен. Иначе избыточный код будет замедлять работу вашего приложения.

Ваша очередь. Часть 1

Скопируйте и вставьте следующие файлы ui.R и server.R в каталог census-app вашего приложения. Затем добавьте в server.R следующие строки:

source("helpers.R")
counties <- readRDS("data/counties.rds")
library(maps)
library(mapproj)

Убедитесь, что вы поместили эти строки в нужное место файла.

Примечание: это первый из двух шагов, которые нужны, чтобы доделать наше приложение. Выберите наилучшее место для вставки показанного выше кода, но пока не пытайтесь запускать приложение. Оно будет возвращать ошибку, пока мы не заменим строку-заглушку # some arguments на настоящий код в разделе Ваша очередь. Часть 2.

ui.R

# ui.R

shinyUI(fluidPage(
  titlePanel("censusVis"),

  sidebarLayout(
    sidebarPanel(
      helpText("Create demographic maps with 
        information from the 2010 US Census."),

      selectInput("var", 
        label = "Choose a variable to display",
        choices = c("Percent White", "Percent Black",
          "Percent Hispanic", "Percent Asian"),
        selected = "Percent White"),

      sliderInput("range", 
        label = "Range of interest:",
        min = 0, max = 100, value = c(0, 100))
    ),

    mainPanel(plotOutput("map"))
  )
))

server.R

# server.R

shinyServer(
  function(input, output) {

    output$map <- renderPlot({

        percent_map( # some arguments )
    })

  }
)

Ответы. Часть 1

Так как вашему приложению нужно загрузить helpers.R и counties.rds лишь один раз, то разместить команды загрузки следует за пределами функции shinyServer. Сделаем это перед кодом shinyServer. Здесь же удобно поместить команду загрузки библиотеки maps (которую использует percent_map).

# server.R
library(maps)
library(mapproj)
source("helpers.R")
counties <- readRDS("data/counties.rds")

shinyServer(
  function(input, output) {

    output$map <- renderPlot({

        percent_map( # some arguments )
    })

  }
)

Вы можете спросить: "Разве не каждому пользователю нужна своя копия counties и percent_map?" (что означает, что этот код нужно поместить внутрь безымянной функции в shinyServer). Отвечаем: нет, не каждому.

Имейте в виду, что компьютер пользователя не будут запускать ни строчки кода вашего Shiny-приложения. В действительности, компьютер пользователя даже не увидит этот код. Вместо этого другой компьютер, используемый в качестве сервера, будет выполнять весь R-код, необходимый для всех пользователей. Затем он будет посылать результаты этой работы пользователям в виде HTML-элементов.

Так вот, чтобы проделать все эти расчеты, ваш сервер будет опираться на единственную глобальную копию counties.rds и percent_map. Создавать отдельные объекты для каждого пользователя нужно только тогда, когда эти объекты должны иметь разные значения для каждого из пользователей.

Доделываем приложение

Приложение censusVis содержит один реактивный объект — график с именем "map". Этот график строится с помощью функции percent_map, которая принимает пять аргументов:

  • три первых аргумента, var, color и legend.title зависят от значения, выбранного в виджете-списке;
  • последние два аргумента, max и min, должны быть равны максимальному (max) и минимальному (min) значениям виджета-ползунка.

Скрипт server.R, приведенный ниже, показывает один из способов передачи "реактивных" аргументов в percent_map. R-функция switch может преобразовать вывод из списка в нужные вам значения. Однако наш скрипт всё ещё неполон. В нем не указаны значения для color, legend.title, max и min.

Примечание: скрипт в его нынешнем виде работать не будет. Его ещё нужно доработать и это будет вашем заданием в разделе Ваша очередь. Часть 2.

# server.R

library(maps)
library(mapproj)
counties <- readRDS("data/counties.rds")
source("helpers.R")

shinyServer(
  function(input, output) {
    output$map <- renderPlot({
      data <- switch(input$var, 
        "Percent White" = counties$white,
        "Percent Black" = counties$black,
        "Percent Hispanic" = counties$hispanic,
        "Percent Asian" = counties$asian)

      percent_map(var = data, color = ?, legend.title = ?, max = ?, min = ?)
    })
  }
)

Ваша очередь. Часть 2.

Пора довести наше приложение censusVis до рабочего состояния.

Когда вы будете готовы к выгрузке вашего приложения на сервер, сохраните файлы server.R и ui.R и запустите runApp("census-app"). Если всё работает как должно,то ваше приложение будет выглядеть так как показано на картинке ниже.

Вам нужно решить

  • как создать значения аргумента для percent_map и
  • куда поместить код, создающий эти аргументы.

Помните, вы что вы работаете со значениями аргументов, которые должны переключатся всякий раз, когда пользователь изменяет соответствующий виджет. Когда закончите, ну или если застрянете, можете прочесть наш вариант ответа ниже.

censusvis.png

Ответы. Часть 2

Простая версия server.R:

# server.R

library(maps)
library(mapproj)
counties <- readRDS("data/counties.rds")
source("helpers.R")

shinyServer(
  function(input, output) {
    output$map <- renderPlot({
      data <- switch(input$var, 
        "Percent White" = counties$white,
        "Percent Black" = counties$black,
        "Percent Hispanic" = counties$hispanic,
        "Percent Asian" = counties$asian)

     color <- switch(input$var, 
        "Percent White" = "darkgreen",
        "Percent Black" = "black",
        "Percent Hispanic" = "darkorange",
        "Percent Asian" = "darkviolet")

      legend <- switch(input$var, 
        "Percent White" = "% White",
        "Percent Black" = "% Black",
        "Percent Hispanic" = "% Hispanic",
        "Percent Asian" = "% Asian")

      percent_map(var = data, 
        color = color, 
        legend.title = legend, 
        max = input$range[2], 
        min = input$range[1])
    })
  }
)

Более короткая версия server.R:

# server.R

library(maps)
library(mapproj)
counties <- readRDS("data/counties.rds")
source("helpers.R")


shinyServer(
  function(input, output) {
    output$map <- renderPlot({
      args <- switch(input$var,
        "Percent White" = list(counties$white, "darkgreen", "% White"),
        "Percent Black" = list(counties$black, "black", "% Black"),
        "Percent Hispanic" = list(counties$hispanic, "darkorange", "% Hispanic"),
        "Percent Asian" = list(counties$asian, "darkviolet", "% Asian"))

      args$min <- input$range[1]
      args$max <- input$range[2]

      do.call(percent_map, args)
    })
  }
)

и

# ui.R

shinyUI(fluidPage(
  titlePanel("censusVis"),

  sidebarLayout(
    sidebarPanel(
      helpText("Create demographic maps with 
        information from the 2010 US Census."),

      selectInput("var", 
        label = "Choose a variable to display",
        choices = c("Percent White", "Percent Black",
          "Percent Hispanic", "Percent Asian"),
        selected = "Percent White"),

      sliderInput("range", 
        label = "Range of interest:",
        min = 0, max = 100, value = c(0, 100))
    ),

    mainPanel(plotOutput("map"))
  )
))

Резюме

Теперь вы умеете создавать приложения на Shiny, которые загружают скрипты, библиотеки R и наборы данных.

Имейте в виду:

  • Каталог, где находится файл server.R, станет рабочим каталогом вашего приложения.
  • Shiny выполнит код, помещенный в начале server.R до shinyServer, только один раз в течение жизни приложения.
  • Shiny будет выполнять код, помещенный внутри shinyServer, множество раз. Так что это может замедлить работу приложения.

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

Становясь более сложными, ваши приложения могут превратиться в неэффективные и медленные. Урок 6 покажет вам как разработать быстрые модульные приложения, реагирующие на действия пользователей.



Комментарии

comments powered by Disqus