How should I use axes.margins method? - python

fig, ax=plt.subplots(len(dagecat), 1, figsize=(15, 9*len(dagecat)))
for i, age in enumerate(list(dagecat.keys())[:9]):
dagecat[age].fillna(0).plot(y='ds', use_index=True, ax=ax[i], kind='bar', color='k', width=-0.4, align='edge') # black bars
dagecat[age].fillna(0).plot(y='dns', use_index=True, ax=ax[i], kind='bar',
width=0.4, color=(0.827, 0.827, 0.827, 1), align='edge') # gray bars
ax[i].margins(x=0.5)
for j, p in enumerate(ax[i].patches):
if j < 20:
ax[i].text(x=p.get_x()+0.5*p.get_width(), y=p.get_height()+0.01*dagecat[age]['dns'].max(), s=f"{p.get_height()}", fontsize=12, ha='center')
else:
ax[i].text(x=p.get_x()+0.5*p.get_width(), y=p.get_height()+0.01*dagecat[age]['dns'].max(), s=f"{p.get_height()}", fontsize=12, ha='center')
The graph at the top is one of the subplot drawn by the code above.
Since the first black bar is completely stuck to y axis, I used ax[i].margins method to make some space but it didn't work.
Am I using margins method in a wrong way? Or is there any problem in the other part of my code?

Related

matplotlib supylabel on second axis of multiplot

I'm not finding it possible to add a second supylabel for a right-hand y-axis of a multiplot.
Can anyone please confirm 1) whether or not it can be done and/or 2)provide guidance on how?
I am trying to achieve this:
Because there are a variable number of subplots (sometimes an odd number, sometimes even) across the broader project, using subplot-level labelling to label the "middle" subplot would be problematic.
I'm presently accomplishing with figure level text. Which looks fine within python, but the right label gets cut-off by savefig. I can only get it to work if I dummy-in null ax-level y-labels " \n".
nrows = len(dftmp.GroupingCol.unique())
ncols = 1
fig, ax = plt.subplots(nrows=nrows, ncols=ncols, figsize=(14,10), constrained_layout=True,
sharex=True)
for e, ep in enumerate(dftmp.GroupingCol.unique(), start=1):
# define a figure axis and plot data
ax = plt.subplot(nrows, ncols, e)
dftmp["ValueCol"].loc[dftmp["GroupingCol"]==ep].plot(ax=ax, kind="bar", color=barcolor_lst) #, use_index=False)
# horizontal reference line (zero change)
zero_line = plt.axhline(0, color='k', linewidth=0.8)
# y-axis extent limits
ax.set_ylim([50*(-1.1), 50*1.1])
# create right-hand y-axis
ax2 = ax.twinx()
# y-axis extent limits
ax2.set_ylim([200*(-1), 200])
# null y-label placeholder to accommodate fig-level pseudo-supylabel
ax2.set_ylabel(" \n") # requires space and newline to work
# create supylabel for left-axis
supy_left = fig.supylabel("Left-hand y-axis super label", fontweight="bold") #, pad = 7)#, fontdict=fontdict) #fontweight='bold')
# use fig-level text as pseudo-supylable for right-axis
fig.text(x=0.97, y=0.5, s="Right-hand y-axis super label\n\n", size=13, fontweight='bold', rotation=270, ha='center', va='center')
# create super-label for x-axis
supx = fig.supxlabel("Bottom super label", fontweight="bold")
In the absence of the fig.text line I tried naming a second supylabel as a different object and the code runs, but doesn't produce the label.
supy_right = fig.supylabel("Cumulative net change (m^3)", fontweight="bold", position=(0.9,0.5))
I have found the suplabels to be a little unreliable in many cases, so I resort to low-level tricks in cases like these:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(4, 4))
# dummy axes 1
ax = fig.add_subplot(1, 1, 1)
ax.set_xticks([])
ax.set_yticks([])
[ax.spines[side].set_visible(False) for side in ('left', 'top', 'right', 'bottom')]
ax.patch.set_visible(False)
ax.set_xlabel('x label', labelpad=30)
ax.set_ylabel('y label left', labelpad=30)
# dummy axes 2 for right ylabel
ax = fig.add_subplot(1, 1, 1)
ax.set_xticks([])
ax.set_yticks([])
[ax.spines[side].set_visible(False) for side in ('left', 'top', 'right', 'bottom')]
ax.patch.set_visible(False)
ax.yaxis.set_label_position('right')
ax.set_ylabel('y label right', labelpad=30)
# actual data axes
num_rows = 4
for i in range(num_rows):
ax = fig.add_subplot(num_rows, 1, i + 1)
...
fig.tight_layout()
You need to adjust the labelpad values according to your liking. The rest can be taken care of by fig.tight_layout() (you might need to specify the rect though).
EDIT: having re-read your question, have you tried increasing the pad_inches value when calling savefig()?

