带注释的热图

问题描述 投票:0回答:1

嘿,我很难用 Plotly 创建自定义热图。

我使用下面的代码来生成自定义 Plotly 热图。我正在使用 create_annotated_heatmap 函数来生成绘图。

from plotly import exceptions, optional_imports
import plotly.colors as clrs
from plotly.figure_factory import utils
from plotly.graph_objs import graph_objs
from plotly.validators.heatmap import ColorscaleValidator

# Optional imports, may be None for users that only use our core functionality.
np = optional_imports.get_module("numpy")


def validate_annotated_heatmap(z, x, y, annotation_text):
    """
    Annotated-heatmap-specific validations

    Check that if a text matrix is supplied, it has the same
    dimensions as the z matrix.

    See FigureFactory.create_annotated_heatmap() for params

    :raises: (PlotlyError) If z and text matrices do not  have the same
        dimensions.
    """
    if annotation_text is not None and isinstance(annotation_text, list):
        utils.validate_equal_length(z, annotation_text)
        for lst in range(len(z)):
            if len(z[lst]) != len(annotation_text[lst]):
                raise exceptions.PlotlyError(
                    "z and text should have the " "same dimensions"
                )

    if x:
        if len(x) != len(z[0]):
            raise exceptions.PlotlyError(
                "oops, the x list that you "
                "provided does not match the "
                "width of your z matrix "
            )

    if y:
        if len(y) != len(z):
            raise exceptions.PlotlyError(
                "oops, the y list that you "
                "provided does not match the "
                "length of your z matrix "
            )


def annotated_heatmap_trace_and_layout(
    z,
    x=None,
    y=None,
    annotation_text=None,
    colorscale="Plasma",
    font_colors=None,
    showscale=False,
    reversescale=False,
    **kwargs
):
    # Avoiding mutables in the call signature
    font_colors = font_colors if font_colors is not None else []
    validate_annotated_heatmap(z, x, y, annotation_text)

    # validate colorscale
    colorscale_validator = ColorscaleValidator()
    colorscale = colorscale_validator.validate_coerce(colorscale)

    annotations = _AnnotatedHeatmap(
        z, x, y, annotation_text, colorscale, font_colors, reversescale, **kwargs
    ).make_annotations()

    if x or y:
        trace = dict(
            type="heatmap",
            z=z,
            x=x,
            y=y,
            colorscale=colorscale,
            showscale=showscale,
            reversescale=reversescale,
            **kwargs
        )
        layout = dict(
            annotations=annotations,
            xaxis=dict(ticks="",
                       dtick=1,
                       side="top",
                       gridcolor="rgb(0, 0, 0)"),
            yaxis=dict(ticks="", dtick=1, ticksuffix="  "),
        )
    else:
        trace = dict(
            type="heatmap",
            z=z,
            colorscale=colorscale,
            showscale=showscale,
            reversescale=reversescale,
            **kwargs
        )
        layout = dict(
            annotations=annotations,
            xaxis=dict(
                ticks="",
                side="top",
                gridcolor="rgb(0, 0, 0)",
                showticklabels=False
            ),
            yaxis=dict(ticks="", ticksuffix="  ", showticklabels=False),
        )
    return trace, layout


def create_annotated_heatmap(
    z,
    x=None,
    y=None,
    annotation_text=None,
    colorscale="Plasma",
    font_colors=None,
    showscale=False,
    reversescale=False,
    **kwargs
):
    trace, layout = annotated_heatmap_trace_and_layout(z, x, y, annotation_text, colorscale, font_colors, showscale, reversescale, **kwargs)
    data = [trace]
    return graph_objs.Figure(data=data, layout=layout)


def to_rgb_color_list(color_str, default):
    if "rgb" in color_str:
        return [int(v) for v in color_str.strip("rgb()").split(",")]
    elif "#" in color_str:
        return clrs.hex_to_rgb(color_str)
    else:
        return default


def should_use_black_text(background_color):
    return (
        background_color[0] * 0.299
        + background_color[1] * 0.587
        + background_color[2] * 0.114
    ) > 186


