Add padding between bars and Y-Axis - python

I am building a bar chart using matplotlib using the code below. When my first or last column of data is 0, my first column is wedged against the Y-axis.
An example of this. Note that the first column is ON the x=0 point.
If I have data in this column, I get a huge padding between the Y-Axis and the first column as seen here. Note the additional bar, now at X=0. This effect is repeated if I have data in my last column as well.
My code is as follows:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import MultipleLocator
binVals = [0,5531608,6475325,1311915,223000,609638,291151,449434,1398731,2516755,3035532,2976924,2695079,1822865,1347155,304911,3562,157,5,0,0,0,0,0,0,0,0]
binTot = sum(binVals)
binNorm = []
for v in range(len(binVals)):
binNorm.append(float(binVals[v])/binTot)
fig = plt.figure(figsize=(6,4))
ax1 = fig.add_subplot(1,1,1)
ax1.bar(range(len(binNorm)),binNorm,align='center', label='Values')
plt.legend(loc=1)
plt.title("Demo Histogram")
plt.xlabel("Value")
plt.xticks(range(len(binLabels)),binLabels,rotation='vertical')
plt.grid(b=True, which='major', color='grey', linestyle='--', alpha=0.35)
ax1.xaxis.grid(False)
plt.ylabel("% of Count")
plt.subplots_adjust(bottom=0.15)
plt.tight_layout()
plt.show()
How can I set a constant margin between the Y-axis and my first/last bar?
Additionally, I realize it's labeled "Demo Histogram", that is a because I missed it when correcting problems discussed here.

I can't run the code snippet you gave, and even with some modification I couldn't replicate the big space. Aside from that, if you need to enforce a border to matplotlib, you ca do somthing like this:
ax.set_xlim( min(your_data) - 10, None )
The first term tells the axis to put the border at 10 units of distance from the minimum of your data, the None parameter teels it to keep the present value.
to put it into contest:
from collections import Counter
from pylab import *
data = randint(20,size=1000)
res = Counter(data)
vals = arange(20)
ax = gca()
ax.bar(vals-0.4, [ res[i] for i in vals ], width=0.8)
ax.set_xlim( min(data)-1, None )
show()
searching around stackoverflow I just learned a new trick: you can call
ax.margins( margin_you_desire )
to let automatically let matplotlib put that amount of space around your plot. It can also be configured differently between x and y.
In your case the best solution would be something like
ax.margins(0.01, None)
The little catch is that the unit is in axes unit, referred to the size of you plot, so a margin of 1 will put space around your plot at both sizes big as your present plot

The problem is align='center'. Remove it.

Related

Setting xticklabels and x-axis limits in a bar plot with matplotlib

I want to plot a bar graph with a variable amount of values along the x-axis. For the data, I have a set of labels which I want to show on the x-axis under the bars. I also want the x-axis limits to start at -1, since otherwise, only half of the first bar at index 0 would be visible. I've tried multiple alternatives for achieving that, none of them worked, because the xticklabels are always one or more off. And IF they work for a given set of data, with another set of data (with more or less bars) it does not work again. See minimum code example below
from matplotlib import pyplot as plt
from matplotlib import ticker
import numpy as np
randData = np.random.rand(100)
xValues = np.linspace(0, len(randData)-1, num=len(randData))
labels = []
for i in range(len(randData)):
labels.append('label' + str(i))
fig, ax = plt.subplots()
ax.bar(np.linspace(0, len(randData)-1, num=len(randData)), randData)
ax.xaxis.set_major_locator(ticker.MultipleLocator(1))
# Alternative 1
# Use an empty string for index -1, set labels, then set new xlim
labels.insert(0, '')
ax.set_xticklabels(labels, size='x-small', rotation=90)
plt.xlim(-1, len(randData))
# Alternative 2
# Use an empty string for index -1, set new xlim, then set labels
labels.insert(0, '')
plt.xlim(-1, len(randData))
ax.set_xticklabels(labels, size='x-small', rotation=90)
# Alternative 3
# Setting limits with ax.set_xlim
ax.set_xticklabels(labels, size='x-small', rotation=90)
ax.set_xlim([-1, len(randData)])
# Alternative 4
# Setting limits with plt.xlim
ax.set_xticklabels(labels, size='x-small', rotation=90)
plt.xlim(-1, len(randData))
plt.show()
None of the variants worked so far. One part of the problem is that the pyplot automatically sets its xlimits depending on the amount of bar graphs (sometimes it starts at -1, with more values it might sometimes start at -4).
One of the faulty results is shown below:
Any help would be appreciated.
P.S.: If I may, I'd like to add a little side question: How can I remove the Warning "UserWarning: FixedFormatter should only be used together with FixedLocator" when setting the xticklabels? Nothing from this answer worked for me.

