Position label of colorbar - python

I have this function:
def scatter_diagdistance(x, y) :
z = abs(y-x)
fig, ax = plt.subplots(dpi=200)
sc = ax.scatter(x, y, c=z, s=50, edgecolor='none')
x_diag = np.arange(min(x*100), max(x*100))/100
ax.plot(x_diag, x_diag, '-', c="red")
cbar = fig.colorbar(sc)
cbar.set_label('Distance from diagonal')
return(fig)
Which gives me this sort of image:
How can I position the "Distance from diagonal" to the left of the colorbar?
(Also, is there a cleaner way to plot the diagonal over a scatter plot like this?)

one way to do it is to use the text as the label for the secondary y-axis. That will keep the text before the colorbar. Also, you can draw a line for the diagonal. The code (without your data) is shown below. If you use transform=ax.transAxes details, the coordinates are interpreted as axes coordinates
fig, ax = plt.subplots(dpi=200)
ax2 = ax.twinx() ##Create secondary axis
ax2.set_yticks([]) ##No ticks for the secondary axis
sc = ax.scatter(0.5, 0.5, c=1, s=50, edgecolor='none')
ax2.set_ylabel('Distance from diagonal') ##Label for secondary axis
ax.plot([0, 1], [0, 1], '-', c="red", transform=ax.transAxes) #Line from 0 to 1
cbar = fig.colorbar(sc)
Plot

Related

Matplotlib: Draw second y-axis with different length

I'm trying to make a matplotlib plot with a second y-axis. This works so far, but I was wondering, wether it was possible to shorten the second y-axis?
Furthermore, I struggle on some other formatting issues.
a) I want to draw an arrow on the second y-axis, just as drawn on the first y-axis.
b) I want to align the second y-axis at -1, so that the intersection of x- and 2nd y-axis is at(...; -1)
c) The x-axis crosses the x- and y-ticks at the origin, which I want to avoid.
d) How can I get a common legend for both y-axis?
Here is my code snippet so far.
fig, ax = plt.subplots()
bx = ax.twinx() # 2nd y-axis
ax.spines['bottom'].set_position(('data',0))
ax.spines['left'].set_position(('data',0))
ax.xaxis.set_ticks_position('bottom')
bx.spines['left'].set_position(('data',-1))
bx.spines['bottom'].set_position(('data',-1))
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
bx.spines["top"].set_visible(False)
bx.spines["bottom"].set_visible(False)
bx.spines["left"].set_visible(False)
## Graphs
x_val = np.arange(0,10)
y_val = 0.1*x_val
ax.plot(x_val, y_val, 'k--')
bx.plot(x_val, -y_val+1, color = 'purple')
## Arrows
ms=2
#ax.plot(1, 0, ">k", ms=ms, transform=ax.get_yaxis_transform(), clip_on=False)
ax.plot(0, 1, "^k", ms=ms, transform=ax.get_xaxis_transform(), clip_on=False)
bx.plot(1, 1, "^k", ms=ms, transform=bx.get_xaxis_transform(), clip_on=False)
plt.ylim((-1, 1.2))
bx.set_yticks([-1, -0.75, -0.5, -0.25, 0, 0.25, 0.5])
## Legend
ax.legend([r'$A_{hull}$'], frameon=False,
loc='upper left', bbox_to_anchor=(0.2, .75))
plt.show()
I've uploaded a screenshot of my plot so far, annotating the questioned points.
EDIT: I've changed the plotted values in the code snippet so that the example is easier to reproduce. However, the question is more or less only related to formatting issues so that the acutual values are not too important. Image is not changed, so don't be surprised when plotting the edited values, the graphs will look differently.
To avoid the strange overlap at x=0 and y=0, you could leave out the calls to ax.spines[...].set_position(('data',0)). You can change the transforms that place the arrows. Explicitly setting the x and y limits to start at 0 will also have the spines at those positions.
ax2.set_bounds(...) shortens the right y-axis.
To put items in the legend, each plotted item needs a label. get_legend_handles_labels can fetch the handles and labels of both axes, which can be combined in a new legend.
Renaming bx to something like ax2 makes the code easier to compare with existing example code. In matplotlib it often also helps to first put the plotting code and only later changes to limits, ticks and embellishments.
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
fig, ax = plt.subplots()
ax2 = ax.twinx() # 2nd y-axis
## Graphs
x_val = np.arange(0, 10)
y_val = 0.1 * x_val
ax.plot(x_val, y_val, 'k--', label=r'$A_{hull}$')
ax2.plot(x_val, -y_val + 1, color='purple', label='right axis')
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax2.spines["top"].set_visible(False)
ax2.spines["bottom"].set_visible(False)
ax2.spines["left"].set_visible(False)
ax2_upper_bound = 0.55
ax2.spines["right"].set_bounds(-1, ax2_upper_bound) # shorten the right y-axis
## add arrows to spines
ms = 2
# ax.plot(1, 0, ">k", ms=ms, transform=ax.get_yaxis_transform(), clip_on=False)
ax.plot(0, 1, "^k", ms=ms, transform=ax.transAxes, clip_on=False)
ax2.plot(1, ax2_upper_bound, "^k", ms=ms, transform=ax2.get_yaxis_transform(), clip_on=False)
# set limits to the axes
ax.set_xlim(xmin=0)
ax.set_ylim(ymin=0)
ax2.set_ylim((-1, 1.2))
ax2.set_yticks(np.arange(-1, 0.5001, 0.25))
## Legend
handles1, labels1 = ax.get_legend_handles_labels()
handles2, labels2 = ax2.get_legend_handles_labels()
ax.legend(handles1 + handles2, labels1 + labels2, frameon=False,
loc='upper left', bbox_to_anchor=(0.2, .75))
plt.show()

