matplotlib fill_between leaving gaps between regions - python

I'm trying to use fill_between to fill different regions of a plot, but I get gaps between the regions I'm trying to fill.
I've tried using interpolate=True, but this results in non rectangular shapes...
`
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
x = np.arange(0, 4 * np.pi, 0.01)
y = np.sin(x)
ax.plot(x, y, color='black')
threshold = 0.75
ax.axhline(threshold, color='green', lw=2, alpha=0.7)
ax.fill_between(x, 0, 1, where=y > threshold,
facecolor=(0.5,0,0,0.5), ec=None,transform=ax.get_xaxis_transform())
ax.fill_between(x, 0, 1, where=y <= threshold,
facecolor=(0,0.5,0,0.5), ec=None, transform=ax.get_xaxis_transform())
`
I've attched a zoomed in screenshot of the plot.

You could do one or both of the following:
use finer-grainded x values, e.g.x = np.arange(0, 4 * np.pi, 0.0001). This will remove the white stripes at full view, but if you zoom in they will re-appear at a certain zoom level.
first draw the green background without a where condition over the full x range and then plot the red sections at the required sections. In case of non-opaque colors as in the example you'll need to manually re-calculate the semitransparent color on the default white background to a fully opaque color:
x = np.arange(0, 4 * np.pi, 0.001)
# ...
ax.fill_between(x, 0, 1, facecolor=(0, 0.5, 0, 0.5), ec=None,
transform=ax.get_xaxis_transform())
ax.fill_between(x, 0, 1, where=y>threshold, facecolor=(0.75, 0.5, 0.5),
ec=None, transform=ax.get_xaxis_transform())

I found an alternative way of solving this problem, by using pcolormesh where the color array is 1xn:
C = np.reshape(np.array(trnsys_out["LCG_state"][:-1].values), (-1, 1)).T
x = trnsys_out.index
y = [Pmin, Pmax]
ctrl = ax2.pcolormesh(x, y, C, shading="flat", cmap="binary", alpha=0.5, vmin=0, vmax=5)

Related

Transform from data to figure coordinates

