Plotly Express连续颜色刻度线

5

我有以下这段代码

import plotly.express as px
import pandas as pd
import numpy as np

x = [1,2,3,4,5,6]

df = pd.DataFrame(
    {
        'x': x*3,
        'y': list(np.array(x)) + list(np.array(x)**2) + list(np.array(x)**.5),
        'color': list(np.array(x)*0) + list(np.array(x)*0+1) + list(np.array(x)*0+2),
    }
)

for plotting_function in [px.scatter, px.line]:
    fig = plotting_function(
        df,
        x = 'x',
        y = 'y',
        color = 'color',
        title = f'Using {plotting_function.__name__}',
    )
    fig.show()

以下是产生如下两个图表的代码:

enter image description here

enter image description here

由于某种原因,px.line不能生成我想要的连续颜色比例尺,在文档中找不到如何用线连接点的方法,请问如何生成每个跟踪的连续颜色比例尺和连接点的线的情节?

这是我想要制作的图表: enter image description here


有一种方法可以为线图设置自己的线条颜色。什么是为折线图设置连续颜色比例尺?通常,变化的线图由单一颜色表示。我需要一个颜色条吗? - r-beginners
3
最简单的方法如下。 fig = px.line(df, x='x', y='y', color='color', markers=True, color_discrete_sequence=['#0d0887','#d8576b','#f0f921']) - r-beginners
连续的颜色对于折线图来说并不常见,但我认为仍然应该支持。那么在一个非常小的数据集中画趋势线呢? - Derek O
1
我认为这并不罕见。在三维图的任何“切片”中,其中一个变量会转换为颜色。我很惊讶 plotly.express.line 函数默认情况下不支持此功能。 - user171780
由于线图的语义略有不同(如果您考虑一下),我认为使用color_discrete_sequence(正如@r-beginners所指出的那样)是最自然的解决方案,而且确实支持Plotly Express线图! - matanster
3个回答

4
您可以只使用px.line中的2个参数来实现此操作:
  • markers=True
  • color_discrete_sequence=my_plotly_continuous_sequence
完整的代码应该像这样(请注意列表切片[::4],以便颜色间隔较大):
import plotly.express as px
import pandas as pd
import numpy as np

x = [1, 2, 3, 4, 5, 6]

df = pd.DataFrame(
    {
        'x': x * 3,
        'y': list(np.array(x)) + list(np.array(x) ** 2) + list(np.array(x) ** .5),
        'color': list(np.array(x) * 0) + list(np.array(x) * 0 + 1) + list(np.array(x) * 0 + 2),
    }
)

fig = px.line(
    df,
    x='x',
    y='y',
    color='color',
    color_discrete_sequence=px.colors.sequential.Plasma[::4],
    markers=True,
    template='plotly'
)
fig.show()

这将产生以下输出。

Plot

如果您有的行数比颜色映射中的颜色更多,您可以构建一个自定义的颜色比例尺,这样您就可以获得一个完整的序列而不是循环序列:
rgb = px.colors.convert_colors_to_same_type(px.colors.sequential.RdBu)[0]

colorscale = []
n_steps = 4  # Control the number of colors in the final colorscale
for i in range(len(rgb) - 1):
    for step in np.linspace(0, 1, n_steps):
        colorscale.append(px.colors.find_intermediate_color(rgb[i], rgb[i + 1], step, colortype='rgb'))

fig = px.line(df_e, x='temperature', y='probability', color='year', color_discrete_sequence=colorscale, height=900)
fig.show()

Plot 2


4

我不确定仅使用plotly.express是否可能。如果你使用px.line,那么你可以像这个答案所描述的那样传递参数markers=True,但从px.line文档中看来,并不支持连续的颜色比例尺。

更新的答案:要同时拥有将线和标记分组的图例,最简单的方法可能是使用go.Scatter和参数mode='lines+markers'。你需要一次添加一个迹线(通过逐一绘制数据的每个唯一颜色部分),以便能够从图例中控制每个线+标记组。

