I just created a horizontal stacked bar chart using matplotlib, and I can't figure out why there is extra space between the x axis and the first bar (code and picture below). Any suggestions or questions? Thanks!
Code:
fig = figure(facecolor="white")
ax1 = fig.add_subplot(111, axisbg="white")
heights = .43
data = np.array([source['loan1'],source['loan2'],source['loan3']])
dat2 = np.array(source2)
ind=np.arange(N)
left = np.vstack((np.zeros((data.shape[1],), dtype=data.dtype), np.cumsum(data, axis=0) [:-1]))
colors = ( '#27A545', '#7D3CBD', '#C72121')
for dat, col, lefts, pname2 in zip(data, colors, left, pname):
ax1.barh(ind+(heights/2), dat, color=col, left=lefts, height = heights, align='center', alpha = .5)
p4 = ax1.barh(ind-(heights/2), dat2, height=heights, color = "#C6C6C6", align='center', alpha = .7)
ax1.spines['right'].set_visible(False)
ax1.yaxis.set_ticks_position('left')
ax1.spines['top'].set_visible(False)
ax1.xaxis.set_ticks_position('bottom')
yticks([z for z in range(N)], namelist)
#mostly for the legend
params = {'legend.fontsize': 8}
rcParams.update(params)
box = ax1.get_position()
ax1.set_position([box.x0, box.y0 + box.height * 0.1, box.width, box.height * 0.9])
l = ax1.legend(loc = 'upper center', bbox_to_anchor=(0.5,-0.05), fancybox=True, shadow = True, ncol = 4)
show()
This is because matplotlib tries to intelligently choose minimum and maximum limits for the plot (i.e. "round-ish" numbers) by default.
This makes a lot of sense for some plots, but not for others.
To disable it, just do ax.axis('tight') to snap the data limits to the strict extents of the data.
If you want a bit of padding despite the "tight" bounds on the axes limits, use ax.margins.
In your case, you'd probably want something like:
# 5% padding on the y-axis and none on the x-axis
ax.margins(0, 0.05)
# Snap to data limits (with padding specified above)
ax.axis('tight')
Also, if you want to set the extents manually, you can just do
ax.axis([xmin, xmax, ymin, ymax])`
or use set_xlim, set_ylim, or even
ax.set(xlim=[xmin, xmax], ylim=[ymin, ymax], title='blah', xlabel='etc')
Related
Dataset: I have a series (n = 30) of X (wavelength) and Y (reflectance) data, each associated with a unique value Z (age). Z values are stored as a separate ordered list.
Goal: I am trying to create a series of line plots which display each of the 30 datasets together, where each line is appropriately colored according their Z value (age). I am hoping for weighted colorization depending on the Z value, and an associated colorbar() or similar.
Attempts: I tried manipulating rcParams to do this by iterating through a color-scheme per plot [i], but the colors are not weighted properly to the Z value. See example figure. I think my issue is similar to this question here.
I feel like this shouldn't be so hard and that I am missing something obvious!
#plot
target_x = nm_names
target_y = data_plot
target_names = ages
N = len(target_y) # number of objects to plot i.e. color cycle count
plt.rcParams["figure.figsize"] = [16,7] # fig size
plt.rcParams["axes.prop_cycle"] = plt.cycler("color", plt.cm.PiYG(np.linspace(0,1,N))) # colors to cycle through, choose default like 'viridis' or 'PiYG'
fig, ax = plt.subplots()
for i in range(N):
ax.plot(target_x, target_y.iloc[i], label = target_names[i]) # for i in range of objects, plot x,y
#axes
plt.xticks(fontsize = 10, rotation=70, size = 8)
ax.xaxis.set_major_locator(ticker.MultipleLocator(50))
plt.xlabel('Wavelength (nm)', fontsize = 14)
plt.yticks(fontsize = 12)
plt.ylabel('Normalized Relative Reflectance', fontsize = 13)
plt.title("Spectral Profile", size = 14)
plt.title
plt.xlim(375,2500)
# legend location
box = ax.get_position()
ax.set_position([box.x0, box.y0 + box.height * 0.1,
box.width, box.height * .9])
ax.legend(loc='lower left', bbox_to_anchor=(1, 0),
fancybox=True, shadow=True, ncol=1, title = 'Age (ky)') # Put a legend below current axis
plt.rcdefaults() # reset global plt parameters, IMPORTANT!
plt.show()
My plot, where 'age' is the 'Z' value
I'd like to plot EEG data and get this result:
But I am stuck on how to display the x axis label and its xlim.
After reading other questions, which use set_visible(False), I cannot resolve my issue.
I write my code in order to be reproducible:
sfreq = 256
raw_data = np.random.rand(14, 1000 * sfreq)
duration = 10 # duration of the signal
start = 200 * sfreq
final = start + int(sfreq * duration)
channels = list(np.arange(1, len(channels) + 1 ))
fig, ax = plt.subplots(len(channels), 1, sharex=True, figsize=(10, 10))
for idx, node in enumerate(channels):
data = raw_data[idx, start:final]
times = np.arange(1, data.size + 1) / sfreq
ax[idx].plot(times, data, lw=1., ls='-', c='k')
ax[idx].axis('off') # to remove bounding subplot
ax[idx].set_yticks([]) # to remove values from y axis
ax[idx].text(-1, 0, node, fontsize=12) # write text
# plt.axis(True)
# plt.axes().get_xaxis().set_visible(True)
# plt.xlim([200, 220])
plt.xlabel('Time (seconds)', fontsize=12)
plt.tight_layout()
plt.show()
This is my result:
But I'd like this:
Here are some possible changes to the plot:
make the code more python by using zip instead of an index in the for loop
change the visibility of the "spines" (the lines surrounding the subplot) instead of use axis('off')
remove the padding (margins)
use the axes transform to position the text of the y-axis
...
import matplotlib.pyplot as plt
import numpy as np
sfreq = 256
raw_data = np.random.rand(14, 1000 * sfreq)
duration = 10 # duration of the signal
start = 200 * sfreq
final = start + int(sfreq * duration)
channels = np.arange(len(raw_data)) + 1
fig, axs = plt.subplots(len(channels), 1, sharex=True, figsize=(10, 10))
for ax, node, data in zip(axs, channels, raw_data):
data = data[start:final]
times = np.arange(1, data.size + 1) / sfreq
ax.plot(times, data, lw=1., ls='-', c='k')
ax.set_yticks([]) # remove y ticks
for sp in ax.spines:
ax.spines[sp].set_visible(False) # hide the 4 lines surrounding the subplot
ax.text(-0.01, 0.5, node, fontsize=12, ha='right', va='center', transform=ax.transAxes) # write text
ax.margins(x=0) # avoid the empty space left and right
if ax != axs[-1]:
# ax.tick_params(axis='x', length=0) # hide the tick marks
ax.tick_params(bottom=False) # no tick marks at the bottom
axs[-1].set_xlabel('Time (seconds)', fontsize=12, labelpad=-10) # use negative padding to get closer to the xaxis
axs[-1].set_xticks([0, duration])
axs[-1].set_xticklabels([start // sfreq, final // sfreq])
axs[-1].spines['bottom'].set_bounds([0, duration]) # only draw the spine between the two ticks
axs[-1].spines['bottom'].set_visible(True)
axs[-1].spines['bottom'].set_linewidth(2)
plt.tight_layout()
plt.show()
This is the code. The two axes have two different scales. (ax1 has both negative and positive values and ax2 has only negative values). The graph should be such a way that ax1 would have one line(the positive points) on top of the x-axis and the other line (with negative x-ticks) under the x-axis. Same for ax2.
you can see the resultant graph here:
x_ax = list(np.arange(10))
y_ax = list(np.arange(-10,0))
x_ax1 = list(np.arange(10))
y_ax1 = list(np.arange(0,10))
x_ax_p = list(np.arange(10))
y_ax_p = list(np.arange(-30,-20))
x_ax1_p = list(np.arange(10))
y_ax1_p = list(np.arange(-40,-30))
fig = plt.figure(figsize = (10,10))
ax1 = fig.add_axes([0.1,0.1,0.8,0.8])
ax2 = fig.add_axes([0.9,0.1,0.8,0.8])
ax1.spines['left'].set_color('none')
# ax1.spines['right'].set_color('none')
ax1.spines['top'].set_color('none')
# ax1.spines['bottom'].set_position('zero')
# ax2.spines['left'].set_position(('axes', 0))
ax2.spines['right'].set_color('none')
ax2.spines['top'].set_color('none')
# ax2.spines['bottom'].set_position('zero')
#ax1.set_xticks([])
ax1.set_yticks([])
#ax2.set_xticks([])
ax2.set_yticks([])
x_offset= 0.2 # for displaying the values
y_offset= 2
##################enter code here#########################
ax1.plot(range(len(x_ax)),y_ax,marker='o', label = 'Test_store')
ax1.plot(range(len(x_ax1)),y_ax1,marker='o', label = 'Control_store')
##############################################
ax2.plot(range(len(x_ax_p)),y_ax_p,marker='o', label = 'Test_store')
ax2.plot(range(len(x_ax1_p)),y_ax1_p,marker='o', label = 'Control_store')
for i,j in zip(range(len(x_ax)),y_ax):
ax1.annotate(round(j,2),xy=(i,j),xytext = (i,j))
for i,j in zip(range(len(x_ax1)),y_ax1):
ax1.annotate(str(round(j,2)),xy=(i,j),xytext = (i,j))
for i,j in zip(range(len(x_ax_p)),y_ax_p):
ax2.annotate(str(round(j,2)),xy=(i,j),xytext = (i,j))
for i,j in zip(range(len(x_ax1_p)),y_ax1_p):
ax2.annotate(str(round(j,2)),xy=(i,j),xytext = (i,j))
ax1.text(8.5, -15, 'Week', ha='right')
ax1.set_ylabel("Sales")
ax1.set_title("Period : "+ per + " "+ "\n" +"Week vs Sales")
ax2.set_title("Period : "+"Pilot Phase"+ "\n"+"Week vs Sales")
ax1.set_xticks(np.arange(len(x_ax)))
ax2.set_xticks(np.arange(len(x_ax)))
ax2.legend()
plt.show()
fig.clear()
To specify the same y-axis limits for both figures, you can use set_ylim:
ymin, ymax = 0, 100 # Change these to whatever values you require
ax1.set_ylim(ymin, ymax)
ax2.set_ylim(ymin, ymax)
You could also use the minimum and maximum of the default y-limits generated by Matplotlib to set the limits dynamically, i.e. without having to specify them manually:
ymin = min([ax1.get_ylim()[0], ax2.get_ylim()[0]])
ymax = max([ax1.get_ylim()[1], ax2.get_ylim()[1]])
ax1.set_ylim(ymin, ymax)
ax2.set_ylim(ymin, ymax)
To set the vertical location of the x-axis ticks at zero, you can use the following:
ax1.spines['bottom'].set_position('zero')
ax2.spines['bottom'].set_position('zero')
If you want to use a value other than zero, the above is shorthand for:
ax1.spines['bottom'].set_position(("data", 0))
ax2.spines['bottom'].set_position(("data", 0))
As an illustration, I present a figure here to depict my question.
fig = plt.figure(figsize = (10,4))
ax1 = plt.subplot(121)
map =Basemap(llcrnrlon=x_map1,llcrnrlat=y_map1,urcrnrlon=x_map2,urcrnrlat=y_map2)
map.readshapefile('shapefile','shapefile',zorder =2,linewidth = 0)
for info, shape in zip(map.shapefile, map.shapefile):
x, y = zip(*shape)
map.plot(x, y, marker=None,color='k',linewidth = 0.5)
plt.title("a")
ax2 = plt.subplot(122)
y_pos = [0.5,1,]
performance = [484.0,1080.0]
bars = plt.bar(y_pos, performance, align='center')
plt.title("b")
Due to the mapping setting is not consistent with the subplot(b). Thus, subplot(a) and subplot(b) has distinct board frame. In my opinion, the un-aligned borders are not pleasant for reader.
Is there any way to adjust the boarder size of subplot(b) in order to harmony as a whole figure.
This is my target:
Notice that, subplot(a) need to contain matplotlib.basemap elements.
Currently, your subplot on the left has an 'equal' aspect ratio, while for the other one it is automatic. Therefore, you have to manually set the aspect ratio of the subplot on the right:
def get_aspect(ax):
xlim = ax.get_xlim()
ylim = ax.get_ylim()
aspect_ratio = abs((ylim[0]-ylim[1]) / (xlim[0]-xlim[1]))
return aspect_ratio
ax2.set_aspect(get_aspect(ax1) / get_aspect(ax2))
After moving all of my 'y' axes to subplots I get an unwanted axis. It's the black one on the left. Does anyone know how to get rid of it? I'm sure it's getting plotted when I call the figure, however I'm not sure how to get rid of it.
def mpl_plot(self, plot_page, replot = 0): #Data stored in lists
if plot_page == 1: #Plot 1st Page
#plt0 = self.mplwidget.axes
fig = self.mplwidget.figure #Add a figure
if plot_page == 2: #Plot 2nd Page
#plt0 = self.mplwidget_2.axes
fig = self.mplwidget_2.figure #Add a figure
if plot_page == 3: #Plot 3rd Page
#plt0 = self.mplwidget_3.axes
fig = self.mplwidget_3.figure #Add a figure
#Clears Figure if data is roplotted
if replot == 1:
fig.clf()
par0 = fig.add_subplot(111)
par1 = fig.add_subplot(111)
par2 = fig.add_subplot(111)
#Add Axes
plt = par0.twinx()
ax1 = par1.twinx()
ax2 = par2.twinx()
impeller = str(self.comboBox_impellers.currentText()) #Get Impeller
fac_curves = self.mpl_factory_specs(impeller)
fac_lift = fac_curves[0]
fac_power = fac_curves[1]
fac_flow = fac_curves[2]
fac_eff = fac_curves[3]
fac_max_eff = fac_curves[4]
fac_max_eff_bpd = fac_curves[5]
fac_ranges = self.mpl_factory_ranges()
min_range = fac_ranges[0]
max_range = fac_ranges[1]
#Plot Chart
plt.hold(True)
plt.plot(fac_flow, fac_lift, 'b', linestyle = "dashed", linewidth = 1)
ax1.plot(fac_flow, fac_power, 'r', linestyle = "dashed", linewidth = 1)
ax2.plot(fac_flow, fac_eff, 'g', linestyle = "dashed", linewidth = 1)
#Move spines
ax2.spines["right"].set_position(("outward", 25))
self.make_patch_spines_invisible(ax2)
ax2.spines["right"].set_visible(True)
#Plot x axis minor tick marks
minorLocatorx = AutoMinorLocator()
ax1.xaxis.set_minor_locator(minorLocatorx)
ax1.tick_params(which='both', width= 0.5)
ax1.tick_params(which='major', length=7)
ax1.tick_params(which='minor', length=4, color='k')
#Plot y axis minor tick marks
minorLocatory = AutoMinorLocator()
plt.yaxis.set_minor_locator(minorLocatory)
plt.tick_params(which='both', width= 0.5)
plt.tick_params(which='major', length=7)
plt.tick_params(which='minor', length=4, color='k')
#Make Border of Chart White
fig.set_facecolor('white')
#Plot Grid
plt.grid(b=True, which='both', color='k', linestyle='-')
#set shaded Area
plt.axvspan(min_range, max_range, facecolor='#9BE2FA', alpha=0.5) #Yellow rectangular shaded area
#Set Vertical Lines
plt.axvline(fac_max_eff_bpd, color = '#69767A')
#BEP MARKER *** Can change marker style if needed
bep = fac_max_eff * 0.90 #bep is 90% of maximum efficiency point
bep_corrected = bep * 0.90 # We knock off another 10% to place the arrow correctly on chart
ax2.annotate('BEP', xy=(fac_max_eff_bpd, bep_corrected), xycoords='data', #Subtract 2.5 shows up correctly on chart
xytext=(-50, 30), textcoords='offset points',
bbox=dict(boxstyle="round", fc="0.8"),
arrowprops=dict(arrowstyle="-|>",
shrinkA=0, shrinkB=10,
connectionstyle="angle,angleA=0,angleB=90,rad=10"),
)
#Set Scales
plt.set_ylim(0,max(fac_lift) + (max(fac_lift) * 0.40)) #Pressure
#plt.set_xlim(0,max(fac_flow))
ax1.set_ylim(0,max(fac_power) + (max(fac_power) * 0.40)) #Power
ax2.set_ylim(0,max(fac_eff) + (max(fac_eff) * 0.40)) #Effiency
plt.yaxis.tick_left()
# Set Axes Colors
plt.tick_params(axis='y', colors='b')
ax1.tick_params(axis='y', colors='r')
ax2.tick_params(axis='y', colors='g')
# Set Chart Labels
plt.yaxis.set_label_position("left")
plt.set_xlabel("BPD")
plt.set_ylabel("Feet" , color = 'b')
#ax1.set_ylabel("BHP", color = 'r')
#ax1.set_ylabel("Effiency", color = 'g')
# Set tight layout
fig.set_tight_layout
# Since we moved Feet Axis to subplot, extra unneeded axis was created. This Removes it
# Refresh
fig.canvas.update()
fig.canvas.draw()
Well it looks like you have three y-axes, referencing the one you want to not be shown, you could try adding:
ax.yaxis.set_tick_params(labelsize=0, length=0, which='major')
to just make invisible the labels and ticks. I think it's ax2 you want gone?