Использование ggplotly rangelider для интерактивной относительной производительности (доходность акций) - PullRequest
1 голос
/ 12 июня 2019

Я пытаюсь сделать интерактивный график производительности акций из R. Это сравнение относительной производительности нескольких акций.Производительность каждой акции должна начинаться с 0%.

Для статических графиков я бы использовал dplyr group_by и mutate для расчета производительности (см. Мой код).

С ggplot2 и plotly / ggplotly, rangeslider() позволяет интерактивно выбирать диапазон оси x.Теперь я хотел бы, чтобы исполнение начиналось с 0 с любого выбранного начального диапазона.

Как я могу либо перенести вычисление dplyr в график, либо использовать цикл обратной связи для пересчета при изменении диапазона?

В идеале он должен использоваться в статическом RMarkdown HTML.В качестве альтернативы я бы также переключился на Shiny.

Я пробовал несколько вариантов для Rangelider .Также я попытался с ggplot stat_function, но не смог достичь желаемого результата.Также я нашел рисунков с dyRangeSelector.Но и здесь я сталкиваюсь с той же проблемой.

Это мой код:

library(plotly)
library(tidyquant)

stocks <- tq_get(c("AAPL", "MSFT"), from = "2019-01-01")

range_from <- as.Date("2019-02-01")

stocks_range <- stocks %>% 
  filter(date >= range_from) %>% 
  group_by(symbol) %>% 
  mutate(performance = adjusted/first(adjusted)-1)

p <- stocks_range %>% 
  ggplot(aes(x = date, y = performance, color = symbol)) +
  geom_line()

ggplotly(p, dynamicTicks = T) %>%
  rangeslider(borderwidth = 1) %>%
  layout(hovermode = "x", yaxis = list(tickformat = "%"))

Ответы [ 2 ]

2 голосов
/ 27 июня 2019

Если вы не хотите использовать shiny, вы можете либо использовать опцию dyRebase в dygraphs, либо вам нужно вставить собственный код javascript в plotly. В обоих примерах я делаю ребаз, а не ноль.

Вариант 1: с dygraphs

library(dygraphs)
library(tidyquant)
library(timetk)
library(tidyr)

stocks <- tq_get(c("AAPL", "MSFT"), from = "2019-01-01")

stocks %>% 
  dplyr::select(symbol, date, adjusted) %>% 
  tidyr::spread(key = symbol, value = adjusted) %>% 
  timetk::tk_xts() %>% 
  dygraph() %>%
  dyRebase(value = 1) %>% 
  dyRangeSelector()

Обратите внимание, что `dyRebase (значение = 0) не работает.

Вариант 2: с plotly с использованием обработчиков событий . Я стараюсь избегать ggplotly, поэтому мое решение plot_ly. Здесь выбор времени осуществляется только с помощью увеличения, но я думаю, что это может быть сделано и с помощью селектора диапазона. Код javascript в onRenderRebaseTxt перебрасывает каждую трассу к первой видимой точке данных (с учетом возможных пропущенных значений). Он вызывается только с событием relayout, следовательно, первый ребазинг должен быть выполнен перед сюжетом.

library(tidyquant)
library(plotly)
library(htmlwidgets)
library(dplyr)

stocks <- tq_get(c("AAPL", "MSFT"), from = "2019-01-01")

pltly <- 
  stocks %>% 
  dplyr::group_by(symbol) %>% 
  dplyr::mutate(adjusted = adjusted / adjusted[1L]) %>% 
  plotly::plot_ly(x = ~date, y = ~adjusted, color = ~symbol,
                  type = "scatter", mode = "lines") %>% 
  plotly::layout(dragmode = "zoom", 
                 datarevision = 0)

onRenderRebaseTxt <- "
  function(el, x) {
el.on('plotly_relayout', function(rlyt) {
        var nrTrcs = el.data.length;
        // array of x index to rebase to; defaults to zero when all x are shown, needs to be one per trace
        baseX = Array.from({length: nrTrcs}, (v, i) => 0);
        // if x zoomed, increase baseX until first x point larger than x-range start
        if (el.layout.xaxis.autorange == false) {
            for (var trc = 0; trc < nrTrcs; trc++) {
                while (el.data[[trc]].x[baseX[trc]] < el.layout.xaxis.range[0]) {baseX[trc]++;}
            }   
        }
        // rebase each trace
        for (var trc = 0; trc < nrTrcs; trc++) {
            el.data[trc].y = el.data[[trc]].y.map(x => x / el.data[[trc]].y[baseX[trc]]);
        }
        el.layout.yaxis.autorange = true; // to show all traces if y was zoomed as well
        el.layout.datarevision++; // needs to change for react method to show data changes
        Plotly.react(el, el.data, el.layout);

});
  }
"
htmlwidgets::onRender(pltly, onRenderRebaseTxt)
0 голосов
/ 16 июня 2019

Я нашел решение с plotly_relayout, которое считывает видимый диапазон оси X. Это используется для пересчета производительности. Это работает как блестящее приложение. Вот мой код:

library(shiny)
library(plotly)
library(tidyquant)
library(lubridate)

stocks <- tq_get(c("AAPL", "MSFT"), from = "2019-01-01")

ui <- fluidPage(
    titlePanel("Rangesliding performance"),
        mainPanel(
           plotlyOutput("plot")
        )
)

server <- function(input, output) {

  d <- reactive({ e <- event_data("plotly_relayout")
                  if (is.null(e)) {
                    e$xaxis.range <- c(min(stocks$date), max(stocks$date))
                  }
                  e })

  stocks_range_dyn <- reactive({
    s <- stocks %>%
      group_by(symbol) %>%
      mutate(performance = adjusted/first(adjusted)-1)

    if (!is.null(d())) {
      s <- s %>%
        mutate(performance = adjusted/nth(adjusted, which.min(abs(date - date(d()$xaxis.range[[1]]))))-1)
    }

    s
  })

    output$plot <- renderPlotly({

      plot_ly(stocks_range_dyn(), x = ~date, y = ~performance, color = ~symbol) %>% 
        add_lines() %>%
        rangeslider(start =  d()$xaxis.range[[1]], end =  d()$xaxis.range[[2]], borderwidth = 1)

      })
}

shinyApp(ui = ui, server = server)

Определение начала / конца ползунка диапазонов работает только с plot_ly, а не с объектом ggplot, преобразованным в ggplotly. Я не уверен, что это ошибка, поэтому открыл проблему на Github .

...