在绘制这些迹线时,你需要一些函数从连续的颜色比例尺中检索线的颜色,因为go.Scatter不会知道你的线应该是什么颜色,除非你指定它们——值得庆幸的是,在这里有一个回答。

此外,你不能通过逐个颜色添加标记来生成颜色条,因此,你可以一次性使用go.Scatter绘制所有标记,但使用参数marker=dict(size=0, color="rgba(0,0,0,0)", colorscale='Plasma', colorbar=dict(thickness=20))来显示颜色条,但确保这些重复标记不可见。

把这些组合在一起:

# import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np

x = [1,2,3,4,5,6]

df = pd.DataFrame(
    {
        'x': x*3,
        'y': list(np.array(x)) + list(np.array(x)**2) + list(np.array(x)**.5),
        'color': list(np.array(x)*0) + list(np.array(x)*0+1) + list(np.array(x)*0+2),
    }
)

# 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://dev59.com/5VIG5IYBdhLWcg3w2VIZ

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",
    )

fig = go.Figure()

## add the lines+markers
for color_val in df.color.unique():
    color_val_normalized = (color_val - min(df.color)) / (max(df.color) - min(df.color))
    # print(f"color_val={color_val}, color_val_normalized={color_val_normalized}")
    df_subset = df[df['color'] == color_val]
    fig.add_trace(go.Scatter(
        x=df_subset['x'],
        y=df_subset['y'],
        mode='lines+markers',
        marker=dict(color=get_color('Plasma', color_val_normalized)),
        name=f"line+marker {color_val}",
        legendgroup=f"line+marker {color_val}"
    ))

## add invisible markers to display the colorbar without displaying the markers
fig.add_trace(go.Scatter(
    x=df['x'],
    y=df['y'],
    mode='markers',
    marker=dict(
        size=0, 
        color="rgba(0,0,0,0)", 
        colorscale='Plasma', 
        cmin=min(df.color),
        cmax=max(df.color),
        colorbar=dict(thickness=40)
    ),
    showlegend=False
))

fig.update_layout(
    legend=dict(
    yanchor="top",
    y=0.99,
    xanchor="left",
    x=0.01),
    yaxis_range=[min(df.y)-2,max(df.y)+2]
)

fig.show()

enter image description here


谢谢您的回答。是否有可能将每行与其对应的散点图所属的“legendgroup”链接起来,以便我可以将线条和标记作为一个唯一实体启用/禁用? - user171780
很遗憾,我认为在使用px.line时,你无法为连续颜色设置图例 - 根据此处的文档所述。对于px.line,你需要在离散颜色(支持图例)和连续颜色(支持色条)之间进行选择。 - Derek O
然而,如果您将每行和标记作为单独的跟踪添加,我认为您可以仅使用 plotly.graph_objects 实现所需的图表 - 我可以在此之后回复您。 - Derek O

2

基于@manuel-montoya的答案,色图生成代码可以使用内置的plotly.express.colors.sample_colorscale函数进行简化,例如:

import numpy as np
import pandas as pd
import plotly.express as px

NO_LINES = 24
X_RANGE = 64
df = pd.DataFrame(
    {
        "c": np.arange(NO_LINES).repeat(X_RANGE),
        "x": np.tile(np.arange(X_RANGE), NO_LINES),
        "y": np.random.rand(X_RANGE * NO_LINES),
    }
)
df["y"] = 0.2 * df["x"] ** 1.2 + 2 * df["c"] + df["y"]

color_variable = "c"

myscale = px.colors.sample_colorscale(
    colorscale=px.colors.sequential.RdBu,
    samplepoints=len(df[color_variable].unique()),
    low=0.0,
    high=1.0,
    colortype="rgb",
)

px.line(
    df,
    x="x",
    y="y",
    markers=True,
    color=color_variable,
    color_discrete_sequence=myscale,
).show()

结果可以在这里看到。然而,它不显示颜色映射,但代码比被接受的答案简单得多。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接