Размещение меток во вложенном категориальном сложенном баре с боке и пандами - PullRequest
0 голосов
/ 27 сентября 2019

Я пытаюсь скопировать диаграмму, как показано ниже, используя пандасный фрейм данных и bokeh vbar .:

Цель

Пока мне удалось разместитьметки имеют соответствующую высоту, но теперь я не могу найти способ получить доступ к числовому значению, где категория (2016,2017,2018) расположена на оси x.Это мой результат:

Мой вложенный график категориальных столбцов с накоплением

Это мой код.Это грязно, но это то, что мне удалось до сих пор.Так есть ли способ получить доступ к числовому значению в 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 также должно быть выполнено

трехуровневая вложенная диаграмма

Ответы [ 2 ]

0 голосов
/ 28 сентября 2019

Мое решение было ..

Создание копии кадра данных, используемого для создания диаграммы.Этот фрейм данных (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 в качестве источника данных.Таким образом, индекс фрейма данных может быть использован для установки 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)

Конечный результат:

Вложенная линейная столбчатая диаграмма с метками

0 голосов
/ 27 сентября 2019

Я не могу найти способ получить доступ к числовому значению, если категория (2016,2017,2018) расположена на оси x.

Нет никакого способа получить доступ к этой информации на стороне Python в автономном выводе Bokeh.Координаты рассчитываются только внутри браузера на стороне JavaScript.т. е. только после того, как ваш код Python завершит работу и полностью исчезнетДаже в контексте приложения сервера Bokeh прямого пути нет, поскольку нет синхронизированных свойств, которые записывают значения.

Начиная с Bokeh 1.3.4, поддержка размещения меток с категориальными координатами - известный открытый выпуск .

В то же время я могу предложить только следующие обходные пути:

  • Используйте метод глифа text с координатами в ColumnDataSource вместо Label.Это должно работать с позицией с фактическими категориальными координатами.(LabelSet может также работать, хотя я не пробовал).Вы можете увидеть пример text с категориальными координатами здесь:

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

  • Используйте числовые координаты для позиционирования Label.Но вам придется поэкспериментировать / лучше всего угадать, чтобы найти числовые координаты, которые работают для вас.Практическое правило заключается в том, что категории имеют ширину 1,0 в синтетическом (числовом) координатном пространстве.

...