У меня есть некоторые данные, например:
import pandas as pd
import datetime as dt
import plotly.graph_objects as go
# Example data
returns = pd.DataFrame({'time': [dt.date(2020,1,1), dt.date(2020,1,2), dt.date(2020,1,3), dt.date(2020,1,4), dt.date(2020,1,5), dt.date(2020,1,6)],
'longs': [0, 1,2,3,4,3],
'shorts': [0, -1,-2,-3,-4,-4]})
и диаграмма типа:
fig = go.Figure()
fig.add_trace(
go.Scatter(x=list(returns.time),
y=list(returns.longs),
name = "Longs",
line=dict(color = "#0c56cc")))
fig.add_trace(
go.Scatter(x=list(returns.time),
y=list(returns.shorts),
name = "Shorts",
line=dict(color = "#850411", dash = "dash")))
fig.show()
Однако, когда я увеличиваю масштаб определенного раздела, я хотел бы, чтобы данные были перенормированы с использованием первой даты в xrange. Таким образом, увеличение периода после 5 января даст мне что-то вроде:
Кажется, это возможно, используя что-то вроде fig.layout.on_change, как обсуждалось здесь. Моя попытка сделать это приведена ниже, но, похоже, она вообще не меняет фигуру.
def renormalise_returns(returns, renorm_date):
long_data = returns.melt(id_vars = ['time'])
long_data.sort_values(by = 'time', inplace=True)
cum_at_start = long_data[long_data['time'] >= renorm_date].groupby(['variable']).first().reset_index().rename(columns = {'value': 'old_value'}).drop(columns = ['time'])
long_data2 = pd.merge(long_data, cum_at_start, how = 'left', on = ['variable'])
long_data2['value'] = long_data2['value'] - long_data2['old_value']
long_data2.drop(columns = ['old_value'], inplace=True)
finn = long_data2.pivot(index = ['time'], columns = ['variable'], values = 'value').reset_index()
return finn
fig = go.FigureWidget([go.Scatter(x=returns['time'], y=returns['longs'], name='Longs', line=dict(color = "#0c56cc")),
go.Scatter(x=returns['time'], y=returns['shorts'], name = "Shorts", line=dict(color = "#850411", dash = "dash"))])
def zoom(xrange):
xrange_zoom_min, xrange_zoom_max = fig.layout.xaxis.range[0], fig.layout.xaxis.range[1]
df2 = renormalise_returns(returns, xrange_zoom_min)
fig = go.FigureWidget([go.Scatter(x=df2['time'], y=df2['longs']), go.Scatter(x=df2['time'], y=df2['shorts'])])
fig.layout.on_change(zoom, 'xaxis.range')
fig.show()






