Plotly — перенормировка данных в ответ на увеличение масштаба графика

У меня есть некоторые данные, например:

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()
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
59
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Во-первых, вам нужно преобразовать 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)

Спасибо за это. Я попытался изменить функцию масштабирования, как вы предложили, но масштаб не реагирует так, как я надеялся. увеличение подмножества дат, похоже, не вызывает перенормировку. Это работает на вашей машине?

Stuart 12.06.2024 23:31

Да, это работает, но я забыл одну важную вещь! Смотрите исправленный ответ. Извините, если вы потеряли время, задаваясь вопросом, почему это до сих пор не работает, мне следовало вставить весь код вчера напрямую.

EricLavault 13.06.2024 13:01

Спасибо, но у меня все равно не работает. Единственное изменение, которое я сделал, заключалось в том, что вместо использования IPython.display я перешел на fig.show(), а также использовал его вstreamlit.plotly(chart), но ни один из них не отличается от случая без этой функции масштабирования. Может быть, здесь есть что-то специфическое для IPython.display?

Stuart 13.06.2024 14:05

Может быть, это странно. Со своей стороны я запускаю весь код в одной ячейке блокнота в 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.

EricLavault 13.06.2024 15:00

Да, делаю это в блокноте ipython, у меня это работает! Кажется, что-то странное в fig.show() иstreamlit.plotly. Думаю, я напишу об этом проблему на GitHub.

Stuart 13.06.2024 17:17
github.com/plotly/plotly.py/issues/4634
Stuart 13.06.2024 17:25

На самом деле я не думаю, что это проблема, go.FigureWidget предназначен именно для использования в блокноте (я предполагал, что вы делаете это в блокноте!), и, как я уже упоминал, он опирается на ipywidgets, который обеспечивает взаимодействие между интерфейсом и сервером. чтобы обратный вызов мог быть запущен. Это не будет работать за пределами ноутбука, поскольку нет http-сервера, позволяющего взаимодействовать интерфейсному и внутреннему обратному вызову.

EricLavault 13.06.2024 17:48

Отсюда у вас есть 2 варианта заставить его работать в окне браузера: 1. использовать Dash (который я рекомендую) или 2. использовать fig.show(post_script=js), где js — это фрагмент javascript, который регистрирует обратный вызов plotly_relayout (см. Plotly.js API) и реализует обратный вызов перенормировки на стороне клиента, что может быть сложно, если вы не знаете Plotly.js и/или не умеете работать с javascript.

EricLavault 13.06.2024 17:53

Другие вопросы по теме