class _AnnotatedHeatmap(object):
    """
    Refer to TraceFactory.create_annotated_heatmap() for docstring
    """

    def __init__(
        self, z, x, y, annotation_text, colorscale, font_colors, reversescale, **kwargs
    ):

        self.z = z
        if x:
            self.x = x
        else:
            self.x = range(len(z[0]))
        if y:
            self.y = y
        else:
            self.y = range(len(z))
        if annotation_text is not None:
            self.annotation_text = annotation_text
        else:
            self.annotation_text = self.z
        self.colorscale = colorscale
        self.reversescale = reversescale
        self.font_colors = font_colors
        self.kwargs = kwargs

    def get_text_color(self):
        """
        Get font color for annotations.

        The annotated heatmap can feature two text colors: min_text_color and
        max_text_color. The min_text_color is applied to annotations for
        heatmap values < (max_value - min_value)/2. The user can define these
        two colors. Otherwise the colors are defined logically as black or
        white depending on the heatmap's colorscale.

        :rtype (string, string) min_text_color, max_text_color: text
            color for annotations for heatmap values <
            (max_value - min_value)/2 and text color for annotations for
            heatmap values >= (max_value - min_value)/2
        """
        # Plotly colorscales ranging from a lighter shade to a darker shade
        colorscales = [
            "Greys",
            "Greens",
            "Blues",
            "YIGnBu",
            "YIOrRd",
            "RdBu",
            "Picnic",
            "Jet",
            "Hot",
            "Blackbody",
            "Earth",
            "Electric",
            "Viridis",
            "Cividis",
        ]
        # Plotly colorscales ranging from a darker shade to a lighter shade
        colorscales_reverse = ["Reds"]

        white = "#FFFFFF"
        black = "#000000"
        if self.font_colors:
            min_text_color = self.font_colors[0]
            max_text_color = self.font_colors[-1]
        elif self.colorscale in colorscales and self.reversescale:
            min_text_color = black
            max_text_color = white
        elif self.colorscale in colorscales:
            min_text_color = white
            max_text_color = black
        elif self.colorscale in colorscales_reverse and self.reversescale:
            min_text_color = white
            max_text_color = black
        elif self.colorscale in colorscales_reverse:
            min_text_color = black
            max_text_color = white
        elif isinstance(self.colorscale, list):

            min_col = to_rgb_color_list(self.colorscale[0][1], [255, 255, 255])
            max_col = to_rgb_color_list(self.colorscale[-1][1], [255, 255, 255])

            # swap min/max colors if reverse scale
            if self.reversescale:
                min_col, max_col = max_col, min_col

            if should_use_black_text(min_col):
                min_text_color = black
            else:
                min_text_color = white

            if should_use_black_text(max_col):
                max_text_color = black
            else:
                max_text_color = white
        else:
            min_text_color = black
            max_text_color = black
        return min_text_color, max_text_color

    def get_z_mid(self):
        """
        Get the mid value of z matrix

        :rtype (float) z_avg: average val from z matrix
        """
        if 'zmid' in self.kwargs:
            return self.kwargs['zmid']
        if np and isinstance(self.z, np.ndarray):
            z_min = np.amin(self.z)
            z_max = np.amax(self.z)
        else:
            z_min = min([v for row in self.z for v in row])
            z_max = max([v for row in self.z for v in row])
        z_mid = (z_max + z_min) / 2
        return z_mid

    def make_annotations(self):
        """
        Get annotations for each cell of the heatmap with graph_objs.Annotation

        :rtype (list[dict]) annotations: list of annotations for each cell of
            the heatmap
        """
        min_text_color, max_text_color = _AnnotatedHeatmap.get_text_color(self)
        z_mid = _AnnotatedHeatmap.get_z_mid(self)
        annotations = []
        for n, row in enumerate(self.z):
            for m, val in enumerate(row):
                font_color = min_text_color if val < z_mid else max_text_color
                annotations.append(
                    graph_objs.layout.Annotation(
                        text=str(self.annotation_text[n][m]),
                        x=self.x[m],
                        y=self.y[n],
                        xref="x1",
                        yref="y1",
                        font=dict(color=font_color),
                        showarrow=False,
                    )
                )
        return annotations

之后我准备我的x、y、z。我使用

fig
来称呼我的
fig.add_trace

x = ['uBQlnFPT', 'KngLzueS', 'QdEFfXmA', 'NMykaKna', 'htTJpYZw', 'QiIbsLNJ', 'ObqTHrzi', 'IvoETTxO', 'UXljefBc', 'gBbjnMss', 
'zZYNxvhB']

y = ['kRzZhZd', 'yybgaGj', 'MeDGGoI', 'RDBkKpq', 'KPZGesp', 'NoIxUYD', 'vvcQJcu', 'nfuyjJx', 'vtdVONv', 'BDrOWby', 'SSDkBSD', 'aYXHtLJ', 'xrkMHye', 'HYCtaLD', 'bKwSDdd', 'COmFvEm', 'lsnaNmS', 'LEybfTZ', 'JrBOAdC', 'LWPfXBS', 'hmYHIUA', 'fOyooId', 'LyJSIlF', 'aFLMJEa']

    
    
