1

I tried to add the percentage on the bar in combo chart, line and column graphs.
However, all the values displayed are messy.

I provide the data here, this is also my previous post and the answer is provided by Quang Hoang.

Group   yq        Value1    Value2
G       2014Q1     0.07        1.1
G       2014Q2     0.06        1.09
G       2014Q3     0.09        1.11
G       2014Q4     0.04        1.13
I       2014Q1     0.10        1.2
I       2014Q2     0.13        1.25
I       2014Q3     0.15        1.23
I       2014Q4     0.18        1.4

I provided the code I tried:

fig, ax1 = plt.subplots(figsize=(7,5))
ax2=ax1.twinx()
sns.lineplot(x='yq',y='Value2', data=dataset, hue='Group', ax=ax1, legend = None)
ax1.set_xticklabels(ax1.get_xticks(), rotation=45)
ax1.set_ylabel("")
ax1.set_ylim((min(dataset['Value2']) - 0.05, max(dataset['Value2']) + 0.05))
sns.barplot(x='yq', y='Value1', data=dataset, hue='Group',ax=ax2)
ax2.set_yticklabels(['{:.1f}%'.format(a*100) for a in ax2.get_yticks()])
ax2.set_ylabel("")
for index, row in dataset.iterrows():
    ax2.text(row.name,row['Value1'], '{:.1f}%'.format(round(row['Value1'],2)), color='black')
plt.show()

The percentages showing on the plot are messy and do not place properly on each bar and group.
I searched on here and here but I cannot solve it.
Any solution?

I provide my result:
enter image description here

I also provide the correct resulting image created by R's package ggplot2.
There are two packages similar to ggplot2 in Python, plotnine and ggplot. However, I cannot use it in my Python.
enter image description here

I provide my R code as your reference if it helps:

