I can't believe that this is so complicated but I tried and googled for a while now.
I just want to analyse my scatter plot with a few graphical features.
For starters, I want to add simply a line.
So, I have a few (4) points and I want to add a line to it, like in this plot (source: http://en.wikipedia.org/wiki/File:ROC_space-2.png)
Now, this won't work. And frankly, the documentation-examples-gallery combo and content of matplotlib is a bad source for information.
My code is based upon a simple scatter plot from the gallery:
# definitions for the axes
left, width = 0.1, 0.85 #0.65
bottom, height = 0.1, 0.85 #0.65
bottom_h = left_h = left+width+0.02
rect_scatter = [left, bottom, width, height]
# start with a rectangular Figure
fig = plt.figure(1, figsize=(8,8))
axScatter = plt.axes(rect_scatter)
# the scatter plot:
p1 = axScatter.scatter(x[0], y[0], c='blue', s = 70)
p2 = axScatter.scatter(x[1], y[1], c='green', s = 70)
p3 = axScatter.scatter(x[2], y[2], c='red', s = 70)
p4 = axScatter.scatter(x[3], y[3], c='yellow', s = 70)
p5 = axScatter.plot([1,2,3], "r--")
plt.legend([p1, p2, p3, p4, p5], [names[0], names[1], names[2], names[3], "Random guess"], loc = 2)
# now determine nice limits by hand:
binwidth = 0.25
xymax = np.max( [np.max(np.fabs(x)), np.max(np.fabs(y))] )
lim = ( int(xymax/binwidth) + 1) * binwidth
axScatter.set_xlim( (-lim, lim) )
axScatter.set_ylim( (-lim, lim) )
xText = axScatter.set_xlabel('FPR / Specificity')
yText = axScatter.set_ylabel('TPR / Sensitivity')
bins = np.arange(-lim, lim + binwidth, binwidth)
plt.show()
Everything works, except the p5 which is a line.
Now how is this supposed to work?
What's good practice here?
plottakes either y values and uses x as index array 0..N-1 or x and y values as described in the documentation. So you could use
p5 = axScatter.plot((0, 1), "r--")
in your code to plot the line.
However, you are asking for "good practice".
The following code (hopefully) shows some "good practise" and some of the capabilities of matplotlib to create the plot you mention in your question.
import numpy as np
import matplotlib.pyplot as plt
# create some data
xy = np.random.rand(4, 2)
xy_line = (0, 1)
# set up figure and ax
fig, ax = plt.subplots(figsize=(8,8))
# create the scatter plots
ax.scatter(xy[:, 0], xy[:, 1], c='blue')
for point, name in zip(xy, 'ABCD'):
ax.annotate(name, xy=point, xytext=(0, -10), textcoords='offset points',
color='blue', ha='center', va='center')
ax.scatter([0], [1], c='black', s=60)
ax.annotate('Perfect Classification', xy=(0, 1), xytext=(0.1, 0.9),
arrowprops=dict(arrowstyle='->'))
# create the line
ax.plot(xy_line, 'r--', label='Random guess')
ax.annotate('Better', xy=(0.3, 0.3), xytext=(0.2, 0.4),
arrowprops=dict(arrowstyle='<-'), ha='center', va='center')
ax.annotate('Worse', xy=(0.3, 0.3), xytext=(0.4, 0.2),
arrowprops=dict(arrowstyle='<-'), ha='center', va='center')
# add labels, legend and make it nicer
ax.set_xlabel('FPR or (1 - specificity)')
ax.set_ylabel('TPR or sensitivity')
ax.set_title('ROC Space')
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.legend()
plt.tight_layout()
plt.savefig('scatter_line.png', dpi=80)
By the way: I think that matplotlibs documentation is quite useful nowadays.
the p5 line should be:
p5 = axScatter.plot([1,2,3],[1,2,3], "r--")
argument 1 is a list of the x values, and argument 2 is a list of y values
If you just want a straight line, you only need to provide values for the extremities of the line.
Related
I have been following a tutorial on plotting F1 data over a circuit, color coded with the fastf1 library.
I wanted to add some extra's to the script to utilize the official team colors.
It works but the end result shows the colormap with the circuit covering the n bins 100.
In the picture above I used the same colormap as in the tutorial 'winter' so there is most certainly something wrong in my code.
However, the original tutorial gets a cleaner end result with only the circuit showing like this:
the tutorial in question uses a default colormap from matplotlib 'winter'. To get the team colors working I had to create a custom colormap from the 2 colors that are fetched from api.
Let's get into the code, I have tried so much and searched everywhere without success...
The custom colormap is build with this sequence of code I got from the matplotlib docs.
# Create custom colormap
teamcolor1 = to_rgb('{}'.format(team1_color))
teamcolor2 = to_rgb('{}'.format(team2_color))
colors = [teamcolor1, teamcolor2]
n_bins = [3, 6, 10, 100]
cmap_name = 'colors'
fig, axs = plt.subplots(2, 2, figsize=(6, 9))
fig.subplots_adjust(left=0.02, bottom=0.06, right=0.95, top=0.94, wspace=0.05)
x = np.arange(0, np.pi, 0.1)
y = np.arange(0, 2 * np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.cos(X) * np.sin(Y) * 10
for n_bin, ax in zip(n_bins, axs.ravel()):
colormap = LinearSegmentedColormap.from_list(cmap_name, colors, N=n_bin)
im = ax.imshow(Z, interpolation='nearest', origin='lower', cmap=colormap)
ax.set_title("N bins: %s" % n_bin)
fig.colorbar(im, ax=ax)
cm.register_cmap(cmap_name, colormap)
I register the colormap to easily call it later in the script with get_cmap.
The eventual plotting of the circuit is done in this piece of code:
x = np.array(telemetry['X'].values)
y = np.array(telemetry['Y'].values)
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
fastest_driver_array = telemetry['Fastest_driver_int'].to_numpy().astype(float)
cmap = cm.get_cmap('winter', 2)
lc_comp = LineCollection(segments, norm=plt.Normalize(1, cmap.N+1), cmap=cmap)
lc_comp.set_array(fastest_driver_array)
lc_comp.set_linewidth(5)
plt.rcParams['figure.figsize'] = [18, 10]
plt.gca().add_collection(lc_comp)
plt.axis('equal')
plt.tick_params(labelleft=False, left=False, labelbottom=False, bottom=False)
cbar = plt.colorbar(mappable=lc_comp, boundaries=np.arange(1, 4))
cbar.set_ticks(np.arange(1.5, 9.5))
cbar.set_ticklabels(['{}'.format(driver1), '{}'.format(driver2)])
plt.savefig(
'{}_'.format(year) + '{}_'.format(driver1) + '{}_'.format(driver2) + '{}_'.format(circuit) + '{}.png'.format(
session), dpi=300)
plt.show()
This is where I think things go wrong, but I'm unsure of what is going wrong. I guess it has to do with how I use the colormap. But everything I changed broke the whole script.
As I don't have a lot of experience with matplotlib, it's getting very complicated.
As I don't want this question to be overly long the whole code can be read here:
https://gist.github.com/platinaCoder/7b5be22405f2003bd577189692a2b36b
Instead of creating a whole custome cmap, I got rid of this piece of code:
# Create custom colormap
teamcolor1 = to_rgb('{}'.format(team1_color))
teamcolor2 = to_rgb('{}'.format(team2_color))
colors = [teamcolor1, teamcolor2]
n_bins = [3, 6, 10, 100]
cmap_name = 'colors'
fig, axs = plt.subplots(2, 2, figsize=(6, 9))
fig.subplots_adjust(left=0.02, bottom=0.06, right=0.95, top=0.94, wspace=0.05)
x = np.arange(0, np.pi, 0.1)
y = np.arange(0, 2 * np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.cos(X) * np.sin(Y) * 10
for n_bin, ax in zip(n_bins, axs.ravel()):
colormap = LinearSegmentedColormap.from_list(cmap_name, colors, N=n_bin)
im = ax.imshow(Z, interpolation='nearest', origin='lower', cmap=colormap)
ax.set_title("N bins: %s" % n_bin)
fig.colorbar(im, ax=ax)
cm.register_cmap(cmap_name, colormap)
and replaced cmap = cm.get_cmap('colors', 2) with cmap = cm.colors.ListedColormap(['{}'.format(team1_color), '{}'.format(team2_color)])
I have run an MCMC chain for parameter estimation and have obtained accepted parameter values. I have 3 parameters and about 300 000 accepted values for each parameter.
I would now like to do a contour plot (doable) but in a 3-choose-2 triangular matrix type (a very specific requirement) Please see the attached photo contour-plot. The image shows some unrelated contour-plots from a paper but I want to have a similar type of plot for my parameters.
In total, I will have 6 plots: 3 single parameter histograms (like the top plot in each column in the image) and 3-choose-2 = 3 contour plots (as the lower triangle). Again, I need it to look as much as possible like the image.
How to achieve this on Python?
Update:
I have been able to write the below code which gives me a plot as my-plot-here.
However, I need an exact / as best as possible match with the type of figure 1. i.e. I need my xticks, yticks to show inside and not outside, the spaces between the figues to go away, a better way to show the left vertical plot labels (I'm currently using set_ylabel), the outer crooked contour level to go away, and have detailed (long-short) ticks along the x-axis of the pdfs.
def plot_histogram_fig(param, nbins, subplot_index, subplot_title):
counts, bins = np.histogram(param, bins = nbins)
plotcounts = np.insert(counts, -1, counts[-1])
bincentres = (bins[:-1] + bins[1:])/2
ax = fig.add_subplot(3, 3, subplot_index)
#ax.step(bins, plotcounts, where='post', c='y')
ax.plot(bincentres, counts, 'b')
#ax.plot([bins[np.argmax(counts)], bins[np.argmax(counts)]], [0, np.max(counts)], 'y')
ax.set_yticks([])
return [ax, counts, bincentres]
def plot_contour_fig(p1, p2, nbins, subplot_index):
H, xedges, yedges = np.histogram2d(p1, p2, bins = nbins)
Z = H.T
#Z_gauss = scipy.ndimage.gaussian_filter(Z, sigma = 0.8, order = 0) #filtering
X, Y = np.meshgrid(xedges[:-1], yedges[:-1])
ax = fig.add_subplot(3, 3, subplot_index)
im = ax.contour(X, Y, Z, levels = 6)
#plt.colorbar(im, ax = ax)
ax.clabel(im, inline=True, fontsize=4)
return [ax, H, xedges, yedges]
nbins = 50
fig = plt.figure(figsize = (10, 6))
#Histograms
ax1 = plot_histogram_fig(all_alphas, nbins, 1, subplot_title = 'alpha')
ax1[0].set_xticks([])
ax1[0].set_ylabel('alpha')
ax5 = plot_histogram_fig(all_betas, nbins, 5, subplot_title = 'beta')
ax5[0].set_xticks([])
ax9 = plot_histogram_fig(all_gammas, nbins, 9, subplot_title = 'gamma')
ax9[0].set_title('gamma', y = -0.5)
#Contours
ax4 = plot_contour_fig(all_alphas, all_betas, nbins, 4)
ax4[0].set_xticklabels([])
ax4[0].set_ylabel('beta')
ax7 = plot_contour_fig(all_alphas, all_gammas, nbins, 7)
ax7[0].set_title('alpha', y = -0.5)
ax7[0].set_ylabel('gamma')
ax8 = plot_contour_fig(all_betas, all_gammas, nbins, 8)
ax8[0].set_yticklabels([])
ax8[0].set_title('beta', y = -0.5)
plt.show()
all_alphas, all_betas, all_gammas are 1d numpy arrays storing the accepted parameter values.
I'm having the same problem presented here, however, the proposed solution didn't work for me.
I'm plotting a set of data which the main plot have this pattern:
Which is a plot which axis limits varies from (-1, 1) in both x and y, with a margin set with this piece of code:
plt.figure()
plt.show(data)
## Add some margin
l, r, b, t = plt.axis()
dx, dy = r-l, t-b
plt.axis([l-0.1*dx, r+0.1*dx, b-0.1*dy, t+0.1*dy])
The problem is 'cause I have more "complex" plot in which some changes had to me made. This is the code that produces it:
def plot_quiver_singularities(min_points, max_points, vector_field_x, vector_field_y, file_path):
"""
Plot the singularities of vector field
:param file_path : the path to save the data
:param vector_field_x : the vector field x component to be plot
:param vector_field_y : the vector field y component to be plot
:param min_points : a set (x, y) of min points field
:param max_points : a set (x, y) of max points field
"""
fig = plt.figure(figsize=(8, 8))
ax = fig.add_axes([.13, .3, .6, .6])
## Plot quiver
x, y = numpy.mgrid[-1:1:100*1j, -1:1:100*1j]
m = numpy.sqrt(numpy.power(vector_field_x, 2) + numpy.power(vector_field_y, 2))
quiver = ax.quiver(x, y, vector_field_x, vector_field_y, m, zorder=1)
## Plot critical points
x = numpy.linspace(-1, 1, x_steps)
y = numpy.linspace(-1, 1, y_steps)
# Draw the min points
x_indices = numpy.nonzero(min_points)[0]
y_indices = numpy.nonzero(min_points)[1]
ax.scatter(x[x_indices], y[y_indices], marker='$\\circlearrowright$', s=100, zorder=2)
# Draw the max points
x_indices = numpy.nonzero(max_points)[0]
y_indices = numpy.nonzero(max_points)[1]
ax.scatter(x[x_indices], y[y_indices], marker='$\\circlearrowleft$', s=100, zorder=2)
## Put legends
marker_min = plt.Line2D((0, 0), (0, 0), markeredgecolor=(1.0, 0.4, 0.0), linestyle='',
marker='$\\circlearrowright$', markeredgewidth=1, markersize=10)
marker_max = plt.Line2D((0, 0), (0, 0), markeredgecolor=(0.2, 0.2, 1.0), linestyle='',
marker='$\\circlearrowleft$', markeredgewidth=1, markersize=10)
plt.legend([marker_min, marker_max], ['CW rot. center', 'CCW rot. center'], numpoints=1,
loc='center left', bbox_to_anchor=(1, 0.5))
quiver_cax = fig.add_axes([.13, .2, .6, .03])
fig.colorbar(quiver, orientation='horizontal', cax=quiver_cax)
## Set axis limits
plt.xlim(-1, 1)
plt.ylim(-1, 1)
## Add some margin
# l, r, b, t = plt.axis()
# dx, dy = r-l, t-b
# plt.axis([l-0.1*dx, r+0.1*dx, b-0.1*dy, t+0.1*dy])
plt.savefig(file_path + '.png', dpi=dpi)
plt.close()
This produces the following image:
As can be seen, the axis limits do not hold and I didn't found why yet.
Any help would be appreciated.
Thank you in advance.
I was able to solve the problem putting this piece of code
plt.xlim(-1, 1)
plt.ylim(-1, 1)
Right after calling scatter().
You can also set those to the ax object:
ax.set_xlim((-1,1))
ax.set_ylim((-1,1))
I would like to set legend and text boxes locations and styles exactly same, the latter especially to make text aligned.
import matplotlib.pyplot as plt
x = np.arange(10)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
for i in range(3):
ax.plot(x, i * x ** 2, label = '$y = %i x^2$'%i)
ax.set_title('example plot')
# Shrink the axis by 20% to put legend and text at the bottom
#+ of the figure
vspace = .2
box = ax.get_position()
ax.set_position([box.x0, box.y0 + box.height * vspace,
box.width, box.height * (1 - vspace)])
# Put a legend to the bottom left of the current axis
x, y = 0, 0
# First solution
leg = ax.legend(loc = 'lower left', bbox_to_anchor = (x, y), \
bbox_transform = plt.gcf().transFigure)
# Second solution
#leg = ax.legend(loc = (x, y)) , bbox_transform = plt.gcf().transFigure)
# getting the legend location and size properties using a code line I found
#+ somewhere in SoF
bb = leg.legendPatch.get_bbox().inverse_transformed(ax.transAxes)
ax.text(x + bb.width, y, 'some text', transform = plt.gcf().transFigure, \
bbox = dict(boxstyle = 'square', ec = (0, 0, 0), fc = (1, 1, 1)))
plt.show()
This should place the text at the right of the legend box but that's not what it does. And the two boxes are not vertically aligned.
The second solution does not actually anchoring the legend to the figure, but to the axes instead.
You can use the frame data to get the right width in order to position the Text() object correctly.
In the example below I had to apply a 1.1 factor for the width (this value I haven't found how to get, and if you don't apply the factor the text clashes with the legend).
Note also that you must plt.draw() before getting the right width value.
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(10)
fig = plt.figure(figsize=(3, 2))
ax = fig.add_subplot(1, 1, 1)
for i in range(3):
ax.plot(x, i*x**2, label=r'$y = %i \cdot x^2$'%i)
ax.set_title('example plot')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
x, y = 0.2, 0.5
leg = ax.legend(loc='lower left', bbox_to_anchor=(x, y),
bbox_transform=fig.transFigure, fontsize=8)
plt.draw()
f = leg.get_frame()
w0, h0 = f.get_width(), f.get_height()
inv = fig.transFigure.inverted()
w, h = inv.transform((w0, h0))
ax.text(x+w*1.1, y+h/2., 'some text', transform=fig.transFigure,
bbox=dict(boxstyle='square', ec=(0, 0, 0), fc=(1, 1, 1)),
fontsize=7)
fig.savefig('test.jpg', bbox_inches='tight')
for x, y = 0.2, 0.5:
for x, y = -0.3, -0.3:
I am pretty new to python and want to plot a dataset using a histogram and a heatmap below. However, I am a bit confused about
How to put a title above both plots and
How to insert some text into bots plots
How to reference the upper and the lower plot
For my first task I used the title instruction, which inserted a caption in between both plots instead of putting it above both plots
For my second task I used the figtext instruction. However, I could not see the text anywhere in the plot. I played a bit with the x, y and fontsize parameters without any success.
Here is my code:
def drawHeatmap(xDim, yDim, plot, threshold, verbose):
global heatmapList
stableCells = 0
print("\n[I] - Plotting Heatmaps ...")
for currentHeatmap in heatmapList:
if -1 in heatmapList[currentHeatmap]:
continue
print("[I] - Plotting heatmap for PUF instance", currentHeatmap,"(",len(heatmapList[currentHeatmap])," values)")
# Convert data to ndarray
#floatMap = list(map(float, currentHeatmap[1]))
myArray = np.array(heatmapList[currentHeatmap]).reshape(xDim,yDim)
# Setup two plots per page
fig, ax = plt.subplots(2)
# Histogram
weights = np.ones_like(heatmapList[currentHeatmap]) / len(heatmapList[currentHeatmap])
hist, bins = np.histogram(heatmapList[currentHeatmap], bins=50, weights=weights)
width = 0.7 * (bins[1] - bins[0])
center = (bins[:-1] + bins[1:]) / 2
ax[0].bar(center, hist, align='center', width=width)
stableCells = calcPercentageStable(threshold, verbose)
plt.figtext(100,100,"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", fontsize=40)
heatmap = ax[1].pcolor(myArray, cmap=plt.cm.Blues, alpha=0.8, vmin=0, vmax=1)
cbar = fig.colorbar(heatmap, shrink=0.8, aspect=10, fraction=.1,pad=.01)
#cbar.ax.tick_params(labelsize=40)
for y in range(myArray.shape[0]):
for x in range(myArray.shape[1]):
plt.text(x + 0.5, y + 0.5, '%.2f' % myArray[y, x],
horizontalalignment='center',
verticalalignment='center',
fontsize=(xDim/yDim)*5
)
#fig = plt.figure()
fig = matplotlib.pyplot.gcf()
fig.set_size_inches(60.5,55.5)
plt.savefig(dataDirectory+"/"+currentHeatmap+".pdf", dpi=800, papertype="a3", format="pdf")
#plt.title("Heatmap for PUF instance "+str(currentHeatmap[0][0])+" ("+str(numberOfMeasurements)+" measurements; "+str(sizeOfMeasurements)+" bytes)")
if plot:
plt.show()
print("\t[I] - Done ...")
And here is my current output:
Perhaps this example will make things easier to understand. Things to note are:
Use fig.suptitle to add a title to the top of a figure.
Use ax[i].text(x, y, str) to add text to an Axes object
Each Axes object, ax[i] in your case, holds all the information about a single plot. Use them instead of calling plt, which only really works well with one subplot per figure or to modify all subplots at once. For example, instead of calling plt.figtext, call ax[0].text to add text to the top plot.
Try following the example code below, or at least read through it to get a better idea how to use your ax list.
import numpy as np
import matplotlib.pyplot as plt
histogram_data = np.random.rand(1000)
heatmap_data = np.random.rand(10, 100)
# Set up figure and axes
fig = plt.figure()
fig.suptitle("These are my two plots")
top_ax = fig.add_subplot(211) #2 rows, 1 col, 1st plot
bot_ax = fig.add_subplot(212) #2 rows, 1 col, 2nd plot
# This is the same as doing 'fig, (top_ax, bot_ax) = plt.subplots(2)'
# Histogram
weights = np.ones_like(histogram_data) / histogram_data.shape[0]
hist, bins = np.histogram(histogram_data, bins=50, weights=weights)
width = 0.7 * (bins[1] - bins[0])
center = (bins[:-1] + bins[1:]) / 2
# Use top_ax to modify anything with the histogram plot
top_ax.bar(center, hist, align='center', width=width)
# ax.text(x, y, str). Make sure x,y are within your plot bounds ((0, 1), (0, .5))
top_ax.text(0.5, 0.5, "Here is text on the top plot", color='r')
# Heatmap
heatmap_params = {'cmap':plt.cm.Blues, 'alpha':0.8, 'vmin':0, 'vmax':1}
# Use bot_ax to modify anything with the heatmap plot
heatmap = bot_ax.pcolor(heatmap_data, **heatmap_params)
cbar = fig.colorbar(heatmap, shrink=0.8, aspect=10, fraction=.1,pad=.01)
# See how it looks
plt.show()