【问题标题】:Plotly Python: Align X-Axes in a grouped bar chart with multiple Y-axisPlotly Python:在具有多个 Y 轴的分组条形图中对齐 X 轴
【发布时间】:2018-11-25 14:50:06
【问题描述】:

我有一个分组条形图,其中有两个 y 轴,每个轴的比例不同。我正在尝试对齐两个组的 x 轴(y = 0)。我发现很少有链接 link1link2 在其中设置 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)

以上代码生成的图:

我该如何解决这个问题?

注意:您可以在代码中看到两条虚拟痕迹。我介绍了它们,以便“y 实际”和“y2 实际”这两条轨迹不会相互重叠。有关我为什么这样做的更多信息,请查看link

【问题讨论】:

    标签: python bar-chart plotly


    【解决方案1】:

    一种可能的解决方法:

    将两个图形的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" 作为场景here。也许开发人员将来会在rangemode 中添加一些内容来纠正这个问题。

    【讨论】:

    • 你是对的。这有点烦人,但它有效。谢谢
    • 如果您提前大致了解数据的结构,您可以编写一个自动计算范围的函数。但是,如果您不知道数据的外观,该函数可能会变得复杂,绘制一次然后手动调整范围可能会更容易。
    • 是的,但我正在为数百个这样的地块自动化这个过程,它们都在不同的范围内,因此在我的情况下手动调整不是一个可行的解决方案
    • @ThReSholD 查看更新以尝试解决自动化问题
    • 感谢这个功能,正是我所需要的。
    猜你喜欢
    • 2015-05-16
    • 2015-06-09
    • 1970-01-01
    • 1970-01-01
    • 2018-08-15
    • 2020-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多