Similar to this post, I would like to transform my data coordinates to figure coordinates. Unfortunately, the transformation tutorial doesn't seem to talk about it. So I came up with something analogous to the answer by wilywampa, but for some reason, there is something wrong and I can't figure it out:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import ConnectionPatch
t = [
0, 6.297, 39.988, 46.288, 79.989, 86.298, 120.005, 126.314, 159.994,
166.295, 200.012, 206.314, 240.005, 246.301, 280.05, 286.35, 320.032,
326.336, 360.045, 366.345, 480.971, 493.146, 1080.117, 1093.154, 1681.019,
1692.266, 2281.008, 2293.146, 2881.014, 2893.178, 3480.988, 3493.149,
4080.077, 4092.298, 4681.007, 4693.275, 5281.003, 5293.183, 5881.023,
5893.188, 6481.002, 6492.31
]
y = np.zeros(len(t))
fig, (axA, axB) = plt.subplots(2, 1)
fig.tight_layout()
for ax in (axA, axB):
ax.set_frame_on(False)
ax.axes.get_yaxis().set_visible(False)
axA.plot(t[:22], y[:22], c='black')
axA.plot(t[:22], y[:22], 'o', c='#ff4500')
axA.set_ylim((-0.05, 1))
axB.plot(t, y, c='black')
axB.plot(t, y, 'o', c='#ff4500')
axB.set_ylim((-0.05, 1))
pos1 = axB.get_position()
pos2 = [pos1.x0, pos1.y0 + 0.3, pos1.width, pos1.height]
axB.set_position(pos2)
trans = [
# (ax.transAxes + ax.transData.inverted()).inverted().transform for ax in
(fig.transFigure + ax.transData.inverted()).inverted().transform for ax in
(axA, axB)
]
con1 = ConnectionPatch(
xyA=trans[0]((0, 0)), xyB=(0, 0.1), coordsA="figure fraction",
coordsB="data", axesA=axA, axesB=axB, color="black"
)
con2 = ConnectionPatch(
xyA=(500, 0), xyB=(500, 0.1), coordsA="data", coordsB="data",
axesA=axA, axesB=axB, color="black"
)
print(trans[0]((0, 0)))
axB.add_artist(con1)
axB.add_artist(con2)
plt.show()
The line on the left is supposed to go to (0, 0) of the upper axis, but it doesn't. The same happens btw if I try to convert to axes coordinates, so there seems be to something fundamentally wrong.
The reason why I want to use figure coords is because I don't actually want the line to end at (0, 0), but slightly below the '0' tick label. I cannot do that in data coords so I tried to swap to figure coods.
Adapting the second example from this tutorial code, it seems no special combinations of transforms is needed. You can use coordsA=axA.get_xaxis_transform(), if x is in data coordinates and y in figure coordinates. Or coordsA=axA.transData if x and y are both in data coordinates. Note that when using data coordinates you are allowed to give coordinates outside the view window; by default a ConnectionPatch isn't clipped.
The following code uses z-order to put the connection lines behind the rest and adds a semi-transparent background to the tick labels of axA (avoiding that the text gets crossed out by the connection line):
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import ConnectionPatch
t = [0, 6.297, 39.988, 46.288, 79.989, 86.298, 120.005, 126.314, 159.994, 166.295, 200.012, 206.314, 240.005, 246.301, 280.05, 286.35, 320.032, 326.336, 360.045, 366.345, 480.971, 493.146, 1080.117, 1093.154, 1681.019, 1692.266, 2281.008, 2293.146, 2881.014, 2893.178, 3480.988, 3493.149, 4080.077, 4092.298, 4681.007, 4693.275, 5281.003, 5293.183, 5881.023, 5893.188, 6481.002, 6492.31]
y = np.zeros(len(t))
fig, (axA, axB) = plt.subplots(2, 1)
fig.tight_layout()
for ax in (axA, axB):
ax.set_frame_on(False)
ax.axes.get_yaxis().set_visible(False)
axA.plot(t[:22], y[:22], c='black')
axA.plot(t[:22], y[:22], 'o', c='#ff4500')
axA.set_ylim((-0.05, 1))
axB.plot(t, y, c='black')
axB.plot(t, y, 'o', c='#ff4500')
axB.set_ylim((-0.05, 1))
pos1 = axB.get_position()
pos2 = [pos1.x0, pos1.y0 + 0.3, pos1.width, pos1.height]
axB.set_position(pos2)
con1 = ConnectionPatch(xyA=(0, 0.02), coordsA=axA.get_xaxis_transform(),
xyB=(0, 0.05), coordsB=axB.get_xaxis_transform(),
# linestyle='--', color='black', zorder=-1)
linestyle='--', color='darkgrey', zorder=-1)
con2 = ConnectionPatch(xyA=(500, 0.02), coordsA=axA.get_xaxis_transform(),
xyB=(500, 0.05), coordsB=axB.get_xaxis_transform(),
linestyle='--', color='darkgrey', zorder=-1)
fig.add_artist(con1)
fig.add_artist(con2)
for lbl in axA.get_xticklabels():
lbl.set_backgroundcolor((1, 1, 1, 0.8))
plt.show()
Possible answer to your last comment:
As you're dealing with figure coords, these can change depending on your screen resolution. So if your other machine has a different res then this could be why its changing. You'll have to look into using Axes coords instead if you don't want these random changes.

Constructing 3D plot with 'negative' Z-axis below zero