Multiple x labels on Pyplot

Below is my code for a line graph. I would like another x label under the current one (so I can show the days of the week).
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns;sns.set()
sns.set()
data = pd.read_csv("123.csv")
data['DAY']=["01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31"]
plt.figure(figsize=(15,8))
plt.plot('DAY','SWST',data=data,linewidth=2,color="k")
plt.plot('DAY','WMID',data=data,linewidth=2,color="m")
plt.xlabel('DAY', fontsize=20)
plt.ylabel('VOLUME', fontsize=20)
plt.legend()
EDIT: After following the documentation, I have 2 issues. The scale has changed from 31 to 16, and the days of the week do not line up with the day number.
data['DAY']=["01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31"]
tick_labels=['1','\n\nThu','2','\n\nFri','3','\n\nSat','4','\n\nSun','5','\n\nMon','6','\n\nTue','7','\n\nWed','8','\n\nThu','9','\n\nFri','10','\n\nSat','11','\n\nSun','12','\n\nMon','13','\n\nTue','14','\n\nWed','15','\n\nThu','16','\n\nFri','17','\n\nSat','18','\n\nSun','19','\n\nMon','20','\n\nTue','21','\n\nWed','22','\n\nThu','23','\n\nFri','24','\n\nSat','25','\n\nSun','26','\n\nMon','27','\n\nTue','28','\n\nWed','29','\n\nThu','30','\n\nFri','31','\n\nSat']
tick_locations = np.arange(31)
plt.figure(figsize=(15,8))
plt.xticks(tick_locations, tick_labels)
plt.plot('DAY','SWST',data=data,linewidth=2,color="k")
plt.plot('DAY','WMID',data=data,linewidth=2,color="m")
plt.xlabel('DAY', fontsize=20)
plt.ylabel('VOLUME', fontsize=20)
plt.legend()
plt.show()
The pyplot function you are looking for is plt.xticks(). This is essentially a combination of ax.set_xticks() and ax.set_xticklabels()
From the documentation:
Parameters:
ticks : array_like
A list of positions at which ticks should be placed. You can pass an
empty list to disable xticks.
labels:
array_like, optional A list of explicit labels to place at the given
locs.
You would want something like the below code. Note you should probably explicitly set the tick locations as well as the labels to avoid setting labels in the wrong positions:
tick_labels = ['1','\n\nThu','2',..., '31','\n\nSat')
plt.xticks(tick_locations, tick_labels)
Note that the object-orientated API (i.e. using ax.) allows for more customisable plots.
Update
After the edit, I see that the labels you want to go below are part of the same list. Therefore your label list actually has a length of 62. So you need to join every 2 elements of your list together:
tick_labels=['1','\n\nThu','2','\n\nFri','3','\n\nSat','4','\n\nSun','5','\n\nMon','6','\n\nTue','7','\n\nWed','8',
'\n\nThu','9','\n\nFri','10','\n\nSat','11','\n\nSun','12','\n\nMon','13','\n\nTue','14','\n\nWed','15',
'\n\nThu','16','\n\nFri','17','\n\nSat','18','\n\nSun','19','\n\nMon','20','\n\nTue','21','\n\nWed','22',
'\n\nThu','23','\n\nFri','24','\n\nSat','25','\n\nSun','26','\n\nMon','27','\n\nTue','28','\n\nWed','29',
'\n\nThu','30','\n\nFri','31','\n\nSat']
tick_locations = np.arange(31)
new_labels = [ ''.join(x) for x in zip(tick_labels[0::2], tick_labels[1::2]) ]
plt.figure(figsize=(15, 8))
plt.xticks(tick_locations, new_labels)
plt.show()
Never use ax.set_xticklabels without setting the locations of the ticks as well. This can be done via ax.set_xticks.
ax.set_xticks(...)
ax.set_xticklabels(...)
Of course you may do the same with pyplot
ax = plt.gca()
ax.set_xticks(...)
ax.set_xticklabels(...)

