【问题标题】:Placing Labels in nested categorical stacked bar with Bokeh and Pandas使用 Bokeh 和 Pandas 将标签放置在嵌套的分类堆叠条中
【发布时间】:2020-01-27 09:19:29
【问题描述】:

我正在尝试使用 pandas 数据框和散景 vbar 复制如下图表:

Objective

到目前为止,我已经设法将标签放置在其相应的高度,但现在我无法找到一种方法来访问类别 (2016,2017,2018) 位于 x 轴上的数值。这是我的结果:

My nested categorical stacked bars chart

这是我的代码。它很乱,但这是我到目前为止所管理的。那么有没有办法访问条形图 x_axis 中的数值?

def make_nested_stacked_bars(source,measurement,dimension_attr):
    #dimension_attr is a list that contains the names of columns in source that will be used as categories
    #measurement containes the name of the column with numeric data.

    data = source.copy()
    #Creates list of values of highest index
    list_attr = source[dimension_attr[0]].unique()
    list_stackers = list(source[dimension_attr[-1]].unique())
    list_stackers.sort()

    #trims labals that are too wide to fit in graph
    for column in data.columns:
        if data[column].dtype.name == 'object':
            data[column] = np.where(data[column].apply(len) > 30, data[column].str[:30]+'...', data[column])

    #Creates a list of dataframes, each grouping a specific value
    list_groups = []
    for item in list_attr:
        list_groups.append(data[data[dimension_attr[0]] == item])
    #Groups data by dimension attrs, aggregates measurement to count

    #Drops highest index from dimension attr
    dropped_attr = dimension_attr[0]
    dimension_attr.remove(dropped_attr)

    #Creates groupby by the last 2 parameters, and aggregates to count
    #Calculates percentage
    for index,value in enumerate(list_groups):
        list_groups[index] = list_groups[index].groupby(by=dimension_attr).agg({measurement: ['count']})
        list_groups[index] = list_groups[index].groupby(level=0).apply(lambda x: round(100 * x / float(x.sum()),1))
        # Resets indexes
        list_groups[index] =  list_groups[index].reset_index()
        list_groups[index] = list_groups[index].pivot(index=dimension_attr[0], columns=dimension_attr[1])
        list_groups[index].index = [(x,list_attr[index]) for x in list_groups[index].index]
        # Drops dimension attr as top level column
        list_groups[index].columns =   list_groups[index].columns.droplevel(0)
        list_groups[index].columns =   list_groups[index].columns.droplevel(0)

    df = pd.concat(list_groups)

    # Get the number of colors needed for the plot.
    colors = brewer["Spectral"][len(list_stackers)]
    colors.reverse()

    p = figure(plot_width=800, plot_height=500, x_range=FactorRange(*df.index))

    renderers = p.vbar_stack(list_stackers, x='index', width=0.3, fill_color=colors, legend=[get_item_value(x)for x in list_stackers], line_color=None, source=df, name=list_stackers,)

    # Adds a different hovertool to a stacked bar

    #empy dictionary with initial values set to zero
    list_previous_y = {}
    for item in df.index:
        list_previous_y[item] = 0

    #loops through bar graphs 
    for r in renderers:
        stack = r.name
        hover = HoverTool(tooltips=[
            ("%s" % stack, "@%s" % stack),
        ], renderers=[r])

        #Initial value for placing label in x_axis
        previous_x = 0.5

        #Loops through dataset rows
        for index, row in df.iterrows():
            #adds value of df column to list 
            list_previous_y[index] = list_previous_y[index] + df[stack][index]
            ## adds label if value is not nan and at least 10
            if not math.isnan(df[stack][index]) and df[stack][index]>=10:
                p.add_layout(Label(x=previous_x, y=list_previous_y[index] -df[stack][index]/2, 
                                   text='% '+str(df[stack][index]), render_mode='css',
                                   border_line_color='black', border_line_alpha=1.0,
                                    background_fill_color='white', background_fill_alpha=1.0))
            # increases position in x_axis
            #this should be done by adding the value of next bar in x_axis
            previous_x = previous_x + 0.8

        p.add_tools(hover)


    p.add_tools(hover)
    p.legend.location = "top_left"
    p.x_range.range_padding = 0.2
    p.xgrid.grid_line_color = None

    return p

或者有没有更简单的方法来完成这一切?

感谢您的宝贵时间!

更新:

添加了一个三级嵌套图表的附加图像,其中 x_axis 中的标签放置也应该完成

Three level nested chart

【问题讨论】:

    标签: python pandas bokeh


    【解决方案1】:

    我找不到访问 x 轴上类别 (2016,2017,2018) 所在的数值的方法。

    没有任何方法可以在 Python 端的独立 Bokeh 输出中访问此信息。坐标仅在 JavaScript 端的浏览器内部计算。即只有在您的 Python 代码完成运行并且完全不在图片中之后。即使在 Bokeh 服务器应用程序上下文中,也没有任何直接的方法,因为没有任何同步属性记录值。

    从 Bokeh 1.3.4 开始,支持使用分类坐标放置标签是 known open issue

    与此同时,我可以建议的唯一解决方法是:

    • 使用text 字形方法和ColumnDataSource 中的坐标,而不是Label。这应该可以使用实际的分类坐标进行定位。 (LabelSet 也可能有效,尽管我没有尝试过)。您可以在此处查看带有分类坐标的text 示例:

      https://github.com/bokeh/bokeh/blob/master/examples/plotting/file/periodic.py

    • 使用数字坐标定位Label。但是您必须进行实验/最佳猜测才能找到适合您的数值坐标。经验法则是类别在合成(数字)坐标空间中的宽度为 1.0。

    【讨论】:

    • 谢谢!使用 Labelset 并对数据框进行一些修改就可以了
    【解决方案2】:

    我的解决方案是..

    创建用于制作图表的数据框的副本。此数据框 (labeling_data) 包含计算的 y_axis 坐标,以便标签位于相应堆叠条的中间。 然后,添加额外的列作为实际标签,其中要显示的值与百分比符号连接。

        labeling_data = df.copy()
        #Cumulative sum of columns
        labeling_data = labeling_data.cumsum(axis=1)
        #New names for columns
        y_position = []
        for item in labeling_data.columns:
            y_position.append(item+'_offset')
        labeling_data.columns = y_position
    
        #Copies original columns
        for item in df:
            #Adding original columns
            labeling_data[item] = df[item]
            #Modifying offset columns to place label in the middle of the bar 
            labeling_data[item+'_offset'] =  labeling_data[item+'_offset']-labeling_data[item]/2
            #Concatenating values with percentage symbol if at least 10
            labeling_data[item+'_label'] = np.where(df[item] >=10 , '% '+df[item].astype(str), "")
    

    最后,通过循环遍历绘图的渲染器,使用 labeling_data 作为 Datasource 将标签集添加到每个堆栈组。通过这样做,数据帧的索引可用于设置标签的 x_coordinate。并为 y_coordinate 和 text 参数添加了相应的列。

        info = ColumnDataSource(labeling_data)
    
        #loops through bar graphs
        for r in renderers:
            stack = r.name
    
            #Loops through dataset rows
            for index, row in df.iterrows():
                #Creates Labelset and uses index, y_offset and label columns 
                #as x, y and text parameters 
                labels = LabelSet(x='index', y=stack+'_offset', text=stack+'_label', level='overlay',
                                      x_offset=-25, y_offset=-5, source=info)
                p.add_layout(labels)
    

    最终结果:

    Nested categorical stacked bar chart with labels

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-22
      • 1970-01-01
      • 1970-01-01
      • 2012-01-04
      相关资源
      最近更新 更多