z = [[2.3, 2.5, np.nan, 2.1, 2.0, np.nan, 2.4, np.nan, 2.6, np.nan, 1.9], 
     [2.5, 2.9, np.nan, np.nan, 2.3, np.nan, 2.8, np.nan, 2.7, 2.4, 2.2], 
     [2.2, np.nan, np.nan, np.nan, np.nan, np.nan, 2.1, np.nan, np.nan, np.nan, np.nan], 
     [3.0, 2.0, np.nan, 2.0, 2.5, np.nan, np.nan, 2.0, 2.0, 2.4, np.nan], 
     [2.1, 2.0, 2.3, np.nan, 2.0, 1.9, 2.1, 2.0, 2.2, 2.1, 2.4], 
     [2.2, 2.1, 2.4, 2.0, 2.3, 2.1, 2.2, 2.2, 2.3, 2.1, 2.2], 
     [2.1, np.nan, np.nan, 2.7, np.nan, np.nan, np.nan, 2.0, np.nan, np.nan, np.nan], 
     [2.0, 2.1, 2.1, 2.0, 2.0, 1.9, 2.2, 2.5, 2.1, 2.2, 2.0], 
     [2.0, 2.0, 2.6, 2.1, 2.7, 2.4, 2.0, 2.3, 2.1, 2.0, 1.9], 
     [1.9, 1.7, np.nan, 1.8, np.nan, 2.4, 2.0, 1.8, 1.9, 2.0, np.nan], 
     [2.4, np.nan, np.nan, np.nan, np.nan, np.nan, 2.1, 2.3, 2.4, 2.6, 2.3], 
     [2.1, 1.9, 1.8, 1.7, 2.0, 2.5, 2.1, np.nan, 2.2, 2.0, 2.1], 
     [2.6, 2.7, 2.5, 1.9, 2.3, 2.1, 2.3, np.nan, 2.4, 2.3, 2.4], 
     [2.5, 2.5, 2.2, 2.1, 2.2, 1.8, 2.5, np.nan, 2.7, 2.4, 2.6], 
     [2.4, np.nan, np.nan, 2.0, np.nan, np.nan, np.nan, 2.0, np.nan, np.nan, np.nan], 
     [2.3, 2.1, 2.0, 2.0, 2.3, 2.0, 2.1, np.nan, 2.3, 2.1, 2.0], 
     [2.1, np.nan, np.nan, 1.9, 1.8, np.nan, np.nan, 2.2, 2.1, 2.3, 1.8], 
     [2.4, np.nan, np.nan, np.nan, 1.5, np.nan, np.nan, 2.0, 2.6, 1.9, np.nan], 
     [2.0, 2.0, np.nan, np.nan, 2.0, np.nan, np.nan, 2.0, 2.3, 2.1, 2.0], 
     [2.5, 2.1, np.nan, np.nan, 2.1, 2.0, 2.2, np.nan, 2.0, 2.2, 2.1], 
     [2.2, 2.2, np.nan, np.nan, 2.5, 2.0, 2.1, 2.2, 2.2, 2.2, 2.2], 
     [2.3, 2.6, np.nan, np.nan, np.nan, 2.5, np.nan, np.nan, np.nan, np.nan, np.nan], 
     [1.8, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, 2.0], 
     [3.0, 3.1, np.nan, 2.7, 2.8, np.nan, 2.6, 2.0, 2.0, 1.9, 1.8]]



# Initialize the figure
fig = go.Figure()

# Add trace for max date
fig.add_trace(
    create_annotated_heatmap(
        z=z,
        x=x, 
        y=z,
        colorscale='Courier',
        reversescale=True,
        zmid=1.0,
        zauto=True,
        font_colors=['#000000', '#FFFFFF'],
        hoverongaps=False,
    )
)