Make x-axes of all subplots same length on the page

I am new to matplotlib and trying to create and save plots from pandas dataframes via a loop. Each plot should have an identical x-axis, but different y-axis lengths and labels. I have no problem creating and saving the plots with different y-axis lengths and labels, but when I create the plots, matplotlib rescales the x-axis depending on how much space is needed for the y-axis labels on the left side of the figure.
These figures are for a technical report. I plan to place one on each page of the report and I would like to have all of the x-axes take up the same amount of space on the page.
Here is an MSPaint version of what I'm getting and what I'd like to get.
Hopefully this is enough code to help. I'm sure there are lots of non-optimal parts of this.
import pandas as pd
import matplotlib.pyplot as plt
import pylab as pl
from matplotlib import collections as mc
from matplotlib.lines import Line2D
import seaborn as sns
# elements for x-axis
start = -1600
end = 2001
interval = 200 # x-axis tick interval
xticks = [x for x in range(start, end, interval)] # create x ticks
# items needed for legend construction
lw_bins = [0,10,25,50,75,90,100] # bins for line width
lw_labels = [3,6,9,12,15,18] # line widths
def make_proxy(zvalue, scalar_mappable, **kwargs):
color = 'black'
return Line2D([0, 1], [0, 1], color=color, solid_capstyle='butt', **kwargs)
# generic image ID
img_path = r'C:\\Users\\user\\chart'
img_ID = 0
for line_subset in data:
# create line collection for this run through loop
lc = mc.LineCollection(line_subset)
# create plot and set properties
sns.set(style="ticks")
sns.set_context("notebook")
fig, ax = pl.subplots(figsize=(16, len(line_subset)*0.5)) # I want the height of the figure to change based on number of labels on y-axis
# Figure width should stay the same
ax.add_collection(lc)
ax.set_xlim(left=start, right=end)
ax.set_xticks(xticks)
ax.set_ylim(0, len(line_subset)+1)
ax.margins(0.05)
sns.despine(left=True)
ax.xaxis.set_ticks_position('bottom')
ax.set_yticks(line_subset['order'])
ax.set_yticklabels(line_subset['ylabel'])
ax.tick_params(axis='y', length=0)
# legend
proxies = [make_proxy(item, lc, linewidth=item) for item in lw_labels]
ax.legend(proxies, ['0-10%', '10-25%', '25-50%', '50-75%', '75-90%', '90-100%'], bbox_to_anchor=(1.05, 1.0),
loc=2, ncol=2, labelspacing=1.25, handlelength=4.0, handletextpad=0.5, markerfirst=False,
columnspacing=1.0)
# title
ax.text(0, len(line_subset)+2, s=str(img_ID), fontsize=20)
# save as .png images
plt.savefig(r'C:\\Users\\user\\Desktop\\chart' + str(img_ID) + '.png', dpi=300, bbox_inches='tight')
Unless you use an axes of specifically defined aspect ratio (like in an imshow plot or by calling .set_aspect("equal")), the space taken by the axes should only depend on the figure size along that direction and the spacings set to the figure.
You are therefore pretty much asking for the default behaviour and the only thing that prevents you from obtaining that is that you use bbox_inches='tight' in the savefig command.
bbox_inches='tight' will change the figure size! So don't use it and the axes will remain constant in size. `
Your figure size, defined like figsize=(16, len(line_subset)*0.5) seems to make sense according to what I understand from the question. So what remains is to make sure the axes inside the figure are the size you want them to be. You can do that by manually placing it using fig.add_axes
fig.add_axes([left, bottom, width, height])
where left, bottom, width, height are in figure coordinates ranging from 0 to 1. Or, you can adjust the spacings outside the subplot using subplots_adjust
plt.subplots_adjust(left, bottom, right, top)
To get matching x axis for the subplots (same x axis length for each subplot) , you need to share the x axis between subplots.
See the example here https://matplotlib.org/examples/pylab_examples/shared_axis_demo.html

