Adjust height for only one bar of my plot? - python

I have this dataframe
data = {'reference':[1, 2, 3, 4, 5, 6, 7, 8, 'Total'],
'label_1':[58.3,75.0,88.0,81.1,60.0,72.0,50.0,85.7,73.8],
'label_2':[41.7, 25.0, 9.3,17.0,40.0,27.3,40.9,14.3,24.5],
'label_3':[0.0,0.0,4.7,1.9,0.0,0.8,9.1,0.0,1.7]}
data = pd.DataFrame(data).set_index('reference')
data
I have made an horizontal barplot who look like this
fig, ax = plt.subplots()
data.plot(kind='barh', stacked=True, width=0.70, ax=ax, figsize= (15, 15))
ax.legend(["label_1",
"label_2",
"label_3"], loc='upper left', bbox_to_anchor=(1, 1));
ax.yaxis.grid(True)
ax.set_axisbelow(True)
plt.xticks(rotation=0)
fmt = '%.0f%%'
xticks = mtick.FormatStrFormatter(fmt)
ax.xaxis.set_major_formatter(xticks)
for c in ax.containers:
ax.bar_label(c, label_type='center', fmt='%.0f%%')
ax.set_yticklabels( ('name_1', 'name_2','name_3', 'name_4', 'name_5', 'name_6', 'name_7', 'name_8', 'TOTAL') )
plt.show()
I want to space up the 'Total' bar, I don't have any idea how to adjust the height for only one specific bar on my plot. Any help is appreciated, thx everyone!

Related

How to remove area under the curve in matplotlib

