Plotly Express 条形图中具有不同组大小的组条

Posted

技术标签:

【中文标题】Plotly Express 条形图中具有不同组大小的组条【英文标题】:Group bars with different group sizes in Plotly Express bar plot 【发布时间】:2021-12-18 19:06:25 【问题描述】:

考虑以下数据框,称为data

“教师”列中只有两个元素出现两次,其他元素仅出现一次。 我用 Plotly Express 制作了一个条形图:

import plotly.express as px
px.bar(data.sort_values("start_time", ascending=False), x="teacher", y="start_time", color="start_time",
       color_continuous_scale="Bluered", barmode="group")

以下是输出:

我希望条形图彼此相邻,而不是堆叠。我认为px 将它们堆叠起来(与他们文档中的行为相反),因为我没有每个老师的出现次数相同。

正确吗? 我该如何解决?

【问题讨论】:

【参考方案1】:

根据this forum post,正在发生的事情是plotly.expressstart_time 解释为一个连续变量,这就是您获得颜色条的原因,但随后又退回到堆叠条形而不是对它们进行分组。

正如@Emmanuelle 所建议的,您可以通过创建一个名为start_time_str 的字符串的新start_time 列来解决此问题,然后将此列传递给color 参数。这迫使 plotly.express 将此变量解释为离散的。但是,您会失去彩条并获得图例:

data['start_time_str'] = data['start_time'].astype('str')
fig = px.bar(data.sort_values("start_time", ascending=False), x="teacher", y="start_time", color="start_time_str",color_continuous_scale="Bluered", barmode="group")

因此,假设您想要保留颜色条,并且有堆叠条,您将需要一个更复杂的解决方法。

您可以使用 plotly.express 绘制第一个条以获得颜色条,然后使用fig.add_trace 将第二个条添加为graph_object。添加第二个条时,您需要指定颜色,为此,您需要一些辅助函数,例如 normalize_color_val 将这个条的 y 值转换为相对于数据的标准化颜色值0 到 1 的比例,get_color 在您传递色阶名称和标准化值时返回条形的颜色(作为 rgb 字符串)。

import pandas as pd
import plotly.express as px
import plotly.graph_objects as go

data = pd.DataFrame(
    'teacher':['Lingrand','Milanesio','Menin','Malot','Malot','Schminke','Cornelli','Milanesio','Marchello','Menin','Huet'],
    'start_time':[12,12,5,0,5,0,4,8,-1,0,4]
)

# This function allows you to retrieve colors from a continuous color scale
# by providing the name of the color scale, and the normalized location between 0 and 1
# Reference: https://***.com/questions/62710057/access-color-from-plotly-color-scale

def get_color(colorscale_name, loc):
    from _plotly_utils.basevalidators import ColorscaleValidator
    # first parameter: Name of the property being validated
    # second parameter: a string, doesn't really matter in our use case
    cv = ColorscaleValidator("colorscale", "")
    # colorscale will be a list of lists: [[loc1, "rgb1"], [loc2, "rgb2"], ...] 
    colorscale = cv.validate_coerce(colorscale_name)
    
    if hasattr(loc, "__iter__"):
        return [get_continuous_color(colorscale, x) for x in loc]
    return get_continuous_color(colorscale, loc)
        

# Identical to Adam's answer
import plotly.colors
from PIL import ImageColor

def get_continuous_color(colorscale, intermed):
    """
    Plotly continuous colorscales assign colors to the range [0, 1]. This function computes the intermediate
    color for any value in that range.

    Plotly doesn't make the colorscales directly accessible in a common format.
    Some are ready to use:
    
        colorscale = plotly.colors.PLOTLY_SCALES["Greens"]

    Others are just swatches that need to be constructed into a colorscale:

        viridis_colors, scale = plotly.colors.convert_colors_to_same_type(plotly.colors.sequential.Viridis)
        colorscale = plotly.colors.make_colorscale(viridis_colors, scale=scale)

    :param colorscale: A plotly continuous colorscale defined with RGB string colors.
    :param intermed: value in the range [0, 1]
    :return: color in rgb string format
    :rtype: str
    """
    if len(colorscale) < 1:
        raise ValueError("colorscale must have at least one color")

    hex_to_rgb = lambda c: "rgb" + str(ImageColor.getcolor(c, "RGB"))

    if intermed <= 0 or len(colorscale) == 1:
        c = colorscale[0][1]
        return c if c[0] != "#" else hex_to_rgb(c)
    if intermed >= 1:
        c = colorscale[-1][1]
        return c if c[0] != "#" else hex_to_rgb(c)

    for cutoff, color in colorscale:
        if intermed > cutoff:
            low_cutoff, low_color = cutoff, color
        else:
            high_cutoff, high_color = cutoff, color
            break

    if (low_color[0] == "#") or (high_color[0] == "#"):
        # some color scale names (such as cividis) returns:
        # [[loc1, "hex1"], [loc2, "hex2"], ...]
        low_color = hex_to_rgb(low_color)
        high_color = hex_to_rgb(high_color)

    return plotly.colors.find_intermediate_color(
        lowcolor=low_color,
        highcolor=high_color,
        intermed=((intermed - low_cutoff) / (high_cutoff - low_cutoff)),
        colortype="rgb",
    )

def normalize_color_val(color_val, data=data):
    return (color_val - min(data.start_time)) / (max(data.start_time - min(data.start_time)))

## add the first bars
fig = px.bar(
    data.sort_values("start_time", ascending=False).loc[~data['teacher'].duplicated()],
    x="teacher", y="start_time", color="start_time",
    color_continuous_scale="Bluered", barmode="group"
)

## add the other bars, these will automatically be grouped
for x,y in data.sort_values("start_time", ascending=False).loc[data['teacher'].duplicated()].itertuples(index=False):
    fig.add_trace(go.Bar(
        x=[x],
        y=[y],
        marker=dict(color=get_color('Bluered', normalize_color_val(y))),
        hovertemplate="teacher=%x<br>start_time=%y<extra></extra>",
        showlegend=False
    ))

fig.show()

【讨论】:

第二张图确实是我想要的,谢谢!。我不敢相信做出如此(看似)微小的改变需要做这么多工作,这太疯狂了 是的,我遇到了你在 plotly.express 中有一个颜色条的问题,但是你需要一些 plotly.express 中没有的其他功能,你有时不得不求助于这样的解决方法.. . 很高兴我的回答有帮助!

以上是关于Plotly Express 条形图中具有不同组大小的组条的主要内容,如果未能解决你的问题,请参考以下文章

如何在 plotly express 条形图中隐藏颜色条和图例?

删除分组 plotly express 条形图中的空条,使每组条之间的视觉距离相等

如何使用 Python 中的 Plotly Express 为每个条形图添加可点击链接?

如何使用字典中的 plotly express 创建(条形)图?

Plotly Python:在具有多个 Y 轴的分组条形图中对齐 X 轴

有没有办法改变 plotly.express.sunburst 图中叶子的不透明度?