Matplotlib: cancelling the offset of axis introduced in matplotlib 2.0 [duplicate] - python

This question already has answers here:
How can I change the x axis in matplotlib so there is no white space?
(2 answers)
Closed 5 years ago.
Just noticed this nuance when I editing my works.
Previously, the matplotlib would look like this:
x=[1,2,3,4,5]
y=[4,5,5,2,1]
plot(x,y,'-')
But after recent upgrade I believe, the there are offset, which would return like this
It's a little bit unncessary from what I seen now. I want to know
If this offset is a good practice in data visualization? If so, I'll leave it as it is.
How to cancel out this offset?
I can manually restore the limit by plt.gca().set_xlim([1, 5]), but that wouldn't scale if I have another 20 plots. I googled around and didn't find too much info on this.

In matplotlib v2.0.x, the default axes margin has changed, from 0 to 0.05, which is the value controlling the whitespace around your data on the axes. See here for more on the reasoning behind this change.
There are several ways to revert to the previous behaviour.
1) To reset margins to 0 for a single Axes instance:
plt.margins(0)
or
ax.margins(0)
2) To reset margins to 0 for all plots in a script, use rcParams and set this at the top of your script:
plt.rcParams['axes.autolimit_mode'] = 'round_numbers'
plt.rcParams['axes.xmargin'] = 0.
plt.rcParams['axes.ymargin'] = 0.
3) To change the default value for all plots on a machine, modify your the matplotlibrc file to include these lines:
axes.autolimit_mode: round_numbers
axes.xmargin : 0.
axes.ymargin : 0.
Note that to use method (1) and truly get the old behaviour, you may also need to set plt.rcParams['axes.autolimit_mode'] = 'round_numbers'.

Guess whether its good practice is a bit of a discussion. It somehow suggest that your plot continues (but you just show a window of it), so if your plot is only defined in this region having an offset would make sense. If it actually continues but you just plot this part, then it's logical to remove it.
The scalable approach is
plt.gca().set_xlim([np.min(x), np.max(x)])

Related

Multiple functions plotted as f(y) instead of f(x) in pandas

I'm new to Python so I hope you'll forgive my silly questions. I have read a dataset from excel with pandas. The dataset is composed by 3 functions (U22, U35, U55) and related same index (called y/75). enter image description here
now I would like to "turn" the graph so that the index "y/75" goes on the y-axis instead of the x-axis, keeping all the functions in the same graph. The results I want to obtain is like in the following picture enter image description here
the code I've used is
var = pd.read_excel('path.xlsx','SummarySheet', index_col=0)
norm_vel=var[['U22',"U35","U55"]]
norm_vel.plot(figsize=(10,10), grid='true')
But with this code I couldn't find a way to change the axes. Then I tried a different approach, so I turned the graph but couldn't add all the functions in the same graph but just one by one
var = pd.read_excel('path.xlsx','SummarySheet', index_col=False)
norm_vel2=var[['y/75','U22',"U35","U55"]]
norm_vel2.plot( x='U22', y='y/75', figsize=(10,10), grid='true' )
plt.title("Velocity profiles")
plt.xlabel("Normalized velocity")
plt.ylabel("y/75")
obtaining this enter image description here
I am not very familiar with dataframes plot. And to be honest, I've been stalking this question expecting that someone would give an obvious answer. But since no one has one (1 hour old questions, is already late for obvious answers), I can at least tell you how I would do it, without the plot method of the dataframe
plt.figure(figsize=(10,10))
plt.grid(True)
plt.plot(var[['U22',"U35","U55"]], var['y/75'])
plt.title("Velocity profiles")
plt.xlabel("Normalized velocity")
plt.ylabel("y/75")
When used to matplotlib, in which, you can have multiple series in both x and y, the instinct says that pandas connections (which are just useful functions to call matplotlib with the correct parameters), should make it possible to just call
var.plot(x=['U22', 'U35', 'U55'], y='y/75')
Since after all,
var.plot(x='y/75', y=['U22', 'U35', 'U55'])
works as expected (3 lines: U22 vs y/75, U35 vs y/75, U55 vs y/75). So the first one should have also worked (3 lines, y/75 vs U22, y/75 vs U35, y/75 vs U55). But it doesn't. Probably the reason why pandas documentation itself says that these matplotlib connections are still a work in progress.
So, all you've to do is call matplotlib function yourself. After all, it is not like pandas is doing much more when calling those .plot method anyway.

Matplotlib change length of legend lines [duplicate]

This question already has an answer here:
Matplotlib: Horizontal Linelength in Legend
(1 answer)
Closed 1 year ago.
I have the following code to generate the plots with a shared legend
from matplotlib.legend_handler import HandlerLine2D, HandlerTuple
import matplotlib.pyplot as pt
fig = pt.figure(figsize = (12,4))
gd = fig.add_gridspec(1,2)
p1 = fig.add_subplot(gd[0])
p2 = fig.add_subplot(gd[1])
redLine, = p1.plot([1,2,3], [4,2,5], 'r-')
greenLine, = p1.plot([1,2,3], [8,9,1], 'g--')
redDot, = p2.plot([1,2,3], [4,2,5], 'ro')
greenDot, = p2.plot([1,2,3], [8,9,1], 'gs')
leg = p2.legend([(redLine, redDot), (greenLine, greenDot)], ['Red', 'Green'], handler_map = {tuple: HandlerTuple(ndivide=None)})
Doing this however makes the legend lines a bit too short to clearly differentiate between solid line and dashed, so I'm trying to figure out how to make them longer without making the entire legend bigger.
From the documentation here https://matplotlib.org/stable/api/_as_gen/matplotlib.lines.Line2D.html#matplotlib.lines.Line2D, it seems I should be able to do this by setting the sketch_params property. I have the following
legLines = leg.get_lines()
pt.setp(legLines, sketch_params = (1,2,3))
but this tells me it must be real number, not tuple -- contrary to what the documentation suggests. Also note the numbers in this example are arbitrary since I was just trying to understand how to use this.
I've tried a bunch of different stuff to get this shared legend to happen, and this is by far the closest I've gotten. So I was just hoping someone could help explain how I'm misusing the sketch_params attribute, since it sounds like I should be able to specify the length with that.
EDIT:
It was mentioned in the comments that to get sketch_params to work, I can simply do
for line in legLines:
line.set_sketch_params(1,2,3)
But it turns out that doesn't actually let me change the length of the lines like I wanted to. So I changed the question for more general help on how to achieve that.
Sorry for the noise, turns out the answer is quite simple, and I found it on this post How to adjust the size of matplotlib legend box? and Matplotlib: Horizontal Linelength in Legend. Just need to use the handlelength keyword. Apologies, I wasn't phrasing the question correctly when looking for it. Marking this as duplicate.

How to increase plot y-range iwith matplotlib? [duplicate]