I have the following code for a Matplotlib plot:
import pandas as pd
from matplotlib import pyplot as plt
columns = ['Price']
price_values = [[4.2],
[4.1],
[4],
[3.8],
[3.9],
[4.2],
[4.5],
[4.8],
[5.2],
[5.2],
[5.2],
[5.6],
[5.2],
[5.1],
[5.3],
[6],
[6.2],
[6.3],
[6.2],
[6],
[5.5] ,
[5.2],
[4.8],
[4.6]]
price_data = pd.DataFrame(price_values, index=range(0, 24), columns=columns)
fig = plt.figure(linewidth=1, figsize=(9, 5))
ax=plt.gca()
for column,color in zip(price_data.columns,['gold']):
ax.fill_between(
x=price_data.index,
y1=price_data[column],
y2=0,
label=column,
color=color,
alpha=.5,
step='post',
linewidth=5,
)
ax.set_facecolor("white")
ax.set_xlabel("Time of day", fontsize = 14, labelpad=8)
ax.set_ylabel("Price [Cent/kWh]", fontsize = 14,labelpad=8)
ax.set_xlim(0, 23)
ax.set_ylim(0, 8)
plt.xticks(price_data.index, labels=[f'{h:02d}:00' for h in price_data.index], rotation=90)
plt.tight_layout()
hours = list(range(25))
labels = [f'{h:02d}:00' for h in hours]
ax.tick_params(axis='both', which='major', labelsize=14)
ax.legend(loc='center left', bbox_to_anchor=(0.15, 1.07), fontsize = 14, ncol=3)
plt.savefig('Diagramm.png', edgecolor='black', dpi=400, bbox_inches='tight')
plt.show()
Now I would like to remove the area under the curve, sucht that I can only see the curve. I tried to use
plt.bar(fill=False)
but I get the error "TypeError: bar() missing 2 required positional arguments: 'x' and 'height'". Any suggestions how I can do that
Using fill_between and later remove the area under the curve seems like a pretty convoluted way to plot your data. But you could just set y2=price_data[column]:
price_data = pd.DataFrame(price_values, index=range(0, 24), columns=columns)
fig = plt.figure(linewidth=1, figsize=(9, 5))
ax=plt.gca()
for column,color in zip(price_data.columns,['gold']):
ax.fill_between(
x=price_data.index,
y1=price_data[column],
y2=price_data[column],
label=column,
color=color,
alpha=.5,
step='post',
linewidth=5,
)
ax.set_facecolor("white")
ax.set_xlabel("Time of day", fontsize = 14, labelpad=8)
ax.set_ylabel("Price [Cent/kWh]", fontsize = 14,labelpad=8)
ax.set_xlim(0, 23)
ax.set_ylim(0, 8)
plt.xticks(price_data.index, labels=[f'{h:02d}:00' for h in price_data.index], rotation=90)
plt.tight_layout()
hours = list(range(25))
labels = [f'{h:02d}:00' for h in hours]
ax.tick_params(axis='both', which='major', labelsize=14)
ax.legend(loc='center left', bbox_to_anchor=(0.15, 1.07), fontsize = 14, ncol=3)
plt.savefig('Diagramm.png', edgecolor='black', dpi=400, bbox_inches='tight')
plt.show()
Output:
Edit: #JohanC rightfully noted that the last value barely appears on the plot. One way to avoid this would be to replace your loop with the following:
price_data.plot(ax=ax, color="gold", drawstyle="steps-mid", linewidth=2)
Note that your solution is missing the last price value, the one between 23 and 24 h. You'll need to repeat the last value for this to work. To draw a step plot, the easiest way is ax.step.
The following example code changes the values for the first and the last value to make them stand out more.
from matplotlib import pyplot as plt
import pandas as pd
columns = ['Price']
price_values = [[1.2], [4.1], [4], [3.8], [3.9], [4.2], [4.5], [4.8], [5.2], [5.2], [5.2], [5.6], [5.2], [5.1], [5.3], [6], [6.2], [6.3], [6.2], [6], [5.5], [5.2], [4.8], [1.6]]
price_data = pd.DataFrame(price_values, index=range(0, 24), columns=columns)
fig, ax = plt.subplots(figsize=(9, 5))
for column, color in zip(price_data.columns, ['gold']):
ax.step(x=range(len(price_data) + 1), y=list(price_data[column]) + list(price_data[column][-1:]),
where='post', color=color, linewidth=5, label=column)
ax.set_xlabel("Time of day", fontsize=14, labelpad=8)
ax.set_ylabel("Price [Cent/kWh]", fontsize=14, labelpad=8)
ax.set_xlim(0, 24)
ax.set_ylim(0, 8)
xs = range(len(price_data) + 1)
ax.set_xticks(xs, labels=[f'{h:02d}:00' for h in xs], rotation=90)
ax.tick_params(axis='both', which='major', labelsize=14)
ax.legend(loc='lower left', bbox_to_anchor=(0.15, 1.01), fontsize=14, ncol=3)
plt.tight_layout()
plt.savefig('Diagramm.png', edgecolor='black', dpi=400, bbox_inches='tight')
plt.show()
Alternatively, you could use Seaborn's histplot, which has a step option (element='step', fill=False), but that works easiest if you'd let seaborn do the counting for the histogram. You could use sns.histplot's weights= parameter to fill in the values, e.g.
sns.histplot(x=price_data.index, weights=price_data[column].values, bins=len(price_data), binrange=(0, 24),
element='step', fill=False, color=color, linewidth=5, label=column, ax=ax)

x-labels rendered twice on matplotlib plot

