Plotly Python: выравнивание осей X на сгруппированной гистограмме с несколькими осями Y

1

У меня есть сгруппированная гистограмма с двумя осями у каждого из них в другом масштабе. Я пытаюсь выровнять ось x (y = 0) обеих групп. Я нашел несколько ссылок link1 и link2, в которых должен быть установлен параметр rangemode = 'zero', однако мои данные состоят из отрицательных значений, из-за которых я полагаю, что установка rangemode в ноль не работает.

Вот мой код:

import plotly.offline as plt
import plotly.graph_objs as go
traces = [go.Bar(x=[1,2,3,4], y=[-1,2,-3,4], name='y actual'), 
          go.Bar(x=[1], y=[0], name='y dummy', hoverinfo='none', showlegend=False), 
          go.Bar(x=[1],y=[0],yaxis='y2', name='y2 dummy', hoverinfo='none', showlegend=False),
          go.Bar(x=[1,2,3,4], y=[22, 2, 13, 25], yaxis='y2', name='y2 actual')]
layout = go.Layout(barmode='group',
                   yaxis=dict(title='y actual', rangemode="tozero", anchor='x', overlaying='y2'),
                   yaxis2=dict(title='y2 actual', side='right', rangemode = "tozero", anchor='x'))
fig = go.Figure(data=traces, layout=layout)
plt.iplot(fig)

Сюжет, сгенерированный вышеуказанным кодом: Изображение 174551

Как это исправить?

Примечание. В коде можно увидеть две фиктивные следы. Я представил их так, чтобы два следа "фактический" и "y2 фактический" не накладывались друг на друга. Для получения дополнительной информации о том, почему я сделал эту проверку, эта ссылка

Теги:
bar-chart
plotly

1 ответ

2

Возможное обходное решение:

установите элемент range для того, чтобы оба графика были пропорциональны друг другу, тогда оси будут выровнены. В основном, ваша проблема - одна ось должна показывать отрицательные числа, а другая - нет. Говоря y2 чтобы показать отрицательные числа, мы получаем нашу цель.

from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
import plotly.graph_objs as go

init_notebook_mode(connected=True)

traces = [
    go.Bar(
        x=[1, 2, 3, 4], 
        y=[-1, 2, -3, 4], 
        name='y actual'
    ), 
    go.Bar(
        x=[1], 
        y=[0], 
        name='y dummy', 
        hoverinfo='none', 
        showlegend=False
    ), 
    go.Bar(
        x=[1],
        y=[0],
        yaxis='y2', 
        name='y2 dummy', 
        hoverinfo='none', 
        showlegend=False
    ),
   go.Bar(
       x=[1, 2, 3, 4], 
       y=[22, 2, 13, 25], 
       yaxis='y2', 
       name='y2 actual'
   )
]

# layout
layout = go.Layout(
    barmode='group',
    yaxis=dict(
        title='y actual', 
        rangemode="tozero", 
        #anchor='x', 
        overlaying='y2',
        side="left",
        range = [-4, 10]
    ),
    yaxis2=dict(
        title='y2 actual', 
        side='right', 
        rangemode = "tozero",
        #anchor='x',
        range = [-12, 30]
    )
)

# make fig
fig = go.Figure(data=traces, layout=layout)
iplot(fig)

Изображение 174551

Это может раздражать необходимость держать их в пропорции, но это обеспечит их выравнивание.

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

