使用 ggplotly rangeslider 实现交互式相对性能(股票 returns)
Using ggplotly rangeslider for interactive relative performance (stock returns)
我正在尝试从 R 制作一个交互式股票表现图。它是为了比较几只股票的相对表现。每只股票的业绩线应从 0% 开始。
对于静态图,我会使用 dplyr group_by
和 mutate
来计算性能(参见我的代码)。
使用 ggplot2 和 plotly/ggplotly,rangeslider()
允许交互 select x 轴范围。现在我希望性能从任何起始范围 selected.
开始
我怎样才能将 dplyr 计算移到绘图中,或者在范围改变时有一个反馈循环来重新计算?
理想情况下,它应该可以在静态 RMarkdown HTML 中使用。或者我也会切换到 Shiny。
我尝试了几个 options for rangeslider. Also I tried with ggplot stat_function
but could not achieve the desired result. Also I found dygraphs,其中有 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 = "%"))
我找到了 plotly_relayout
的解决方案,它读取了可见的 x-axis 范围。这用于重新计算性能。它作为一个闪亮的应用程序工作。这是我的代码:
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)
定义 rangeslider 的 start/end 仅适用于 plot_ly
,不适用于由 ggplotly
转换的 ggplot 对象。我不确定这是否是一个错误,因此打开了一个 issue on Github.
如果您不想使用 shiny
,您可以在 dygraphs
中使用 dyRebase
选项,或者您必须在 plotly
。在这两个示例中,我都将基数改为 1,而不是 0。
选项 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(value = 0) 不起作用。
选项 2:plotly
使用 event handlers。我尽量避免 ggplotly
,因此我的解决方案是 plot_ly
。这里的时间选择只是通过缩放,但我认为它也可以通过范围选择器来完成。 onRenderRebaseTxt
中的 javascript
代码将每个跟踪重新定位到第一个可见数据点(注意可能的缺失值)。它仅在 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)
我正在尝试从 R 制作一个交互式股票表现图。它是为了比较几只股票的相对表现。每只股票的业绩线应从 0% 开始。
对于静态图,我会使用 dplyr group_by
和 mutate
来计算性能(参见我的代码)。
使用 ggplot2 和 plotly/ggplotly,rangeslider()
允许交互 select x 轴范围。现在我希望性能从任何起始范围 selected.
我怎样才能将 dplyr 计算移到绘图中,或者在范围改变时有一个反馈循环来重新计算?
理想情况下,它应该可以在静态 RMarkdown HTML 中使用。或者我也会切换到 Shiny。
我尝试了几个 options for rangeslider. Also I tried with ggplot stat_function
but could not achieve the desired result. Also I found dygraphs,其中有 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 = "%"))
我找到了 plotly_relayout
的解决方案,它读取了可见的 x-axis 范围。这用于重新计算性能。它作为一个闪亮的应用程序工作。这是我的代码:
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)
定义 rangeslider 的 start/end 仅适用于 plot_ly
,不适用于由 ggplotly
转换的 ggplot 对象。我不确定这是否是一个错误,因此打开了一个 issue on Github.
如果您不想使用 shiny
,您可以在 dygraphs
中使用 dyRebase
选项,或者您必须在 plotly
。在这两个示例中,我都将基数改为 1,而不是 0。
选项 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(value = 0) 不起作用。
选项 2:plotly
使用 event handlers。我尽量避免 ggplotly
,因此我的解决方案是 plot_ly
。这里的时间选择只是通过缩放,但我认为它也可以通过范围选择器来完成。 onRenderRebaseTxt
中的 javascript
代码将每个跟踪重新定位到第一个可见数据点(注意可能的缺失值)。它仅在 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)