【问题标题】:How to create a heatmap where each cell is divided into 4 triangles?如何创建将每个单元格分为 4 个三角形的热图?
【发布时间】:2021-05-08 22:11:46
【问题描述】:

我想将一个单元格分成一个单独的热图/具有多个颜色代码。

每个正方形分为 4 个三角形,每个三角形都有自己的数值/颜色代码。

【问题讨论】:

    标签: python matplotlib data-visualization heatmap


    【解决方案1】:

    这是一个创建三角形的示例,有点类似于Python package to plot two heatmaps in one (split each square into two triangles)?。 许多变化和改进是可能的。 [更新:分为函数和数据框示例]

    from matplotlib import pyplot as plt
    from matplotlib.tri import Triangulation
    import numpy as np
    
    def create_demo_data(M, N):
        # create some demo data for North, East, South, West
        # note that each of the 4 arrays can be either 2D (N by M) or 1D (N*M)
        # M columns and N rows
        valuesN = np.repeat(np.abs(np.sin(np.arange(N))), M)
        valuesE = np.arange(M * N) / (N * M)
        valuesS = np.random.uniform(0, 1, (N, M))
        valuesW = np.random.uniform(0, 1, (N, M))
        return [valuesN, valuesE, valuesS, valuesW]
    
    def triangulation_for_triheatmap(M, N):
        xv, yv = np.meshgrid(np.arange(-0.5, M), np.arange(-0.5, N))  # vertices of the little squares
        xc, yc = np.meshgrid(np.arange(0, M), np.arange(0, N))  # centers of the little squares
        x = np.concatenate([xv.ravel(), xc.ravel()])
        y = np.concatenate([yv.ravel(), yc.ravel()])
        cstart = (M + 1) * (N + 1)  # indices of the centers
    
        trianglesN = [(i + j * (M + 1), i + 1 + j * (M + 1), cstart + i + j * M)
                      for j in range(N) for i in range(M)]
        trianglesE = [(i + 1 + j * (M + 1), i + 1 + (j + 1) * (M + 1), cstart + i + j * M)
                      for j in range(N) for i in range(M)]
        trianglesS = [(i + 1 + (j + 1) * (M + 1), i + (j + 1) * (M + 1), cstart + i + j * M)
                      for j in range(N) for i in range(M)]
        trianglesW = [(i + (j + 1) * (M + 1), i + j * (M + 1), cstart + i + j * M)
                      for j in range(N) for i in range(M)]
        return [Triangulation(x, y, triangles) for triangles in [trianglesN, trianglesE, trianglesS, trianglesW]]
    
    M, N = 5, 4  # e.g. 5 columns, 4 rows
    values = create_demo_data(M, N)
    triangul = triangulation_for_triheatmap(M, N)
    cmaps = ['Blues', 'Greens', 'Purples', 'Reds']  # ['winter', 'spring', 'summer', 'autumn']
    norms = [plt.Normalize(-0.5, 1) for _ in range(4)]
    fig, ax = plt.subplots()
    imgs = [ax.tripcolor(t, np.ravel(val), cmap=cmap, norm=norm, ec='white')
            for t, val, cmap, norm in zip(triangul, values, cmaps, norms)]
    
    ax.set_xticks(range(M))
    ax.set_yticks(range(N))
    ax.invert_yaxis()
    ax.margins(x=0, y=0)
    ax.set_aspect('equal', 'box')  # square cells
    plt.tight_layout()
    plt.show()
    

    这是相同数据的变体,在子单元格中添加了文本:

    imgs = [ax.tripcolor(t, val.ravel(), cmap='RdYlGn', vmin=0, vmax=1, ec='white')
            for t, val in zip(triangul, values)]
    for val, dir in zip(values, [(-1, 0), (0, 1), (1, 0), (0, -1)]):
        for i in range(M):
            for j in range(N):
                v = val[j, i]
                ax.text(i + 0.3 * dir[1], j + 0.3 * dir[0], f'{v:.2f}', color='k' if 0.2 < v < 0.8 else 'w', ha='center', va='center')
    cbar = fig.colorbar(imgs[0], ax=ax)
    

    要使用数据框,可以使用pd.pivot_table()。请注意,在最终图中,空单元格最终会是空的(显示白色背景颜色)。完全空的行和列将被自动排除。

    import pandas as pd
    
    days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']
    df = pd.DataFrame({'cols': np.random.choice([*'abcdefghij'], 40),
                       'rows': np.random.choice(days, 40),
                       'north': np.random.rand(40),
                       'east': np.random.rand(40),
                       'south': np.random.rand(40),
                       'west': np.random.rand(40)})
    df['rows'] = pd.Categorical(df['rows'], categories=days)  # fix an ordering
    df_piv = df.pivot_table(index='rows', columns='cols')
    M = len(df_piv.columns) // 4
    N = len(df_piv)
    values = [df_piv[dir] for dir in
              ['north', 'east', 'south', 'west']]  # these are the 4 column names in df
    
    triangul = triangulation_for_triheatmap(M, N)
    cmaps = ['RdYlBu'] * 4
    norms = [plt.Normalize(0, 1) for _ in range(4)]
    fig, ax = plt.subplots(figsize=(10, 4))
    imgs = [ax.tripcolor(t, np.ravel(val), cmap=cmap, norm=norm, ec='white')
            for t, val, cmap, norm in zip(triangul, values, cmaps, norms)]
    
    ax.tick_params(length=0)
    ax.set_xticks(range(M))
    ax.set_xticklabels(df_piv['north'].columns)
    ax.set_yticks(range(N))
    ax.set_yticklabels(df_piv.index)
    ax.invert_yaxis()
    ax.margins(x=0, y=0)
    ax.set_aspect('equal', 'box')  # square cells
    plt.colorbar(imgs[0], ax=ax)
    plt.tight_layout()
    plt.show()
    

    PS:这里是创建一个分成 4 个正方形的热图的可能方法。白色网格线可用于绘制分隔线。

    from matplotlib import pyplot as plt
    import numpy as np
    
    M, N = 5, 4
    values = np.random.uniform(0, 100, (N * 2, M * 2))
    
    fig, ax = plt.subplots()
    ax.imshow(values, extent=[-0.5, M - 0.5, N - 0.5, -0.5], cmap='viridis')
    
    ax.set_xticks(np.arange(0, M))
    ax.set_xticks(np.arange(-0.5, M), minor=True)
    ax.set_yticks(np.arange(0, N))
    ax.set_yticks(np.arange(-0.5, N), minor=True)
    ax.grid(which='minor', lw=4, color='white', clip_on=False)
    ax.grid(which='major', lw=2, color='white', clip_on=False)
    ax.tick_params(length=0)
    for s in ax.spines:
        ax.spines[s].set_visible(False)
    plt.show()
    

    【讨论】:

    • 非常感谢@JohanC!有用!我会将其标记为已回答,还有一个问题:您是否知道,如何使用 PYTHON 使用正方形而不是三角形来分割单元格?喜欢这里的帖子:stackoverflow.com/questions/45798250/…
    • 非常感谢!
    • @Berk 我更新了将计算三角形几何的函数分开的答案,并添加了一个如何使用数据框的示例。请删除旧的 cmets,因为它们使其他人很难知道发生了什么。
    • 谢谢,但 NaN 值的颜色与最小值相同。
    猜你喜欢
    • 2020-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-06
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    • 2017-10-28
    相关资源
    最近更新 更多