【问题标题】:Hierarchical labels on seaborn FacetGrid vertical axisseaborn FacetGrid 垂直轴上的分层标签
【发布时间】:2021-10-28 17:31:04
【问题描述】:

我有一份问卷,用户回答的分数从 1 到 7(李克特量表)。问卷分为两部分。用户属于两个组(X 和 Y),每个用户可能具有两个角色(A 和 B)之一。我在 FacetGrid 上使用 seaborn heatmap 来显示问卷结果。

这是我的代码:

import pandas as pd
import seaborn as sns

df = pd.DataFrame(
    data={
        'Group': ['X', 'Y', 'Y', 'X', 'Y', 'X', 'Y', 'Y', 'X', 'X'],
        'Role':  ['A', 'B', 'A', 'A', 'B', 'B', 'A', 'A', 'A', 'B'],        
        'Question 1': [3,6,5,5,6,6,4,5,7,5],
        'Question 2': [7,7,5,6,4,4,4,4,7,5],
        'Question 3': [6,5,3,5,7,7,6,5,4,4],
        'Question 4': [6,3,4,5,5,7,6,5,4,4]
    }
)

def f(group):
    gg = group[group.columns[-4:]].T.apply(lambda row : row.value_counts(), axis=1)
    for score in range(1, 8):
        if score not in gg:
            gg[score] = 0.0
    return gg

df1 = df.groupby(['Group', 'Role']) \
        .apply(f) \
        .fillna(0) \
        .reset_index() \
        .rename(columns={'level_2':'Question'})

fg = sns.FacetGrid(
    data=df1,
    row='Group',
    col='Role'
)

def draw_heatmap(*args, **kwargs):
    data = kwargs.pop('data')
    d = data[['Question', 1, 2, 3, 4, 5, 6, 7]] \
            .melt(id_vars='Question', var_name="Likert Score", value_name="Count") \
            .pivot(index="Question", columns="Likert Score", values="Count")
    d = d.div(d.sum(axis=1), axis=0).round(2)
    sns.heatmap(d, **kwargs)
    
fg.map_dataframe(
    draw_heatmap, 
    cbar_ax=fg.fig.add_axes([1, 0.3, .02, .4]),
    cbar_kws={'label': 'Percentage of responses'},
    vmin=0,
    vmax=1,
    cmap="Blues",
    linewidths=.1
)

这是输出:

我想显示问题属于调查问卷的哪个部分,理想情况下如下所示:

我看到了这个question,但我无法将建议的解决方案应用于我的案例。

非常感谢任何帮助。谢谢!

【问题讨论】:

    标签: python matplotlib seaborn


    【解决方案1】:

    我找到了解决方案;我不确定这是不是最好的解决方案,但我会分享以防其他人需要做同样的事情或类似的事情。

    from typing import List            
                     
    def draw_labels_groups(groups: List[List[int]], groups_labels: List[str]) -> None:    
        delta_x = 0.08
        delta_y = 0.08   
        for index, ax in enumerate(fg.axes.flat):
            if index % 2 == 0:  # only modify axis on the left column (assuming we have 2 columns)       
                yticklabels = ax.get_yticklabels()
                r = ax.figure.canvas.get_renderer()
                # get bounding boxes of y tick labels
                bounding_boxes = [t.get_window_extent(renderer=r).transformed(ax.transAxes.inverted()) \
                                  for t in yticklabels]
                # compute left-most x coordinates of all bounding boxes of y tick labels
                # xmin will be the right-most x coordinate of the grouping line
                xmin = min([bb.get_points()[0][0] for bb in bounding_boxes])
                # we need to move the label for the yaxis to the left, and x_coordinate_for_yaxis_label
                # will be its coordinate
                x_coordinate_for_yaxis_label = 0
                # draw every group
                for group_index, group in enumerate(groups):
                    first, last = group[0], group[1]
                    (x0f, y0f), (x1f, y1f) = bounding_boxes[first].get_points()
                    (x0l, y0l), (x1l, y1l) = bounding_boxes[last].get_points()
                    a = (xmin, y1f + delta_y)
                    b = (xmin - delta_x, y1f + delta_y)
                    c = (xmin - delta_x, y0l - delta_y)
                    d = (xmin, y0l - delta_y)
                    # uncomment the following four lines if you want to see 
                    # positions of points a, b, c, and d
    #                 ax.text(a[0], a[1], "a", ha='center', va='center', transform=ax.transAxes) 
    #                 ax.text(b[0], b[1], "b", ha='center', va='center', transform=ax.transAxes)                 
    #                 ax.text(c[0], c[1], "c", ha='center', va='center', transform=ax.transAxes)                 
    #                 ax.text(d[0], d[1], "d", ha='center', va='center', transform=ax.transAxes)                
                    line = plt.Line2D([a[0], b[0], c[0], d[0]], [a[1], b[1], c[1], d[1]],
                                      color='black', 
                                      transform=ax.transAxes, 
                                      linewidth=0.5)
                    line.set_clip_on(False)
                    ax.add_line(line)
                    t = ax.text(
                        b[0] - delta_x * 1.5,
                        c[1] + (b[1] - c[1]) / 2,
                        textwrap.fill(groups_labels[group_index], 10),
                        ha='center', 
                        va='center',
                        rotation=90,
                        wrap=True,
                        transform=ax.transAxes
                    )
                    x_coordinate_for_yaxis_label = min(
                        x_coordinate_for_yaxis_label, 
                        t.get_window_extent(renderer=r).transformed(ax.transAxes.inverted()).get_points()[0][0])
                # move the label of the y axis
                ax.yaxis.set_label_coords(x_coordinate_for_yaxis_label - delta_x, 0.5)
                 
    

    在我的问题示例中调用 draw_labels_groups([[0,2], [3,3]], ["Section 1", "Section 2 with long label"]) 会产生以下结果(注意:我在原始示例中修改了 yticklables 的值,添加了一个长值,以演示代码的工作原理)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-12-21
      • 2020-11-27
      • 1970-01-01
      • 2018-03-03
      • 2018-02-01
      • 1970-01-01
      相关资源
      最近更新 更多