Dear matplotlib community,
I have a very quick question regarding logarithmic axis labelling that I'm sure one of you could answer at the drop of a hat.
Essentially I have a log axis in matplotlib with labels of 10^-2, 10^-1, 10^0, 10^1, 10^2 etc
However, I would like 0.01, 0.1, 1, 10, 100.
Could anyone guide me on this. I have tried a few options, such as:
ax.set_xticks([0.01,0.1,1,10,100])
ax.set_xlabels([0.01,0.1,1,10,100])
Any pro tips would be greatly appreciated!
I think you want ax.xaxis.set_major_formatter(FormatStrFormatter("%g")), as in:
x=[0.1,1,10,100,1000]
y=[-1,0,1,2,3]
fig,ax=plt.subplots()
ax.plot(x,y,'.-')
ax.set_xscale('log')
ax.xaxis.set_major_formatter(FormatStrFormatter("%g"))
Example output:
A nice way is to use the FuncFormatter class of the matplotlib.ticker module. In conjunction with a custom function definition of your own making, this can help to customise your ticks to the exact way you want them. This particular bit of code works well with the logarithmic scale employed by matplotlib.
import numpy as np
import matplotlib.pylab as plt
x = np.linspace(-10,10)
y = np.exp(x)
plt.close('all')
fig,ax = plt.subplots(1,1)
ax.plot(x,y,'bo')
ax.set_yscale('log')
#Placed the import/function definitions here to emphasize
#the working lines of code for this particular task.
from matplotlib.ticker import FuncFormatter
def labeller(x, pos):
"""
x is the tick value, pos is the position. These args are needed by
FuncFormatter.
"""
if x < 1:
return '0.'+'0'*(abs(int(np.log10(x)))-1)+\
format(x/10**(np.floor(np.log10(x))),'.0f')
else:
return format(x,'.0f')
#FuncFormatter class instance defined from the function above
custom_formatter = FuncFormatter(labeller)
ax.yaxis.set_major_formatter(custom_formatter)
plt.show()
Result:
First, instead of set_xlabels you should call set_xticklabels for the actual tick labels. That said, at least in my current environment (python 2.7, matplotlib 1.4.3, OS X 10.10) that is not always enough. When doing instruction by instruction in a REPL (e.g. ipython) sometimes is necessary to update the axis after calling set_xticklabels. A quick hack to do that is to simply call grid(True) or grid(False). For example:
x = np.logspace(-2,2, 1000)
y = np.sin(x)
l = [0.01,0.1,1,10,100]
plt.semilogx(x,y)
plt.gca().set_xticks(l)
plt.gca().set_xticklabels(l)
plt.grid(True)
Empirical note: the grid(False) trick doesn't seem to be necessary when pasting that gist using ipython's %paste magic (anybody know why?)
Related
I want to plot a graph with one logarithmic axis using matplotlib.
I've been reading the docs, but can't figure out the syntax. I know that it's probably something simple like 'scale=linear' in the plot arguments, but I can't seem to get it right
Sample program:
import pylab
import matplotlib.pyplot as plt
a = [pow(10, i) for i in range(10)]
fig = plt.figure()
ax = fig.add_subplot(2, 1, 1)
line, = ax.plot(a, color='blue', lw=2)
pylab.show()
You can use the Axes.set_yscale method. That allows you to change the scale after the Axes object is created. That would also allow you to build a control to let the user pick the scale if you needed to.
The relevant line to add is:
ax.set_yscale('log')
You can use 'linear' to switch back to a linear scale. Here's what your code would look like:
import pylab
import matplotlib.pyplot as plt
a = [pow(10, i) for i in range(10)]
fig = plt.figure()
ax = fig.add_subplot(2, 1, 1)
line, = ax.plot(a, color='blue', lw=2)
ax.set_yscale('log')
pylab.show()
First of all, it's not very tidy to mix pylab and pyplot code. What's more, pyplot style is preferred over using pylab.
Here is a slightly cleaned up code, using only pyplot functions:
from matplotlib import pyplot
a = [ pow(10,i) for i in range(10) ]
pyplot.subplot(2,1,1)
pyplot.plot(a, color='blue', lw=2)
pyplot.yscale('log')
pyplot.show()
The relevant function is pyplot.yscale(). If you use the object-oriented version, replace it by the method Axes.set_yscale(). Remember that you can also change the scale of X axis, using pyplot.xscale() (or Axes.set_xscale()).
Check my question What is the difference between ‘log’ and ‘symlog’? to see a few examples of the graph scales that matplotlib offers.
if you want to change the base of logarithm, just add:
plt.yscale('log',base=2)
Before Matplotlib 3.3, you would have to use basex/basey as the bases of log
You simply need to use semilogy instead of plot:
from pylab import *
import matplotlib.pyplot as pyplot
a = [ pow(10,i) for i in range(10) ]
fig = pyplot.figure()
ax = fig.add_subplot(2,1,1)
line, = ax.semilogy(a, color='blue', lw=2)
show()
I know this is slightly off-topic, since some comments mentioned the ax.set_yscale('log') to be "nicest" solution I thought a rebuttal could be due. I would not recommend using ax.set_yscale('log') for histograms and bar plots. In my version (0.99.1.1) i run into some rendering problems - not sure how general this issue is. However both bar and hist has optional arguments to set the y-scale to log, which work fine.
references:
http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.bar
http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.hist
So if you are simply using the unsophisticated API, like I often am (I use it in ipython a lot), then this is simply
yscale('log')
plot(...)
Hope this helps someone looking for a simple answer! :).
I want to plot a graph with one logarithmic axis using matplotlib.
I've been reading the docs, but can't figure out the syntax. I know that it's probably something simple like 'scale=linear' in the plot arguments, but I can't seem to get it right
Sample program:
import pylab
import matplotlib.pyplot as plt
a = [pow(10, i) for i in range(10)]
fig = plt.figure()
ax = fig.add_subplot(2, 1, 1)
line, = ax.plot(a, color='blue', lw=2)
pylab.show()
You can use the Axes.set_yscale method. That allows you to change the scale after the Axes object is created. That would also allow you to build a control to let the user pick the scale if you needed to.
The relevant line to add is:
ax.set_yscale('log')
You can use 'linear' to switch back to a linear scale. Here's what your code would look like:
import pylab
import matplotlib.pyplot as plt
a = [pow(10, i) for i in range(10)]
fig = plt.figure()
ax = fig.add_subplot(2, 1, 1)
line, = ax.plot(a, color='blue', lw=2)
ax.set_yscale('log')
pylab.show()
First of all, it's not very tidy to mix pylab and pyplot code. What's more, pyplot style is preferred over using pylab.
Here is a slightly cleaned up code, using only pyplot functions:
from matplotlib import pyplot
a = [ pow(10,i) for i in range(10) ]
pyplot.subplot(2,1,1)
pyplot.plot(a, color='blue', lw=2)
pyplot.yscale('log')
pyplot.show()
The relevant function is pyplot.yscale(). If you use the object-oriented version, replace it by the method Axes.set_yscale(). Remember that you can also change the scale of X axis, using pyplot.xscale() (or Axes.set_xscale()).
Check my question What is the difference between ‘log’ and ‘symlog’? to see a few examples of the graph scales that matplotlib offers.
if you want to change the base of logarithm, just add:
plt.yscale('log',base=2)
Before Matplotlib 3.3, you would have to use basex/basey as the bases of log
You simply need to use semilogy instead of plot:
from pylab import *
import matplotlib.pyplot as pyplot
a = [ pow(10,i) for i in range(10) ]
fig = pyplot.figure()
ax = fig.add_subplot(2,1,1)
line, = ax.semilogy(a, color='blue', lw=2)
show()
I know this is slightly off-topic, since some comments mentioned the ax.set_yscale('log') to be "nicest" solution I thought a rebuttal could be due. I would not recommend using ax.set_yscale('log') for histograms and bar plots. In my version (0.99.1.1) i run into some rendering problems - not sure how general this issue is. However both bar and hist has optional arguments to set the y-scale to log, which work fine.
references:
http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.bar
http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.hist
So if you are simply using the unsophisticated API, like I often am (I use it in ipython a lot), then this is simply
yscale('log')
plot(...)
Hope this helps someone looking for a simple answer! :).
When I plot some data with matplotlib without setting any parameters, the data gets plotted with both x and y axis limits set correctly, meaning that all data is shown and no space is wasted (case 1):
import matplotlib
matplotlib.use('QT5Agg')
import matplotlib.pyplot as plt
x = range(10)
plt.plot(x,'-o',markersize='10')
plt.tight_layout()
plt.show()
Result:
If I set some limits for e. g. the x axis, even using autoscale() does not autoscale the y axis anymore (case 2):
import matplotlib
matplotlib.use('QT5Agg')
import matplotlib.pyplot as plt
x = range(10)
plt.plot(x,'-o',markersize='10')
plt.autoscale(enable=True,axis='y')
plt.xlim(7.5,11)
plt.tight_layout()
plt.show()
Result:
Question: which function is used internally by matplotlib to determine the limits for both axes and update the plot in case 1?
Background: I want to use this function as a base for reimplementing / extending this functionality for case 2.
As #ImportanceOfBeingEarnest pointed out in the answer below, there is no such automatized way at the moment. So, in case you are interested in knowing how to rescale your y-axis, one way to do so is by recomputing the corresponding y-values and then reassigning the y-limits using the method specified in this unaccepted answer. I haven't marked this as a duplicate because there are certain different issues in your example:
First (major one), you have plotted only x-values. So, to apply the method in the other answer, I had to first get the y-values in an array. This is done using get_ydata()
Second, the x-values were changed from range() generator to a NumPy array, as the former does not support indexing.
Third, I had to use a variable for the x-limits to be consistent with the function.
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(10)
plt.plot(x,'-o',markersize='10')
x_lims = [7.5, 11]
plt.xlim(x_lims)
ax = plt.gca()
y = ax.lines[0].get_ydata()
def find_nearest(array,value):
idx = (np.abs(array-value)).argmin()
return idx
y_low = y[find_nearest(x, x_lims[0])]
y_high = y[find_nearest(x, x_lims[1])]
ax.set_ylim(y_low, y_high)
plt.tight_layout()
plt.show()
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 2 years ago.
Improve this question
Default matplotlib graphs look really unattractive and even unprofessional. I tried out couple of packages include seaborn as well as prettyplotlib but both of these just barely improves the styles.
So far I've gotten to following using seaborn package:
Below is the appearance I'm looking for which is far cry from above:
Notice the following niceness in the 2nd example:
Area under the graph is filled with much more eye pleasing color.
The graph line is thinker and nicely stands out.
Axis lines are thinker and again nicely stands out.
Area under the curve is transparent.
X-Axis tick marks are more denser.
My questions are: Do you recognize above as some kind of popular theme or style that I can quickly use in matplotlib? Or if I can use from some package? Failing that, is there anyway to set this style as my global preference? Failing that, is it even possible to do this in matlibplot?
Thanks!
This is really a matter of taste, and also a matter of target audience. matplotlib tries to produce clear illustrations for scientific purposes. This is - necessarily - a compromise, and the illustrations are not something you would print in a magazine or show in an advertisement.
There are some good news and some bad news about matplotlib in this sense.
Bad news:
There is no single magical command or package which would create beautiful plots with matplotlib.
Good news:
There are simple ways to change the default settings, see: http://matplotlib.org/users/customizing.html
The object model enables the user to change almost everything and introduce complex new features.
The source code is available, and even it can be changed quite easily by the user.
In my opinion the most difficult thing is to decide what you want. Then doing what you want is easier, even though there is a steepish learning curve in the beginning.
Just as an example:
import numpy as np
import matplotlib.pyplot as plt
# create some fictive access data by hour
xdata = np.arange(25)
ydata = np.random.randint(10, 20, 25)
ydata[24] = ydata[0]
# let us make a simple graph
fig = plt.figure(figsize=[7,5])
ax = plt.subplot(111)
l = ax.fill_between(xdata, ydata)
# set the basic properties
ax.set_xlabel('Time of posting (US EST)')
ax.set_ylabel('Percentage of Frontpaged Submissions')
ax.set_title('Likelihood of Reaching the Frontpage')
# set the limits
ax.set_xlim(0, 24)
ax.set_ylim(6, 24)
# set the grid on
ax.grid('on')
(Just a comment: The X-axis limits in the original image do not take the cyclicity of the data into account.)
This will give us something like this:
It is easy to understand that we need to do a lot of changes in order to be able to show this to a less-engineering-minded audience. At least:
make the fill transparent and less offensive in colour
make the line thicker
change the line colour
add more ticks to the X axis
change the fonts of the titles
# change the fill into a blueish color with opacity .3
l.set_facecolors([[.5,.5,.8,.3]])
# change the edge color (bluish and transparentish) and thickness
l.set_edgecolors([[0, 0, .5, .3]])
l.set_linewidths([3])
# add more ticks
ax.set_xticks(np.arange(25))
# remove tick marks
ax.xaxis.set_tick_params(size=0)
ax.yaxis.set_tick_params(size=0)
# change the color of the top and right spines to opaque gray
ax.spines['right'].set_color((.8,.8,.8))
ax.spines['top'].set_color((.8,.8,.8))
# tweak the axis labels
xlab = ax.xaxis.get_label()
ylab = ax.yaxis.get_label()
xlab.set_style('italic')
xlab.set_size(10)
ylab.set_style('italic')
ylab.set_size(10)
# tweak the title
ttl = ax.title
ttl.set_weight('bold')
Now we have:
This is not exactly as in the question, but everything can be tuned towards that direction. Many of the things set here can be set as defaults for matplotlib. Maybe this gives an idea of how to change things in the plots.
To get closer to the style you prefer, you could use the whitegrid style in seaborn. As the other answers have noted, you control the transparency of the fill with the alpha parameter to fill_between.
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
sns.set_style("whitegrid")
blue, = sns.color_palette("muted", 1)
x = np.arange(23)
y = np.random.randint(8, 20, 23)
fig, ax = plt.subplots()
ax.plot(x, y, color=blue, lw=3)
ax.fill_between(x, 0, y, alpha=.3)
ax.set(xlim=(0, len(x) - 1), ylim=(0, None), xticks=x)
More information on seaborn styles can be found in the docs.
matplotlib is almost infinitely flexible so you can do almost anything with it and if it doesn't exist you can write it yourself! Obviously the defaults are bland, this is because everyone has there own idea of what is "nice" so it is pointless to impose a predefined style.
Here is a really simple example that addresses 4 of your points.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import MultipleLocator, FormatStrFormatter
x = np.linspace(-10, 10, 1000)
y = 1+np.sinc(x)
ax = plt.subplot(111)
ax.plot(x, y, lw=2)
ax.fill_between(x, 0, y, alpha=0.2)
ax.grid()
majorLocator = MultipleLocator(1)
ax.xaxis.set_major_locator(majorLocator)
plt.show()
If your want to set defaults so all your plots look the same then you should generate a custom matplotlibrc file or use style. A useful guide is here. To view a list of all the available options just call print plt.rcParams from an interactive terminal.
Some of the other features such as filling will need to be done on a per plot basis. You can standardise this across your work by creating a function which adds the fill between given some input such as the axis instance and data.
You can customize plots style as follow:
import numpy as np
import matplotlib.pyplot as plt
plt.use_style('ggplot') # customize your plots style
x = np.linspace(0,2*np.pi,100)
y = np.sin(x)
plt.fill_between(x,y)
plt.show()
I have two similar pieces of matplotlib codes that produce different results.
1:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0,10,100)
y = np.linspace(0,10,100)
y[10:40] = np.nan
plt.plot(x,y)
plt.savefig('fig')
2:
from pylab import *
x = linspace(0,10,100)
y = linspace(0,10,100)
y[10:40] = np.nan
plot(x,y)
savefig('fig')
Code #1 produces a straight line with the NaN region filled in with a solid line of a different color
Code #2 produces a figure with a straight line but does not fill in the NaN region with a line. Instead there is a gap there.
How can I make code # 1 produce a gap in place of NaN's like code #2. I have been googling for a couple of days and have come up with nothing. Any help or advice would be appreciated. Thanks in advance
Just to explain what's probably happening:
The two pieces of code you showed are identical. They will always produce the same output if called by themselves. pylab is basically a just a few lines of code that does: (There's a bit more to it than this, but it's the basic idea.)
from numpy import *
from matplotlib.mlab import *
from matplotlib.pyplot import *
There's absolutely no way for pylab.plot to reference a different function than plt.plot
However, if you just call plt.plot (or pylab.plot, they're the same function), it plots on the current figure.
If you plotted something on that figure before, it will still be there. (If you're familiar with matlab, matplotlib defaults to hold('on'). You can change this with plt.hold, but it's best to be more explicit in python and just create a new figure.)
Basically, you probably did this:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0,10,100)
y = np.linspace(0,10,100)
plt.plot(x,y)
plt.savefig('fig')
And then, in the same interactive ipython session, you did this:
y[10:40] = np.nan
plt.plot(x, y)
plt.savefig('fig')
Because you didn't call show, the current figure is still the same one as it was before. The "full" line is still present beneath the second one, and the second line with the NaN's is a different color because you've plotted on the same axes.
This is one of the many reasons why it's a good idea to use the object-oriented interface. That way you're aware of exactly which axes and figure you're plotting on.
For example:
fig, ax = plt.subplots()
ax.plot(x, y)
fig.savefig('test.png')
If you're not going to do that, at very least always explicitly create a new figure and/or axes when you want a new figure. (e.g. start by calling plt.figure())