Related
EDIT: My question was closed because someone thought another question was responding to it (but it doesn't: Matplotlib different size subplots). To clarify what I want:
I would like to replicate something like what is done on this photo: having a 3rd dataset plotted on top of 2 subplots, with its y-axis displayed on the right.
I have 3 datasets spanning the same time interval (speed, position, precipitation). I would like to plot the speed and position in 2 horizontal subplots, and the precipitation spanning the 2 subplots.
For example in the code below, instead of having the twinx() only on the first subplot, I would like to have it overlap the two subplots (ie. on the right side have a y-axis with 0 at the bottom right of the 2nd subplot, and 20 at the top right of the 1st subplot).
I could I achieve that ?
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(2,1,figsize=(20,15), dpi = 600)
#plot 1:
x = np.array([0, 1, 2, 3])
y = np.array([3, 8, 1, 10])
ax[0].plot(x,y, label = 'speed')
plt.legend()
#plot 2:
x = np.array([0, 1, 2, 3])
y = np.array([3, 8, 1, 10])
ax[1].plot(x,y, label = 'position')
plt.legend()
#plot 3:
x = np.array([0, 1, 2, 3])
y = np.array([10, 0, 4, 20])
ax2=ax[0].twinx()
ax2.plot(x,y, label = 'precipitation')
plt.legend(loc='upper right')
plt.show()
Best way I found is not very elegant but it works:
# Prepare 2 subplots
fig, ax = plt.subplots(2,1,figsize=(20,15), dpi = 600)
#plot 1:
# Dummy values for plotting
x = np.array([0, 1, 2, 3])
y = np.array([3, 8, 1, 10])
ax[0].plot(x,y, label = 'speed')
# Prints the legend
plt.legend()
#plot 2:
x = np.array([0, 1, 2, 3])
y = np.array([3, 8, 1, 10])
ax[1].plot(x,y, label = 'position')
plt.legend()
#plot 3:
x = np.array([0, 1, 2, 3])
y = np.array([10, 0, 4, 20])
# Add manually a 3rd subplot that stands on top of the 2 others
ax2 = fig.add_subplot(111, label="new subplot", facecolor="none")
# Move the y-axis to the right otherwise it will overlap with the ones on the left
ax2.yaxis.set_label_position("right")
# "Erase" every tick and label of this 3rd plot
ax2.tick_params(left=False, right=True, labelleft=False, labelright=True,
bottom=False, labelbottom=False)
# This line merges the x axes of the 1st and 3rd plot, and indicates
# that the y-axis of the 3rd plot will be drawn on the entirety of the
# figure instead of just 1 subplot (because fig.add_subplot(111) makes it spread on the entirety of the figure)
ax[0].get_shared_x_axes().join(ax[0],ax2)
ax2.plot(x,y, label = 'precipitation')
# Prints the legend in the upper right corner
plt.legend(loc='upper right')
plt.show()
I am working with a plot that contains an uneven length of data. I created another group of females (green bars), and I would like to label these two female groups F1 and F2.
Here is my code:
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
labels = ['G1', 'G2', 'G3', 'G4']
labels2 = ['F1', 'F2']
male = [1, 3, 10, 20]
female = [2, 7]
female_2 = [3, 11]
x_male = np.arange(len(male))
x_female = np.arange(len(female))
offset_male = np.zeros(len(male))
offset_female = np.zeros(len(female))
shorter = min(len(x_male), len(x_female))
width = 0.25 # the width of the bars
offset_male[:shorter] = width/2
offset_female[:shorter] = width/2
fig, ax = plt.subplots()
rects1 = ax.bar(x_male - offset_male, male, width, label='male')
rects2 = ax.bar(x_female + offset_female, female, width, label='female')
rects3 = ax.bar(x_female + 3 * offset_female, female_2, width, label='female')
# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_xticks(x_male)
ax.set_xticklabels(labels)
ax.legend()
fig.tight_layout()
plt.show()
Do you have any idea how I can do it?
blend all ticks together
ax.set_xticks(list(x_male)+list(x_female + 3 * offset_female))
ax.set_xticklabels(labels+labels2)
I would like to change the label color of the first block (dark color one) in each column for a better visualization. Is there any way?
ps: I wouldn't want to change the current color palette. Just the color label of the first block!
Code below:
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
sns.set_style("white")
sns.set_context({"figure.figsize": (7, 5)})
df = pd.DataFrame(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]),
columns=['a', 'b', 'c'])
fig, ax = plt.subplots()
ax = df.plot.bar(stacked=True, cmap="cividis", alpha=1, edgecolor="black")
sns.despine(top=False, right=True, left=False, bottom=True)
#add text
for p in ax.patches:
left, bottom, width, height = p.get_bbox().bounds
if height > 0 :
ax.annotate("{0:.0f}".format(height), xy=(left+width/2, bottom+height/2), ha='center', va='center')
If you want to keep the same color map and change label color, you could specify color parameter in annotate function as follows.
ax.annotate("{0:.0f}".format(height), xy=(left+width/2, bottom+height/2), ha='center', va='center', color="white")
There are other configs such as font size etc.
The first block means 1, 4, 7 blocks in the array. Therefore, you could extract the first row of the data frame and check if height is one of the cell values using np.isin() like;
firstblocks = (df.iloc[:, 0])
for p in ax.patches:
left, bottom, width, height = p.get_bbox().bounds
if np.isin(p.get_height(), firstblocks):
ax.annotate("{0:.0f}".format(height), xy=(left + width / 2, bottom + height / 2), ha='center', va='center',
color="white", fontsize=12)
else:
ax.annotate("{0:.0f}".format(height), xy=(left + width / 2, bottom + height / 2), ha='center', va='center')
Hope this helps.
I cannot seem to make the plots work with labels correctly. The plots work in terms of generating three sub plots bar charts. But what I want to label each and every plot (3) with labels cr_lst. How do I ensure that I can label each of these bars with cr_lst and on each bar.
plt.figure(0)
width = 0.35 # the width of the bars
cr_lst = ['A', 'B', 'C', 'D']
A_lst = [1, 2, 3, 4]
B_lst = [2, 2, 6, 7]
A_lst = [8, 8, 6, 7]
ind = np.arange(len(A_lst)) # the x locations for the groups
f, axarr = plt.subplots(3, sharex=True)
axarr[0].set_title('Three plots\n')
axarr[0].set_ylabel('A')
axarr[1].set_ylabel('B')
axarr[2].set_ylabel('C')
axarr[0].set_ylim(ymin=0.001,ymax=max(A_lst)*1.10)
axarr[1].set_ylim(ymin=0.001,ymax=max(B_lst)*1.10)
axarr[2].set_ylim(ymin=0.001,ymax=max(B_lst)*1.10)
axarr[0].grid()
axarr[1].grid()
axarr[2].grid()
rects1 = axarr[0].bar(ind, A_lst, width, color='r', linewidth=1,alpha=0.8, label=cr_lst)
rects2 = axarr[1].bar(ind, B_lst, width, color='y', linewidth=1,alpha=0.8, label=cr_lst)
rects3 = axarr[2].bar(ind, C_lst, width, color='blue', linewidth=1, alpha=0.8, label=cr_lst)
plt.savefig("ByC.png")
I'd like to have the labels shown on the x-axis.
This will get you the labels under each bar on every axes:
width = 0.35 # the width of the bars
cr_lst = ['A', 'B', 'C', 'D']
x = range(len(cr_lst)) # the x locations for the groups
A_lst = [1, 2, 3, 4]
B_lst = [2, 2, 6, 7]
C_lst = [8, 8, 6, 7]
f, axarr = plt.subplots(3, sharex=False)
axarr[0].set_title('Three plots\n')
axarr[0].set_ylabel('A')
axarr[1].set_ylabel('B')
axarr[2].set_ylabel('C')
axarr[0].set_ylim(ymin=0.001,ymax=max(A_lst)*1.10)
axarr[1].set_ylim(ymin=0.001,ymax=max(B_lst)*1.10)
axarr[2].set_ylim(ymin=0.001,ymax=max(B_lst)*1.10)
axarr[0].grid()
axarr[1].grid()
axarr[2].grid()
rects1 = axarr[0].bar(x, A_lst, width, color='r', align='center', linewidth=1,alpha=0.8)
rects2 = axarr[1].bar(x, B_lst, width, color='y', align='center', linewidth=1,alpha=0.8)
rects3 = axarr[2].bar(x, C_lst, width, color='blue', align='center', linewidth=1, alpha=0.8)
for ax in axarr:
ax.set_xticks(x)
ax.set_xticklabels(cr_lst)
plt.savefig("ByC.png")
Note that share=False in plt.subplots. If you set it to True it hides all other labels but the lowest ax.
Also note the use of align='center' in .bar().
This yields:
I am plotting to different datasets into one graph with pylab.plot(), which works great. But one dataset has values between 0% an 25% and the other has values between 75% and 100%. I want to skip 30% to 70% on the y-axis to save some space. Do you have any suggestions how this might be work with pyplot?
EDIT:
For clearness I added the following graphic. I want to skip 30% to 60% on the y axis, so that the red line and the green line come closer together.
The solution is based on Space_C0wb0ys post.
fig = pylab.figure()
ax = fig.add_subplot(111)
ax.plot( range(1,10), camean - 25, 'ro-' )
ax.plot( range(1,10), oemean , 'go-' )
ax.plot( range(1,10), hlmean , 'bo-' )
ax.set_yticks(range(5, 60, 5))
ax.set_yticklabels(["5","10","15","20","25","30","...","65","70","75"])
ax.legend(('ClassificationAccuracy','One-Error','HammingLoss'),loc='upper right')
pylab.show()
This code creates the following graphic.
You could subtract 40 from the x-values for your second functions to make the range of x-values continuous. This would give you a range from 0% to 70%. Then you can make set the tics and labes of the x-axis as follows:
x_ticks = range(71, 0, 10)
a.set_xticks(x_ticks)
a.set_xticklabels([str(x) for x in [0, 10, 20, 30, 70, 80, 90, 100]])
Where a is the current axes. So basically, you plot your functions in the range from 0% to 70%, but label the axis with a gap.
To illustrate - the following script:
from numpy import arange
import matplotlib.pyplot as plt
x1 = arange(0, 26) # first function
y1 = x1**2
x2 = arange(75, 100) # second function
y2 = x2*4 + 10
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x1, y1)
ax.plot(x2 - 40, y2) # shift second function 40 to left
ax.set_xticks(range(0, 61, 5)) # set custom x-ticks
# set labels for x-ticks - labels have the gap we want
ax.set_xticklabels([str(x) for x in range(0, 26, 5) + range(70, 101, 5)])
plt.show()
Produces the following plot (note the x-labels):
The matplotlib documentation actually has an example of how to do this.
The basic idea is to break up the plotting into two subplots, putting the same graph on each plot, then change the axes for each one to only show the specific part, then make it look nicer.
So, let's apply this. Imagine this is your starting code:
import matplotlib.pyplot as plt
import random, math
# Generates data
i = range(10)
x = [math.floor(random.random() * 5) + 67 for i in range(10)]
y = [math.floor(random.random() * 5) + 22 for i in range(10)]
z = [math.floor(random.random() * 5) + 13 for i in range(10)]
# Original plot
fig, ax = plt.subplots()
ax.plot(i, x, 'ro-')
ax.plot(i, y, 'go-')
ax.plot(i, z, 'bo-')
plt.show()
And we went to make it so that x is shown split off from the rest.
First, we want to plot the same graph twice, one on top of the other. To do this, the plotting function needs to be generic. Now it should look something like this:
# Plotting function
def plot(ax):
ax.plot(i, x, 'ro-')
ax.plot(i, y, 'go-')
ax.plot(i, z, 'bo-')
# Draw the graph on two subplots
fig, (ax1, ax2) = plt.subplots(2, 1)
plot(ax1)
plot(ax2)
Now this seems worse, but we can change the range for each axis to focus on what we want. For now I'm just choosing easy ranges that I know will capture all the data, but I'll focus on making the axes equal later.
# Changes graph axes
ax1.set_ylim(65, 75) # Top graph
ax2.set_ylim(5, 30) # Bottom graph
This is getting closer to what we're looking for. Now we need to just make it look a little nicer:
# Hides the spines between the axes
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()
# Adds slanted lines to axes
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)
Finally, let's fix the axes. Here you need to do a little math and decide more on the layout. For instance, maybe we want to make the top graph smaller, since the bottom graph has two lines. To do that, we need to change the height ratios for the subplots, like so:
# Draw the graph on two subplots
# Bottom graph is twice the size of the top one
fig, (ax1, ax2) = plt.subplots(2, 1, gridspec_kw={'height_ratios': [1, 2]})
Finally, It's a good idea to make the axes match. In this case, because the bottom image is twice the size of the top one, we need to change the axes of one to reflect that. I've chosen to modify the top one in this time. The bottom graph covers a range of 25, which means the top one should cover a range of 12.5.
# Changes graph axes
ax1.set_ylim(60.5, 73) # Top graph
ax2.set_ylim(5, 30) # Bottom graph
This looks good enough to me. You can play around more with the axes or tick labels if you don't want the ticks to overlap with the broken lines.
Final code:
import matplotlib.pyplot as plt
import random, math
# Generates data
i = range(10)
x = [math.floor(random.random() * 5) + 67 for i in range(10)]
y = [math.floor(random.random() * 5) + 22 for i in range(10)]
z = [math.floor(random.random() * 5) + 13 for i in range(10)]
# Plotting function
def plot(ax):
ax.plot(i, x, 'ro-')
ax.plot(i, y, 'go-')
ax.plot(i, z, 'bo-')
# Draw the graph on two subplots
# Bottom graph is twice the size of the top one
fig, (ax1, ax2) = plt.subplots(2, 1, gridspec_kw={'height_ratios': [1, 2]})
plot(ax1)
plot(ax2)
# Changes graph axes
ax1.set_ylim(60.5, 73) # Top graph
ax2.set_ylim(5, 30) # Bottom graph
# Hides the spines between the axes
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()
# Adds slanted lines to axes
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.show()