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

Я столкнулся с проблемой нехватки памяти в R, когда пытался загрузить несколько таблиц и отобразить их с помощью DT в блестящем.

Мне интересно, можно ли предоставить только структуру таблицы (например, без имен строк и столбцов) для DT и предварительно загрузить данные первых N строк для отображения в приложении, а затем загрузить еще N строк, когда пользователь щелкает другую страницу ( разбиение на страницы включено). Я обнаружил, что в DT есть функция dataTableAjax, которая возвращает URL-адрес Ajax и может быть запрошена DT (не знаю, как это делается)

Оригинальная JS-библиотека datatables имеет аналогичную функцию (если я не ошибаюсь), как в https://datatables.net/examples/server_side/defer_loading.html

Например,

sample_table <- data.frame(a = rnorm(1e7), b = rnorm(1e7), c = rnorm(1e7))

library(fst)

# write large data on disk
write_fst(sample_table, "sample_table.fst")

# how to load data on disk on-demand using Ajax?
shinyApp(
  ui = fluidPage(
    title = 'Server-side processing of DataTables',
    fluidRow(
      DT::dataTableOutput('tbl')
    )
  ),
  server = function(input, output, session) {
    # create a widget using an Ajax URL created above
    tbl_ajax_url <- reactiveVal({
      dataTableAjax(
        session, 
        read_fst("sample_table.fst", from = 1, to = 100, as.data.table = TRUE), 
        outputId = 'tbl')
    })
    observeEvent(input$tbl_rows_current, {
      rows <- input$tbl_rows_current
      tbl_ajax_url(dataTableAjax(
        session, 
        # random access like fst, only load required data when user click the page
        read_fst("sample_table.fst", from = min(rows), to = max(rows), 
                 as.data.table = TRUE), 
        outputId = 'tbl'))
    })

    output$tbl = DT::renderDataTable({
      datatable(data.table(
        a = numeric(), b = numeric(), c = numeric(),
        check.names = FALSE), rownames = FALSE, options = list(
          ajax = list(
            serverSide = TRUE, processing = TRUE,
            # not sure how to do this part, where url only return part of data
            url = tbl_ajax_url()
          )
        ))
    })
  }
)

Если у вас есть другие предложения, пожалуйста, дайте мне знать. Моя основная цель — предотвратить одновременную загрузку всех таблиц в R, а лишь частично загружать их по требованию.

PS: я не знаком с HTML, CSS и JS, пожалуйста, наберитесь терпения и предоставьте как можно больше подробностей, заранее спасибо!


person jonekeat    schedule 31.12.2020    source источник


Ответы (1)


Я нашел решение сам, но я просто поместил здесь, если кому-то интересно.

Используя funcFilter в renderDT, мы можем создать новый источник данных и выполнить визуализацию в DT. Я создал источник данных на диске, который считывает данные, сохраненные на диске, только при необходимости, используя fst.

Код:

sample_table <- data.frame(a = rnorm(1e7), b = rnorm(1e7), c = rnorm(1e7))

library(fst)
library(shiny)
library(DT)
library(data.table)

# write large data on disk
write_fst(sample_table, "sample_table.fst")

shinyApp(
  ui = fluidPage(
    title = 'Server-side processing of DataTables',
    fluidRow(
      DT::dataTableOutput('tbl')
    )
  ),
  server = function(input, output, session) {
    output$tbl = DT::renderDataTable({
      datatable(data.frame(
        a = numeric(), b = numeric(), c = numeric(),
        check.names = FALSE), rownames = FALSE)
    }, funcFilter = dataTablesFilterOnDisk)
  }
)

dataTablesFilterOnDisk <- function(data, params) {
  start <- as.integer(params$start)
  length <- as.integer(params$length)
  total_rows <- fst::metadata_fst("sample_table.fst")$nrOfRows
  cleanDataFrame <- function(x, escape = params$escape) {
    if (escape != "false") {
      k = seq_len(ncol(x))
      if (escape != "true") {
        k = k[as.integer(strsplit(escape, ",")[[1]])]
      }
      for (j in k) if (is.character(x[, j]) || is.factor(x[, j])) 
        x[, j] = htmltools::htmlEscape(x[, j])
    }
    x = unname(x)  # remove column names
    if (!is.data.frame(x)) return(x)
    for (j in seq_len(ncol(x))) {
      xj = x[, j]
      xj = unname(xj)  # remove names
      dim(xj) = NULL  # drop dimensions
      if (is.table(xj)) xj = c(xj)  # drop the table class
      x[[j]] = xj
    }
    unname(x)
  }
  row_range <- c(start + 1L, start + length)

  data <- fst::read_fst("sample_table.fst", columns = colnames(data), 
                        from = row_range[1L], 
                        to = min(row_range[2L], total_rows))
  
  list(draw = as.integer(params$draw), recordsTotal = total_rows, 
       recordsFiltered = total_rows, data = cleanDataFrame(data), 
       DT_rows_all = NULL, 
       DT_rows_current = seq.int(row_range[1L], row_range[2L], by = 1L))
}

Смотрите больше ссылок:

  1. https://github.com/grahamrp/dtdatasources
person jonekeat    schedule 04.01.2021