Matplotlib: Draw second y-axis with different length

I'm trying to make a matplotlib plot with a second y-axis. This works so far, but I was wondering, wether it was possible to shorten the second y-axis?
Furthermore, I struggle on some other formatting issues.
a) I want to draw an arrow on the second y-axis, just as drawn on the first y-axis.
b) I want to align the second y-axis at -1, so that the intersection of x- and 2nd y-axis is at(...; -1)
c) The x-axis crosses the x- and y-ticks at the origin, which I want to avoid.
d) How can I get a common legend for both y-axis?
Here is my code snippet so far.
fig, ax = plt.subplots()
bx = ax.twinx() # 2nd y-axis
ax.spines['bottom'].set_position(('data',0))
ax.spines['left'].set_position(('data',0))
ax.xaxis.set_ticks_position('bottom')
bx.spines['left'].set_position(('data',-1))
bx.spines['bottom'].set_position(('data',-1))
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
bx.spines["top"].set_visible(False)
bx.spines["bottom"].set_visible(False)
bx.spines["left"].set_visible(False)
## Graphs
x_val = np.arange(0,10)
y_val = 0.1*x_val
ax.plot(x_val, y_val, 'k--')
bx.plot(x_val, -y_val+1, color = 'purple')
## Arrows
ms=2
#ax.plot(1, 0, ">k", ms=ms, transform=ax.get_yaxis_transform(), clip_on=False)
ax.plot(0, 1, "^k", ms=ms, transform=ax.get_xaxis_transform(), clip_on=False)
bx.plot(1, 1, "^k", ms=ms, transform=bx.get_xaxis_transform(), clip_on=False)
plt.ylim((-1, 1.2))
bx.set_yticks([-1, -0.75, -0.5, -0.25, 0, 0.25, 0.5])
## Legend
ax.legend([r'$A_{hull}$'], frameon=False,
loc='upper left', bbox_to_anchor=(0.2, .75))
plt.show()
I've uploaded a screenshot of my plot so far, annotating the questioned points.
EDIT: I've changed the plotted values in the code snippet so that the example is easier to reproduce. However, the question is more or less only related to formatting issues so that the acutual values are not too important. Image is not changed, so don't be surprised when plotting the edited values, the graphs will look differently.
To avoid the strange overlap at x=0 and y=0, you could leave out the calls to ax.spines[...].set_position(('data',0)). You can change the transforms that place the arrows. Explicitly setting the x and y limits to start at 0 will also have the spines at those positions.
ax2.set_bounds(...) shortens the right y-axis.
To put items in the legend, each plotted item needs a label. get_legend_handles_labels can fetch the handles and labels of both axes, which can be combined in a new legend.
Renaming bx to something like ax2 makes the code easier to compare with existing example code. In matplotlib it often also helps to first put the plotting code and only later changes to limits, ticks and embellishments.
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
fig, ax = plt.subplots()
ax2 = ax.twinx() # 2nd y-axis
## Graphs
x_val = np.arange(0, 10)
y_val = 0.1 * x_val
ax.plot(x_val, y_val, 'k--', label=r'$A_{hull}$')
ax2.plot(x_val, -y_val + 1, color='purple', label='right axis')
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax2.spines["top"].set_visible(False)
ax2.spines["bottom"].set_visible(False)
ax2.spines["left"].set_visible(False)
ax2_upper_bound = 0.55
ax2.spines["right"].set_bounds(-1, ax2_upper_bound) # shorten the right y-axis
## add arrows to spines
ms = 2
# ax.plot(1, 0, ">k", ms=ms, transform=ax.get_yaxis_transform(), clip_on=False)
ax.plot(0, 1, "^k", ms=ms, transform=ax.transAxes, clip_on=False)
ax2.plot(1, ax2_upper_bound, "^k", ms=ms, transform=ax2.get_yaxis_transform(), clip_on=False)
# set limits to the axes
ax.set_xlim(xmin=0)
ax.set_ylim(ymin=0)
ax2.set_ylim((-1, 1.2))
ax2.set_yticks(np.arange(-1, 0.5001, 0.25))
## Legend
handles1, labels1 = ax.get_legend_handles_labels()
handles2, labels2 = ax2.get_legend_handles_labels()
ax.legend(handles1 + handles2, labels1 + labels2, frameon=False,
loc='upper left', bbox_to_anchor=(0.2, .75))
plt.show()

