I just upgraded to matplotlib 2.0, and I feel like I'm on crazy pills. I'm trying to make a log-linear plot, with the y-axis on a linear scale and the x-axis on a log10 scale. Previously, the following code would have allowed me to specify exactly where I want my ticks, and what I want their labels to be:
import matplotlib.pyplot as plt
plt.plot([0.0,5.0], [1.0, 1.0], '--', color='k', zorder=1, lw=2)
plt.xlim(0.4,2.0)
plt.ylim(0.0,2.0)
plt.xscale('log')
plt.tick_params(axis='x',which='minor',bottom='off',top='off')
xticks = [0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0]
ticklabels = ['0.4', '0.6', '0.8', '1.0', '1.2', '1.4', '1.6', '1.8', '2.0']
plt.xticks(xticks, ticklabels)
plt.show()
But in matplotlib 2.0, this now causes me to get a set of overlapping tick labels where matplotlib apparently wants to auto-create ticks:
But if I comment out the "plt.xlim(0.4,2.0)" line and let it automatically determine the axis limits, there are no overlapping tick labels and I just get the ones I want:
But that doesn't work because I now have useless x-axis limits.
Any ideas?
Edit: for people searching the internet in the future, I'm becoming more convinced that this is actually a bug in matplotlib itself. I reverted back to v. 1.5.3. to just avoid the issue.
The additional ticklabels that overlap originate from some minor ticklabels, which are present in the plot. To get rid of them, one can set the minor formatter to the NullFormatter:
plt.gca().xaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
The complete code from the question might then look like
import matplotlib.pyplot as plt
import matplotlib.ticker
import numpy as np
x = np.linspace(0,2.5)
y = np.sin(x*6)
plt.plot(x,y, '--', color='k', zorder=1, lw=2)
plt.xlim(0.4,2.0)
plt.xscale('log')
xticks = [0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0]
ticklabels = ['0.4', '0.6', '0.8', '1.0', '1.2', '1.4', '1.6', '1.8', '2.0']
plt.xticks(xticks, ticklabels)
plt.gca().xaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
plt.show()
A code that may be more intuitive as it is not setting the xticklabels as strings would be the following, where we use a FixedLocator and a ScalarFormatter.
This code produces the identical plot as the above.
import matplotlib.pyplot as plt
import matplotlib.ticker
import numpy as np
x = np.linspace(0,2.5)
y = np.sin(x*6)
plt.plot(x,y, '--', color='k', zorder=1, lw=2)
plt.xlim(0.4,2.0)
plt.xscale('log')
xticks = [0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0]
xmajorLocator = matplotlib.ticker.FixedLocator(locs=xticks)
xmajorFormatter = matplotlib.ticker.ScalarFormatter()
plt.gca().xaxis.set_major_locator( xmajorLocator )
plt.gca().xaxis.set_major_formatter( xmajorFormatter )
plt.gca().xaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
plt.show()
Related
I have some issues using plt.errorbar and plt.scatter. I want to create a plot with points with their own error bars where both of them follow a certain color scale.
The problem is that to create this I have to use plt.errorbar and plt.scatter at the same time to report the points in the same color scale, but doing this the errorbar are above my points and overlap onto other points and I don't want this. Someone can help?
I add an image of what is happening at the moment and the code.
import matplotlib.pyplot as plt
import IPython
from astropy.cosmology import FlatLambdaCDM
import numpy as np
import math as ma
import matplotlib
import matplotlib.cm as cm
from matplotlib.colors import Normalize
cosmo = FlatLambdaCDM(H0=70, Om0=0.3)
Fu20_beam_814=np.array([0.91, 0.65, 0.58, 0.47, 0.64, 0.59, 0.77, 0.57, 0.59, 1.63])
err_Fu20_beam_814=np.array([0.13, 0.20, 0.14, 0.32, 0.17, 0.17, 0.16, 0.24, 0.15, 0.35])
Gav_beam_814=np.array([0.45, 0.41, 0.44, 0.55, 0.45, 0.47, 0.50, 0.56, 0.44, 0.45])
Z_beam_814=np.array([ 4.4105, 5.6700, 4.4381, 5.6704, 4.5134, 5.5448, 5.1818, 5.5420, 4.5785, 4.5802 ])
fake_814=np.array([0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2])*(-1/2)
M_beam_814=np.array([ 9.7, 9.8, 9.9, 10.2, 9.6, 10.2, 9.4, 10.2, 10.0, 9.7])
def f(Re,z):
return Re/(cosmo.arcsec_per_kpc_proper(z).value)
ReG_beam_814 = f(Gav_beam_814, Z_beam_814)
col='jet'
size=150
cap=1
norm = matplotlib.colors.Normalize(vmin=min(M_beam_814), vmax=max(M_beam_814), clip=True)
mapper = cm.ScalarMappable(norm=norm, cmap='jet')
M_color = np.array([(mapper.to_rgba(v)) for v in M_beam_814])
for x, y, ye, xe, color in zip(Fu20_beam_814, ReG_beam_814, fake_814, err_Fu20_beam_814, M_color):
plt.errorbar(x, y, ye, xe, c=color)
Beam_814 = plt.scatter(Fu20_beam_814, ReG_beam_814, s=size, marker='s', c=M_beam_814, cmap=col, edgecolors='#000000', linewidths=0.7)
plt.colorbar()
[1]: https://i.stack.imgur.com/EmkNT.png
I have this code:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import EngFormatter, LogLocator
fig, ax0 = plt.subplots(figsize=(10, 8))
fmin, fmax = 1, 1e9
zmin, zmax = 1e-3, 1e6
ax0.set_xscale('log', base=10)
ax0.set_yscale('log', base=10)
ax0.set_xlim(fmin, fmax)
ax0.set_ylim(zmin, zmax)
ax0.xaxis.set_major_formatter(EngFormatter(unit='Hz'))
ax0.yaxis.set_major_formatter(EngFormatter(unit='Ω'))
ax0.xaxis.set_major_locator(LogLocator(base=10, numticks=100))
locmin = LogLocator(base=10.0,subs=(0.2,0.4,0.6,0.8))
ax0.xaxis.set_minor_locator(locmin)
ax0.yaxis.set_minor_locator(locmin)
ax0.grid(which='both')
plt.show()
It produces the follwoing output.
Anyone's guess why the minor grid lines are missing on the last two decades?
I am on matplotlib 3.4.3.
You shouldn't reuse the LogLocator object for both x&y axis, assigning it will modify it's properties. The docstring already hints at this:
numticks : None or int, default: None
The maximum number of ticks to allow on a given axis. The default
of None will try to choose intelligently as long as this
Locator has already been assigned to an axis using
~.axis.Axis.get_tick_space, but otherwise falls back to 9.
So in this specific case, first assigning to y before x would make it "work". I assume that means the last assignment determines some of the properties.
But of course it's best to simply create a separate instance of the LogLocator object for each axis (both x/y & major/minor).
I'm not sure why the minor-x ticks won't show when leaving numticks=None.
So:
fig, ax0 = plt.subplots(figsize=(10, 8), facecolor='w')
fmin, fmax = 1, 1e9
zmin, zmax = 1e-3, 1e6
ax0.set_xscale('log', base=10)
ax0.set_yscale('log', base=10)
ax0.set_xlim(fmin, fmax)
ax0.set_ylim(zmin, zmax)
ax0.xaxis.set_major_formatter(EngFormatter(unit='Hz'))
ax0.yaxis.set_major_formatter(EngFormatter(unit='Ω'))
ax0.xaxis.set_major_locator(LogLocator(base=10, subs=(1.0,), numticks=100))
ax0.xaxis.set_minor_locator(LogLocator(base=10.0, subs=(0.2, 0.4, 0.6, 0.8), numticks=100))
ax0.yaxis.set_minor_locator(LogLocator(base=10.0, subs=(0.2, 0.4, 0.6, 0.8), numticks=100))
ax0.grid(which='major', axis='both', color='k', alpha=0.6)
ax0.grid(which='minor', axis='both', color='k', alpha=0.2)
plt.show()
Please note, I've looked at other questions like question and my problem is different and not a duplicate!
I would like to have two plots, with the same x axis in matplotlib. I thought this should be achieved via constrained_layout, but apparently this is not the case. Here is an example code.
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.gridspec as grd
x = np.arange(0, 30, 0.001)
df_line = pd.DataFrame({"x": x, "y": np.sin(x)})
df_bar = pd.DataFrame({
"x_bar": [1, 7, 10, 20, 30],
"y_bar": [0.0, 0.3, 0.4, 0.1, 0.2]
})
fig = plt.subplots(constrained_layout=True)
gs = grd.GridSpec(2, 1, height_ratios=[3, 2], wspace=0.1)
ax1 = plt.subplot(gs[0])
sns.lineplot(data=df_line, x=df_line["x"], y=df_line["y"], ax=ax1)
ax1.set_xlabel("time", fontsize="22")
ax1.set_ylabel("y values", fontsize="22")
plt.yticks(fontsize=16)
plt.xticks(fontsize=16)
plt.setp(ax1.get_legend().get_texts(), fontsize="22")
ax2 = plt.subplot(gs[1])
sns.barplot(data=df_bar, x="x_bar", y="y_bar", ax=ax2)
ax2.set_xlabel("time", fontsize="22")
ax2.set_ylabel("y values", fontsize="22")
plt.yticks(fontsize=16)
plt.xticks(fontsize=16)
this leads to the following figure.
However, I would like to see the corresponding x values of both plot aligned. How can I achieve this? Note, I've tried to use the following related question. However, this doesn't fully apply to my situation. First with the high number of x points (which I need in reality) point plots is make the picture to big and slow for loading. On top, I can't use the rank method as my categories for the barplot are not evenly distributed. They are specific points on the x axis which should be aligned with the corresponding point on the lineplot
x = np.arange(0, 30, 0.001)
df_line = pd.DataFrame({"x": x, "y": np.sin(x)})
df_bar = pd.DataFrame({
"x_bar": [1, 7, 10, 20, 30],
"y_bar": [0.0, 0.3, 0.4, 0.1, 0.2]
})
fig, (ax1, ax2) = plt.subplots(2,1)
ax1.plot(df_line['x'], df_line['y'])
for i in range(len(df_bar['x_bar'])):
ax2.axvline(x=df_bar['x_bar'][i], ymin=0, ymax=df_bar['y_bar'][i])
Output:
---edit---
I incorporated #mozway advice for linewidth:
lw = (300/ax1.get_xlim()[1])
ax2.axvline(x=df_bar['x_bar'][i], ymin=0, ymax=df_bar['y_bar'][i], solid_capstyle='butt', lw=lw)
Output:
or:
Look at the chart below. In this chart, I want to draw to circle with the centre coordinates at (45, 0). But, as you see, bottom limit of chart is zero and half of my circle is not shown. So, I need to extend this chart to the bottom a little bit. "Margins" method of axes (ax.margins()) doesn't work, since bottom line of this chart is zero and zero multiplied by any number is equal to zero.
Note: Please, do not post replies like ax.set_ylim(...) or ax.set_yticks(...). I am looking for a general solution for this like problems.
Code
import matplotlib.pyplot as plt
import numpy as np
values = np.array([0.00388632352941, 0.00375827941176, 0.00355033823529, 0.00328273529412, 0.00294677941176, 0.00272142647059, 0.00246463235294, 0.00227766176471, 0.00213151470588, 0.00202594117647, 0.00183544117647, 0.00162102941177, 0.00148372058824, 0.00128380882353, 0.00112252941176, 0.000931544117647, 0.000786573529412, 0.000658220588235, 0.000584485294118, 0.000524044117647, 0.000562485294118, 0.000716441176471, 0.000872617647059, 0.00109039705882, 0.00124138235294, 0.00136894117647, 0.00143985294118, 0.00134760294118, 0.00121794117647, 0.00112772058824, 0.00109435294118, 0.00102432352941, 0.00101069117647, 0.00102417647059, 0.00104895588235, 0.00101776470588, 0.00101494117647, 0.000885558823529, 0.00078075, 0.000752647058824, 0.000667691176471, 0.000593220588236, 0.000658647058823, 0.000742117647059, 0.000651470588235, 0.000604647058824, 0.000584573529412, 0.00049530882353, 0.000281235294118, 0.000355029411765])
fig, ax = plt.subplots()
ax.bar(np.arange(values.shape[0]), values)
plt.show()
In the question there is no circle, so my answer doesn't include any circle either.
In order to have some space around the data in the plot, you can use ax.margins(y=ymargin), where ymargin is the percentage of space to add on each side of the data. I.e. if data goes from 0 to 1 and you add ymargin = 0.1 of margin, the ylimits will be (-0.1, 1.1). (This is independent on whether or not one limit would be zero or not.)
Now, by default this does not work for a bar plot, as it would in the general case be undesireable to have the bars start somewhere in the air, as opposed to the bottom axis. This behaviour is steered using a flag called use_sticky_edges. We can set this flag to False to get back the behaviour of margins being applied to both ends of the axis. For taking effect, we need to call ax.autoscale_view afterwards.
import matplotlib.pyplot as plt
import numpy as np
values = np.array([3.89, 3.76, 3.55, 3.28, 2.95, 2.72, 2.46, 2.28, 2.13, 2.03, 1.84,
1.62, 1.48, 1.28, 1.12, 0.93, 0.79, 0.66, 0.58, 0.52, 0.56, 0.72,
0.87, 1.09, 1.24, 1.37, 1.44, 1.35, 1.22, 1.13, 1.09, 1.02, 1.01,
1.02, 1.05, 1.02, 1.01, 0.89, 0.78, 0.75, 0.67, 0.59, 0.66, 0.74,
0.65, 0.60, 0.58, 0.50, 0.28, 0.36])
fig, ax = plt.subplots()
ax.bar(np.arange(values.shape[0]), values)
ax.margins(y=0.3)
ax.use_sticky_edges = False
ax.autoscale_view(scaley=True)
plt.show()
The following screenshot shows my x-axis.
I added some labels and rotated them by 90 degrees in order to better read them. However, pyplot truncates the bottom such that I'm not able to completely read the labels.
How do I extend the bottom margin in order to see the complete labels?
Two retroactive ways:
fig, ax = plt.subplots()
# ...
fig.tight_layout()
Or
fig.subplots_adjust(bottom=0.2) # or whatever
Here's a subplots_adjust example: http://matplotlib.org/examples/pylab_examples/subplots_adjust.html
(but I prefer tight_layout)
A quick one-line solution that has worked for me is to use pyplot's auto tight_layout method directly, available in Matplotlib v1.1 onwards:
plt.tight_layout()
This can be invoked immediately before you show the plot (plt.show()), but after your manipulations on the axes (e.g. ticklabel rotations, etc).
This convenience method avoids manipulating individual figures of subplots.
Where plt is the standard pyplot from:
import matplotlib.pyplot as plt
fig.savefig('name.png', bbox_inches='tight')
works best for me, since it doesn't reduce the plot size compared to
fig.tight_layout()
Subplot-adjust did not work for me, since the whole figure would just resize with the labels still out of bounds.
A workaround I found was to keep the y-axis always a certain margin over the highest or minimum y-values:
x1,x2,y1,y2 = plt.axis()
plt.axis((x1,x2,y1 - 100 ,y2 + 100))
fig, ax = plt.subplots(tight_layout=True)
This is rather complicated, but it gives a general and neat solution.
import numpy as np
value1 = 3
xvalues = [0, 1, 2, 3, 4]
line1 = [2.0, 3.0, 2.0, 5.0, 4.0]
stdev1 = [0.1, 0.2, 0.1, 0.4, 0.3]
line2 = [1.7, 3.1, 2.5, 4.8, 4.2]
stdev2 = [0.12, 0.18, 0.12, 0.3, 0.35]
max_times = [max(line1+stdev1),max(line2+stdev2)]
min_times = [min(line1+stdev1),min(line2+stdev2)]
font_size = 25
max_total = max(max_times)
min_total = min(min_times)
max_minus_min = max_total - min_total
step_size = max_minus_min/10
head_space = (step_size*3)
plt.figure(figsize=(15, 15))
plt.errorbar(xvalues, line1, yerr=stdev1, fmt='', color='b')
plt.errorbar(xvalues, line2, yerr=stdev2, fmt='', color='r')
plt.xlabel("xvalues", fontsize=font_size)
plt.ylabel("lines 1 and 2 Test "+str(value1), fontsize=font_size)
plt.title("Let's leave space for the legend Experiment"+ str(value1), fontsize=font_size)
plt.legend(("Line1", "Line2"), loc="upper left", fontsize=font_size)
plt.tick_params(labelsize=font_size)
plt.yticks(np.arange(min_total, max_total+head_space, step=step_size) )
plt.grid()
plt.tight_layout()
Result: