Related
I am plotting separate figures for each attribute and label for each data sample. Here is the illustration:
As illustrated in the the last subplot (Label), my data contains seven classes (numerically) (0 to 6). I'd like to visualize these classes using a different fancy colors and a legend. Please note that I just want colors for last subplot. How should I do that?
Here is the code of above plot:
x, y = test_data["x"], test_data["y"]
# determine the total number of plots
n, off = x.shape[1] + 1, 0
plt.rcParams["figure.figsize"] = (40, 15)
# plot all the attributes
for i in range(6):
plt.subplot(n, 1, off + 1)
plt.plot(x[:, off])
plt.title('Attribute:' + str(i), y=0, loc='left')
off += 1
# plot Labels
plt.subplot(n, 1, n)
plt.plot(y)
plt.title('Label', y=0, loc='left')
plt.savefig(save_file_name, bbox_inches="tight")
plt.close()
First, just to set up a similar dataset:
import matplotlib.pyplot as plt
import numpy as np
x = np.random.random((100,6))
y = np.random.randint(0, 6, (100))
fig, axs = plt.subplots(6, figsize=(40,15))
We could use plt.scatter() to give individual points different marker styles:
for i in range(x.shape[-1]):
axs[i].scatter(range(x.shape[0]), x[:,i], c=y)
Or we could mask the arrays we're plotting:
for i in range(x.shape[-1]):
for j in np.unique(y):
axs[i].plot(np.ma.masked_where(y!=j, x[:,i]), 'o')
Either way we get the same results:
Edit: Ah you've edited your question! You can do exactly the same thing for your last plot only, just modify my code above to take it out of the loop of subplots :)
As suggested, we imitate the matplotlib step function by creating a LineCollection to color the different line segments:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.collections import LineCollection
from matplotlib.patches import Patch
#random data generation
np.random.seed(12345)
number_of_categories=4
y = np.concatenate([np.repeat(np.random.randint(0, number_of_categories), np.random.randint(1, 30)) for _ in range(20)])
#check the results with less points
#y = y[:10]
x = y[None] * np.linspace(1, 5, 3)[:, None]
x += 2 * np.random.random(x.shape) - 1
#your initial plot
num_plots = x.shape[0] + 1
fig, axes = plt.subplots(num_plots, 1, sharex=True, figsize=(10, 8))
for i, ax in enumerate(axes.flat[:-1]):
ax.plot(x[i,:])
#first we create the matplotlib step function with x-values as their midpoint
axes.flat[-1].step(np.arange(y.size), y, where="mid", color="lightgrey", zorder=-1)
#then we plot colored segments with shifted index simulating the step function
shifted_x = np.arange(y.size+1)-0.5
#and identify the step indexes
idx_steps, = np.nonzero(np.diff(y, prepend=np.inf, append=np.inf))
#create collection of plateau segments
colored_segments = np.zeros((idx_steps.size-1, 2, 2))
colored_segments[:, :, 0] = np.vstack((shifted_x[idx_steps[:-1]], shifted_x[idx_steps[1:]])).T
colored_segments[:, :, 1] = np.repeat(y[idx_steps[:-1]], 2).reshape(-1, 2)
#generate discrete color list
n_levels, idx_levels = np.unique(y[idx_steps[:-1]], return_inverse=True)
colorarr = np.asarray(plt.cm.tab10.colors[:n_levels.size])
#and plot the colored segments
lc_cs = LineCollection(colored_segments, colors=colorarr[idx_levels, :], lw=10)
lines_cs = axes.flat[-1].add_collection(lc_cs)
#scaling and legend generation
axes.flat[-1].set_ylim(n_levels.min()-0.5, n_levels.max()+0.5)
axes.flat[-1].legend([Patch(color=colorarr[i, :]) for i, _ in enumerate(n_levels)],
[f"cat {i}" for i in n_levels],
loc="upper center", bbox_to_anchor=(0.5, -0.15),
ncol=n_levels.size)
plt.show()
Sample output:
Alternatively, you can use broken barh plots or color this axis or even all axes using axvspan.
I am plotting a convergence graph and to show deviations from the mean I am using connected boxplots:
For some reason Matplotlib forces ticks for each boxplot and I cannot seem to get them removed. My code for the current plot looks something like this:
label = ["" for i in range(160)]
no_labels = int(np.floor(len(label)/20))
for i in range(no_labels):
label[i*20] = str(i*no_samples/no_labels)
# Weird behaviour for the last label so adding it manually
label[-1] = no_samples
fig = plt.figure(figsize=(10,5))
ax = fig.add_axes([0,0,1,1])
ax.set_xlabel("Samples", labelpad=10)
ax.set_ylabel("Error (MSE)", labelpad=10)
ax.set_ylim(0, 0.11)
ax.boxplot(data, flierprops=flyprops, showcaps=False,
boxprops=colorprops, whiskerprops={'color' : 'tab:blue'},
labels=label, patch_artist=True)
I have tried multiple ways of manipulating axis ticks which are available in MPL.
1) Trying to let MPL do the work:
ax.xaxis.set_major_locator(MultipleLocator(20))
2) Trying to set ticks manually: ax.set_xticks([list_of_ticks])
3) Tried a workaround
ax.xaxis.set_minor_locator(MultipleLocator(20))
# Removing major ticks, setting minor ticks
ax.xaxis.set_tick_params(which='major', size=0, width=2, direction='in')
ax.yaxis.set_tick_params(which='major', size=5, width=2, direction='in')
None of these seemed to work and I am unsure why. I think it may have something to do with my label variable but if I don't include it in this way MPL with include an axis lable for every entry which is a mess.
How can I set axis ticks once every 1000 entries in a connected boxplots figure?`
Edit: The input data is a numpy array of shape (15, 160) s.t. there are 160 boxplots plotted of 15 samples each. Example data for 5 boxplots of 3 samples each would look like:
np.random.rand(3,5)
>>> array([[0.05942481, 0.03408175, 0.84021109, 0.27531937, 0.62428798],
[0.24658313, 0.77910387, 0.2161348 , 0.39101172, 0.14038211],
[0.40694432, 0.22979738, 0.87056873, 0.788295 , 0.29337562]])
The main issue seems to be that the ticks need to be updated after drawing the main plot, never before.
(Having ax = fig.add_axes([0, 0, 1, 1]) is also quite unusual to work with. The standard way is fig, ax = plt.subplots(figsize=(10, 5)) which lets matplotlib a bit of flexibility for the whitespace around the plot.)
The code of the question has some missing variables and data, but the following toy example should create something similar:
import numpy as np
import matplotlib.pyplot as plt
no_samples = 8000
x = np.linspace(0, no_samples, 160)
no_labels = int(np.floor(len(x) / 20))
label = [f'{i * no_samples / no_labels:.0f}' for i in range(no_labels+1)]
fig = plt.figure(figsize=(10, 5))
ax = fig.add_axes([0.1, 0.1, 0.85, 0.85])
N = 100
data = np.random.normal(np.tile(100 / (x+1000), N), 0.001).reshape(N, -1)
flyprops = {'markersize':0.01}
colorprops = None
ax.boxplot(data, flierprops=flyprops, showcaps=False,
boxprops=colorprops, whiskerprops={'color': 'tab:blue'},
patch_artist=True)
ax.set_xlabel("Samples", labelpad=10)
ax.set_ylabel("Error (MSE)", labelpad=10)
ax.set_ylim(0, 0.11)
ax.set_xticks(range(0, len(x)+1, 20))
ax.set_xticklabels(label)
plt.show()
Here is an example of setting the tick marks:
import matplotlib.pyplot as plt
import numpy as np
data=np.random.rand(3,50)
fig = plt.figure(figsize=(10,5))
ax = fig.add_axes([0,0,1,1])
ax.set_xlabel("Samples", labelpad=10)
ax.set_ylabel("Error (MSE)", labelpad=10)
ax.boxplot(data,
showcaps=False,
whiskerprops={'color' : 'tab:blue'},
patch_artist=True
)
plt.xticks([10, 20, 30, 40, 50],
["10", "20", "30", "40", "50"])
EDIT:
You can also avoid messing with strings and set the marks like this:
N=50
plt.xticks(np.linspace(0, N, num=6), np.linspace(0, N, num=6))
See here and this example.
Simple ticks can be acheived in a similar mannar as here (note data as transposed numpy array) using
import numpy as np
import matplotlib.pyplot as plt
data = np.array([ np.random.rand(100) for i in range(3) ]).T
plt.boxplot(data)
plt.xticks([1, 2, 3], ['mon', 'tue', 'wed'])
This question already has an answer here:
Set subplot(or gridspec) with same size
(1 answer)
Closed 3 years ago.
I am trying to plot 3 graphs in a single row, but all plots should be the same size (at least the same height).
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.gridspec as gridspec
x = np.random.uniform(size=2000) - 0.5
y = np.random.uniform(size=2000) - 0.5
z = np.random.uniform(size=2000) - 0.5
DPI = 106
fig = plt.figure(figsize=(900 / DPI, 350 / DPI))
gs = gridspec.GridSpec(1, 3, width_ratios=[1,1,1])
# 1D
r = np.abs(x)
# plot
ax1 = fig.add_subplot(gs[0])
plot = ax1.scatter(x, r, s = 1, c=r, cmap='jet', marker='.', alpha = 1, vmax = 0.5)
ax1.set_xlabel('$x$')
ax1.set_ylabel('$y$')
ax1.set_aspect('equal')
# 2D
r = np.sqrt(x * x + y * y)
# plot
ax2 = fig.add_subplot(gs[1])
plot = ax2.scatter(x, y, s = 1, c=r, cmap='jet', marker='.', alpha = 1, vmax = 0.5)
ax2.set_xlabel('$x$')
ax2.set_ylabel('$y$')
ax2.set_aspect('equal')
fig.colorbar(plot, shrink = 1, ax = ax2)
# 3D
r = np.sqrt(x * x + y * y + z * z)
ax3 = fig.add_subplot(gs[2], projection='3d')
plot = ax3.scatter(x, y, z, s = 10, c=r, cmap='jet', marker='.', alpha = 1, vmax = 0.5)
ax3.set_xlabel('$x$')
ax3.set_ylabel('$y$')
ax3.set_zlabel('$z$')
ax3.view_init(30, 240)
ax3.set_aspect('equal', 'box')
fig.colorbar(plot, shrink = 1,ax = ax3)
fig.tight_layout()
The code above does produce three columns of plots, first 1D, than 2D and lastly a 3D plot. However, as you can see from attached image, the plots are not of the same size eventhough I tried using gridspecas suggested here.
Any ideas on how to change the size of subplots?
Matplotlib.pyplot's auto-layout algorithm does not care if you are plotting a 3D object, 2D object or 1D (points). Of course the syntax for defining the object will change and a 3D object will take 3 parameters. But how the objects are placed in a row is not changed. I see a few possible reasons why your specific data may be causing you trouble.
The first image is 2D and has a smaller y-axis scale than the other 2 images. Also the 1st image x-axis scale is twice as wide as y-axis scale is tall. The 2nd and 3rd images include vertical colormaps, which makes these images taller in total.
1) You can change the 1st plot's y-axis to be taller than it currently is.
ax1.set_aspect('equal')
This code in your 1st plot is preventing you from changing the y-axis scale only. You can remove this line and manually set the scale of y-axis scale to be larger.
2) Make your entire row taller, so the vertical colormaps in 2nd and 3rd plots will not determine the overall height of the figure space. Set the figsize's x and y attribute to (12, 12) and see if that fixes the issue. The 2nd number in figsize sets height.
fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(12, 12))
3) Alternatively, you can plot 1st graph in it's own 1st row, and 2nd and 3rd graphs in a separate 2nd row. Set nrows to 2 and ncols to 2, then add 1st plot to row 1 and col 1, and 2nd plot to row 2, col 1, and 3rd plot to row 2, col 2.
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(12, 18))
You can refer to Matplotlib documentation for getting details setting layout parameters. Hope one of these will work. :-)
https://matplotlib.org/3.1.0/api/_as_gen/matplotlib.axes.Axes.set_aspect.html
I am trying to make use the polar plot projection to make a radar chart. I would like to know how to put only one grid line in bold (while the others should remain standard).
For my specific case, I would like to highlight the gridline associated to the ytick "0".
from matplotlib import pyplot as plt
import pandas as pd
import numpy as np
#Variables
sespi = pd.read_csv("country_progress.csv")
labels = sespi.country
progress = sespi.progress
angles=np.linspace(0, 2*np.pi, len(labels), endpoint=False)
#Concatenation to close the plots
progress=np.concatenate((progress,[progress[0]]))
angles=np.concatenate((angles,[angles[0]]))
#Polar plot
fig=plt.figure()
ax = fig.add_subplot(111, polar=True)
ax.plot(angles, progress, '.--', linewidth=1, c="g")
#ax.fill(angles, progress, alpha=0.25)
ax.set_thetagrids(angles * 180/np.pi, labels)
ax.set_yticklabels([-200,-150,-100,-50,0,50,100,150,200])
#ax.set_title()
ax.grid(True)
plt.show()
The gridlines of a plot are Line2D objects. Therefore you can't make it bold. What you can do (as shown, in part, in the other answer) is to increase the linewidth and change the colour but rather than plot a new line you can do this to the specified gridline.
You first need to find the index of the y tick labels which you want to change:
y_tick_labels = [-100,-10,0,10]
ind = y_tick_labels.index(0) # find index of value 0
You can then get a list of the gridlines using gridlines = ax.yaxis.get_gridlines(). Then use the index you found previously on this list to change the properties of the correct gridline.
Using the example from the gallery as a basis, a full example is shown below:
r = np.arange(0, 2, 0.01)
theta = 2 * np.pi * r
ax = plt.subplot(111, projection='polar')
ax.set_rmax(2)
ax.set_rticks([0.5, 1, 1.5, 2]) # less radial ticks
ax.set_rlabel_position(-22.5) # get radial labels away from plotted line
ax.grid(True)
y_tick_labels = [-100, -10, 0, 10]
ax.set_yticklabels(y_tick_labels)
ind = y_tick_labels.index(0) # find index of value 0
gridlines = ax.yaxis.get_gridlines()
gridlines[ind].set_color("k")
gridlines[ind].set_linewidth(2.5)
plt.show()
Which gives:
It is just a trick, but I guess you could just plot a circle and change its linewidth and color to whatever could be bold for you.
For example:
import matplotlib.pyplot as plt
import numpy as np
Yline = 0
Npoints = 300
angles = np.linspace(0,360,Npoints)*np.pi/180
line = 0*angles + Yline
ax = plt.subplot(111, projection='polar')
plt.plot(angles, line, color = 'k', linewidth = 3)
plt.ylim([-1,1])
plt.grid(True)
plt.show()
In this piece of code, I plot a line using plt.plot between any point of the two vectors angles and line. The former is actually all the angles between 0 and 2*np.pi. The latter is constant, and equal to the 'height' you want to plot that line Yline.
I suggest you try to decrease and increase Npoints while having a look to the documentaion of np.linspace() in order to understand your problem with the roundness of the circle.
I want 3 graphs on one axes object, for example:
#example x- and y-data
x_values1=[1,2,3,4,5]
y_values1=[1,2,3,4,5]
x_values2=[-1000,-800,-600,-400,-200]
y_values2=[10,20,39,40,50]
x_values3=[150,200,250,300,350]
y_values3=[10,20,30,40,50]
#make axes
fig=plt.figure()
ax=fig.add_subplot(111)
now I want to add all three data sets to ax. But they shouldn't share any x- or y-axis (since then because of the diffenrent scales one would be way smaller thant the other. I need something like ax.twinx(), ax.twiny(), but both the x- and y-axis need to be independent.
I want to do this, because I want to put the two attached plots (and a third one, that is similar to the second one) in one plot ("put them on top of each other").
Plot1
Plot2
I then would put the x/y-labels (and/or ticks, limits) of the second plot on the right/top and the x/y-limits of another plot in the bottom/left. I dont need x/y-labels of the 3. plot.
How do I do this?
The idea would be to create three subplots at the same position. In order to make sure, they will be recognized as different plots, their properties need to differ - and the easiest way to achieve this is simply to provide a different label, ax=fig.add_subplot(111, label="1").
The rest is simply adjusting all the axes parameters, such that the resulting plot looks appealing.
It's a little bit of work to set all the parameters, but the following should do what you need.
import matplotlib.pyplot as plt
x_values1=[1,2,3,4,5]
y_values1=[1,2,2,4,1]
x_values2=[-1000,-800,-600,-400,-200]
y_values2=[10,20,39,40,50]
x_values3=[150,200,250,300,350]
y_values3=[10,20,30,40,50]
fig=plt.figure()
ax=fig.add_subplot(111, label="1")
ax2=fig.add_subplot(111, label="2", frame_on=False)
ax3=fig.add_subplot(111, label="3", frame_on=False)
ax.plot(x_values1, y_values1, color="C0")
ax.set_xlabel("x label 1", color="C0")
ax.set_ylabel("y label 1", color="C0")
ax.tick_params(axis='x', colors="C0")
ax.tick_params(axis='y', colors="C0")
ax2.scatter(x_values2, y_values2, color="C1")
ax2.xaxis.tick_top()
ax2.yaxis.tick_right()
ax2.set_xlabel('x label 2', color="C1")
ax2.set_ylabel('y label 2', color="C1")
ax2.xaxis.set_label_position('top')
ax2.yaxis.set_label_position('right')
ax2.tick_params(axis='x', colors="C1")
ax2.tick_params(axis='y', colors="C1")
ax3.plot(x_values3, y_values3, color="C3")
ax3.set_xticks([])
ax3.set_yticks([])
plt.show()
You could also standardize the data so it shares the same limits and then plot the limits of the desired second scale "manually".
This function standardizes the data to the limits of the first set of points:
def standardize(data):
for a in range(2):
span = max(data[0][a]) - min(data[0][a])
min_ = min(data[0][a])
for idx in range(len(data)):
standardize = (max(data[idx][a]) - min(data[idx][a]))/span
data[idx][a] = [i/standardize + min_ - min([i/standardize
for i in data[idx][a]]) for i in data[idx][a]]
return data
Then, plotting the data is easy:
import matplotlib.pyplot as plt
data = [[[1,2,3,4,5],[1,2,2,4,1]], [[-1000,-800,-600,-400,-200], [10,20,39,40,50]], [[150,200,250,300,350], [10,20,30,40,50]]]
limits = [(min(data[1][a]), max(data[1][a])) for a in range(2)]
norm_data = standardize(data)
fig, ax = plt.subplots()
for x, y in norm_data:
ax.plot(x, y)
ax2, ax3 = ax.twinx(), ax.twiny()
ax2.set_ylim(limits[1])
ax3.set_xlim(limits[0])
plt.show()
Since all data points have the limits of the first set of points, we can just plot them on the same axis. Then, using the limits of the desired second x and y axis we can set the limits for these two.
In this example, you can plot multiple lines in each x-y-axis, and legend each line.
import numpy as np
import matplotlib.pyplot as plt
X1 = np.arange(10)
X1 = np.stack([X1, X1])
Y1 = np.random.randint(1, 10, (2, 10))
X2 = np.arange(0, 1000, 200)
X2 = np.stack([X2, X2])
Y2 = np.random.randint(100, 200, (2, 5))
x_label_names = ['XXX', 'xxx']
y_label_names = ['YYY', 'yyy']
X1_legend_names = ['X1_legend1', 'X1_legend2']
X2_legend_names = ['X2_legend1', 'X2_legend2']
def plot_by_two_xaxis(X1, Y1, X2, Y2, x_label_names: list, y_label_names: list, X1_legend_names: list, X2_legend_names: list):
fig = plt.figure()
ax1s = []
ax2s = []
lines = []
j = 0
for i in range(len(X1)):
j += 1
ax1s.append(fig.add_subplot(111, label=f"{j}", frame_on=(j == 1)))
for i in range(len(X2)):
j += 1
ax2s.append(fig.add_subplot(111, label=f"{j}", frame_on=(j == 1)))
k = 0
for i in range(len(X1)):
lines.append(ax1s[i].plot(X1[i], Y1[i], color=f"C{k}")[0])
if i == 0:
ax1s[i].set_xlabel(x_label_names[0], color=f"C{k}")
ax1s[i].set_ylabel(y_label_names[0], color=f"C{k}")
ax1s[i].tick_params(axis='x', colors=f"C{k}")
ax1s[i].tick_params(axis='y', colors=f"C{k}")
else:
ax1s[i].set_xticks([])
ax1s[i].set_yticks([])
k += 1
for i in range(len(X1)):
lines.append(ax2s[i].plot(X2[i], Y2[i], color=f"C{k}")[0])
if i == 0:
ax2s[i].xaxis.tick_top()
ax2s[i].yaxis.tick_right()
ax2s[i].set_xlabel(x_label_names[1], color=f"C{k}")
ax2s[i].set_ylabel(y_label_names[1], color=f"C{k}")
ax2s[i].xaxis.set_label_position('top')
ax2s[i].yaxis.set_label_position('right')
ax2s[i].tick_params(axis='x', colors=f"C{k}")
ax2s[i].tick_params(axis='y', colors=f"C{k}")
else:
ax2s[i].set_xticks([])
ax2s[i].set_yticks([])
k += 1
ax1s[0].legend(lines, X1_legend_names + X2_legend_names)
plt.show()
plot_by_two_xaxis(X1, Y1, X2, Y2, x_label_names,
y_label_names, X1_legend_names, X2_legend_names)