matplotlib ticks thickness - matplotlib

Is there a way to increase the thickness and size of ticks in matplotlib without having to write a long piece of code like this:
for line in ax1.yaxis.get_ticklines():
line.set_markersize(25)
line.set_markeredgewidth(3)
The problem with this piece of code is that it uses a loop which costs usually a lot of CPU usage.

A simpler way is to use the set_tick_params function of axis objects:
ax.xaxis.set_tick_params(width=5)
ax.yaxis.set_tick_params(width=5)
Doing it this way means you can change this on a per-axis basis with out worrying about global state and with out making any assumptions about the internal structure of mpl objects.
If you want to set this for all the ticks in your axes,
ax = plt.gca()
ax.tick_params(width=5,...)
Take a look at set_tick_params doc and tick_params valid keywords

You can change all matplotlib defaults using rcParams like in
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
# set tick width
mpl.rcParams['xtick.major.size'] = 20
mpl.rcParams['xtick.major.width'] = 4
mpl.rcParams['xtick.minor.size'] = 10
mpl.rcParams['xtick.minor.width'] = 2
x = np.linspace(0., 10.)
plt.plot(x, np.sin(x))
plt.show()

You can use matplotlib.pyplot.setp
plt.setp(ax.yaxis.get_ticklines(), 'markersize', 25)
plt.setp(ax.yaxis.get_ticklines(), 'markeredgewidth', 3)
You can also use list comprehension, although not having a return value probably does not make much sense, besides reducing the number of lines in the code see e.g. here
[line.set_markersize(25) for line in ax1.yaxis.get_ticklines()]
[line.set_markeredgewidth(3) for line in ax1.yaxis.get_ticklines()]

Related

How to plot Series with selective ticks?

I have a Series that I would like to plot as a bar chart: pd.Series([-4,2, 3,3, 4,5,9,20]).value_counts()
Since I have many bars I only want to display some (equidistant) ticks.
However, unless I actively work against it, pyplot will print the wrong labels. E.g. if I leave out set_xticklabels in the code below I get
where every element from the index is taken and just displayed with the specified distance.
This code does what I want:
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
s = pd.Series([-4,2, 3,3, 4,5,9,20]).value_counts().sort_index()
mi,ma = min(s.index), max(s.index)
s = s.reindex(range(mi,ma+1,1), fill_value=0)
distance = 10
a = s.plot(kind='bar')
condition = lambda t: int(t[1].get_text()) % 10 == 0
ticks_,labels_=zip(*filter(condition, zip(a.get_xticks(), a.get_xticklabels())))
a.set_xticks(ticks_)
a.set_xticklabels(labels_)
plt.show()
But I still feel like I'm being unnecessarily clever here. Am I missing a function? Is this the best way of doing that?
Consider not using a pandas bar plot in case you intend to plot numeric values; that is because pandas bar plots are categorical in nature.
If instead using a matplotlib bar plot, which is numeric in nature, there is no need to tinker with any ticks at all.
s = pd.Series([-4,2, 3,3, 4,5,9,20]).value_counts().sort_index()
plt.bar(s.index, s)
I think you overcomplicated it. You can simply use the following. You just need to find the relationship between the ticks and the ticklabels.
a = s.plot(kind='bar')
xticks = np.arange(0, max(s)*10+1, 10)
plt.xticks(xticks + abs(mi), xticks)

Matplotlib streamplot with streamlines that don't break or end

I'd like to make a streamplot with lines that don't stop when they get too close together. I'd rather each streamline be calculated in both directions until it hits the edge of the window. The result is there'd be some areas where they'd all jumble up. But that's what I want.
I there anyway to do this in matplotlib? If not, is there another tool I can use for this that could interface with python/numpy?
import numpy as np
import matplotlib.pyplot as plt
Y,X = np.mgrid[-10:10:.01, -10:10:.01]
U, V = Y**2, X**2
plt.streamplot(X,Y, U,V, density=1)
plt.show(False)
Ok, I've figured out I can get mostly what I want by turning up the density a lot and using custom start points. I'm still interested if there is a better or alternate way to do this.
Here's my solution. Doesn't it look so much better?
import numpy as np
import matplotlib.pyplot as plt
Y,X = np.mgrid[-10:10:.01, -10:10:.01]
y,x = Y[:,0], X[0,:]
U, V = Y**2, X**2
stream_points = np.array(zip(np.arange(-9,9,.5), -np.arange(-9,9,.5)))
plt.streamplot(x,y, U,V, start_points=stream_points, density=35)
plt.show(False)
Edit: By the way, there seems to be some bug in streamplot such that start_points keyword only works if you use 1d arrays for the grid data. See Python Matplotlib Streamplot providing start points
As of Matplotlib version 3.6.0, an optional parameter broken_streamlines has been added for disabling streamline breaks.
Adding it to your snippet produces the following result:
import numpy as np
import matplotlib.pyplot as plt
Y,X = np.mgrid[-10:10:.01, -10:10:.01]
U, V = Y**2, X**2
plt.streamplot(X,Y, U,V, density=1, broken_streamlines=False)
plt.show(False)
Note
This parameter just extends the streamlines which were originally drawn (as in the question). This means that the streamlines in the modified plot above are much more uneven than the result obtained in the other answer, with custom start_points. The density of streamlines on any stream plot does not represent the magnitude of U or V at that point, only their direction. See the documentation for the density parameter of matplotlib.pyplot.streamplot for more details on how streamline start points are chosen by default, when they aren't specified by the optional start_points parameter.
For accurate streamline density, consider using matplotlib.pyplot.contour, but be aware that contour does not show arrows.
Choosing start points automatically
It may not always be easy to choose a set of good starting points automatically. However, if you know the streamfunction corresponding to the flow you wish to plot you can use matplotlib.pyplot.contour to produce a contour plot (which can be hidden from the output), and then extract a suitable starting point from each of the plotted contours.
In the following example, psi_expression is the streamfunction corresponding to the flow. When modifying this example for your own needs, make sure to update both the line defining psi_expression, as well as the one defining U and V. Ensure these both correspond to the same flow.
The density of the streamlines can be altered by changing contour_levels. Here, the contours are uniformly distributed.
import numpy as np
import matplotlib.pyplot as plt
import sympy as sy
x, y = sy.symbols("x y")
psi_expression = x**3 - y**3
psi_function = sy.lambdify((x, y), psi_expression)
Y, X = np.mgrid[-10:10:0.01, -10:10:0.01]
psi_evaluated = psi_function(X, Y)
U, V = Y**2, X**2
contour_levels = np.linspace(np.amin(psi_evaluated), np.amax(psi_evaluated), 30)
# Draw a temporary contour plot.
temp_figure = plt.figure()
contour_plot = plt.contour(X, Y, psi_evaluated, contour_levels)
plt.close(temp_figure)
points_list = []
# Iterate over each contour.
for collection in contour_plot.collections:
# Iterate over each segment in this contour.
for path in collection.get_paths():
middle_point = path.vertices[len(path.vertices) // 2]
points_list.append(middle_point)
# Reshape python list into numpy array of coords.
stream_points = np.reshape(np.array(points_list), (-1, 2))
plt.streamplot(X, Y, U, V, density=1, start_points=stream_points, broken_streamlines=False)
plt.show(False)

Better ticks and tick labels with log scale

I am trying to get better looking log-log plots and I almost got what I want except for a minor problem.
The reason my example throws off the standard settings is that the x values are confined within less than one decade and I want to use decimal, not scientific notation.
Allow me to illustrate with an example:
import matplotlib.pyplot as plt
%matplotlib inline
import matplotlib as mpl
import numpy as np
x = np.array([0.6,0.83,1.1,1.8,2])
y = np.array([1e-5,1e-4,1e-3,1e-2,0.1])
fig1,ax = plt.subplots()
ax.plot(x,y)
ax.set_xscale('log')
ax.set_yscale('log')
which produces:
There are two problems with the x axis:
The use of scientific notation, which in this case is counterproductive
The horrible "offset" at the lower right corner
After much reading, I added three lines of code:
ax.xaxis.set_major_formatter(mpl.ticker.ScalarFormatter())
ax.xaxis.set_minor_formatter(mpl.ticker.ScalarFormatter())
ax.ticklabel_format(style='plain',axis='x',useOffset=False)
This produces:
My understanding of this is that there are 5 minor ticks and 1 major one. It is much better, but still not perfect:
I would like some additional ticks between 1 and 2
Formatting of label at 1 is wrong. It should be "1.0"
So I inserted the following line before the formatter statement:
ax.xaxis.set_major_locator(mpl.ticker.MultipleLocator(0.2))
I finally get the ticks I want:
I now have 8 major and 2 minor ticks. Now, this almost looks right except for the fact that the tick labels at 0.6, 0.8 and 2.0 appear bolder than the others. What is the reason for this and how can I correct it?
The reason, some of the labels appear bold is that they are part of the major and minor ticklabels. If two texts perfectly overlap, they appear bolder due to the antialiasing.
You may decide to only use minor ticklabels and set the major ones with a NullLocator.
Since the locations of the ticklabels you wish to have is really specific there is no automatic locator that would provide them out of the box. For this special case it may be easiest to use a FixedLocator and specify the labels you wish to have as a list.
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
x = np.array([0.6,0.83,1.1,1.8,2])
y = np.array([1e-5,1e-4,1e-3,1e-2,0.1])
fig1,ax = plt.subplots(dpi=72, figsize=(6,4))
ax.plot(x,y)
ax.set_xscale('log')
ax.set_yscale('log')
locs = np.append( np.arange(0.1,1,0.1),np.arange(1,10,0.2))
ax.xaxis.set_minor_locator(ticker.FixedLocator(locs))
ax.xaxis.set_major_locator(ticker.NullLocator())
ax.xaxis.set_minor_formatter(ticker.ScalarFormatter())
plt.show()
For a more generic labeling, one could of course subclass a locator, but we would then need to know the logic to use to determine the ticklabels. (As I do not see a well defined logic for the desired ticks from the question, I feel it would be wasted effort to provide such a solution for now.)

Basic axis malfuction in matplotlib

When plotting using matplotlib, I ran into an interesting issue where the y axis is scaled by a very inconvenient quantity. Here's a MWE that demonstrates the problem:
import numpy as np
import matplotlib.pyplot as plt
l = np.linspace(0.5,2,2**10)
a = (0.696*l**2)/(l**2 - 9896.2e-9**2)
plt.plot(l,a)
plt.show()
When I run this, I get a figure that looks like this picture
The y-axis clearly is scaled by a silly quantity even though the y data are all between 1 and 2.
This is similar to the question:
Axis numerical offset in matplotlib
I'm not satisfied with the answer to this question in that it makes no sense to my why I need to go the the convoluted process of changing axis settings when the data are between 1 and 2 (EDIT: between 0 and 1). Why does this happen? Why does matplotlib use such a bizarre scaling?
The data in the plot are all between 0.696000000017 and 0.696000000273. For such cases it makes sense to use some kind of offset.
If you don't want that, you can use you own formatter:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker
l = np.linspace(0.5,2,2**10)
a = (0.696*l**2)/(l**2 - 9896.2e-9**2)
plt.plot(l,a)
fmt = matplotlib.ticker.StrMethodFormatter("{x:.12f}")
plt.gca().yaxis.set_major_formatter(fmt)
plt.show()

Change figsize in matplotlib polar contourf

I am using the following example Example to create two polar contour subplots. When I create as the pdf there is a lot of white space which I want to remove by changing figsize.
I know how to change figsize usually but I am having difficulty seeing where to put it in this code example. Any guidance or hint would be greatly appreciated.
Many thanks!
import numpy as np
import matplotlib.pyplot as plt
#-- Generate Data -----------------------------------------
# Using linspace so that the endpoint of 360 is included...
azimuths = np.radians(np.linspace(0, 360, 20))
zeniths = np.arange(0, 70, 10)
r, theta = np.meshgrid(zeniths, azimuths)
values = np.random.random((azimuths.size, zeniths.size))
#-- Plot... ------------------------------------------------
fig, ax = plt.subplots(subplot_kw=dict(projection='polar'))
ax.contourf(theta, r, values)
plt.show()
Another way to do this would be to use the figsize kwarg in your call to plt.subplots.
fig, ax = plt.subplots(figsize=(6,6), subplot_kw=dict(projection='polar')).
Those values are in inches, by the way.
You can easily just put plt.figsize(x,y) at the beginning of the code, and it will work. plt.figsize changes the size of all future plots, not just the current plot.
However, I think your problem is not what you think it is. There tends to be quite a bit of whitespace in generated PDFs unless you change options around. I usually use
plt.savefig( 'name.pdf', bbox_inches='tight', pad_inches=0 )
This gives as little whitespace as possible. bbox_inches='tight' tries to make the bounding box as small as possible, while pad_inches sets how many inches of whitespace there should be padding it. In my case I have no extra padding at all, as I add padding in whatever I'm using the figure for.