Plotting Points on Matplotlib Colored Grid

I am working with the matplotlib library to generate colored graphs which need to have specific points overlayed on top of them. After messing around with matplotlib, I came up with a method to properly color my grid, however I am unable to plot points manually.
def generate_grid(x, y, data):
fig, ax = plt.subplots(1, 1, tight_layout=True)
my_cmap = matplotlib.colors.ListedColormap(['grey'])
my_cmap.set_bad(color='w', alpha=0)
for x in range(x + 1):
ax.axhline(x, lw=2, color='k', zorder=5)
for y in range(y+1):
ax.axvline(y, lw=2, color='k', zorder=5)
ax.imshow(data, interpolation='none', cmap=my_cmap,
extent=[0, y, 0, x], zorder=0)
plt.locator_params(axis="x", nbins=x+1)
plt.locator_params(axis="y", nbins=y+1)
locs, labels = plt.xticks()
labels = [int(item)+1 for item in locs]
plt.xticks(locs, labels)
locs, labels = plt.yticks()
z = len(locs)
labels = [z-int(item) for item in locs]
plt.yticks(locs, labels)
ax.xaxis.tick_top()
plt.show()
How would I go about plotting a point at any given location ie at (4,2) or (2,1)?
You may simply use the scatter method from within your generate_grid function, for instance, immediately before plt.show().
However, note that if you simply use ax.scatter(2,1, s=50) the symbol will end up under your grid.
You need to play with the zorder parameter to ensure that it appears over the grid. For instance ax.scatter(2,1, s=50, zorder=50) did the trick for me:

Legend handle not correctly aligned in matplotlib

I'm plotting 3 things at once: a multicolored line via a LineCollection (following this) and a scatter (for the markers to "cover" where the lines are joining) for an average value, and a fill_between for min/max. I get all the legend returns to plot a single legend handle. The graph looks like this:
As one can note, the circle marker is not aligned with the line. How can I adjust this?
The piece of the code that is plotting them and the legend looks like:
lc = LineCollection(segments, cmap='turbo',zorder=3)
p1 = ax.add_collection(lc)
p2 = ax.fill_between(x, errmin,errmax, color=colors[1],zorder=2)
ps = ax.scatter(x,y,marker='o',s=1,c=y,cmap='turbo',zorder=4)
ax.legend([(p2, p1, ps)], ["(min/avg/max)"],fontsize=tinyfont, facecolor='white', loc='lower right')
The legend has a parameter scatteryoffsets= which defaults to [0.375, 0.5, 0.3125]. As only one point is shown, setting it to [0.5] should show the dot in the center of the legend marker.
To change the color of the line in the legend, one could create a copy of the existing line, change its color and create the legend with that copy.
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
import numpy as np
from copy import copy
x = np.arange(150)
y = np.random.randn(150).cumsum()
y -= y.min()
y /= y.max()
errmin = 0
errmax = 1
segments = np.array([x[:-1], y[:-1], x[1:], y[1:]]).T.reshape(-1, 2, 2)
fig, ax = plt.subplots(figsize=(12, 3))
lc = LineCollection(segments, cmap='turbo', zorder=3)
lc.set_array(y[:-1])
p1 = ax.add_collection(lc)
p2 = ax.fill_between(x, errmin, errmax, color='lightblue', zorder=2, alpha=0.4)
ps = ax.scatter(x, y, marker='o', s=5, color='black', zorder=4)
p1copy = copy(p1)
p1copy.set_color('crimson')
leg = ax.legend([(p2, p1copy, ps)], ["(min/avg/max)"], fontsize=10, facecolor='white', loc='lower right',
scatteryoffsets=[0.5])
ax.margins(x=0.02)
plt.show()

Matplotlib - create a scatter plot with points that fill the available space

I can create a scatter plot as follows:
fig, ax = plt.subplots()
x1 = [1, 1, 2]
y1 = [1, 2, 1]
x2 = [2]
y2 = [2]
ax.scatter(x1, y1, color="red", s=500)
ax.scatter(x2, y2, color="blue", s=500)
which gives
What I would like is something like the following (apologies for poor paint work):
I am plotting data that is all integer values, so they're all on a grid. I would like to be able to control the size of the scatter marker so that I could have white space around the points, or I could make the points large enough such that there would be no white space around them (as I have done in the above paint image).
Note - ideally the solution will be in pure matplotlib, using the OOP interface as they suggest in the documentation.
import matplotlib.pyplot as plt
import matplotlib as mpl
# X and Y coordinates for red circles
red_xs = [1,2,3,4,1,2,3,4,1,2,1,2]
red_ys = [1,1,1,1,2,2,2,2,3,3,4,4]
# X and Y coordinates for blue circles
blu_xs = [3,4,3,4]
blu_ys = [3,3,4,4]
# Plot with a small markersize
markersize = 5
fig, ax = plt.subplots(figsize=(3,3))
ax.plot(red_xs, red_ys, marker="o", color="r", linestyle="", markersize=markersize)
ax.plot(blu_xs, blu_ys, marker="o", color="b", linestyle="", markersize=markersize)
plt.show()
# Plot with a large markersize
markersize = 50
fig, ax = plt.subplots(figsize=(3,3))
ax.plot(red_xs, red_ys, marker="o", color="r", linestyle="", markersize=markersize)
ax.plot(blu_xs, blu_ys, marker="o", color="b", linestyle="", markersize=markersize)
plt.show()
# Plot with using patches and radius
r = 0.5
fig, ax = plt.subplots(figsize=(3,3))
for x, y in zip(red_xs, red_ys):
ax.add_patch(mpl.patches.Circle((x,y), radius=r, color="r"))
for x, y in zip(blu_xs, blu_ys):
ax.add_patch(mpl.patches.Circle((x,y), radius=r, color="b"))
ax.autoscale()
plt.show()

Rotating 2nd y-axis tick labels in matplotlib

I need to rotate the 2nd y-axis ticklabel and add a label for this axis as well in the figure below
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
import matplotlib.transforms as mtransforms
fig, ax1 = plt.subplots(constrained_layout=True)
x = [61,62,62,59,62,59,62,63,61,60,103,104,109,105,109,105,109,111,110,107]
y = [62,62,62,62,60,60,62,62,62,63,106,107,106,106,105,105,105,106,107,108]
ax1.plot(x,y,'b.')
x2 = [2.2,3.4,4.3,5.1,5.5,5.7]
y2 = [2.3,2.8,3.2,3.9,4.5,5.9]
ax2 = ax1.twinx().twiny()
ax2.tick_params(axis="y",labelrotation=90,direction='out',length=6, width=2, colors='r',grid_color='r', grid_alpha=0.5) #called tick_params before the plot and didn't work
ax2.plot(x2,y2,'r.')
ax2.set_xlim(0,10)
ax2.set_ylim(0,10)
ax2.set_yticklabels(['Label1', 'Label2', 'Label3'], rotation=90) #y ticklabels is not rotating
ax2.set_xlabel('abc', rotation=0, fontsize=20, labelpad=20)
ax2.set_ylabel('abc', rotation=0, fontsize=20, labelpad=20) #y label is not wroking
plt.yticks(rotation=90)
line = mlines.Line2D([0, 1], [0, 1], color='red')
transform = ax2.transAxes
line.set_transform(transform)
ax2.add_line(line)
plt.show()
This code produced the figure below
The problem is ax2.set_yticklabels and ax2.set_ylabel don't work.
I want to add a label to 2nd y-axis and rotate the tick label for that axis. Also, how to control the position of the tick mark at these axes, I want it to be at the same position of tick marks of 1st y-axis and 1st x-axis. So Label1 will shift up and 0 will shift right
Thanks
When you are instancing ax2 = ax1.twinx().twiny(), you can no longer modify the y axis. Instead, create two axes and modify accordingly. Modified code and the result is below.
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
import matplotlib.transforms as mtransforms
fig, ax1 = plt.subplots(constrained_layout=True)
x = [61,62,62,59,62,59,62,63,61,60,103,104,109,105,109,105,109,111,110,107]
y = [62,62,62,62,60,60,62,62,62,63,106,107,106,106,105,105,105,106,107,108]
ax1.plot(x,y,'b.')
x2 = [2.2,3.4,4.3,5.1,5.5,5.7]
y2 = [2.3,2.8,3.2,3.9,4.5,5.9]
ax2 = ax1.twinx() # ax2 handles y
ax3 = ax2.twiny() # ax3 handles x
ax3.plot(x2,y2,'r.')
ax3.set_xlim(0,10)
ax2.set_ylim(0,10)
ax3.set_xlabel('abc', rotation=0, fontsize=20, labelpad=20)
ax2.set_ylabel('abc', rotation=0, fontsize=20, labelpad=20)
ax2.tick_params(axis="y",labelrotation=90,direction='out',length=6, width=2, colors='r',grid_color='r', grid_alpha=0.5)
ax2.set_yticklabels(['Label1', 'Label2', 'Label3'], rotation=-90)
plt.yticks(rotation=90)
line = mlines.Line2D([0, 1], [0, 1], color='red')
transform = ax2.transAxes
line.set_transform(transform)
ax2.add_line(line)
plt.show()
The resulting graph has all the y label/tick modifications.

Categories