I would like to plot a set of points using pyplot in matplotlib but have none of the points be on the edge of my axes. The autoscale (or something) sets the xlim and ylim such that often the first and last points lie at x = xmin or xmax making it difficult to read in some situations.
This is more often problematic with loglog() or semilog() plots because the autoscale would like xmin and xmax to be exact powers of ten, but if my data contains only three points, e.g. at xdata = [10**2,10**3,10**4] then the first and last points will lie on the border of the plot.
Attempted Workaround
This is my solution to add a 10% buffer to either side of the graph. But is there a way to do this more elegantly or automatically?
from numpy import array, log10
from matplotlib.pyplot import *
xdata = array([10**2,10**3,10**4])
ydata = xdata**2
figure()
loglog(xdata,ydata,'.')
xmin,xmax = xlim()
xbuff = 0.1*log10(xmax/xmin)
xlim(xmin*10**(-xbuff),xmax*10**(xbuff))
I am hoping for a one- or two-line solution that I can easily use whenever I make a plot like this.
Linear Plot
To make clear what I'm doing in my workaround, I should add an example in linear space (instead of log space):
plot(xdata,ydata)
xmin,xmax = xlim()
xbuff = 0.1*(xmax-xmin)
xlim(xmin-xbuff,xmax+xbuff))
which is identical to the previous example but for a linear axis.
Limits too large
A related problem is that sometimes the limits are too large. Say my data is something like ydata = xdata**0.25 so that the variance in the range is much less than a decade but ends at exactly 10**1. Then, the autoscale ylim are 10**0 to 10**1 though the data are only in the top portion of the plot. Using my workaround above, I can increase ymax so that the third point is fully within the limits but I don't know how to increase ymin so that there is less whitespace at the lower portion of my plot. i.e., the point is that I don't always want to spread my limits apart but would just like to have some constant (or proportional) buffer around all my points.
#askewchan I just succesfully achieved how to change matplotlib settings by editing matplotlibrc configuration file and running python directly from terminal. Don't know the reason yet, but matplotlibrc is not working when I run python from spyder3 (my IDE). Just follow steps here matplotlib.org/users/customizing.html.
1) Solution one (default for all plots)
Try put this in matplotlibrc and you will see the buffer increase:
axes.xmargin : 0.1 # x margin. See `axes.Axes.margins`
axes.ymargin : 0.1 # y margin See `axes.Axes.margins`
Values must be between 0 and 1.
Obs.: Due to bugs, scale is not correctly working yet. It'll be fixed for matplotlib 1.5 (mine is 1.4.3 yet...). More info:
axes.xmargin/ymargin rcParam behaves differently than pyplot.margins() #2298
Better auto-selection of axis limits #4891
2) Solution two (individually for each plot inside the code)
There is also the margins function (for put directly in the code). Example:
import numpy as np
from matplotlib import pyplot as plt
t = np.linspace(-6,6,1000)
plt.plot(t,np.sin(t))
plt.margins(x=0.1, y=0.1)
plt.savefig('plot.png')
Obs.: Here scale is working (0.1 will increase 10% of buffer before and after x-range and y-range).
A similar question was posed to the matplotlib-users list earlier this year. The most promising solution involves implementing a Locator (based on MaxNLocator in this case) to override MaxNLocator.view_limits.

Colorbar based legend in python matplotlib