library(data.table)
library(ggplot2)
library(zoo)
dataset <- fread("Group   yq        Value1    Value2
G       2014/1/1     0.07        1.1
G       2014/4/1     0.06        1.09
G       2014/7/1     0.09        1.11
G       2014/10/1     0.04        1.13
I       2014/1/1     0.10        1.2
I       2014/4/1     0.13        1.25
I       2014/7/1     0.15        1.23
I       2014/10/1     0.18        1.4", header = T)
dataset$yq <- as.Date(dataset$yq)
dataset[, yq := as.yearqtr(dataset$yq, format = "%Y-%m-%d")]

ggplot(data = dataset, aes(x = yq, colour = Group, fill = Group,
                           label = scales::percent(Value1, accuracy = 0.1))) + 
  geom_col(aes(y = sec_axis_mult * Value1), position = position_dodge2(width = 0)) +
  geom_line(aes(y = Value2)) +
  scale_colour_manual(values = c("red", "darkblue"), labels = c("G", "I")) +
  scale_fill_manual(values = c("red", "darkblue"), labels = NULL, breaks = NULL) +
  scale_x_yearqtr(format = "%YQ%q", breaks = unique(dataset$yq)) +
  scale_y_continuous(name = "Value2",
                     sec.axis = sec_axis(~./sec_axis_mult, name = "Value1",
                                         labels = scales::percent)) +
  theme_bw() +
  theme(axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        axis.title.y.right = element_blank(),
        axis.ticks.x=element_blank(),
        axis.ticks.y=element_blank(),
        axis.text.x=element_text(angle = 45, size = 12, vjust = 0.5, face = "bold"),
        axis.text.y=element_blank(),
        axis.line = element_line(colour = "white"),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.border = element_blank(),
        panel.background = element_blank(),
        plot.background=element_blank(),
        legend.position="left",
        legend.title=element_blank(),
        legend.text = element_text(size = 16, face = "bold"),
        legend.key = element_blank(),
        legend.box.background =  element_blank()) +
  guides(colour = guide_legend(override.aes = list(shape = 15, size = 10))) +
  geom_text(data = dataset, aes(y = sec_axis_mult * Value1, colour = Group), 
            position = position_dodge(width = 0.25),
            vjust = -0.3, size = 4)
Peter Chen
  • 1,464
  • 3
  • 21
  • 48

1 Answers1

2

What I'm doing is dynamically labeling the bar graphs based on the coordinates for each patch that's drawn by the bar function.

fig, ax1 = plt.subplots(figsize=(7,5))
ax2=ax1.twinx()
sns.lineplot(x='yq',y='Value2', data=dataset, hue='Group', ax=ax1, legend = None)
ax1.set_xticklabels(ax1.get_xticks(), rotation=45)
ax1.set_ylabel("")
ax1.set_ylim((min(dataset['Value2']) - 0.05, max(dataset['Value2']) + 0.05))
sns.barplot(x='yq', y='Value1', data=dataset, hue='Group',ax=ax2)
ax2.set_yticklabels(['{:.1f}%'.format(a*100) for a in ax2.get_yticks()])
ax2.set_ylabel("")
#iterate through each group of bars
for group in ax2.containers:
    for bar in group:
        #label the bar graphs based on the coordinates of the bar patches
        ax2.text(
            bar.get_xy()[0]+bar.get_width()/2,
            bar.get_height(), 
            '{:.1f}%'.format(round(100*bar.get_height(),2)), 
            color='black',
            horizontalalignment='center'
        )

Output: enter image description here

I've adjusted the code to more closely match the desired output that's been added to the original question.

fig, ax1 = plt.subplots(figsize=(7,5))
ax2=ax1.twiny().twinx()
sns.lineplot(x='yq',y='Value2', data=dataset, hue='Group', ax=ax1, legend = None)
ax1.set_xticklabels(dataset['yq'], rotation=45)
ax1.set_ylabel("")
ax1.set_ylim((0, max(dataset['Value2']) + 0.05))
ax2.set_ylim(0, max(dataset['Value2']) + 0.05)
sns.barplot(x='yq', y='Value1', data=dataset, hue='Group',ax=ax2)

#iterate through each group of bars
for group in ax2.containers:
    for bar in group:
        #label the bar graphs based on the coordinates of the bar patches
        ax2.text(
            bar.get_xy()[0]+bar.get_width()/2,
            bar.get_height(), 
            '{:.1f}%'.format(100*bar.get_height()), 
            color='black',
            horizontalalignment='center'
        )

ax1.yaxis.set_visible(False)
ax2.yaxis.set_visible(False)
ax2.xaxis.set_visible(False)

Outputs: enter image description here

iamchoosinganame
  • 1,090
  • 6
  • 15
  • Personally, I would use [PercentFormatter](https://matplotlib.org/3.1.0/api/ticker_api.html) to render percentages, but I kept my code consistent with how the question labeled percentages. – iamchoosinganame May 31 '19 at 15:26
  • Could you also provide `PercentFormatter`'s code that you will user to draw? – Peter Chen May 31 '19 at 15:31
  • PercentFormatter doesn't draw on the axis. You can format the ticks on the axis. In your original code you labeled all the ticks with percent, but you can just set the axis ticker to PercentFormatter and then decimal values will be labeled as percentages. – iamchoosinganame May 31 '19 at 15:33
  • OK. Is it possible to make line and bar apart the plot I provide above? It seems that bar will cover the line. – Peter Chen May 31 '19 at 15:40
  • I tried to align the text color by their groups. I modified `color = group` in the `ax2.text` but got error – Peter Chen May 31 '19 at 15:53
  • 1
    ```group``` is an array of bar container objects. Each bar should have a facecolor property, but I do not recommend making the text the same color as the bar as it will make it hard to read. – iamchoosinganame May 31 '19 at 16:06
  • 1
    Try ```color=bar.get_facecolor()``` Also if you want red and blue as the colors. You can change the seaborn palette. – iamchoosinganame May 31 '19 at 16:08
  • the second plot you draw shows label and tick of x-axis on the top. It seems weird – Peter Chen May 31 '19 at 17:05
  • I would recommend using `annotate` instead of `text` and apply some offset just as shown in [the matplotlib example](https://matplotlib.org/gallery/lines_bars_and_markers/barchart.html). – ImportanceOfBeingErnest May 31 '19 at 23:07