How to move the x-axis label "number of patients" right below the x-tick value '0'?
The code I used to create the bar chart is as follows:
n_counts.plot.barh(stacked=True, width=0.7, figsize=(12,8), color=['navy','cornflowerblue']);
plt.draw()
# Get current tick positions and labels
pos, lab = plt.xticks()
# Edit labels
new_label = [int(x)*-1 if int(x)<0 else int(x) for x in pos]
# Set new labels
plt.xticks(pos, new_label)
plt.ylabel('neighbourhood', fontsize=13)
plt.xlabel('number of patients', fontsize=13)
plt.legend(['attended', 'not attended'], title='Attendance')
plt.title(' Attendance Counts in Top 20 Regions', loc='left', fontsize=16);
You need to transform the data coordinate of the x-label (0 in the example) to axes coordinates to place the label:
import matplotlib.pyplot as plt
import matplotlib as mpl
fig, ax = plt.subplots()
ax.set_xlim((-2, 10))
x = 0 # position of xlabel
ax.set_xlabel("xlabel", ha='left', x=ax.transAxes.inverted().transform(ax.transData.transform((x, 0)))[0])
transData transforms data coordinates into display coordinates that are then transformed back into Axes coordinates using the inverse transAxes Axes transformation. See the Transformations Tutorial for further details.
If you want to shift the x-axis label, you can use set_label_coords() to do this. Below is a stripped down version of your plot with the adjusted position.
The position of (0,0) is the bottom left corner of the box. As the x-axis ticks go from 0 to 9, with the 0-value tick at the second position from left corner, I used 2/9 = 0.22 as the x-coordinate. This will allow you to keep the text below the second tick. For y-coordinate, -0.05 suits this figure size and you can adjust it to a larger or smaller number based on the plot getting smaller or bigger respectively. You can fine tune it to suit your requirement...
fig,ax = plt.subplots(figsize=(12,8))
ax.set_xlim(-2000, 7000)
start, end = ax.get_xlim()
# Edit labels
labels = [-2000,-1000,0,1000,2000,3000,4000,5000,6000,7000]
ax.xaxis.set_ticks(np.arange(start, end+1, 1000))
ax.set_xticklabels(labels)
# Set new labels
ax.set_ylabel('neighbourhood', fontsize=13)
ax.set_xlabel('number of patients', fontsize=13)
ax.legend(['attended', 'not attended'], title='Attendance')
ax.set_title('Attendance Counts in Top 20 Regions', loc='left', fontsize=16);
ax.xaxis.set_label_coords(.22, -0.05)
Related
import matplotlib.pyplot as plt
import seaborn as sns
low = '#00FF00'
medium = '#FFFF00'
high = '#FF0000'
plt.figure(figsize=(1,2))
sns.color_palette("blend:#00FF00,#FFFF00,#FF0000",as_cmap=True)
The result that I get is:
But the labels are "under, bad, over". Not sure from where it is pulling it, is there a way to rename or remove those variables? I tried the following, but did not work
ax = sns.color_palette("blend:#00FF00,#FFFF00,#FF0000",as_cmap=True)
ax.collections[0].colorbar.set_label("Hello")
I want the labels to be low, med and high
Using appropriate grid of subplots, we can create the expected figure. However, it involves fine adjustments to the plot size and grid size to really get the expected result:
import matplotlib.pyplot as plt
import matplotlib as mpl
import seaborn as sns
low = '#00FF00'
medium = '#FFFF00'
high = '#FF0000'
plt.figure(figsize = (10, 2))
# Plot the wider colorbar
ax = plt.subplot(4, 10, (1, 30))
cmap = sns.color_palette("blend:#00FF00,#FFFF00,#FF0000",as_cmap=True)
cbar = plt.colorbar(mpl.cm.ScalarMappable(cmap=cmap), cax = ax, orientation="horizontal", ticks=None)
cbar.set_ticks([])
ax.set_title("blend", loc='left', fontweight='bold')
# Function to create box labels and arrange corresponding text
def labels(position, color, label, label_position):
"""
The first and second arguments decides the position of the subplot and its fill color, respectively.
The third argument decides the text to put alongside the subplot.
"""
ax = plt.subplot(5, 100, position)
ax.set_xticks([])
ax.set_yticks([])
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
x = [0, 1]
y = [1, 1]
ax.fill_between(x, y, color=color)
ax.text(*label_position, label)
labels((401, 403), low, "low", (1.5, 0.2))
labels((449, 451), medium, "medium", (-3.0, 0.2))
labels((498, 500), high, "high", (-1.5, 0.2))
plt.show()
This gives:
In the code above:
The colorbar spans first thirty subplots of the grid with size 4 x 10 (created using plt.subplot(4, 10, (1, 30)). However, to add labels below the colorbar, we can use a grid that has a lot more Axes in a given row, so we have a 5 x 100 grid for labels, but we only utilise a few Axes of this grid to get label boxes (created using plt.subplot(5, 100, position). I was not exactly sure how many rows would be suitable for the grid of labels, but the number of rows definitely need to be more than 3. Otherwise, the grid of label boxes will cover colorbar and it will not be visible.
We want to make sure that label boxes don't cover the colorbar, thus we use the 5th row of the grid for the boxes. As there are 100 columns in this grid, the first box spans the grid from the index 401 and ends at 403: I used more than one Axes on the grid otherwise the boxes might have looked very thin. This indexing is motivated directly by the expected output: the medium box must cover 50th Axes in the last row of the grid (since it should be in the middle), and the high box must be at the end of the row, thus it should span from the index 498 to 500.
plt.subplot creates an Axes for label boxes whose xlim and ylim are both set to (0, 1). This means the squares we see at the bottom of the figure are of size 1 x 1.
Using ax.set_ticks, we have removed the ticks on x- and y-axis of the Axes giving us squares without any ticks. Same for colorbar as well.
ax.text adds the label at the position label_position. I wasn't sure about the y-coordinate of the label. But since we have already set the x-lim and y-lim of the Axes to (0, 1), x-coordinate in the label_position will be more than one if the label needs on the right side of the Axes and less than zero if it needs to be on the left side of the Axes e.g. the xs in the label_position of the labels low and medium are of opposite sign. The position of the text ultimately boils down to the font size and the length of the string the label represents, thus it's a matter of fine adjustments, but can be programmatically taken care of.
I want to duplicate axes so that I can express an exponent in terms of its doubling time.
I think I am doing things right, but I have two problems
no label on the right hand side of the chart and
y-axis gridlines that are plotted above the data that I cannot shift to the bottom, nor remove.
Example code follows:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
MARGINS = 0.02
data = pd.Series(np.arange(0.05, 1.0, 0.1))
# preliminaries
plt.style.use('ggplot')
fig, ax = plt.subplots()
ax.figure.set_size_inches((8, 4))
ax.set_ylabel('$k$') # This works
ax.margins(MARGINS)
ax.set_axisbelow(True)
# duplicate the axes
axr = ax.twinx().twiny()
axr.margins(MARGINS)
axr.set_ylabel('Doubling time') # This does not work
# No x-ticks at the top
axr.xaxis.set_ticks([])
axr.xaxis.set_ticklabels([])
# plot the data
ax.plot(data.index, data)
# label right-hand y-axis
locations = ax.get_yticks()
new_labels = [f'{np.log(2)/x:,.2f}' if x != 0 else '∞' for x in locations ]
axr.yaxis.set_ticks(locations)
axr.yaxis.set_ticklabels(new_labels)
axr.set_axisbelow(True) # this does not work
# match the left and right ylim settings
axr.set_ylim(ax.get_ylim())
axr.set_xlim(ax.get_xlim())
# remove the grid
axr.grid(False, which='both')
axr.yaxis.grid(False, which='both') # this does not work
# finish-up
ax.set_title('Chart')
fig.tight_layout(pad=1.1)
plt.show()
plt.close('all')
Desired output:
Similar chart to above but with:
a right hand side y-axis label
no y-axis gridlines over the data line (but keep the horizontal gridlines under the dataline)
Change the order of twiny and twinx:
axr = ax.twiny().twinx()
I created a cumulative histogram. Now I want to draw a line from top of any bin to the y-axis in that histogram and show the value of it like this:
Can you show me the way to do?
Below is my code to draw that histogram:
plt.rcParams['ytick.right'] = plt.rcParams['ytick.labelright'] = True
plt.rcParams['ytick.left'] = plt.rcParams['ytick.labelleft'] = False
plt.figure(figsize=[8, 6])
plt.hist(df['days'], bins=range(0, 50, 1), color="dodgerblue", edgecolor='black'
,cumulative=-1, density=True
,histtype='barstacked')
plt.xlabel('Number of Days')
plt.ylabel('Density')
Thank you so much!
Oneliner:
plt.axhline(y, color='k', linestyle='dashed', linewidth=1)
Use this to add a horizontal line to your histogram.
Place your mean or value of y in place of y in the above code snippet.
Simply drawing a horizontal line rises two problems:
The line will be drawn on top of the bars, from the left to the right. To have it behind the bars, use zorder=0.
The line will still be visible at the far left, as there are no bars there. Changing the x-axis to a "tight" layout with plt.autoscale(enable=True, axis='x', tight=True) solves that.
To add a new tick at the specific y-position, you can take the list of existing ticks, create a list including the new tick and set those as the new ticks.
To change the color of the newly added tick, you first find its index in the list, and then change the color of the tick with that index.
One problem with this approach, is that the new tick might overlap with an existing tick. This could be solved by looping through the list and if an existing tick is nearer than some epsilon to the new tick, remove the existing tick. This is not yet implemented in the code example.
Alternatively, the tick value could be displayed to the left of the axis, on top of the horizontal line. Of course, that would lead to a problem in case there wouldn't be enough place for the text.
You might want to round the value of the special tick to the nearest hundredths to prevent that the other ticks also get displayed with more digits.
I created an example with simulated data:
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
df = pd.DataFrame({"days": np.random.normal(25, 10, 10000)})
plt.rcParams['ytick.right'] = plt.rcParams['ytick.labelright'] = True
plt.rcParams['ytick.left'] = plt.rcParams['ytick.labelleft'] = False
plt.figure(figsize=[8, 6])
bin_heights, _, _ = plt.hist(df['days'], bins=range(0, 50, 1), color="dodgerblue", edgecolor='black',
cumulative=-1, density=True,
histtype='barstacked')
plt.autoscale(enable=True, axis='both', tight=True) # use axis='x' to only set the x axis tight
special_y = bin_heights[15]
# draw a horizontal line, use zorder=0 so it is drawn behind the bars
plt.axhline(special_y, 0, 1, color='red', linestyle='dashed', linewidth=1, zorder=0)
plt.yticks(list(plt.yticks()[0]) + [special_y]) # add a tick in y for special_y
# find the index of special_y in the new ticks (ticks are sorted automatically)
index_special_y = list(plt.yticks()[0]).index(special_y)
plt.gca().get_yticklabels()[index_special_y].set_color('red') # change the color of the special tick
plt.xlabel('Number of Days')
plt.ylabel('Density')
plt.show()
I have a Pandas DataFrame with a column called "AXLES", which can take an integer value between 3-12. I am trying to use Seaborn's countplot() option to achieve the following plot:
left y axis shows the frequencies of these values occurring in the data. The axis extends are [0%-100%], tick marks at every 10%.
right y axis shows the actual counts, values correspond to tick marks determined by the left y axis (marked at every 10%.)
x axis shows the categories for the bar plots [3, 4, 5, 6, 7, 8, 9, 10, 11, 12].
Annotation on top of the bars show the actual percentage of that category.
The following code gives me the plot below, with actual counts, but I could not find a way to convert them into frequencies. I can get the frequencies using df.AXLES.value_counts()/len(df.index) but I am not sure about how to plug this information into Seaborn's countplot().
I also found a workaround for the annotations, but I am not sure if that is the best implementation.
Any help would be appreciated!
Thanks
plt.figure(figsize=(12,8))
ax = sns.countplot(x="AXLES", data=dfWIM, order=[3,4,5,6,7,8,9,10,11,12])
plt.title('Distribution of Truck Configurations')
plt.xlabel('Number of Axles')
plt.ylabel('Frequency [%]')
for p in ax.patches:
ax.annotate('%{:.1f}'.format(p.get_height()), (p.get_x()+0.1, p.get_height()+50))
EDIT:
I got closer to what I need with the following code, using Pandas' bar plot, ditching Seaborn. Feels like I'm using so many workarounds, and there has to be an easier way to do it. The issues with this approach:
There is no order keyword in Pandas' bar plot function as Seaborn's countplot() has, so I cannot plot all categories from 3-12 as I did in the countplot(). I need to have them shown even if there is no data in that category.
The secondary y-axis messes up the bars and the annotation for some reason (see the white gridlines drawn over the text and bars).
plt.figure(figsize=(12,8))
plt.title('Distribution of Truck Configurations')
plt.xlabel('Number of Axles')
plt.ylabel('Frequency [%]')
ax = (dfWIM.AXLES.value_counts()/len(df)*100).sort_index().plot(kind="bar", rot=0)
ax.set_yticks(np.arange(0, 110, 10))
ax2 = ax.twinx()
ax2.set_yticks(np.arange(0, 110, 10)*len(df)/100)
for p in ax.patches:
ax.annotate('{:.2f}%'.format(p.get_height()), (p.get_x()+0.15, p.get_height()+1))
You can do this by making a twinx axes for the frequencies. You can switch the two y axes around so the frequencies stay on the left and the counts on the right, but without having to recalculate the counts axis (here we use tick_left() and tick_right() to move the ticks and set_label_position to move the axis labels
You can then set the ticks using the matplotlib.ticker module, specifically ticker.MultipleLocator and ticker.LinearLocator.
As for your annotations, you can get the x and y locations for all 4 corners of the bar with patch.get_bbox().get_points(). This, along with setting the horizontal and vertical alignment correctly, means you don't need to add any arbitrary offsets to the annotation location.
Finally, you need to turn the grid off for the twinned axis, to prevent grid lines showing up on top of the bars (ax2.grid(None))
Here is a working script:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import matplotlib.ticker as ticker
# Some random data
dfWIM = pd.DataFrame({'AXLES': np.random.normal(8, 2, 5000).astype(int)})
ncount = len(dfWIM)
plt.figure(figsize=(12,8))
ax = sns.countplot(x="AXLES", data=dfWIM, order=[3,4,5,6,7,8,9,10,11,12])
plt.title('Distribution of Truck Configurations')
plt.xlabel('Number of Axles')
# Make twin axis
ax2=ax.twinx()
# Switch so count axis is on right, frequency on left
ax2.yaxis.tick_left()
ax.yaxis.tick_right()
# Also switch the labels over
ax.yaxis.set_label_position('right')
ax2.yaxis.set_label_position('left')
ax2.set_ylabel('Frequency [%]')
for p in ax.patches:
x=p.get_bbox().get_points()[:,0]
y=p.get_bbox().get_points()[1,1]
ax.annotate('{:.1f}%'.format(100.*y/ncount), (x.mean(), y),
ha='center', va='bottom') # set the alignment of the text
# Use a LinearLocator to ensure the correct number of ticks
ax.yaxis.set_major_locator(ticker.LinearLocator(11))
# Fix the frequency range to 0-100
ax2.set_ylim(0,100)
ax.set_ylim(0,ncount)
# And use a MultipleLocator to ensure a tick spacing of 10
ax2.yaxis.set_major_locator(ticker.MultipleLocator(10))
# Need to turn the grid on ax2 off, otherwise the gridlines end up on top of the bars
ax2.grid(None)
plt.savefig('snscounter.pdf')
I got it to work using core matplotlib's bar plot. I didn't have your data obviously, but adapting it to yours should be straight forward.
Approach
I used matplotlib's twin axis and plotted the data as bars on the second Axes object. The rest ist just some fiddeling around to get the ticks right and make annotations.
Hope this helps.
Code
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from mpl_toolkits.mplot3d import Axes3D
import seaborn as sns
tot = np.random.rand( 1 ) * 100
data = np.random.rand( 1, 12 )
data = data / sum(data,1) * tot
df = pd.DataFrame( data )
palette = sns.husl_palette(9, s=0.7 )
### Left Axis
# Plot nothing here, autmatically scales to second axis.
fig, ax1 = plt.subplots()
ax1.set_ylim( [0,100] )
# Remove grid lines.
ax1.grid( False )
# Set ticks and add percentage sign.
ax1.yaxis.set_ticks( np.arange(0,101,10) )
fmt = '%.0f%%'
yticks = matplotlib.ticker.FormatStrFormatter( fmt )
ax1.yaxis.set_major_formatter( yticks )
### Right Axis
# Plot data as bars.
x = np.arange(0,9,1)
ax2 = ax1.twinx()
rects = ax2.bar( x-0.4, np.asarray(df.loc[0,3:]), width=0.8 )
# Set ticks on x-axis and remove grid lines.
ax2.set_xlim( [-0.5,8.5] )
ax2.xaxis.set_ticks( x )
ax2.xaxis.grid( False )
# Set ticks on y-axis in 10% steps.
ax2.set_ylim( [0,tot] )
ax2.yaxis.set_ticks( np.linspace( 0, tot, 11 ) )
# Add labels and change colors.
for i,r in enumerate(rects):
h = r.get_height()
r.set_color( palette[ i % len(palette) ] )
ax2.text( r.get_x() + r.get_width()/2.0, \
h + 0.01*tot, \
r'%d%%'%int(100*h/tot), ha = 'center' )
I think you can first set the y major ticks manually and then modify each label
dfWIM = pd.DataFrame({'AXLES': np.random.randint(3, 10, 1000)})
total = len(dfWIM)*1.
plt.figure(figsize=(12,8))
ax = sns.countplot(x="AXLES", data=dfWIM, order=[3,4,5,6,7,8,9,10,11,12])
plt.title('Distribution of Truck Configurations')
plt.xlabel('Number of Axles')
plt.ylabel('Frequency [%]')
for p in ax.patches:
ax.annotate('{:.1f}%'.format(100*p.get_height()/total), (p.get_x()+0.1, p.get_height()+5))
#put 11 ticks (therefore 10 steps), from 0 to the total number of rows in the dataframe
ax.yaxis.set_ticks(np.linspace(0, total, 11))
#adjust the ticklabel to the desired format, without changing the position of the ticks.
_ = ax.set_yticklabels(map('{:.1f}%'.format, 100*ax.yaxis.get_majorticklocs()/total))
I have a Pandas DataFrame with a column called "AXLES", which can take an integer value between 3-12. I am trying to use Seaborn's countplot() option to achieve the following plot:
left y axis shows the frequencies of these values occurring in the data. The axis extends are [0%-100%], tick marks at every 10%.
right y axis shows the actual counts, values correspond to tick marks determined by the left y axis (marked at every 10%.)
x axis shows the categories for the bar plots [3, 4, 5, 6, 7, 8, 9, 10, 11, 12].
Annotation on top of the bars show the actual percentage of that category.
The following code gives me the plot below, with actual counts, but I could not find a way to convert them into frequencies. I can get the frequencies using df.AXLES.value_counts()/len(df.index) but I am not sure about how to plug this information into Seaborn's countplot().
I also found a workaround for the annotations, but I am not sure if that is the best implementation.
Any help would be appreciated!
Thanks
plt.figure(figsize=(12,8))
ax = sns.countplot(x="AXLES", data=dfWIM, order=[3,4,5,6,7,8,9,10,11,12])
plt.title('Distribution of Truck Configurations')
plt.xlabel('Number of Axles')
plt.ylabel('Frequency [%]')
for p in ax.patches:
ax.annotate('%{:.1f}'.format(p.get_height()), (p.get_x()+0.1, p.get_height()+50))
EDIT:
I got closer to what I need with the following code, using Pandas' bar plot, ditching Seaborn. Feels like I'm using so many workarounds, and there has to be an easier way to do it. The issues with this approach:
There is no order keyword in Pandas' bar plot function as Seaborn's countplot() has, so I cannot plot all categories from 3-12 as I did in the countplot(). I need to have them shown even if there is no data in that category.
The secondary y-axis messes up the bars and the annotation for some reason (see the white gridlines drawn over the text and bars).
plt.figure(figsize=(12,8))
plt.title('Distribution of Truck Configurations')
plt.xlabel('Number of Axles')
plt.ylabel('Frequency [%]')
ax = (dfWIM.AXLES.value_counts()/len(df)*100).sort_index().plot(kind="bar", rot=0)
ax.set_yticks(np.arange(0, 110, 10))
ax2 = ax.twinx()
ax2.set_yticks(np.arange(0, 110, 10)*len(df)/100)
for p in ax.patches:
ax.annotate('{:.2f}%'.format(p.get_height()), (p.get_x()+0.15, p.get_height()+1))
You can do this by making a twinx axes for the frequencies. You can switch the two y axes around so the frequencies stay on the left and the counts on the right, but without having to recalculate the counts axis (here we use tick_left() and tick_right() to move the ticks and set_label_position to move the axis labels
You can then set the ticks using the matplotlib.ticker module, specifically ticker.MultipleLocator and ticker.LinearLocator.
As for your annotations, you can get the x and y locations for all 4 corners of the bar with patch.get_bbox().get_points(). This, along with setting the horizontal and vertical alignment correctly, means you don't need to add any arbitrary offsets to the annotation location.
Finally, you need to turn the grid off for the twinned axis, to prevent grid lines showing up on top of the bars (ax2.grid(None))
Here is a working script:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import matplotlib.ticker as ticker
# Some random data
dfWIM = pd.DataFrame({'AXLES': np.random.normal(8, 2, 5000).astype(int)})
ncount = len(dfWIM)
plt.figure(figsize=(12,8))
ax = sns.countplot(x="AXLES", data=dfWIM, order=[3,4,5,6,7,8,9,10,11,12])
plt.title('Distribution of Truck Configurations')
plt.xlabel('Number of Axles')
# Make twin axis
ax2=ax.twinx()
# Switch so count axis is on right, frequency on left
ax2.yaxis.tick_left()
ax.yaxis.tick_right()
# Also switch the labels over
ax.yaxis.set_label_position('right')
ax2.yaxis.set_label_position('left')
ax2.set_ylabel('Frequency [%]')
for p in ax.patches:
x=p.get_bbox().get_points()[:,0]
y=p.get_bbox().get_points()[1,1]
ax.annotate('{:.1f}%'.format(100.*y/ncount), (x.mean(), y),
ha='center', va='bottom') # set the alignment of the text
# Use a LinearLocator to ensure the correct number of ticks
ax.yaxis.set_major_locator(ticker.LinearLocator(11))
# Fix the frequency range to 0-100
ax2.set_ylim(0,100)
ax.set_ylim(0,ncount)
# And use a MultipleLocator to ensure a tick spacing of 10
ax2.yaxis.set_major_locator(ticker.MultipleLocator(10))
# Need to turn the grid on ax2 off, otherwise the gridlines end up on top of the bars
ax2.grid(None)
plt.savefig('snscounter.pdf')
I got it to work using core matplotlib's bar plot. I didn't have your data obviously, but adapting it to yours should be straight forward.
Approach
I used matplotlib's twin axis and plotted the data as bars on the second Axes object. The rest ist just some fiddeling around to get the ticks right and make annotations.
Hope this helps.
Code
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from mpl_toolkits.mplot3d import Axes3D
import seaborn as sns
tot = np.random.rand( 1 ) * 100
data = np.random.rand( 1, 12 )
data = data / sum(data,1) * tot
df = pd.DataFrame( data )
palette = sns.husl_palette(9, s=0.7 )
### Left Axis
# Plot nothing here, autmatically scales to second axis.
fig, ax1 = plt.subplots()
ax1.set_ylim( [0,100] )
# Remove grid lines.
ax1.grid( False )
# Set ticks and add percentage sign.
ax1.yaxis.set_ticks( np.arange(0,101,10) )
fmt = '%.0f%%'
yticks = matplotlib.ticker.FormatStrFormatter( fmt )
ax1.yaxis.set_major_formatter( yticks )
### Right Axis
# Plot data as bars.
x = np.arange(0,9,1)
ax2 = ax1.twinx()
rects = ax2.bar( x-0.4, np.asarray(df.loc[0,3:]), width=0.8 )
# Set ticks on x-axis and remove grid lines.
ax2.set_xlim( [-0.5,8.5] )
ax2.xaxis.set_ticks( x )
ax2.xaxis.grid( False )
# Set ticks on y-axis in 10% steps.
ax2.set_ylim( [0,tot] )
ax2.yaxis.set_ticks( np.linspace( 0, tot, 11 ) )
# Add labels and change colors.
for i,r in enumerate(rects):
h = r.get_height()
r.set_color( palette[ i % len(palette) ] )
ax2.text( r.get_x() + r.get_width()/2.0, \
h + 0.01*tot, \
r'%d%%'%int(100*h/tot), ha = 'center' )
I think you can first set the y major ticks manually and then modify each label
dfWIM = pd.DataFrame({'AXLES': np.random.randint(3, 10, 1000)})
total = len(dfWIM)*1.
plt.figure(figsize=(12,8))
ax = sns.countplot(x="AXLES", data=dfWIM, order=[3,4,5,6,7,8,9,10,11,12])
plt.title('Distribution of Truck Configurations')
plt.xlabel('Number of Axles')
plt.ylabel('Frequency [%]')
for p in ax.patches:
ax.annotate('{:.1f}%'.format(100*p.get_height()/total), (p.get_x()+0.1, p.get_height()+5))
#put 11 ticks (therefore 10 steps), from 0 to the total number of rows in the dataframe
ax.yaxis.set_ticks(np.linspace(0, total, 11))
#adjust the ticklabel to the desired format, without changing the position of the ticks.
_ = ax.set_yticklabels(map('{:.1f}%'.format, 100*ax.yaxis.get_majorticklocs()/total))