In the graphic below, I want to put in a legend for the calendar plot. The calendar plot was made using ax.plot(...,label='a') and drawing rectangles in a 52x7 grid (52 weeks, 7 days per week).
The legend is currently made using:
plt.gca().legend(loc="upper right")
How do I correct this legend to something more like a colorbar? Also, the colorbar should be placed at the bottom of the plot.
EDIT:
Uploaded code and data for reproducing this here:
https://www.dropbox.com/sh/8xgyxybev3441go/AACKDiNFBqpsP1ZttsZLqIC4a?dl=0
Aside - existing bugs
The code you put on the dropbox doesn't work "out of the box". In particular - you're trying to divide a datetime.timedelta by a numpy.timedelta64 in two places and that fails.
You do your own normalisation and colour mapping (calling into color_list based on an int() conversion of your normalised value). You subtract 1 from this and you don't need to - you already floor the value by using int(). The result of doing this is that you can get an index of -1 which means your very smallest values are incorrectly mapped to the colour for the maximum value. This is most obvious if you plot column 'BIOM'.
I've hacked this by adding a tiny value (0.00001) to the total range of the values that you divide by. It's a hack - I'm not sure that this method of mapping is at all the best use of matplotlib, but that's a different question entirely.
Solution adapting your code
With those bugs fixed, and adding a last suplot below all the existing ones (i.e. replacing 3 with 4 on all your calls to subplot2grid(), you can do the following:
Replace your
plt.gca().legend(loc="upper right")
with
# plot an overall colorbar type legend
# Grab the new axes object to plot the colorbar on
ax_colorbar = plt.subplot2grid((4,num_yrs), (3,0),rowspan=1,colspan=num_yrs)
mappableObject = matplotlib.cm.ScalarMappable(cmap = palettable.colorbrewer.sequential.BuPu_9.mpl_colormap)
mappableObject.set_array(numpy.array(df[col_name]))
col_bar = fig.colorbar(mappableObject, cax = ax_colorbar, orientation = 'horizontal', boundaries = numpy.arange(min_val,max_val,(max_val-min_val)/10))
# You can change the boundaries kwarg to either make the scale look less boxy (increase 10)
# or to get different values on the tick marks, or even omit it altogether to let
col_bar.set_label(col_name)
ax_colorbar.set_title(col_name + ' color mapping')
I tested this with two of your columns ('NMN' and 'BIOM') and on Python 2.7 (I assume you're using Python 2.x given the print statement syntax)
The finalised code that works directly with your data file is in a gist here
You get
How does it work?
It creates a ScalarMappable object that matplotlib can use to map values to colors. It set the array to base this map on to all the values in the column you are dealing with. It then used Figure.colorbar() to add the colorbar - passing in the mappable object so that the labels are correct. I've added boundaries so that the minimum value is shown explicitly - you can omit that if you want matplotlib to sort that out for itself.
P.S. I've set the colormap to palettable.colorbrewer.sequential.BuPu_9.mpl_colormap, matching your get_colors() function which gets these colours as a 9 member list. I strongly recommend importing the colormap you want to use as a nice name to make the use of mpl_colors and mpl_colormap more easy to understand e.g.
import palettable.colorbrewer.sequential.BuPu_9 as color_scale
Then access it as
color_scale.mpl_colormap
That way, you can keep your code DRY and change the colors with only one change.
Layout (in response to comments)
The colorbar may be a little big (certainly tall) for aesthetic ideal. There are a few possible options to do that. I'll point you to two:
The "right" way to do it is probably to use a Gridspec
You could use your existing approach, but increase the number of rows and have the colorbar still in one row, while the other elements span more rows than they do currently.
I've implemented that with 9 rows, an extra column (so that the month labels don't get lost) and the colorbar on the bottom row, spanning 2 less columns than the main figure. I've also used tight_layout with w_pad=0.0 to avoid label clashes. You can play with this to get your exact preferred size. New code here.
This gives:
:
There are functions to do this in matplotlib.colorbar. With some specific code from your example, I could give you a better answer, but you'll use something like:
myColorbar = matplotlib.colorbar.ColorbarBase(myAxes, cmap=myColorMap,
norm=myNorm,
orientation='vertical')

Matplotlib questions [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Plotting with Python
I've been using Matprolib for plotting. I've found it very useful, but I dont know exactly how to use the 'fill_between()' function. I have tried several times to fix it but I cant get to the point.
I have been studying several tutorials, but I havent found very useful ones (according to my task).
My task is to fill the area that all lines have in common, just like this:
Whatitshoulddo
And this is what the system does:
Whatitshouldntdo
Is there any way to do it? Anyone knows how to use 'fill_between()' in a correct way?
You can use the where option. As in the example you can do something like:
fill_between(x, myzero, y1, where=y2>=y1, facecolor='blue', interpolate=True)
Where y2 is the line that is sometimes on top. You will have do do this for each of the lines, though.
Another option is to define a minimum of the functions and fill below that, though you will either need to know before hand that the ys are defined at the same points or use interpolation so that they are all defined at the same points.
mymin = np.minimum(y1,y2,y3)
fill_between(x, myzero, mymin)
Hope that helps.
Edit: To find the minimum from several functions which are all defined for the same x-values you can use the following:
def OverallMinimum(*ys):
mymin = y[0].copy()
for y in ys:
min = np.minimum(mymin,y)
return mymin
Then you can use the second fill_between from above to plot between zero and the minimum from that function.

Categories

Resources