I have a plot that uses 9 labels on the x-axis. However, because I have split up the plot into two axes, it seems that it requires 18 labels (hence the added list of empty strings) instead for some reason.
This seems to make the x-labels be rendered twice, making them seem to have a bold typeface. Image of the problem is attached.
And here is the current code I'm using. I apologize for the quality of the code. I am new to matplotlib.
benchmark_data = all_benchmark_data[loader.pingpongKey]
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(9,7), dpi=80)
fig.subplots_adjust(hspace=0.05)
ax1.boxplot(benchmark_data.values())
ax2.boxplot(benchmark_data.values())
ax1.set_ylim(380, 650)
ax2.set_ylim(110, 180)
# hide the spines between ax and ax2
ax1.spines.bottom.set_visible(False)
ax2.spines.top.set_visible(False)
ax1.xaxis.tick_top()
ax1.tick_params(labeltop=False) # don't put tick labels at the top
ax2.xaxis.tick_bottom()
ax1.tick_params(axis='both', labelsize=10)
ax2.tick_params(axis='both', labelsize=10)
xlabels = ['', '', '', '', '', '', '', '', ''] + (list(benchmark_data.keys()))
ax1.set_xticklabels(xlabels)
ax1.set_ylabel('Time (ms)', fontsize=10)
ax1.yaxis.set_label_coords(-0.06,0)
#ax2.set_ylabel('Time (ms)', fontsize=10)
plt.xticks(fontsize=10, rotation=45)
ax1.yaxis.set_major_locator(ticker.MaxNLocator(nbins=5, min_n_ticks=5))
ax2.yaxis.set_major_locator(ticker.MaxNLocator(nbins=5, min_n_ticks=5))
d = .5 # proportion of vertical to horizontal extent of the slanted line
kwargs = dict(marker=[(-1, -d), (1, d)], markersize=12,
linestyle="none", color='k', mec='k', mew=1, clip_on=False)
ax1.plot([0, 1], [0, 0], transform=ax1.transAxes, **kwargs)
ax2.plot([0, 1], [1, 1], transform=ax2.transAxes, **kwargs)
plt.tight_layout()
plt.savefig('plots/boxplots/' + loader.pingpongKey + '-boxplot.png',
bbox_inches='tight')
Because you are using sharex=True, the second time you plot the boxplot you will create another 9 ticks that are added to the axis (which is in common between ax1 and ax2). The solution in your case is to turn off sharex (the axis will be aligned anyway) and set the xticklabels on ax2:
# No sharex.
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9,7), dpi=80)
# ...
# Set ticks for ax2 instead of ax1 and only the 9 labels are needed.
ax2.set_xticklabels(list(benchmark_data.keys()))

Color bar limits matplotlib

I'm trying to create a plot with 4 hist2d subplots and one color bar.
The thing is that each subplot can have different ranges of z values, so the color bar is not uniform.
I want to set the color bar to a pre-defined range.
here is the code I'm using:
def multiple_med_plot_test(file):
extent = [-8, 37, 28, 46]
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(26, 11), constrained_layout=True,
subplot_kw={'projection': ccrs.PlateCarree()})
ax0 = axes[0][0]
ax1 = axes[0][1]
ax2 = axes[1][0]
ax3 = axes[1][1]
axes_dict = {'Dec': ax0, 'Aug': ax1, 'Sep': ax2, 'Sum': ax3}
for month in axes_dict.keys():
ax = axes_dict[month]
ax.add_feature(cfeature.LAND, edgecolor='k', zorder=50)
ax.set_extent(extent)
gl = ax.gridlines(draw_labels=True, zorder=100, color='grey', linestyle='--')
gl.top_labels = False
gl.right_labels = False
gl.xlabel_style = {'size': 16}
gl.ylabel_style = {'size': 16}
if ax in [ax1, ax3]:
gl.left_labels = False
ax.set_title(month, fontsize=18, color='darkred')
if month != 'Sum':
hist0 = ax.hist2d(file.Long, file.Lat, range=[(-8, 37), (28, 46)], bins=(500, 200))
elif month == 'Sum':
hist1 = ax.hist2d(file.Long, file.Lat, range=[(-8, 37), (28, 46)], bins=(500, 200))
fig.suptitle('Lightning Density per Month', fontsize=22)
cbar = fig.colorbar(hist1[3], ax=axes, shrink=0.95)
cbar.set_label('# of lightnings', fontsize=20, rotation=-90, labelpad=30)
cbar.ax.tick_params(labelsize=16)
# plt.savefig('D:/Lightning Data/Yearly_Summary', dpi=100)
plt.show()
In previous versions of the code I used plt.clim and that was awesome, but the way my code is right now doesn't let me do it.
I would like to get some help on this!
If you want a linear scale, set vmin and vmax parameters. For log-like scale or similar, use norm. See hist2d documentation.

How to add multiple data labels in a bar chart

