Ostatnio pisałem o tym co nowego znaleźć można w trzecim wydaniu ,,Przewodnika po pakiecie R”, czyli o pakiecie plyr.
Ale nowych rzeczy jest więcej. Jest podrozdział o pakiecie knitr [następca Sweave, super prosty w użyciu, daje fantastyczne wyniki], o pakiecie slidify [jak tworzyć prezentacje w HTML5 używając tylko R i markdown, rewelacyjny pakiet] i jest podrozdział o tym jak tworzyć aplikacje www z użyciem shiny.
I dziś właśnie wklejam poniżej ten podrozdział o shiny.
Fragment książki ,,Przewodnik po pakiecie R”, Przemysław Biecek, wydawnictwo GiS, wydanie 3.
Budowa aplikacji www z pakietem shiny
W poprzednim rozdziale pokazaliśmy, jak tworzyć automatyzowalne raporty, prezentacje, ilustracje kodu z użyciem pakietów knitr
i Sweave
. Jednym z częstych zastosowań raportów jest przygotowanie zbioru zestawień dla innej osoby (klienta, przełożonego, recenzenta, kolegi z zespołu). Wadą raportu jest jego statyczność i liniowość. Raz wygenerowany raport ma określoną treść, którą zazwyczaj przegląda się strona po stronie.
Co jednak, gdybyśmy chcieli w interaktywny sposób eksplorować dane? Dać odbiorcy możliwość wyboru interesujących go parametrów i wyznaczenia opisu dla zadanych parametrów? W takiej sytuacji często stosowanym rozwiązaniem jest zbudowanie aplikacji z różnymi przyciskami, pozwalającymi na interakcję z danymi. W programie R można taką interaktywną aplikację zbudować na wiele sposobów. Najłatwiejszym a jednocześnie sposobem o olbrzymich możliwościach jest skorzystanie z pakietu shiny
.
Przedstawimy ten pakiet na przykładzie. Używając go stworzymy aplikację, którą będzie można otworzyć w przeglądarce internetowej. Dzięki temu możemy samą aplikację umieścić na serwerze i udostępnić innym użytkownikom. Jeżeli z jakichś powodów nie chcemy jej udostępniać innym, możemy też z tą aplikacją pracować lokalnie, na własnym komputerze.
Model budowy aplikacji z użyciem shiny
jest zgodny z modelem akcja-reakcja (ang. reactive programming). Jest to popularny model budowy aplikacji wokół pracy z danymi, znany np. z arkuszy kalkulacyjnych w których część komórek arkusza może zależeć od innych komórek. Zmieniając stan komórek wejściowych automatycznie zmienia się stan komórek zależnych (mówimy, że wartości są ,,odświeżone”). Kontrolując, które komórki zależą od których, możemy ,,odświeżyć” (czyli przeliczyć na nowo wartości) tylko tych komórek, które mogą się zmienić.
Podobnie będzie w aplikacji zbudowanej z biblioteką shiny
, tyle że struktury zależności pomiędzy wejściem a wyjściem nie musimy jawnie opisywać, będzie ona odtworzona przez samą bibliotekę. Takie ,,inteligentne odświeżanie” tylko tych wartości, które mogą się zmienić pozwala na budowę szybkich i jednocześnie złożonych aplikacji. Wszystkie elementy aplikacji podzielone są na elementy wejściowe i wyjściowe. Model akcja-reakcja wymaga oprogramowania sposobu w jaki elementy wejściowe wpływają na wygląd/stan/wartość elementów wyjściowych. Jeżeli w trakcie pracy zmieniona zostanie wartość jakiegoś elementu wejściowego (kliknięty przycisk, wpisana wartość liczbowa, przesunięty suwak) to przeliczone zostaną odpowiednie elementy wyjściowe. [
Zobaczmy jak to wygląda na przykładzie. Zaczniemy od instalacji i włączenia pakietu shiny
.
install.packages('shiny') library(shiny) |
Aby zbudować w pełni funkcjonalną aplikację, wystarczy stworzyć dwa pliki, domyślnie o nazwach ui.r
i server.r
. W pliku ui.r
określa się interfejs użytkownika, definiuje się elementy widoczne, określa się gdzie i jak będą wyglądać elementy wejściowe i elementy wyjściowe. W pliku server.r
określa się funkcje/transformacje, wyznaczające wartości elementów wyjściowych.
I tyle. Plik ui.r
określa jak wygląda aplikacja, plik server.r
określa jak aplikacja funkcjonuje.
Skoro to takie proste, to zbudujmy prostą aplikację, która odczyta z Internetu dane o cenach mieszkań w Warszawie a następnie przedstawi graficznie i tekstowo rozkład cen za metr kwadratowy. Aplikacja ma umożliwiać określenie dzielnicy Warszawy, którą chcemy analizować, oraz określenie powierzchni mieszkań, które nas interesują. Jako wynik chcemy zobaczyć jak wygląda rozkład cen za metr kwadratowy mieszkań z określonej dzielnicy o określonym rozmiarze.
W poniższym przykładzie wykorzystamy dane pochodzące z portalu SmarterPoland.pl
. Zacznijmy od ich wczytania.
mieszkaniaKWW2011 <- read.table("http://tofesi.mimuw.edu.pl/~cogito/smarterpoland/mieszkaniaKWW2011/mieszkaniaKWW2011.csv", row.names=NULL, sep=";", header=TRUE, colClasses=c("factor", "factor", "numeric", "numeric", "factor", "numeric", "numeric", "factor", "Date")) |
Zobaczmy co jest w dwóch pierwszych wierszach tego zbioru danych.
head(mieszkaniaKWW2011, 2) ## miasto ulica pokoi powierzchnia pietro cena ## 1 Warszawa skwer wyszynskiego 2 56 3 550000 ## 2 Warszawa barska 2 50 2 497000 ## cenam2 dzielnica data ## 1 9822 Wola 2011-09-13 ## 2 9940 Ochota 2011-09-13 |
Wczytane dane zawierają informacje o cenach ofertowych różnych mieszkań z Warszawy i innych miast (te inne miasta nas na razie nie interesują) z lat 2007-2011. Analiza takich danych to bardzo ciekawe zadanie, ale wykraczające poza zakres tej książki. Skupimy się tutaj na wykorzystaniu tych danych do ilustracji działania biblioteki shiny
. [
Zacznijmy od określenia interfejsu, a zarazem pliku ui.r
. Chcemy oprogramować aplikację wyglądającą tak, jak na rysunkach [fig:shiny1] – [fig:shiny3]. Posiadającą pole wyboru określające dzielnice i suwak do wyboru zakresu powierzchni, dwa panele na których będzie przedstawiony histogram i tekstowy opis cen metra kwadratowego.
Rysunek 1. Początkowy wygląd oprogramowanej aplikacji. Po lewej stronie widoczne są dwa elementy wejściowe. Po prawej można przełączać się pomiędzy elementami wyjściowymi.
Aplikację zawierającą blok wejścia i wyjścia, można utworzyć z użyciem funkcji pageWithSidebar()
, której przekazuje się opis elementów wejściowych i wyjściowych. Zacznijmy od opisu elementów wyjściowych. W tym przykładzie do zdefiniowania pola wyboru użyjemy funkcji selectInput()
(argumenty określają identyfikator tego wejścia, nazwę, listę możliwych wartości i wartość domyślną), a do zdefiniowania suwaka użyjemy funkcji sliderInput()
(kolejne argumenty to identyfikator tego wejścia, zakres zmienności i wartości domyślne). Biblioteka shiny
udostępnia wiele szablonów różnego typu elementów wejściowych (listy jednokrotnego lub wielokrotnego wyboru, pola tekstowe itp), patrz . Elementy wyjściowe to dwa panele, umieszczone w aplikacji jako dwie zakładki. Na pierwszym panelu będziemy wyświetlać wykres z użyciem funkcji plotOutput()
(argumentem jest identyfikator pola wyjściowego, tutaj o nazwie wyjscieHistogram
). Na drugim panelu wyświetlimy opis tekstowy z użyciem funkcji verbatimTextOutput()
(argumentem jest identyfikator wyjścia, tutaj wyjscieTabela
). Biblioteka shiny
ma dostępnych wiele szablonów różnego typu elementów wyjściowych, pozwalających na wyświetlenie tabeli, pobranie pliku, wyświetlenie kodu HTML itp. [
Poniżej znajduje się zawartość pliku ui.r
, opisująca interfejs.
# plik: ui.r shinyUI(pageWithSidebar( headerPanel("Ceny mieszkan"), sidebarPanel( selectInput(inputId = "dzielnica", label = "Ktora dzielnica Cie interesuje", choices = c("Bemowo", "Bialoleka", "Bielany", "Mokotow", "Ochota", "Praga-Polnoc", "Praga-Poludnie", "Srodmiescie", "Targowek", "Ursus", "Ursynow", "Wilanow", "Wlochy", "Wola", "Zoliborz"), selected = "Bemowo"), sliderInput("zakres", "Jaka powierzchnia:", min = 10, max = 200, value = c(10,200), step= 10)), mainPanel( tabsetPanel( tabPanel("Histogram", plotOutput("wyjscieHistogram")), tabPanel("Podsumowanie", verbatimTextOutput("wyjscieTabela")) )) )) |
Mając zdefiniowany interfejs, powinniśmy teraz zdefiniować transformacje. Opiszmy sposób wyznaczania wartości elementów wyjściowych, czyli zawartość pliku server.r
(kod poniżej). Na początku tego pliku wczytujemy dane. Może to potrwać do kilkunastu sekund (dane pobierane są z Internetu). Pobieranie danych będzie wykonane tylko raz na sesję, uruchomiona aplikacja będzie pamiętała te dane w ramach swojej sesji. [ Następnie definiujemy funkcje generujące wartość elementów wyjściowych. Na poniższym przykładzie opisane są elementy wyjscieHistogram
i wyjscieTabela
. Oba są tworzone na podstawie szablonów generujących wykresy (funkcja renderPlot()
) i opisy tekstowe (funkcja renderPrint()
).
Na poniższych przykładach w przypadku obu funkcji, najpierw z całego zbioru danych wybierane są tylko wiersze odpowiadające mieszkaniom z określonej dzielnicy i o określonej powierzchni. Następnie te wiersze są przedstawiane graficznie lub tekstowo. Warto zauważyć, że w opisie elementów wyjściowych wykorzystywane są wartości określone przez elementy wejściowe. Na poniższym przykładzie zmienna input$dzielnica
przyjmuje wartość wybraną w polu wyboru. Wektor input$zakres
to dwie liczby określone przez suwak aplikacji.
Cały kod przykładowego pliku server.r
wygląda następująco.
# plik: server.r mieszkaniaKWW2011 <- read.table("http://tofesi.mimuw.edu.pl/~cogito/smarterpoland/mieszkaniaKWW2011/mieszkaniaKWW2011.csv", row.names=NULL, sep=";", header=TRUE, colClasses=c("factor", "factor", "numeric", "numeric", "factor", "numeric", "numeric", "factor", "Date")) shinyServer(function(input, output) { output$wyjscieHistogram <- renderPlot({ wybrane <- mieszkaniaKWW2011[ mieszkaniaKWW2011$dzielnica == input$dzielnica & mieszkaniaKWW2011$powierzchnia >= input$zakres[1] & mieszkaniaKWW2011$powierzchnia <= input$zakres[2], ] hist(wybrane$cenam2, xlim=c(5000,20000), main=paste("Rozklad ceny za metr kwadratowy", input$dzielnica)) }) output$wyjscieTabela <- renderPrint({ wybrane <- mieszkaniaKWW2011[ mieszkaniaKWW2011$dzielnica == input$dzielnica & mieszkaniaKWW2011$powierzchnia >= input$zakres[1] & mieszkaniaKWW2011$powierzchnia <= input$zakres[2], ] summary(wybrane$cenam2) }) }) |
Powyżej opisane dwa pliki to już cała aplikacja, której działanie przedstawione jest na rysunkach [fig:shiny1] – [fig:shiny3]. Aby ją uruchomić, wystarczy użyć funkcji runApp()
jako argument wskazując katalog z powyżej opisanymi plikami.
runApp('katalog_z_plikami_ui.r_server.r') ## ## Listening on port 8100 |
Jak widzimy, tworzenie własnych aplikacji jest bardzo proste, a praca do wykonania sprowadza się do zdefiniowania interfejsu oraz oprogramowania funkcji wyznaczających wartości wyjściowe. Dzięki tej prostocie biblioteka shiny
w bardzo szybkim tempie zdobywa popularność.
Rozkład cen dla mieszkań z Ochoty. Definicja z pliku ui.r została przekształcona na pole wyboru.
Zakładka z tekstowym wyjściem. Aby wyświetlić tę stronę uaktualniono wyjście o identyfikatorze wyjscieTabela.
Świetny wpis, nic tylko czekać na nowe wydanie książki:-) swoją drogą może to być świetne narzędzie do tłumaczenia statystyki.