y-axis range is not logical (matplotlib python)

I just specify the x and y axis limitations but the numbers' order is wrong. how can I fix this?
here is my code:
fig, ax = plt.subplots(figsize=(20,10))
ax.plot(df.finish_price, label="Stock Values", color = 'blue')
plt.ylabel("Price", color='b')
# Generate a new Axes instance, on the twin-X axes (same position)
ax2 = ax.twinx()
ax2.plot(df.sentiment, label= 'Sentiment', color='green')
ax2.tick_params(axis='y', labelcolor='green')
plt.ylim(bottom = -1)
plt.ylim(top=1)
plt.xlabel("Days")
plt.ylabel("Sentiment", color='g')
fig.legend()
plt.show()
and here is the result:
as you can see the numbers' order on the right y-axis is wrong.

matplotlib, bi-directional bar plot, moving yticks down (or up)

fig, axes = plt.subplots(ncols=2, sharey=True, figsize=(12,12))
axes[0].barh(bar_chart.index, bar_chart['% Vaccinated'], align='edge', height=0.3,
color='red', zorder=1)
axes[0].set(title='% Fully Vaccinated)')
axes[1].barh(bar_chart.index, bar_chart['Deaths_Per_Confirmed_Case'], align='edge',height=0.3,
color='pink', zorder=1)
axes[1].set(title='% Deaths Per New Confirmed Cases')
axes[0].invert_xaxis()
axes[0].set(yticks=bar_chart.index)
axes[0].yaxis.tick_right()
for ax in axes.flat:
ax.margins(0.09)
ax.grid(True)
fig.tight_layout()
fig.subplots_adjust(wspace=.06)
plt.show()
So basically I just want to move my yticks up or down a notch. The above code produces the following graph:
You can probably see my problem there. My y-axis values are getting lost behind the bars. I can widen the space between the bars with the subplots_adjust at the bottom, but that's less pretty. Is there any way I can move the yticks up (or down)?
Also it'd be really nice to get ride of the 0 and 0.0 on the xticks.
Any helps appreciated.
Cheers folks.
Since it is not possible to display the y-axis up across the graphs, I suggest merging the two graphs with zero spacing. How about annotating the country names and numbers in that state to ensure a good look? To deal with the starting point of the x-axis tick marks on the right, I got the current tick value and replaced the first tick value with zero as a string.
fig, axes = plt.subplots(ncols=2, sharey=True, figsize=(12,12))
axes[0].barh(bar_chart.index, bar_chart['% Vaccinated'], align='center', height=0.3, color='red', zorder=1)
axes[0].set(title='% Fully Vaccinated)')
axes[1].barh(bar_chart.index, bar_chart['Deaths_Per_Confirmed_Case'], align='center',height=0.3, color='pink', zorder=1)
axes[1].set(title='% Deaths Per New Confirmed Cases')
axes[0].invert_xaxis()
axes[0].set(yticks=bar_chart.index)
axes[0].yaxis.tick_right()
new_labels = bar_chart.index.tolist()
for ax in axes.flat:
ax.margins(0.09)
ax.grid(True)
new_labels = bar_chart.index.tolist()
for rect,rect2,lbl in zip(axes[0].patches, axes[1].patches,new_labels):
width = rect.get_width()
width2 = rect2.get_width()
ypos = rect.get_y()
axes[0].annotate(lbl, (width, ypos+0.1), xytext=(3, 0), textcoords='offset points', size=20, color='white')
axes[0].annotate(str(width), (12, ypos+0.1), xytext=(3, 0), textcoords='offset points', size=20, color='white')
axes[1].annotate(str(width2), (0.01, ypos+0.1), xytext=(3, 0), textcoords='offset points', size=20, color='red')
ax1_labels = [str(round(l,1)) for l in axes[1].get_xticks()]
ax1_labels[0] = '0'
axes[1].set_xticklabels(ax1_labels)
fig.tight_layout()
fig.subplots_adjust(wspace=0.0)
plt.show()

Adding count plot totals and removing specific labels