I have two grouped bar charts of value changes between cases for two systems among 3 groups as below. Here I was able to add data labels to the bars using the code below (figure produced attached)
What I want to do is on top (or bottom for the negative change in value cases), add an extra data label that captures the % of the value changes as shown in the second figure with the 33% in red (I edited it in by hands). How do I achieve that from this code? Thank you in advance.
import matplotlib.pyplot as plt
import numpy as np
value_case0_system1 = np.array([30, 20, 40])
value_case1_system1 = np.array([20, 25, 50])
value_case2_system1 = np.array([10, 35, 45])
value_case1_system2 = np.array([60, 50, 40])
value_case2_system2 = np.array([50, 40, 55])
change_case0_to_case1_system1 = np.subtract(value_case1_system1,value_case0_system1)
change_case1_to_case2_system1 = np.subtract(value_case2_system1,value_case1_system1)
change_case1_to_case2_system2 = np.subtract(value_case2_system2,value_case1_system2)
fig, (ax0, ax1) = plt.subplots(nrows=1, ncols=2, sharey=True, figsize=(18,10))
labels = ['Group 1', 'Group 2', 'Group 3']
x = np.arange(len(labels))
ax0.set_ylabel('Change in Values', va='center', rotation='vertical',
fontsize=17, fontweight='bold')
width = 0.28
ax0.set_title('System 1', fontsize=17, fontweight='bold')
ax0.axhline(y=0, color='black', ls=':', lw=2)
ax0.set_xticks(x)
ax0.set_xticklabels(labels,fontsize=15)
rects1 = ax0.bar(x-width/2, change_case0_to_case1_system1, width, label='Case 0 to Case 1',
color='#292929', edgecolor='black', linewidth=1)
rects2 = ax0.bar(x+width/2, change_case1_to_case2_system1, width, label='Case 1 to Case 2',
color='#7f6d5f', edgecolor='black', linewidth=1)
ax0.bar_label(rects1, padding=3, fontsize=11)
ax0.bar_label(rects2, padding=3, fontsize=11)
leg = ax0.legend(loc="upper left", bbox_to_anchor=[0, 1],
ncol=1, fancybox=True)
ax0.legend(fontsize=15)
ax1.set_title('System 2', fontsize=17, fontweight='bold')
ax1.axhline(y=0, color='black', ls=':', lw=2)
ax1.set_xticks(x)
ax1.set_xticklabels(labels,fontsize=15)
rects3 = ax1.bar(x, change_case1_to_case2_system2, width, label='Case 1 to Case 2',
color='#7f6d5f', edgecolor='black', linewidth=1)
ax1.legend(shadow=True, fancybox=True)
ax1.bar_label(rects3, padding=3, fontsize=11)
leg = ax1.legend(loc="upper left", bbox_to_anchor=[0, 1],
ncol=1, fancybox=True)
ax1.legend(fontsize=15)
plt.tight_layout()
plt.show()
The code for the extra plot formatting has been left out, because it's not relevant for the answer. It can be added back, as per your requirements.
Each .bar_label colors the label globally, so unlike this answer, a second .bar_label needs to be added for the percent change, with a different color and padding
For each case-to-case, calculate the percent change, and set the string format in a list comprehension.
Set the list of string formatted calculations to the labels parameter in .bar_label.
Given the code in the OP, 6 lines of code need to be added, 3 for creating the list of labels, and 3 for adding the labels to the plot.
Additional resources:
matplotlib: Bar Label Demo
Adding value labels on a matplotlib bar chart
Tested in python 3.8.11, matplotlib 3.4.3
change_case0_to_case1_system1 = np.subtract(value_case1_system1, value_case0_system1)
# add list of string formatted percent change calculation
per_change_case0_to_case1_system1 = [f'({v}%)' for v in (change_case0_to_case1_system1 / value_case0_system1).round(2)*100]
change_case1_to_case2_system1 = np.subtract(value_case2_system1, value_case1_system1)
# add list of string formatted percent change calculation
per_change_case1_to_case2_system1 = [f'({v}%)' for v in (change_case1_to_case2_system1 / value_case1_system1).round(2)*100]
change_case1_to_case2_system2 = np.subtract(value_case2_system2, value_case1_system2)
# add list of string formatted percent change calculation
per_case1_to_case2_system2 = [f'({v}%)' for v in (change_case1_to_case2_system2 / value_case1_system2).round(2)*100]
fig, (ax0, ax1) = plt.subplots(nrows=1, ncols=2, sharey=True, figsize=(18,10))
labels = ['Group 1', 'Group 2', 'Group 3']
x = np.arange(len(labels))
width = 0.28
ax0.set_xticks(x)
ax0.set_xticklabels(labels, fontsize=15)
rects1 = ax0.bar(x-width/2, change_case0_to_case1_system1, width, label='Case 0 to Case 1', color='#292929', edgecolor='black', linewidth=1)
rects2 = ax0.bar(x+width/2, change_case1_to_case2_system1, width, label='Case 1 to Case 2', color='#7f6d5f', edgecolor='black', linewidth=1)
ax0.bar_label(rects1, padding=3, fontsize=11)
# add a second annotation with the string formatted labels
ax0.bar_label(rects1, labels=per_change_case0_to_case1_system1, padding=15, fontsize=11, color='red')
ax0.bar_label(rects2, padding=3, fontsize=11)
# add a second annotation with the string formatted labels
ax0.bar_label(rects2, labels=per_change_case1_to_case2_system1, padding=15, fontsize=11, color='red')
rects3 = ax1.bar(x, change_case1_to_case2_system2, width, label='Case 1 to Case 2', color='#7f6d5f', edgecolor='black', linewidth=1)
ax1.set_xticks(x)
ax1.set_xticklabels(labels,fontsize=15)
ax1.bar_label(rects3, padding=3, fontsize=11)
# add a second annotation with the string formatted labels
ax1.bar_label(rects3, labels=per_case1_to_case2_system2, padding=15, fontsize=11, color='red')
plt.tight_layout()
plt.show()