We are working on a project in which we would like to construct 3D plots. Python is our main language, and therefore naturally chose to use matplotlib as our plotting library. Various tutorials (here, here and here) have teached us how to perform 3D plotting using the mplot3d functionality of matplotlib. Consequently, various StackOverflow answers helped us to move the origin of each of the axes to different locations (here and here).
After searching for a couple of hours we have a hard time finding an answer to our next question, however. We would like to have a positive and negative side for our Z-axis (see the picture below, orange part). This would mean that data points with Z>0 are above origin, and with Z<0 are below origin. We tried several things, but our Z-axis origin always ends up at the most negative value of our dataset.
With great help of the community here, we've come to a minimal example showcasing what I want. The code I used is:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(10, 10))
ax = fig.gca(projection='3d')
# Some settings
sn = 2 #limits in x,y,z
n = 50 #number of sample points
x1, x2 = 0, sn
y1, y2 = 0, sn
z1, z2 = -sn, sn
# Data for points
xs = (x2 - x1)*np.random.rand(n) + x1
ys = (y2 - y1)*np.random.rand(n) + y1
zs = (z2 - z1)*np.random.rand(n) + z1
# Points with z >= 0, plotted in green
ax.scatter(xs[zs>=0], ys[zs>=0], zs[zs>=0], color='green')
# Points with z < 0, plotted in red
ax.scatter(xs[zs<0], ys[zs<0], zs[zs<0], color='red')
# Data for plotting plane x|y|z=0 within the domain
tmp = np.linspace(0, sn, 8)
x, y = np.meshgrid(tmp, tmp)
z = 0*x
# Plot grid lines
ax.plot([0, sn], [0, 0], [0, 0], color='black')
ax.plot([0, 0], [0, sn], [0, 0], color='black')
ax.plot([0, 0], [0, 0], [-sn, sn], color='black')
# Maximum tick labels for X, Y, and Z (x3)
ax.plot([sn, sn], [0, 0], [-.05, .02], color='black')
ax.plot([0, 0], [sn, sn], [-.05, .02], color='black')
ax.plot([-.05, .02], [-.05, .02], [sn, sn], color='black')
ax.plot([-.05, .02], [-.05, .02], [-sn, -sn], color='black')
ax.plot([-.05, .02], [-.05, .02], [0, 0], color='black')
# Label texts
ax.text(sn/2, 0, -.2*sn, 'xlabel', 'x', ha='center')
ax.text(0, sn/2, -.2*sn, 'ylabel', 'y', ha='center')
ax.text(-.1*sn, 0, 0, 'zlabel', 'z', ha='center')
# Maximum limit text for X, Y and Z (x3)
ax.text(sn, 0, -.1*sn, f'{sn}', 'x', ha='center')
ax.text(0, sn, -.1*sn, f'{sn}', 'y', ha='center')
ax.text(-.05*sn, -.05*sn, 0, '0', 'x', ha='center')
ax.text(-.05*sn, -.05*sn, sn, f'{sn}', 'x', ha='right')
ax.text(-.05*sn, -.05*sn, -sn, f'{-sn}', 'x', ha='center')
# Set limits of the 3D display
ax.set_xlim3d([-sn, sn])
ax.set_ylim3d([-sn, sn])
ax.set_zlim3d([-sn, sn])
ax.set_axis_off()
plt.show()
This results in the graph below:
Although I am very happy with the outcome, this is still kind of 'hacky' solution with manually drawing the axis, ticks and labels. If anybody would have a solution in which we can re-design the axis from the mplot3d API that would be very helpful.
(Swatchai creates this as a community wiki):
Sometime, discussion without some runnable code to play/experiment with is not the best approach to get a solution. Here I propose this code to use for further discussion.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(10, 10))
ax = fig.gca(projection='3d')
# Quivers for axes x,y,z from (0,0,0)
quiver1 = ax.quiver([0],[0],[0],[2],[0],[0], colors='r')
quiver2 = ax.quiver([0],[0],[0],[0],[2],[0], colors='g')
quiver3 = ax.quiver([0],[0],[0],[0],[0],[2], colors='b')
# Some settings
sn = 2 #limits in x,y,z
n = 50 #number of sample points
x1, x2 = -sn, sn
y1, y2 = -sn, sn
z1, z2 = -sn, sn
# Data for points
xs = (x2 - x1)*np.random.rand(n) + x1
ys = (y2 - y1)*np.random.rand(n) + y1
zs = (z2 - z1)*np.random.rand(n) + z1
# Points with z >= 0, plotted in green
ax.scatter(xs[zs>=0], ys[zs>=0], zs[zs>=0], color='green')
# Points with z < 0, plotted in red
ax.scatter(xs[zs<0], ys[zs<0], zs[zs<0], color='red')
# Data for plotting plane x|y|z=0 within the domain
tmp = np.linspace(0, sn, 8)
x,y = np.meshgrid(tmp,tmp)
z = 0*x
ax.plot_surface(z,x,y, alpha=0.15, color='red') # plot the plane x=0
ax.plot_surface(x,z,y, alpha=0.15, color='green') # plot the plane y=0
ax.plot_surface(x,y,z, alpha=0.15, color='blue') # plot the plane z=0
# Set limits of the 3D display
ax.set_xlim3d([-sn, sn])
ax.set_ylim3d([-sn, sn])
ax.set_zlim3d([-sn, sn])
# Set labels at the 3d box/frame
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()
Output plot:

Python matplotlib - Combine categorical background along with scatter plot

I am trying to figure out a right library in Python to create a complex plot which looks something like this:
The plot background is classified into 3 regions (Yellow, Red, Green) based on conditions of X and Y. For Example :
For Green area: (X<=1 and Y<=1) OR (X<0.5)
For Yellow area: (0.5<X<1 and Y>1) OR (1<X<1.5 and 1<Y<3) OR (1.5<X<2 and Y<2)
Similarly for the Red area....
These conditions remain the same throughout my application.
I have the coordinates in a csv file and know how to plot the scatter plot. But I am stuck because of the background color code.
Is there a Python library that I can use to plot the scatter plot along with these grid colors at the back. I checked many sites and questions but unfortunately found nothing useful/related.
Any suggestions/help is appreciated.
You can use matplotlib's imshow() with a 2D array. The coordinates of the 2D array can be created using np.meshgrid(). These coordinates will be the lower left vertices of each grid cell. They can address into the 2D array, e.g. with [((X < 1) & (Y < 1)) | (X < 0.5)]. Filling the 2D arrays with 0, 1 and 2 at the appropriate locations allows to create the background.
Matplotlib's scatter() will place scatter dots.
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from matplotlib.ticker import MultipleLocator
import numpy as np
xvals = np.random.uniform(0, 3, 50)
yvals = np.random.uniform(0, 4.2, 50)
X1d = np.arange(0, 3.0001, 0.25)
Y1d = np.arange(0, 4.2001, 0.20)
X, Y = np.meshgrid(X1d, Y1d)
backgr = np.full_like(X, 2)
backgr[((X < 1.5) & (Y < 3)) | ((X < 2) & (Y < 2)) | (X < 1)] = 1
backgr[((X < 1) & (Y < 1)) | (X < 0.5)] = 0
fig, ax = plt.subplots()
ax.scatter(xvals, yvals, color='black')
cmap = ListedColormap(['lime', 'gold', 'crimson'])
ax.imshow(backgr[:-1, :-1], cmap=cmap, alpha=0.2, extent=[0, X1d[-1], 0, Y1d[-1]], origin='lower', aspect='auto')
ax.set_xticks(X1d, minor=True)
ax.set_yticks(Y1d, minor=True)
ax.xaxis.set_major_locator(MultipleLocator(1))
ax.yaxis.set_major_locator(MultipleLocator(1))
ax.grid(True, which='both', lw=1, ls=':', color='black')
plt.show()

How to fill areas with color gradient in matplotlib?

I have a model which divides dots on XY plane into 2 parts: it returns a range of values (0, -1) for every input pair XY above the orange and below the blue line, between the lines it returns (0, +1). How can I fill areas with color gradient according to these values? It should be very common thing to do but I didn't managed find anything more useful than fill_between function.
You could create a 2D array depending on your model values (named z in the code below). And then color the pixels using that z-value, making sure to set vmin and vmax symmetrically to make the central color mark the zero of your z. If the colors are too bright, an appropriate alpha can help to soften them.
from matplotlib import pyplot as plt
import numpy as np
N = 20
red_dots_x = np.concatenate([np.random.normal(0.5, 0.1, N), np.random.normal(-0.5, 0.1, N)])
red_dots_y = np.concatenate([np.random.normal(-0.5, 0.1, N), np.random.normal(0.5, 0.1, N)])
blue_dots_x = np.concatenate([np.random.normal(0.5, 0.1, N), np.random.normal(-0.5, 0.1, N)])
blue_dots_y = np.concatenate([np.random.normal(0.5, 0.1, N), np.random.normal(-0.5, 0.1, N)])
plt.plot(red_dots_x, red_dots_y, 'ro', ls='')
plt.plot(blue_dots_x, blue_dots_y, 'b*', ls='')
x0, x1 = plt.xlim()
y0, y1 = plt.ylim()
x, y = np.meshgrid(np.linspace(x0, x1, 10), np.linspace(y0, y1, 10))
z = abs(x - y) - 0.6
plt.imshow(z, cmap='bwr', vmin=-1, vmax=1, interpolation='bilinear', alpha=0.4,
extent=[x0, x1, y0, y1], origin='lower')
plt.show()

How can I create custom break points in a matplotlib colorbar?

I'm borrowing an example from the matplotlib custom cmap examples page:
https://matplotlib.org/examples/pylab_examples/custom_cmap.html
This produces the same image with different numbers of shading contours, as specified in the number of bins: n_bins:
https://matplotlib.org/_images/custom_cmap_00.png
However, I'm interested not only in the number of bins, but the specific break points between the color values. For example, when nbins=6 in the top right subplot, how can I specify the ranges of the bins to such that the shading is filled in these custom areas:
n_bins_ranges = ([-10,-5],[-5,-2],[-2,-0.5],[-0.5,2.5],[2.5,7.5],[7.5,10])
Is it also possible to specify the inclusivity of the break points? For example, I'd like to specify in the range between -2 and 0.5 whether it's -2 < x <= -0.5 or -2 <= x < -0.5.
EDIT WITH ANSWER BELOW:
Using the accepted answer below, here is code that plots each step including finally adding custom colorbar ticks at the midpoint. Note I can't post an image since I'm a new user.
Set up data and 6 color bins:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
# Make some illustrative fake data:
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
# Create colormap with 6 discrete bins
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] # R -> G -> B
n_bin = 6
cmap_name = 'my_list'
cm = matplotlib.colors.LinearSegmentedColormap.from_list(
cmap_name, colors, N=n_bin)
Plot different options:
# Set up 4 subplots
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)
# Plot 6 bin figure
im = axs[0,0].imshow(Z, interpolation='nearest', origin='lower', cmap=cm)
axs[0,0].set_title("Original 6 Bin")
fig.colorbar(im, ax=axs[0,0])
# Change the break points
n_bins_ranges = [-10,-5,-2,-0.5,2.5,7.5,10]
norm = matplotlib.colors.BoundaryNorm(n_bins_ranges, len(n_bins_ranges))
im = axs[0,1].imshow(Z, interpolation='nearest', origin='lower', cmap=cm, norm=norm)
axs[0,1].set_title("Custom Break Points")
fig.colorbar(im, ax=axs[0,1])
# Arrange color labels by data interval (not colors)
im = axs[1,0].imshow(Z, interpolation='nearest', origin='lower', cmap=cm, norm=norm)
axs[1,0].set_title("Linear Color Distribution")
fig.colorbar(im, ax=axs[1,0], spacing="proportional")
# Provide custom labels at color midpoints
# And change inclusive equality by adding arbitrary small value
n_bins_ranges_arr = np.asarray(n_bins_ranges)+1e-9
norm = matplotlib.colors.BoundaryNorm(n_bins_ranges, len(n_bins_ranges))
n_bins_ranges_midpoints = (n_bins_ranges_arr[1:] + n_bins_ranges_arr[:-1])/2.0
im = axs[1,1].imshow(Z, interpolation='nearest', origin='lower', cmap=cm ,norm=norm)
axs[1,1].set_title("Midpoint Labels\n Switched Equal Sign")
cbar=fig.colorbar(im, ax=axs[1,1], spacing="proportional",
ticks=n_bins_ranges_midpoints.tolist())
cbar.ax.set_yticklabels(['Red', 'Brown', 'Green 1','Green 2','Gray Blue','Blue'])
plt.show()
You can use a BoundaryNorm as follows:
import matplotlib.pyplot as plt
import matplotlib.colors
import numpy as np
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
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] # R -> G -> B
n_bin = 6 # Discretizes the interpolation into bins
n_bins_ranges = [-10,-5,-2,-0.5,2.5,7.5,10]
cmap_name = 'my_list'
fig, ax = plt.subplots()
# Create the colormap
cm = matplotlib.colors.LinearSegmentedColormap.from_list(
cmap_name, colors, N=n_bin)
norm = matplotlib.colors.BoundaryNorm(n_bins_ranges, len(n_bins_ranges))
# Fewer bins will result in "coarser" colomap interpolation
im = ax.imshow(Z, interpolation='nearest', origin='lower', cmap=cm, norm=norm)
ax.set_title("N bins: %s" % n_bin)
fig.colorbar(im, ax=ax)
plt.show()
Or, if you want proportional spacing, i.e. the distance between colors according to their values,
fig.colorbar(im, ax=ax, spacing="proportional")
As the boundary norm documentation states
If b[i] <= v < b[i+1]
then v is mapped to color j; as i varies from 0 to len(boundaries)-2, j goes from 0 to ncolors-1.
So the colors are always chosen as -2 <= x < -0.5, in order to obtain the equal sign on the other side you would need to supply
something like n_bins_ranges = np.array([-10,-5,-2,-0.5,2.5,7.5,10])-1e-9

Categories