Во-первых, вам нужно преобразовать returns['time'], чтобы предотвратить ошибку сериализации JSON с данными datetime (кажется, с go.Figure() Plotly это обрабатывается, но с go.FigureWidget() это не так).
Во-вторых, при выполнении fig.layout.on_change(zoom, 'xaxis.range') обратный вызов функции zoom должен ожидать 2 параметра, первый из которых — объект макета, а второй — наблюдаемый диапазон xaxis.
Кроме того, в обратном вызове вам необходимо обновить фигуру, используя метод обновления, потому что при повторном назначении новой фигуры переменной fig ссылка на фактическую (построенную) фигуру теряется, и изменение не будет отражено на графике. .
И последнее (см. Отображение фигур с помощью ipywidgets):
Важно отметить, что
FigureWidgetне предназначен для использования структуры рендеринга [...], поэтому вам не следует использовать методshow()Figure или функциюplotly.io.show()дляFigureWidgetобъектов.
Фактически, FigureWidget также является объектом ipywidgets. ipywidgets имеют собственный дисплей repr, который позволяет отображать их с помощью структуры отображения IPython. Таким образом, в конце ячейки использование fig отдельно или display(fig) должно правильно представлять фигуру, так же, как fig.show(), но с включенным контекстом ipywidgets.
import pandas as pd
import datetime as dt
import plotly.graph_objects as go
from IPython.display import display
returns = pd.DataFrame({
'time': [dt.date(2020, 1, 1), dt.date(2020, 1, 2), dt.date(2020, 1, 3), dt.date(2020, 1, 4), dt.date(2020, 1, 5), dt.date(2020, 1, 6)],
'longs': [0, 1, 2, 3, 4, 3],
'shorts': [0, -1, -2, -3, -4, -4]
})
returns['time'] = returns['time'].astype(str)
def renormalise_returns(returns, renorm_date):
long_data = returns.melt(id_vars = ['time'])
long_data.sort_values(by = 'time', inplace=True)
cum_at_start = long_data[long_data['time'] >= renorm_date].groupby(['variable']).first().reset_index().rename(columns = {'value': 'old_value'}).drop(columns = ['time'])
long_data2 = pd.merge(long_data, cum_at_start, how = 'left', on = ['variable'])
long_data2['value'] = long_data2['value'] - long_data2['old_value']
long_data2.drop(columns = ['old_value'], inplace=True)
finn = long_data2.pivot(index = ['time'], columns = ['variable'], values = 'value').reset_index()
return finn
fig = go.FigureWidget([
go.Scatter(x=returns['time'], y=returns['longs'], name='Longs', line=dict(color = "#0c56cc")),
go.Scatter(x=returns['time'], y=returns['shorts'], name = "Shorts", line=dict(color = "#850411", dash = "dash"))
])
def zoom(layout, xrange):
xrange_zoom_min, xrange_zoom_max = xrange
df2 = renormalise_returns(returns, xrange_zoom_min)
fig.update_traces(selector=dict(name='Longs'), x=df2['time'], y=df2['longs'])
fig.update_traces(selector=dict(name='Shorts'), x=df2['time'], y=df2['shorts'])
fig.layout.on_change(zoom, 'xaxis.range')
display(fig)
Да, это работает, но я забыл одну важную вещь! Смотрите исправленный ответ. Извините, если вы потеряли время, задаваясь вопросом, почему это до сих пор не работает, мне следовало вставить весь код вчера напрямую.
Спасибо, но у меня все равно не работает. Единственное изменение, которое я сделал, заключалось в том, что вместо использования IPython.display я перешел на fig.show(), а также использовал его вstreamlit.plotly(chart), но ни один из них не отличается от случая без этой функции масштабирования. Может быть, здесь есть что-то специфическое для IPython.display?
Может быть, это странно. Со своей стороны я запускаю весь код в одной ячейке блокнота в VS Code, и он отлично работает с fig или display(fig), а с fig.show() обратный вызов не запускается. Я использую Python 3.11.6, и для задействованных пакетов я получил:plotly 5.18.0, ipykernel 6.25.2, ipython 8.16.1, ipywidgets 8.1.1.
Да, делаю это в блокноте ipython, у меня это работает! Кажется, что-то странное в fig.show() иstreamlit.plotly. Думаю, я напишу об этом проблему на GitHub.
На самом деле я не думаю, что это проблема, go.FigureWidget предназначен именно для использования в блокноте (я предполагал, что вы делаете это в блокноте!), и, как я уже упоминал, он опирается на ipywidgets, который обеспечивает взаимодействие между интерфейсом и сервером. чтобы обратный вызов мог быть запущен. Это не будет работать за пределами ноутбука, поскольку нет http-сервера, позволяющего взаимодействовать интерфейсному и внутреннему обратному вызову.
Отсюда у вас есть 2 варианта заставить его работать в окне браузера: 1. использовать Dash (который я рекомендую) или 2. использовать fig.show(post_script=js), где js — это фрагмент javascript, который регистрирует обратный вызов plotly_relayout (см. Plotly.js API) и реализует обратный вызов перенормировки на стороне клиента, что может быть сложно, если вы не знаете Plotly.js и/или не умеете работать с javascript.
Спасибо за это. Я попытался изменить функцию масштабирования, как вы предложили, но масштаб не реагирует так, как я надеялся. увеличение подмножества дат, похоже, не вызывает перенормировку. Это работает на вашей машине?