2014-06-12 32 views
7

Zastanawiam się, czy istnieją sztuczki/sposoby, w których mógłbym buforować działki generowane przez naszą błyszczącą aplikację.Buforowanie działek w R/Shiny

Tło:

Robimy nieco obliczyć intensywnych obliczeń, które ostatecznie prowadzą do działki. Już buforuję (używając memoise) obliczenia zrobione, globalnie w błyszczącym, ale wciąż trwa około 0,75 sekundy, aby wyrenderować wykres. Zastanawiałem się tylko, czy możemy zmniejszyć ten czas, usuwając czas potrzebny na renderowanie obrazu i czy istnieją już zręczne sposoby na jego wykonanie.

Więcej szczegółów:.

używam siatki aby stworzyć wykres (mapę cieplną w tym przypadku Idealnie chciałby buforowanie być oparta na dysku jako przechowywania działek w pamięci przyzwyczajenie skalowanie

Dzięki. ! -Abhi

+0

Zobacz przykład na '? RenderImage', to może dać ci kilka pomysłów. Zasadniczo chcesz zapamiętać funkcję fabuły, która zwraca plik PNG, myślę; i użyj renderImage, aby wywołać tę zapamiętaną funkcję. –

+0

Dzięki Joe. Wszelkie pomysły na temat tego, jak moglibyśmy uzyskać efekt lśnienia, aby automatycznie skalować statyczne obrazy renderujemy za pomocą renderImage. – Abhi

Odpowiedz

4

Zakładając, że używasz ggplot (który z błyszczącą, założę się sprawiedliwego założenie).

  1. Utwórz pustą listę do przechowywania groba, na przykład Plist.
  2. Gdy żądanie użytkownika wykresem, utwórz skrót ciąg w oparciu o błyszczących wejść
  3. Sprawdź, czy wykres jest już zapisana, np hash %in% names(Plist)
  4. Jeśli tak, służyć do tego wykresu
  5. Jeśli nie, generowania wykres, zapisać Grob do listy, nazwa elementu poprzez mieszania, np Plist[hash] <- new_graph
+0

dzięki za szybką odpowiedź. Używam siatki do tego ... po prostu zastanawiasz się, co masz na myśli przez Groba, a także przechowywanie wielu wątków w pamięci może być kosztowne, a także chciałbym, aby buforowanie było trwałe po ponownym uruchomieniu serwera. Jakieś pakiety, które buforują działki na dysk i renderują je stamtąd? – Abhi

+0

również zastanawiasz się, czy znasz jakiś pakiet, który mógłby utworzyć mieszanie łańcucha na podstawie danych wejściowych do funkcji. Byłoby to przydatne w moim przypadku. – Abhi

+0

'grob' jest po prostu' obiektem graficznym'. Możesz je zapisać na dysku jak każdy inny obiekt R. (patrz '? saveRDS'). Jednak ładowanie ich z dysku może trwać tak długo, jak obliczanie ich od zera. –

2

odpowiedź z Ricardo Saporta jest bardzo dobre, a co kiedyś rozwiązać podobny problem, ale chciałem dodać kod rozwiązanie również.

Do buforowania użyłem digest::digest(), gdzie właśnie dodałem listę parametrów dla danego wykresu do tej funkcji, aby utworzyć ciąg skrótu. Początkowo sądziłem, że będę musiał wyodrębnić ciąg mieszający z observe(), a następnie użyć instrukcji if/else, aby określić, czy powinienem wysłać ją do renderImage() lub renderPlot() na podstawie tego, czy obraz został wcześniej utworzony. Uderzałem w to przez chwilę, a następnie natknąłem się po prostu przy użyciu renderImage(). Nie jest to doskonałe zastępowanie obrazu, ale wystarczająco bliskie na potrzeby tego demo.

ui.R

library(shiny) 

fluidPage(
    sidebarLayout(
    sidebarPanel(
     sliderInput("bins", 
        "Number of bins:", 
        min = 1, 
        max = 50, 
        value = 25), 
     selectInput("plot_color", "Barplot color", 
        c("green"="green", 
         "blue"="blue")) 
    ), 
    mainPanel(
     plotOutput("distPlot", width='100%', height='480px') 
    ) 
) 
) 

i server.R

library(shiny) 

function(input, output) { 

base <- reactive({ 
    fn <- digest::digest(c(input$bins, input$plot_color)) 
    fn}) 

output$distPlot <- renderImage({ 
    filename <- paste0(base(), ".png") 
    if(filename %in% list.files()){ 
     list(src=filename) 
    } else { 
    x <- faithful[, 2] 
    bins <- seq(min(x), max(x), length.out = input$bins + 1) 
    png(filename) 
    hist(x, breaks = bins, col = input$plot_color, border = 'white') 
    dev.off() 
list(src=filename) 
    } 

    }, deleteFile = FALSE) 
} 
0

Choć obie odpowiedzi na to pytanie jest bardzo dobra, nie chciałem dodać jeszcze jeden korzystając shiny modules. Poniższy moduł przyjmuje funkcję plotowania i reaktywną wersję jego argumentów jako danych wejściowych. W końcu do utworzenia wykresu użyto do.call(plotfun, args()).

library(shiny) 

cachePlot <- function(input, output, session, plotfun, args, width = 480, height = 480, 
         dir = tempdir(), prefix = "cachedplot", deleteonexit = TRUE){ 
    hash <- function(args) digest::digest(args) 

    output$plot <- renderImage({ 
    args <- args() 
    if (!is.list(args)) args <- list(args) 
    imgpath <- file.path(dir, paste0(prefix, "-", hash(args), ".png")) 

    if(!file.exists(imgpath)){ 
     png(imgpath, width = width, height = height) 
     do.call(plotfun, args) 
     dev.off() 
    } 
    list(src = imgpath) 
    }, deleteFile = FALSE) 

    if (deleteonexit) session$onSessionEnded(function(){ 
    imgfiles <- list.files(tempdir(), pattern = prefix, full.names = TRUE) 
    file.remove(imgfiles) 
    }) 
} 

cachePlotUI <- function(id){ 
    ns <- NS(id) 
    imageOutput(ns("plot")) 
} 

Jak widzimy, moduł usuwa pliki obrazów utworzonych w razie potrzeby i daje możliwość używania niestandardowego buforowania-katalog, w przypadku potrzebne jest trwałe buforowanie (jak to jest w moim rzeczywistego usecase).

Dla przykładu użycia użyję przykładu hist(faithful[, 2]) podobnie jak Stedy.

histfaithful <- function(bins, col){ 
    message("calling histfaithful with args ", bins, " and ", col) 
    x <- faithful[, 2] 
    bins <- seq(min(x), max(x), length.out = bins + 1) 
    hist(x, breaks = bins, col = col, border = 'white') 
} 

shinyApp(
    ui = fluidPage(
    inputPanel(
     sliderInput("bins", "bins", 5, 30, 10, 1), 
     selectInput("col", "color", c("blue", "red")) 
    ), 
    cachePlotUI("cachedPlot") 
), 
    server = function(input, output, session){ 
    callModule(
     cachePlot, "cachedPlot", histfaithful, 
     args = reactive(list(bins = input$bins, col = input$col)) 
    ) 
    } 
)