Change the ticklabel orientation and legend position of plot

I am plotting a bar graph by reading data from a CSV using pandas in Python. I read a CSV into a DataFrame and plot them using matplotlib.
Here is how my CSV looks like:
SegmentName Sample1 Sample2 Sample3
Loop1 100 100 100
Loop2 100 100 100
res = DataFrame(pd.read_csv("results.csv", index_col="SegmentName"))
I plot and set the legend to be outside.
plt.figure()
ax = res.plot(kind='bar')
ax.legend(loc='center left', bbox_to_anchor=(1, 0.5))
plt.savefig("results.jpg")
However, the x-axis ticklabels are orientated vertically and hence I can't read the text. Also my legend outside is cut off.
Can I change the orientation of the ticklabels to be horizontal, and then adjust the entire figure so that the legend is visible?
Try using the 'rotation' keyword when you set the label. E.g.:
plt.xlabel('hi',rotation=90)
Or if you need to rotate the tick labels, try:
plt.xticks(rotation=90)
As for the positioning of the legend etc., it is probably worth taking a look at the tight layout guide
You should use the matplotlib API and call ax.set_xticklabels(res.index, rotation=0) like so:
index = Index(['loop1', 'loop2'], name='segment_name')
data = [[100] * 3, [100] * 3]
columns = ['sample1', 'sample2', 'sample3']
df = DataFrame(data, index=index, columns=columns)
fig, ax = subplots()
df.plot(ax=ax, kind='bar', legend=False)
ax.set_xticklabels(df.index, rotation=0)
ax.legend(loc='center left', bbox_to_anchor=(1, 0.5))
fig.savefig('results.png', bbox_inches='tight')
to get the resulting plot:
Alternatively you can call fig.autofmt_xdate() for a nice tilted effect, which you can of course tinker with with the above (and more general) ax.set_xticklabels():
fig, ax = subplots()
df.plot(ax=ax, kind='bar', legend=False)
fig.autofmt_xdate()
ax.legend(loc='center left', bbox_to_anchor=(1, 0.5))
fig.savefig('results-tilted.png', bbox_inches='tight')
For the rotation of the labels, you can simply tell pandas to rotate it for you by giving the number of degrees to the rot argument.
The legends being cut off is answered elsewhere as well, like here:
df = pd.DataFrame.from_items([('A', [1, 2, 3]), ('B', [4, 5, 6])],
orient='index', columns=['one', 'two', 'three'])
ax = df.plot(kind='bar', rot=90)
lgd = ax.legend(loc='center left', bbox_to_anchor=(1, 0.5))
fig.savefig("results.jpg", bbox_extra_artists=(lgd,), bbox_inches='tight')

Categories

Resources