У меня есть сгруппированная гистограмма с двумя осями у каждого из них в другом масштабе. Я пытаюсь выровнять ось 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)
Сюжет, сгенерированный вышеуказанным кодом:
Как это исправить?
Примечание. В коде можно увидеть две фиктивные следы. Я представил их так, чтобы два следа "фактический" и "y2 фактический" не накладывались друг на друга. Для получения дополнительной информации о том, почему я сделал эту проверку, эта ссылка
Возможное обходное решение:
установите элемент 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)
Это может раздражать необходимость держать их в пропорции, но это обеспечит их выравнивание.
чтобы помочь автоматизировать процесс, вы можете использовать следующую функцию для создания двух диапазонов, пропорциональных друг другу.
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
чтобы исправить это.