Plotting dot plot with enough space of ticks in Python/matplotlib?

In the following code snippet:
import numpy as np
import pandas as pd
import pandas.rpy.common as com
import matplotlib.pyplot as plt
mtcars = com.load_data("mtcars")
df = mtcars.groupby(["cyl"]).apply(lambda x: pd.Series([x["cyl"].count(), np.mean(x["wt"])], index=["n", "wt"])).reset_index()
plt.plot(df["n"], range(len(df["cyl"])), "o")
plt.yticks(range(len(df["cyl"])), df["cyl"])
plt.show()
This code outputs the dot plot graph, but the result looks quite awful, since both the xticks and yticks don't have enough space, that it's quite difficult to notice both 4 and 8 of the cyl variable output its values in the graph.
So how can I plot it with enough space in advance, much like you can do it without any hassles in R/ggplot2?
For your information, both of this code and this doesn't work in my case. Anyone knows the reason? And do I have to bother to creating such subplots in the first place? Is it impossible to automatically adjust the ticks with response to the input values?
I can't quite tell what you're asking...
Are you asking why the ticks aren't automatically positioned or are you asking how to add "padding" around the inside edges of the plot?
If it's the former, it's because you've manually set the tick locations with yticks. This overrides the automatic tick locator.
If it's the latter, use ax.margins(some_percentage) (where some_percentage is between 0 and 1, e.g. 0.05 is 5%) to add "padding" to the data limits before they're autoscaled.
As an example of the latter, by default, the data limits can be autoscaled such that a point can lie on the boundaries of the plot. E.g.:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot(range(10), 'ro')
plt.show()
If you want to avoid this, use ax.margins (or equivalently, plt.margins) to specify a percentage of padding to be added to the data limits before autoscaling takes place.
E.g.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot(range(10), 'ro')
ax.margins(0.04) # 4% padding, similar to R.
plt.show()

Overlapping y-axis tick label and x-axis tick label in matplotlib

If I create a plot with matplotlib using the following code:
import numpy as np
from matplotlib import pyplot as plt
xx = np.arange(0,5, .5)
yy = np.random.random( len(xx) )
plt.plot(xx,yy)
plt.imshow()
I get a result that looks like the attached image. The problem is the
bottom-most y-tick label overlaps the left-most x-tick label. This
looks unprofessional. I was wondering if there was an automatic
way to delete the bottom-most y-tick label, so I don't have
the overlap problem. The fewer lines of code, the better.
In the ticker module there is a class called MaxNLocator that can take a prune kwarg.
Using that you can remove the first tick:
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
import numpy as np
xx = np.arange(0,5, .5)
yy = np.random.random( len(xx) )
plt.plot(xx,yy)
plt.gca().xaxis.set_major_locator(MaxNLocator(prune='lower'))
plt.show()
Result:
You can pad the ticks on the x-axis:
ax.tick_params(axis='x', pad=15)
Replace ax with plt.gca() if you haven't stored the variable ax for the current figure.
You can also pad both the axes removing the axis parameter.
A very elegant way to fix the overlapping problem is increasing the padding of the x- and y-tick labels (i.e. the distance to the axis). Leaving out the corner most label might not always be wanted. In my opinion, in general it looks nice if the labels are a little bit farther from the axis than given by the default configuration.
The padding can be changed via the matplotlibrc file or in your plot script by using the commands
import matplotlib as mpl
mpl.rcParams['xtick.major.pad'] = 8
mpl.rcParams['ytick.major.pad'] = 8
Most times, a padding of 6 is also sufficient.
This is answered in detail here. Basically, you use something like this:
plt.xticks([list of tick locations], [list of tick lables])

Categories

Resources