它引发了这个错误:

 line 312, in raise_invalid_elements 
    raise ValueError(
ValueError:
    Invalid element(s) received for the 'data' property of
        Invalid elements include: [Figure({
    'data': [{'colorscale': [[0.0, 'rgb(23, 28, 66)'], [0.09090909090909091,
                             'rgb(41, 58, 143)'], [0.18181818181818182, 'rgb(11,
                             102, 189)'], [0.2727272727272727, 'rgb(69, 144,
                             185)'], [0.36363636363636365, 'rgb(142, 181, 194)'],
                             [0.45454545454545453, 'rgb(210, 216, 219)'],
                             [0.5454545454545454, 'rgb(230, 210, 204)'],
                             [0.6363636363636364, 'rgb(213, 157, 137)'],
                             [0.7272727272727273, 'rgb(196, 101, 72)'],
                             [0.8181818181818182, 'rgb(172, 43, 36)'],
                             [0.9090909090909091, 'rgb(120, 14, 40)'], [1.0,
                             'rgb(60, 9, 17)']],
              'hoverongaps': False,
              'reversescale': True,
              'showscale': False,
              'type': 'heatmap',
              'x': [uBQlnFPT, KngLzueS, QdEFfXmA, NMykaKna, htTJpYZw, QiIbsLNJ,
                    ObqTHrzi, IvoETTxO, UXljefBc, gBbjnMss, zZYNxvhB],
              'y': [kRzZhZd, yybgaGj, MeDGGoI, RDBkKpq, KPZGesp, NoIxUYD, vvcQJcu,
                    nfuyjJx, vtdVONv, BDrOWby, SSDkBSD, aYXHtLJ, xrkMHye, HYCtaLD,
                    bKwSDdd, COmFvEm, lsnaNmS, LEybfTZ, JrBOAdC, LWPfXBS, hmYHIUA,
                    fOyooId, LyJSIlF, aFLMJEa],
              'z': [[2.3, 2.5, nan, 2.1, 2.0, nan, 2.4, nan, 2.6, nan, 1.9], [2.5,
                    2.9, nan, nan, 2.3, nan, 2.8, nan, 2.7, 2.4, 2.2], [2.2, nan,
                    nan, nan, nan, nan, 2.1, nan, nan, nan, nan], [3.0, 2.0, nan,
                    2.0, 2.5, nan, nan, 2.0, 2.0, 2.4, nan], [2.1, 2.0, 2.3, nan,
                    2.0, 1.9, 2.1, 2.0, 2.2, 2.1, 2.4], [2.2, 2.1, 2.4, 2.0, 2.3,
                    2.1, 2.2, 2.2, 2.3, 2.1, 2.2], [2.1, nan, nan, 2.7, nan, nan,
                    nan, 2.0, nan, nan, nan], [2.0, 2.1, 2.1, 2.0, 2.0, 1.9, 2.2,
                    2.5, 2.1, 2.2, 2.0], [2.0, 2.0, 2.6, 2.1, 2.7, 2.4, 2.0, 2.3,
                    2.1, 2.0, 1.9], [1.9, 1.7, nan, 1.8, nan, 2.4, 2.0, 1.8, 1.9,
                    2.0, nan], [2.4, nan, nan, nan, nan, nan, 2.1, 2.3, 2.4, 2.6,
                    2.3], [2.1, 1.9, 1.8, 1.7, 2.0, 2.5, 2.1, nan, 2.2, 2.0, 2.1],
                    [2.6, 2.7, 2.5, 1.9, 2.3, 2.1, 2.3, nan, 2.4, 2.3, 2.4], [2.5,
                    2.5, 2.2, 2.1, 2.2, 1.8, 2.5, nan, 2.7, 2.4, 2.6], [2.4, nan,
                    nan, 2.0, nan, nan, nan, 2.0, nan, nan, nan], [2.3, 2.1, 2.0,
                    2.0, 2.3, 2.0, 2.1, nan, 2.3, 2.1, 2.0], [2.1, nan, nan, 1.9,
                    1.8, nan, nan, 2.2, 2.1, 2.3, 1.8], [2.4, nan, nan, nan, 1.5,
                    nan, nan, 2.0, 2.6, 1.9, nan], [2.0, 2.0, nan, nan, 2.0, nan,
                    nan, 2.0, 2.3, 2.1, 2.0], [2.5, 2.1, nan, nan, 2.1, 2.0, 2.2,
                    nan, 2.0, 2.2, 2.1], [2.2, 2.2, nan, nan, 2.5, 2.0, 2.1, 2.2,
                    2.2, 2.2, 2.2], [2.3, 2.6, nan, nan, nan, 2.5, nan, nan, nan,
                    nan, nan], [1.8, nan, nan, nan, nan, nan, nan, nan, nan, nan,
                    2.0], [3.0, 3.1, nan, 2.7, 2.8, nan, 2.6, 2.0, 2.0, 1.9, 1.8]],
              'zauto': True,
              'zmid': 1.0}],
    'layout': {'annotations': [{'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': '2.3',
                                'x': 'uBQlnFPT',
                                'xref': 'x',
                                'y': 'kRzZhZd',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': '2.5',
                                'x': 'KngLzueS',
                                'xref': 'x',
                                'y': 'kRzZhZd',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': 'nan',
                                'x': 'QdEFfXmA',
                                'xref': 'x',
                                'y': 'kRzZhZd',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': '2.1',
                                'x': 'NMykaKna',
                                'xref': 'x',
                                'y': 'kRzZhZd',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': '2.0',
                                'x': 'htTJpYZw',
                                'xref': 'x',
                                'y': 'kRzZhZd',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': 'nan',
                                'x': 'QiIbsLNJ',
                                'xref': 'x',
                                'y': 'kRzZhZd',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': '2.4',
                                'x': 'ObqTHrzi',
                                'xref': 'x',
                                'y': 'kRzZhZd',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': 'nan',
                                'x': 'IvoETTxO',
                                'xref': 'x',
                                'y': 'kRzZhZd',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': '2.6',
                                'x': 'UXljefBc',
                                'xref': 'x',
                                'y': 'kRzZhZd',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': 'nan',
                                'x': 'gBbjnMss',
                                'xref': 'x',
                                'y': 'kRzZhZd',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': '1.9',
                                'x': 'zZYNxvhB',
                                'xref': 'x',
                                'y': 'kRzZhZd',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': '2.5',
                                'x': 'uBQlnFPT',
                                'xref': 'x',
                                'y': 'yybgaGj',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': '2.9',
                                'x': 'KngLzueS',
                                'xref': 'x',
                                'y': 'yybgaGj',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': 'nan',
                                'x': 'QdEFfXmA',
                                'xref': 'x',
                                'y': 'yybgaGj',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': 'nan',
                                'x': 'NMykaKna',
                                'xref': 'x',
                                'y': 'yybgaGj',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': '2.3',
                                'x': 'htTJpYZw',
                                'xref': 'x',
                                'y': 'yybgaGj',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': 'nan',
                                'x': 'QiIbsLNJ',
                                'xref': 'x',
                                'y': 'yybgaGj',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': '2.8',
                                'x': 'ObqTHrzi',
                                'xref': 'x',
                                'y': 'yybgaGj',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': 'nan',
                                'x': 'IvoETTxO',
                                'xref': 'x',
                                'y': 'yybgaGj',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': '2.7',
                                'x': 'UXljefBc',
                                'xref': 'x',
                                'y': 'yybgaGj',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': '2.4',
                                'x': 'gBbjnMss',
                                'xref': 'x',
                                'y': 'yybgaGj',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': '2.2',
                                'x': 'zZYNxvhB',
                                'xref': 'x',
                                'y': 'yybgaGj',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': '2.2',
                                'x': 'uBQlnFPT',
                                'xref': 'x',
                                'y': 'MeDGGoI',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': 'nan',
                                'x': 'KngLzueS',
                                'xref': 'x',
                                'y': 'MeDGGoI',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},
                                'showarrow': False,
                                'text': 'nan',
                                'x': 'QdEFfXmA',
                                'xref': 'x',
                                'y': 'MeDGGoI',
                                'yref': 'y'},
                               {'font': {'color': '#FFFFFF'},

我认为这与我们调用热图时使用

fig.add_trace
的事实有关。有什么建议如何解决问题吗?

python plotly plotly-python
1个回答
0
投票

测试你的代码,它似乎可以正常工作。第一个是一个小细节,但

Courier
不是 Plotly 中的色标。将其更改为有效的色标即可解决。第二点是您将一个完整的图形添加到另一个图形中:Plotly 文档。前进的方法有两种:将
create_annotated_heatmap
的结果添加为迹线或使用结果作为图形。

选项1:

import plotly.graph_objects as go
# Initialize the figure
fig = go.Figure()

# Add trace for max date
fig.add_trace(
    create_annotated_heatmap(
        z=z,
        x=x, 
        y=z,
        colorscale='agsunset',
        reversescale=True,
        zmid=1.0,
        zauto=True,
        font_colors=['#000000', '#FFFFFF'],
        hoverongaps=False,
    ).data[0]
)

选项2:

import plotly.graph_objects as go
fig = create_annotated_heatmap(z=z,
                               x=x, 
                               y=z,
                               colorscale='agsunset',
                               reversescale=True,
                               zmid=1.0,
                               zauto=True,
                               font_colors=['#000000', '#FFFFFF'],
                               hoverongaps=False,
                               )
© www.soinside.com 2019 - 2024. All rights reserved.