def make_proportional_intervals(a, b):
    """
    Given two list like objects, compute two proprotionally sized ranges.
    This function assumes the max value in both lists is positive and non-zero
    """
    min_a, min_b = min(a), min(b)
    max_a, max_b = max(a), max(b)
    if (min_a >=0) & (min_b >= 0):
        # provide a 20% cushion to the scale
        return [0, round(1.2*max_a)], [0, round(1.2*max_b)]
    else:
        if (min_a < min_b) & (max_a < max_b):
            n = -(-max_b // max_a)
            # n = math.ceil(max_b / max_a), if you cannot assume ints.
            return [min_a, max_a], [n*min_a, n*max_a]

        elif (min_b < min_a) & (max_b < max_a):
            n = -(-max_a // max_b)
            # n = math.ceil(max_b / max_a), if you cannot assume ints.
            return [n*min_b, n*max_b], [min_b, max_b]

        elif (min_b < min_a) & (max_a < max_b):
            n = max( -(-max_b // max_a), -(min_b // min_a) )
            return [min_b / n, max_b / n], [min_b, max_b]

        elif (min_a < min_b) & (max_b < max_a):
            n = max( -(-max_a // max_b), -(min_a // min_b) )
            return [min_a, max_a], [min_a / n, max_a / n]
        elif (min_a == min_b):
            m = max(max_a, max_b)
            return [min_a, m], [min_b,  m]
        elif max_a == max_b:
            m = min(min_a, min_b)
            return [m, max_a], [m, max_b]

Эта функция предполагает, что ваши значения будут целыми, но если вы не можете import math и использовать math.ceil() вместо моего целочисленного деления. Я избегал добавлять больше импорта. Если вы хотите увидеть этот код в действии, я создал пример в jupyter notebook, который вы можете запускать несколько раз, чтобы увидеть, как он обрабатывает разные массивы.

from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
import plotly.graph_objs as go
import numpy as np

def make_proportional_intervals(a, b):
    """
    Given two list like objects, compute two proprotionally sized ranges.
    This function assumes the max value in both lists is positive and non-zero
    """
    min_a, min_b = min(a), min(b)
    max_a, max_b = max(a), max(b)
    if (min_a >=0) & (min_b >= 0):
        # provide a 20% cushion to the scale
        return [0, round(1.2*max_a)], [0, round(1.2*max_b)]
    else:
        if (min_a < min_b) & (max_a < max_b):
            n = -(-max_b // max_a)
            # n = math.ceil(max_b / max_a), if you cannot assume ints.
            return [min_a, max_a], [n*min_a, n*max_a]

        elif (min_b < min_a) & (max_b < max_a):
            n = -(-max_a // max_b)
            # n = math.ceil(max_b / max_a), if you cannot assume ints.
            return [n*min_b, n*max_b], [min_b, max_b]

        elif (min_b < min_a) & (max_a < max_b):
            n = max( -(-max_b // max_a), -(min_b // min_a) )
            return [min_b / n, max_b / n], [min_b, max_b]

        elif (min_a < min_b) & (max_b < max_a):
            n = max( -(-max_a // max_b), -(min_a // min_b) )
            return [min_a, max_a], [min_a / n, max_a / n]
        elif (min_a == min_b):
            m = max(max_a, max_b)
            return [min_a, m], [min_b,  m]
        elif max_a == max_b:
            m = min(min_a, min_b)
            return [m, max_a], [m, max_b]

init_notebook_mode(connected=True)

y0 = np.random.randint(-5, 35, 6)
y1 = np.random.randint(-7, 28, 6)

print(y0, y1)
range0, range1 = make_proportional_intervals(y0, y1)

traces = [
    go.Bar(
        x=[1, 2, 3, 4, 5, 6], 
        y=y0, 
        name='y actual'
    ), 
    go.Bar(
        x=[1], 
        y=[0], 
        name='y dummy', 
        hoverinfo='none', 
        showlegend=False
    ), 
    go.Bar(
        x=[1],
        y=[0],
        yaxis='y2', 
        name='y2 dummy', 
        hoverinfo='none', 
        showlegend=False
    ),
   go.Bar(
       x=[1, 2, 3, 4, 5, 6], 
       y=y1, 
       yaxis='y2', 
       name='y2 actual'
   )
]

# layout
layout = go.Layout(
    barmode='group',
    yaxis=dict(
        title='y actual', 
        rangemode="tozero", 
        #anchor='x', 
        overlaying='y2',
        side="left",
        range = range0
    ),
    yaxis2=dict(
        title='y2 actual', 
        side='right', 
        rangemode = "tozero",
        #anchor='x',
        range = range1
    )
)

fig = go.Figure(data=traces, layout=layout)
iplot(fig)

Опять же, это всего лишь работа для того, что у вас есть отрицательные числа, и я не могу использовать rangemode = "tozero" качестве сцены здесь. Возможно, разработчики добавят что-то в будущем к rangemode чтобы исправить это.

  • 0
    Ты прав. это немного раздражает, но это работает. Спасибо
  • 0
    Если у вас есть общее представление о том, как ваши данные будут структурированы заблаговременно, вы можете написать функцию, которая будет автоматически вычислять диапазоны для вас. Однако, если вы не знаете, как будут выглядеть ваши данные, функция может стать сложной, и вам может быть проще нанести ее один раз, а затем отрегулировать диапазон вручную.
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню