I use the following code to plot the bar graph and need to present a legend in reverse order. How can I do it?
colorsArr = plt.cm.BuPu(np.linspace(0, 0.5, len(C2)))
p = numpy.empty(len(C2), dtype=object)
plt.figure(figsize=(11, 11))
prevBar = 0
for index in range(len(C2)):
plt.bar(ind, C2[index], width, bottom=prevBar, color=colorsArr[index],
label=C0[index])
prevBar = prevBar + C2[index]
# Positions of the x-axis ticks (center of the bars as bar labels)
tick_pos = [i + (width/2) for i in ind]
plt.ylabel('Home Category')
plt.title('Affinity - Retail Details(Home category)')
# Set the x ticks with names
plt.xticks(tick_pos, C1)
plt.yticks(np.arange(0, 70000, 3000))
plt.legend(title="Line", loc='upper left')
# Set a buffer around the edge
plt.xlim(-width*2, width*2)
plt.show()
You could call
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles[::-1], labels[::-1], title='Line', loc='upper left')
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(2016)
C0 = list('ABCDEF')
C2 = np.random.randint(20000, size=(len(C0), 3))
width = 1.0
C1 = ['foo', 'bar', 'baz']
ind = np.linspace(-width, width, len(C1))
colorsArr = plt.cm.BuPu(np.linspace(0, 0.5, len(C2)))
fig = plt.figure(figsize=(11,11))
ax = fig.add_subplot(1, 1, 1)
prevBar = 0
for height, color, label in zip(C2, colorsArr, C0):
h = ax.bar(ind, height, width, bottom=prevBar, color=color, label=label)
prevBar = prevBar + height
plt.ylabel('Home Category')
plt.title('Affinity - Retail Details(Home category)')
# positions of the x-axis ticks (center of the bars as bar labels)
tick_pos = [i+(width/2.0) for i in ind]
# set the x ticks with names
plt.xticks(tick_pos, C1)
plt.yticks(np.arange(0,70000,3000))
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles[::-1], labels[::-1], title='Line', loc='upper left')
plt.show()
Or you could use the simpler
handles, labels = ax.get_legend_handles_labels()
ax.legend(reversed(handles), reversed(labels), title='Line', loc='upper left')
Use a negative number for the legend vertical spacing, like this:
matplotlib.pyplot.stackplot(X, *revDataValues,
linewidth=1.0,
edgecolor='black')
matplotlib.pyplot.legend(revNames,
loc=6, bbox_to_anchor=(1.05, 0.5),
labelspacing=-2.5, frameon=False, # reverse legend
fontsize=9.0)
Stacked Area Chart with reversed legend
I've not tested this as I don't have your data, but this is based on the documentation here on controlling legend entries.
handles = []
for index in range(len(C2)):
h = plt.bar(ind, C2[index], width, bottom=prevBar, color=colorsArr[index], label=C0[index])
handles.append(h)
prevBar = prevBar + C2[index]
plt.legend(title="Line", loc='upper left', handles=handles[::-1])
The newest version of matplotlib (>=3.7) now provides this feature out of the box:
plt.legend(title="Line", loc='upper left', reverse=True)
The default is reverse=False (the previous behaviour)—setting it to reverse=True will now show the entries in the legend in the same order as the stacked bars. See Axes.legend documentation.
Related
I have the following code which produces a plot.
labels = sorted(set(df.index))
a = df.loc[df.user_id ==1234, 'count']
b = df.loc[df.user_id ==5678, 'count']
width = 0.5
x = np.arange(len(labels)) # the label locations
fig, ax = plt.subplots(figsize=(10,5))
rects1 = plt.plot_date(x, a, width, label='a')
rects2 = plt.plot_date(x, b, width, label='b', color = 'orange')
ax.set_xticks(x)
ax.set_xticklabels(labels, rotation=90)
ax.legend(['a','b'])
plt.ylabel('counts')
fig.tight_layout()
plt.gcf().autofmt_xdate()
plt.show()
The obvious problem is not being able to read the densely packed x labels. Without changing the plot, how can I label by week rather than day?
plt.xticks(range(0,len(label), 7), label[::7])
given a daily data this show ticks every 7 days
Based on the following example from matplotlib, I have made a function that plots two weekly time series as a side-by-side bar chart.
https://matplotlib.org/3.1.1/gallery/lines_bars_and_markers/barchart.html#sphx-glr-gallery-lines-bars-and-markers-barchart-py
My problem is that I set the xticks explicitly, and that creates messy xtick-labels. Is there a way to get matplotlib to choose xticks (position and labels) explicitly in such a plot?
I must say that I find the whole operation with specifycing the position of the bar using (x - width/2) quite inelegant to get side-by-side-bars - are there other options (other packages than matplotlib or other specifications in matplotlib) to avoid writing such explicit code?
Below is code and result. I'm seeking a solution that selects the number and placements of xticks and xticklabels that leaves it readable:
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
labels = ['W1-2020', 'W2-2020', 'W3-2020', 'W4-2020', 'W5-2020','W6-2020','W7-2020','W8-2020','W9-2020','W10-2020','W11-2020','W12-2020','W13-2020','W14-2020','W15-2020']
men_means = [20, 34, 30, 35, 27,18,23,29,27,29,38,28,17,28,23]
women_means = [25, 32, 34, 20, 25,27,18,23,29,27,29,38,19,20, 34]
x = np.arange(len(labels)) # the label locations
width = 0.35 # the width of the bars
fig, ax = plt.subplots()
rects1 = ax.bar(x - width/2, men_means, width, label='Men')
rects2 = ax.bar(x + width/2, women_means, width, label='Women')
# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(x)
ax.set_xticklabels(labels)
ax.legend()
def autolabel(rects):
"""Attach a text label above each bar in *rects*, displaying its height."""
for rect in rects:
height = rect.get_height()
ax.annotate('{}'.format(height),
xy=(rect.get_x() + rect.get_width() / 2, height),
xytext=(0, 3), # 3 points vertical offset
textcoords="offset points",
ha='center', va='bottom')
autolabel(rects1)
autolabel(rects2)
fig.tight_layout()
plt.show()
Solution 1 : Using Pandas
You can first create a pandas DataFrame and then plot a multiple bar chart directly. The formatting of labels on the x-axis is much neater
df = pd.DataFrame(
{'labels': labels,
'men_means': men_means,
'women_means': women_means
})
df.plot(x="labels", y=["men_means", "women_means"], kind="bar")
Solution 2: Using Seaborn (adapted from this answer)
import seaborn as sns
fig, ax = plt.subplots(figsize=(6, 4))
tidy = df.melt(id_vars='labels').rename(columns=str.title)
sns.barplot(x='Labels', y='Value', hue='Variable', data=tidy, ax=ax)
sns.despine(fig)
ax.tick_params(axis='x', labelrotation=90)
To hide only every n-th tick, you can do the following as adapted from this answer
n = 2
for label in ax.xaxis.get_ticklabels()[::n]:
label.set_visible(False)
To show every n-th label, you can use the following trick
fig.canvas.draw()
n = 4
labels = [item.get_text() if i%n == 0 else "" for i, item in enumerate(ax.get_xticklabels())]
ax.set_xticklabels(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
for a certain manuscript i need to position my label of the Graph exactly in the right or left top corner. The label needs a border with the same thickness as the spines of the graph. Currently i do it like this:
import matplotlib.pyplot as plt
import numpy as np
my_dpi=96
xposr_box=0.975
ypos_box=0.94
nrows=3
Mytext="label"
GLOBAL_LINEWIDTH=2
fig, axes = plt.subplots(nrows=nrows, sharex=True, sharey=True, figsize=
(380/my_dpi, 400/my_dpi), dpi=my_dpi)
fig.subplots_adjust(hspace=0.0001)
colors = ('k', 'r', 'b')
for ax, color in zip(axes, colors):
data = np.random.random(1) * np.random.random(10)
ax.plot(data, marker='o', linestyle='none', color=color)
for ax in ['top','bottom','left','right']:
for idata in range(0,nrows):
axes[idata].spines[ax].set_linewidth(GLOBAL_LINEWIDTH)
axes[0].text(xposr_box, ypos_box , Mytext, color='black',fontsize=8,
horizontalalignment='right',verticalalignment='top', transform=axes[0].transAxes,
bbox=dict(facecolor='white', edgecolor='black',linewidth=GLOBAL_LINEWIDTH))
plt.savefig("Label_test.png",format='png', dpi=600,transparent=True)
So i control the position of the box with the parameters:
xposr_box=0.975
ypos_box=0.94
If i change the width of my plot, the position of my box also changes, but it should always have the top and right ( or left) spine directly on top of the graphs spines:
import matplotlib.pyplot as plt
import numpy as np
my_dpi=96
xposr_box=0.975
ypos_box=0.94
nrows=3
Mytext="label"
GLOBAL_LINEWIDTH=2
fig, axes = plt.subplots(nrows=nrows, sharex=True, sharey=True, figsize=
(500/my_dpi, 400/my_dpi), dpi=my_dpi)
fig.subplots_adjust(hspace=0.0001)
colors = ('k', 'r', 'b')
for ax, color in zip(axes, colors):
data = np.random.random(1) * np.random.random(10)
ax.plot(data, marker='o', linestyle='none', color=color)
for ax in ['top','bottom','left','right']:
for idata in range(0,nrows):
axes[idata].spines[ax].set_linewidth(GLOBAL_LINEWIDTH)
axes[0].text(xposr_box, ypos_box , Mytext, color='black',fontsize=8,
horizontalalignment='right',verticalalignment='top',transform=axes[0].transAxes,
bbox=dict(facecolor='white', edgecolor='black',linewidth=GLOBAL_LINEWIDTH))
plt.savefig("Label_test.png",format='png', dpi=600,transparent=True)
This should also be the case if the image is narrower not wider as in this example.I would like to avoid doing this manually. Is there a way to always position it where it should? Independent on the width and height of the plot
and the amount of stacked Graphs?
The problem is that the position of a text element is relative to the text's extent, not to its surrounding box. While it would in principle be possible to calculate the border padding and position the text such that it sits at coordinates (1,1)-borderpadding, this is rather cumbersome since (1,1) is in axes coordinates and borderpadding in figure points.
There is however a nice alternative, using matplotlib.offsetbox.AnchoredText. This creates a textbox which can be placed easily relative the the axes, using the location parameters like a legend, e.g. loc="upper right". Using a zero padding around that text box directly places it on top of the axes spines.
from matplotlib.offsetbox import AnchoredText
txt = AnchoredText("text", loc="upper right", pad=0.4, borderpad=0, )
ax.add_artist(txt)
A complete example:
import matplotlib.pyplot as plt
from matplotlib.offsetbox import AnchoredText
import numpy as np
my_dpi=96
nrows=3
Mytext="label"
plt.rcParams["axes.linewidth"] = 2
plt.rcParams["patch.linewidth"] = 2
fig, axes = plt.subplots(nrows=nrows, sharex=True, sharey=True, figsize=
(500./my_dpi, 400./my_dpi), dpi=my_dpi)
fig.subplots_adjust(hspace=0.0001)
colors = ('k', 'r', 'b')
for ax, color in zip(axes, colors):
data = np.random.random(1) * np.random.random(10)
ax.plot(data, marker='o', linestyle='none', color=color)
txt = AnchoredText(Mytext, loc="upper right",
pad=0.4, borderpad=0, prop={"fontsize":8})
axes[0].add_artist(txt)
plt.show()
In principle you can align text to the Axes spines using Annotations and position them in Axes coordinates (x and y between 0 and 1) using xycoords = 'axes fraction. However, because you use a bbox that bbox will overlap with the spines.
Instead, you can use ax.text together a ScaledTransformation, which, if done right, also positions the text in Axes coordinates and shifts it by a fixed amount. If you provide a pad size to the bbox keyword, you know exactly how much the bbox will overlap with the spines in figure points (1 inch is 72 points), so that the shift is easily calculated. Here a little demonstration how to do this:
from matplotlib import pyplot as plt
import numpy as np
import matplotlib.transforms as transforms
GLOBAL_LINEWIDTH=2
pad = 10
fig,ax = plt.subplots()
x = np.linspace(0,1,20)
ax.plot(x,x**2, 'ro')
offset_bl = transforms.ScaledTranslation(
pad/72, pad/72, fig.dpi_scale_trans,
)
offset_br = transforms.ScaledTranslation(
-pad/72, pad/72, fig.dpi_scale_trans,
)
offset_tl = transforms.ScaledTranslation(
pad/72, -pad/72, fig.dpi_scale_trans,
)
offset_tr = transforms.ScaledTranslation(
-pad/72, -pad/72, fig.dpi_scale_trans,
)
for pos in ['top','bottom','left','right']:
ax.spines[pos].set_linewidth(GLOBAL_LINEWIDTH)
ax.text(
0,0, 'bottom left',
fontsize = 16, fontweight='bold', va='bottom', ha='left',
bbox=dict(
facecolor = 'white', edgecolor='black', lw = GLOBAL_LINEWIDTH,
pad = pad
),
transform=ax.transAxes + offset_bl,
)
ax.text(
1,0, 'bottom right',
fontsize = 16, fontweight='bold', va='bottom', ha='right',
bbox=dict(
facecolor = 'white', edgecolor='black', lw = GLOBAL_LINEWIDTH,
pad = pad
),
transform=ax.transAxes + offset_br,
)
ax.text(
0,1, 'top left',
fontsize = 16, fontweight='bold', va='top', ha='left',
bbox=dict(
facecolor = 'white', edgecolor='black', lw = GLOBAL_LINEWIDTH,
pad = pad
),
transform=ax.transAxes + offset_tl,
)
ax.text(
1,1, 'top right',
fontsize = 16, fontweight='bold', va='top', ha='right',
bbox=dict(
facecolor = 'white', edgecolor='black', lw = GLOBAL_LINEWIDTH,
pad = pad
),
transform=ax.transAxes + offset_tr,
)
plt.show()
And here is the result:
How do we draw an average line (horizontal) for a histogram in using matplotlib?
Right now, I'm able to draw the histogram without any issues.
Here is the code I'm using:
## necessary variables
ind = np.arange(N) # the x locations for the groups
width = 0.2 # the width of the bars
plt.tick_params(axis='both', which='major', labelsize=30)
plt.tick_params(axis='both', which='minor', labelsize=30)
ax2 = ax.twinx()
## the bars
rects1 = ax.bar(ind, PAAE1, width,
color='0.2',
error_kw=dict(elinewidth=2,ecolor='red'),
label='PAAE1')
rects2 = ax.bar(ind+width, PAAE2, width,
color='0.3',
error_kw=dict(elinewidth=2,ecolor='black'),
label='PAAE2')
rects3 = ax2.bar(ind+width+width, AAE1, width,
color='0.4',
error_kw=dict(elinewidth=2,ecolor='red'),
label='AAE1')
rects4 = ax2.bar(ind+3*width, AAE2, width,
color='0.5',
error_kw=dict(elinewidth=2,ecolor='black'),
label='AAE3')
maxi = max(dataset[2])
maxi1 = max(dataset[4])
f_max = max(maxi, maxi1)
lns = [rects1,rects2,rects3,rects4]
labs = [l.get_label() for l in lns]
ax.legend(lns, labs, loc='upper center', ncol=4)
# axes and labels
ax.set_xlim(-width,len(ind)+width)
ax.set_ylim(0, 100)
ax.set_ylabel('PAAE', fontsize=25)
ax2.set_ylim(0, f_max+500)
ax2.set_ylabel('AAE (mW)', fontsize=25)
xTickMarks = dataset[0]
ax.set_xticks(ind+width)
xtickNames = ax.set_xticklabels(xTickMarks)
plt.setp(xtickNames, rotation=90, fontsize=25)
I want to plot the average line for PAAE 1, 2 and AAE 1, 2.
What should I be using to plot the average line?
If you'd like a vertical line to denote the mean use axvline(x_value). This will place a vertical line that always spans the full (or specified fraction of) y-axis. There's also axhline for horizontal lines.
In other works, you might have something like this:
ax.axvline(data1.mean(), color='blue', linewidth=2)
ax.axvline(data2.mean(), color='green', linewidth=2)
As a more complete, but unnecessarily complex example (most of this is nicely annotating the means with curved arrows):
import numpy as np
import matplotlib.pyplot as plt
data1 = np.random.normal(0, 1, 1000)
data2 = np.random.normal(-2, 1.5, 1000)
fig, ax = plt.subplots()
bins = np.linspace(-10, 5, 50)
ax.hist(data1, bins=bins, color='blue', label='Dataset 1',
alpha=0.5, histtype='stepfilled')
ax.hist(data2, bins=bins, color='green', label='Dataset 2',
alpha=0.5, histtype='stepfilled')
ax.axvline(data1.mean(), color='blue', linewidth=2)
ax.axvline(data2.mean(), color='green', linewidth=2)
# Add arrows annotating the means:
for dat, xoff in zip([data1, data2], [15, -15]):
x0 = dat.mean()
align = 'left' if xoff > 0 else 'right'
ax.annotate('Mean: {:0.2f}'.format(x0), xy=(x0, 1), xytext=(xoff, 15),
xycoords=('data', 'axes fraction'), textcoords='offset points',
horizontalalignment=align, verticalalignment='center',
arrowprops=dict(arrowstyle='-|>', fc='black', shrinkA=0, shrinkB=0,
connectionstyle='angle,angleA=0,angleB=90,rad=10'),
)
ax.legend(loc='upper left')
ax.margins(0.05)
plt.show()