Hi I have the following code. The code is in a for loop, and it makes over 300 plots.
sns.set(style='white', palette='cubehelix', font='sans-serif')
fig, axs = plt.subplots(2, 3, dpi =200);
fig.subplots_adjust(hspace=0.5, wspace=1)
plt.tick_params(
axis='x', # changes apply to the x-axis
which='both', # both major and minor ticks are affected
bottom=False, # ticks along the bottom edge are off
top=False, # ticks along the top edge are off
labelbottom=False) # labels along the bottom edge are off
#tmppath = 'path/{0}'.format(key);
##
sns.countplot(y='Ethnicity', data=value, orient='h', ax=axs[0,0]);
sns.despine(top=True, right=True, left=True, bottom=True,offset=True)
sns.countplot(y='Program Ratio', data=value,orient='v',ax=axs[1,0]);
sns.despine(offset=True)
sns.countplot(y='Site', data = value, ax=axs[0,1]);
sns.despine(offset=True)
sns.countplot(y='HOUSING_STATUS', data = value, ax = axs[1,1])
sns.despine(offset=True)
sns.countplot(y='Alt. Assessment', data = value, ax = axs[0,2])
sns.despine(offset=True)
pth = os.path.join(tmppath, '{0}'.format(key))
for p in axs.patches:
ax.text(p.get_x() + p.get_width()/2., p.get_width(), '%d' %
int(p.get_width()),
fontsize=12, color='red', ha='center', va='bottom')
#plt.tight_layout(pad=2.0, w_pad=1.0, h_pad=2.0);
plt.set_title('{0}'.format(key)+'Summary')
sns.despine()
axs[0,0].set_xticklabels('','Ethnicity')
axs[1,0].set_axis_labels('','Program Ratio')
axs[0,1].set_axis_labels('','Students by Site')
axs[1,1].set_axis_labels('','Housing Status')
axs[0,2].set_axis_labels('','Alt Assessment')
fig.tight_layout()
fig.subplots_adjust(top=0.88)
fig.suptitle('{0}'.format(key)+' Summary')
plt.suptitle('{0}'.format(key)+' Summary')
plt.savefig("path/{0}/{1}.pdf".format(key,key), bbox_inches = 'tight');
plt.clf()
plt.suptitle('{0} Summary'.format(key))
plt.savefig("path/{0}/{1}.pdf".format(key,key), bbox_inches = 'tight');
plt.clf()
I've checked out the links below ( and more):
Remove xticks in a matplotlib plot?
https://datascience.stackexchange.com/questions/48035/how-to-show-percentage-text-next-to-the-horizontal-bars-in-matplotlib
When I try the method from the second link. I end up with graphs like so
Without that the graph looks something like so
I want to get rid of the words count and the ticks on each subplot xaxis.
#ImportanceOfBeingErnest
Thanks, I followed your advice and this post.
Here is what is a compact version of what I ended up with
sns.set(style='white', palette=sns.palplot(sns.color_palette(ui)), font='sans-serif')
plt.figure(figsize=(20,20))
fig, axs2 = plt.subplots(2, 3, dpi =300);
fig.subplots_adjust(top=.8)
fig.subplots_adjust(hspace=1, wspace=1.5)
plt.tick_params(
axis='x', # changes apply to the x-axis
which='both', # both major and minor ticks are affected
bottom=False, # ticks along the bottom edge are off
top=False, # ticks along the top edge are off
labelbottom=False) # labels along the bottom edge are off
sns.countplot(y='column',palette = ui,order = df.value_counts().index, data=df,
orient='h', ax=axs2[0,0]);
axs2[0,0].set_xlabel('')
axs2[0,0].set_xticks([])
axs2[0,0].set_ylabel('')
axs2[0,0].set_title('label',size = 'small')
axs2[0,0].tick_params(axis='y', which='major', labelsize=8)
sns.despine(top=True, right=True, left=True, bottom=True,offset=True)
for p in axs2[0,0].patches:
axs2[0,0].annotate(int(p.get_width()),((p.get_x() + p.get_width()), p.get_y()), xytext=(15, -10), fontsize=8,color='#000000',textcoords='offset points'
,horizontalalignment='center')
fig.suptitle('{0}#{1}'.format(dur,key)+' Summary', va = 'top', ha= 'center') #size = 'small')
props = dict(boxstyle='square', facecolor='white', alpha=0.5)
fig.text(0.85, 0.925, dt.date.today().strftime("%b %d, %Y"), fontsize=9, verticalalignment='top', bbox=props)
fig.text(0.15, 0.925, 'No. of stuff'+ str(len(value['column'].unique())),fontsize = 10, va = 'top', ha = 'center')
plt.savefig("path/{0}/{1} # {2}.pdf".format(dur,dur,key), bbox_inches = 'tight');
plt.clf()
plt.close('all')
Excuse the black marks, didn't want to show the info

Categories