Matplotlib fill_between invert? - python

I am trying to fill the regions below two intersecting lines and above both lines, using matplotlib. I can fill between both lines, but haven't found a simple way to invert the region obtained previously. The only workaround I have is to created some extra functions (a low one and a min one for the bottom, and the equivalents for the top), which is a bit cumbersome and requires manual inputs (see below). Any better solutions?
import numpy as np
import matplotlib.pyplot as plt
# Doesn't work
def f1(x): return 32.0 * x + 2.0
def f2(x): return -55.0 * x
xRng=[-1, 1]
plt.plot(xRng, [f1(x) for x in xRng], 'b-')
plt.plot(xRng, [f2(x) for x in xRng], 'r-')
plt.fill_between(xRng, [f1(x) for x in xRng], [f2(x) for x in xRng], color='g') # Would like the fill inverted
plt.title('Not good'); plt.show()
# Works, but clumsy
def fLo(x): return -100
def fHi(x): return 100
def fMin(x): return min(f1(x), f2(x))
def fMax(x): return max(f1(x), f2(x))
xRng=np.linspace(-1, 1, 100)
plt.plot(xRng, [f1(x) for x in xRng], 'b-')
plt.plot(xRng, [f2(x) for x in xRng], 'r-')
plt.fill_between(xRng, [fMin(x) for x in xRng], [fLo(x) for x in xRng], color='g')
plt.fill_between(xRng, [fMax(x) for x in xRng], [fHi(x) for x in xRng], color='g')
plt.title('Complicated'); plt.show()
EDIT: swapping BG and FG colors as suggested by #Mad Physicist will work if basic case, but not if there are several such areas to overlay

It appears that fill_between does not do well with infinite values (e.g. Fill area under curve in matlibplot python on log scale). However, if you're only trying to plot those specific lines, you could just invert the colors of the plot:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-1, 1, 100)
y1 = 32.0 * x + 2.0
y2 = -55.0 * x
fig, ax = plt.subplots()
ax.set_facecolor('g')
ax.plot(x, y1, 'b-')
ax.plot(x, y2, 'r-')
ax.fill_between(x, y1, y2, color='w')
ax.set_xlim(x.min(), x.max())
plt.show()
This is very hacky and won't work well with interactive plots, but it will display the plot you want, hopefully fairly painlessly.
A slightly better approach might be to set the background of only the region covered by x to a green patch:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-1, 1, 100)
y1 = 32.0 * x + 2.0
y2 = -55.0 * x
fig, ax = plt.subplots()
ax.plot(x, y1, 'b-')
ax.plot(x, y2, 'r-')
ax.axvspan(x.min(), x.max(), color='g')
ax.fill_between(x, y1, y2, color='w')
ax.set_xlim(x.min(), x.max())
plt.show()

Related

How to fill plotted graph in python with simbol (it can be text '#' or '*)

I have done python code to draw the heart and stuck when i comes to fill this graph with text.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-1, 1, 100)
plt.title('Heart', fontsize=24)
y = np.sqrt(1-np.power(x, 2))+np.power(np.square(x), 1/3)
y1= -np.sqrt(1-np.power(x, 2))+np.power(np.square(x), 1/3)
plt.plot(x, y, 'b')
plt.plot(x, y1, 'b')
my_x_ticks = np.arange(-2, 2.5, 0.5)
my_y_ticks = np.arange(-2, 2.5, 0.5)
plt.xticks(my_x_ticks)
plt.yticks(my_y_ticks)
plt.fill_between(x, y, y1,color="red",alpha=0.9)
plt.grid(False)`enter code here`
plt.show()
If i understood correctly you want to put a text field on your plot:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-1, 1, 100)
plt.title('Heart', fontsize=24)
y = np.sqrt(1-np.power(x, 2))+np.power(np.square(x), 1/3)
y1 = -np.sqrt(1-np.power(x, 2))+np.power(np.square(x), 1/3)
plt.plot(x, y, 'b')
plt.plot(x, y1, 'b')
# this is your text; first argument is x-coordinate, second argument y-coordinate
plt.text(0, 0, "My Text", fontsize=18, color="r", va="center", ha="center", backgroundcolor="w")
my_x_ticks = np.arange(-2, 2.5, 0.5)
my_y_ticks = np.arange(-2, 2.5, 0.5)
plt.xticks(my_x_ticks)
plt.yticks(my_y_ticks)
plt.fill_between(x, y, y1, color="red",alpha=0.9)
plt.grid(False)
plt.show()

Draw the profile of a scatter plot

I have a scatter plot of Lc and Fc values (please, refer to plot1).
Lc= [360.66832393 388.26294316 392.9410819 ... 384.31751584 403.52581547
384.22929343]
Fc= [77.3294787 47.5926941 44.53032575 ... 50.44012265 38.99666318
50.54763385]
plot.scatter(Lc, Fc)
I would like to draw the Fc profile of this scatter plot as shown in plot2. Does anyone have an efficient way to do it?
Here is an idea drawing a Gaussian curve through each of the points and then take the maximum of these curves. You might want to experiment with the curve widths.
import matplotlib.pyplot as plt
import numpy as np
low_lim = 30
fc = np.random.rand(120) * np.random.rand(120) * 120
fc = fc[fc > low_lim]
lc = np.random.uniform(50, 250, len(fc))
x = np.linspace(0, 300, 5000)
sigma = 15
ys = np.exp(- np.power((x.reshape(-1, 1) - lc) / sigma, 2) / 2) * fc
ymax = ys.max(axis=1)
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(15, 4))
for ax in (ax1, ax2):
if ax == ax1:
ax.plot(x, ymax, color='black', ls=':', lw=3)
for l, f, y in zip(lc, fc, ys.T):
ax.plot(x, y)
ax.fill_between(x, 0, y, color='r', alpha=0.05)
else:
ax.plot(x, ymax, color='black', lw=2)
ax.fill_between(x, 0, ymax, color='r', alpha=0.2)
ax.scatter(lc, fc, color='darkorange')
ax.axhline(low_lim, ls='--', color='skyblue')
ax.set_ylim(ymin=0)
ax.margins(x=0)
plt.tight_layout()
plt.show()
Here is an attempt to smooth out the sharp corners, which might or might not work with your data. The effect is only very local; trying to smooth out more resulted in also losing the general shape.
from scipy.special import softmax
ys = np.exp(- np.power((x.reshape(-1, 1) - lc) / sigma, 2) / 2) * fc
softmax_weights = softmax(np.power(ys, 0.8), axis=1)
ymax = np.sum(ys * softmax_weights, axis=1)

Fill between subplots with matplotlib cmap

I have 2 line plots on the same figure, plotted from pandas dataframes.
I want to fill between them with a gradient/colour map of sorts.
I understand I can do this with a cmap, only it will not work for me (see code below).
General example I found are filling between x axis and line, i do not want that and also i am interested in simplest solution possible for this as i am a begginer at this and complicated, though maybe better code will just make it more confusing honestly.
Code for which fill is plain blue:
import matplotlib.pyplot as plt
import pandas as pd
ax = plt.gca()
df0.plot(kind='line', x='something', y='other', color='orange', ax=ax, legend=False, figsize=(20,10))
df1.plot(kind='line', x='something', y='other2', color='c', ax=ax, legend=False, figsize=(20,10))
ax.fill_between(x=df0['daysInAYear'], y1=df0['other'], y2 = df1['other2'], alpha=0.2, cmap=plt.cm.get_cmap("winter"))
plt.show()
EDIT/UPDATE: DATA EXAMPLE
other is ALWAYS >= other2
other other2 something (same for both)
15.6 -16.0 1
13.9 -26.7 2
13.3 -26.7 3
10.6 -26.1 4
12.8 -15.0 5
Final graph example:
I would like the fill to go from orange on top to blue at the bottom
Edit
In response to the edited question, here is an alternative approach which does the gradient vertically but doesn't use imshow.
import matplotlib.pyplot as plt
from matplotlib import colors, patches
import numpy as np
import pandas as pd
n = 100
nc = 100
x = np.linspace(0, np.pi*5, n)
y1 = [-50.0]
y2 = [50.0]
for ii in range(1, n):
y1.append(y1[ii-1] + (np.random.random()-0.3)*3)
y2.append(y2[ii-1] + (np.random.random()-0.5)*3)
y1 = np.array(y1)
y2 = np.array(y2)
z = np.linspace(0, 10, nc)
normalize = colors.Normalize(vmin=z.min(), vmax=z.max())
cmap = plt.cm.get_cmap('winter')
fig, ax = plt.subplots(1)
for ii in range(len(df['x'].values)-1):
y = np.linspace(y1[ii], y2[ii], nc)
yn = np.linspace(y1[ii+1], y2[ii+1], nc)
for kk in range(nc - 1):
p = patches.Polygon([[x[ii], y[kk]],
[x[ii+1], yn[kk]],
[x[ii+1], yn[kk+1]],
[x[ii], y[kk+1]]], color=cmap(normalize(z[kk])))
ax.add_patch(p)
plt.plot(x, y1, 'k-', lw=1)
plt.plot(x, y2, 'k-', lw=1)
plt.show()
The idea here being similar to that in my original answer, except the trapezoids are divided into nc pieces and each piece is colored separately. This has the advantage of scaling correctly for varying y1[ii], y2[ii] distances, as shown in this comparison,
It does, however, have the disadvantages of being much, much slower than imshow or the horizontal gradient method and of being unable to handle 'crossing' correctly.
The code to generate the second image in the above comparison:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import patches
from matplotlib.path import Path
x = np.linspace(0, 10, n)
y1 = [-50.0]
y2 = [50.0]
for ii in range(1, n):
y1.append(y1[ii-1] + (np.random.random()-0.2)*3)
y2.append(y2[ii-1] + (np.random.random()-0.5)*3)
y1 = np.array(y1)
y2 = np.array(y2)
verts = np.vstack([np.stack([x, y1], 1), np.stack([np.flip(x), np.flip(y2)], 1)])
path = Path(verts)
patch = patches.PathPatch(path, facecolor='k', lw=2, alpha=0.0)
plt.gca().add_patch(patch)
plt.imshow(np.arange(10).reshape(10,-1), cmap=plt.cm.winter, interpolation="bicubic",
origin='upper', extent=[0,10,-60,60], aspect='auto', clip_path=patch,
clip_on=True)
plt.show()
Original
This is a bit of a hack, partly based on the answers in this question. It does seem to work fairly well but works best with higher density along the x axis. The idea is to call fill_between separately for each trapezoid corresponding to x pairs, [x[ii], x[ii+1]]. Here is a complete example using some generated data
import matplotlib.pyplot as plt
from matplotlib import colors
import numpy as np
import pandas as pd
n = 1000
X = np.linspace(0, np.pi*5, n)
Y1 = np.sin(X)
Y2 = np.cos(X)
Z = np.linspace(0, 10, n)
normalize = colors.Normalize(vmin=Z.min(), vmax=Z.max())
cmap = plt.cm.get_cmap('winter')
df = pd.DataFrame({'x': X, 'y1': Y1, 'y2': Y2, 'z': Z})
x = df['x'].values
y1 = df['y1'].values
y2 = df['y2'].values
z = df['z'].values
for ii in range(len(df['x'].values)-1):
plt.fill_between([x[ii], x[ii+1]], [y1[ii], y1[ii+1]],
[y2[ii], y2[ii+1]], color=cmap(normalize(z[ii])))
plt.plot(x, y1, 'k-', x, y2, 'k-')
plt.show()
This can be generalized to a 2 dimensional color grid but would require non-trivial modification

3D plot of the CONE using matplotlib

I'm looking for help to draw a 3D cone using matplotlib.
My goal is to draw a HSL cone, then base on the vertex coordinats i will select the color.
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
theta1 = np.linspace(0, 2*np.pi, 100)
r1 = np.linspace(-2, 0, 100)
t1, R1 = np.meshgrid(theta1, r1)
X1 = R1*np.cos(t1)
Y1 = R1*np.sin(t1)
Z1 = 5+R1*2.5
theta2 = np.linspace(0, 2*np.pi, 100)
r2 = np.linspace(0, 2, 100)
t2, R2 = np.meshgrid(theta2, r2)
X2 = R2*np.cos(t2)
Y2 = R2*np.sin(t2)
Z2 = -5+R2*2.5
ax.set_xlabel('x axis')
ax.set_ylabel('y axis')
ax.set_zlabel('z axis')
# ax.set_xlim(-2.5, 2.5)
# ax.set_ylim(-2.5, 2.5)
# ax.set_zlim(0, 5)
ax.set_aspect('equal')
ax.plot_surface(X1, Y1, Z1, alpha=0.8, color="blue")
ax.plot_surface(X2, Y2, Z2, alpha=0.8, color="blue")
# ax.plot_surface(X, Y, Z, alpha=0.8)
#fig. savefig ("Cone.png", dpi=100, transparent = False)
plt.show()
HSL CONE
My cone
So my question now is how to define color of each element.
i have found a solution, maybe it will be usefull for others.
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
import colorsys
from matplotlib.tri import Triangulation
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
n_angles = 80
n_radii = 20
# An array of radii
# Does not include radius r=0, this is to eliminate duplicate points
radii = np.linspace(0.0, 0.5, n_radii)
# An array of angles
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
# Repeat all angles for each radius
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
# Convert polar (radii, angles) coords to cartesian (x, y) coords
# (0, 0) is added here. There are no duplicate points in the (x, y) plane
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())
# Pringle surface
z = 1+-np.sqrt(x**2+y**2)*2
print(x.shape, y.shape, angles.shape, radii.shape, z.shape)
# NOTE: This assumes that there is a nice projection of the surface into the x/y-plane!
tri = Triangulation(x, y)
triangle_vertices = np.array([np.array([[x[T[0]], y[T[0]], z[T[0]]],
[x[T[1]], y[T[1]], z[T[1]]],
[x[T[2]], y[T[2]], z[T[2]]]]) for T in tri.triangles])
x2 = np.append(0, (radii*np.cos(angles)).flatten())
y2 = np.append(0, (radii*np.sin(angles)).flatten())
# Pringle surface
z2 = -1+np.sqrt(x**2+y**2)*2
# NOTE: This assumes that there is a nice projection of the surface into the x/y-plane!
tri2 = Triangulation(x2, y2)
triangle_vertices2 = np.array([np.array([[x2[T[0]], y2[T[0]], z2[T[0]]],
[x2[T[1]], y2[T[1]], z2[T[1]]],
[x2[T[2]], y2[T[2]], z2[T[2]]]]) for T in tri2.triangles])
triangle_vertices = np.concatenate([triangle_vertices, triangle_vertices2])
midpoints = np.average(triangle_vertices, axis=1)
def find_color_for_point(pt):
c_x, c_y, c_z = pt
angle = np.arctan2(c_x, c_y)*180/np.pi
if (angle < 0):
angle = angle + 360
if c_z < 0:
l = 0.5 - abs(c_z)/2
#l=0
if c_z == 0:
l = 0.5
if c_z > 0:
l = (1 - (1-c_z)/2)
if c_z > 0.97:
l = (1 - (1-c_z)/2)
col = colorsys.hls_to_rgb(angle/360, l, 1)
return col
facecolors = [find_color_for_point(pt) for pt in midpoints] # smooth gradient
# facecolors = [np.random.random(3) for pt in midpoints] # random colors
coll = Poly3DCollection(
triangle_vertices, facecolors=facecolors, edgecolors=None)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.add_collection(coll)
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)
ax.set_zlim(-1, 1)
ax.elev = 50
plt.show()
Inspired from Jake Vanderplas with Python Data Science Handbook, when you are drawing some 3-D plot whose base is a circle, it is likely that you would try:
# Actually not sure about the math here though:
u, v = np.mgrid[0:2*np.pi:100j, 0:np.pi:20j]
x = np.cos(u)*np.sin(v)
y = np.sin(u)*np.sin(v)
and then think about the z-axis. Since viewing from the z-axis the cone is just a circle, so the relationships between z and x and y is clear, which is simply: z = np.sqrt(x ** 2 + y ** 2). Then you can draw the cone based on the codes below:
from mpl_toolkits import mplot3d
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
def f(x, y):
return np.sqrt(x ** 2 + y ** 2)
fig = plt.figure()
ax = plt.axes(projection='3d')
# Can manipulate with 100j and 80j values to make your cone looks different
u, v = np.mgrid[0:2*np.pi:100j, 0:np.pi:80j]
x = np.cos(u)*np.sin(v)
y = np.sin(u)*np.sin(v)
z = f(x, y)
ax.plot_surface(x, y, z, cmap=cm.coolwarm)
# Some other effects you may want to try based on your needs:
# ax.plot_surface(x, y, -z, cmap=cm.coolwarm)
# ax.scatter3D(x, y, z, color="b")
# ax.plot_wireframe(x, y, z, color="b")
# ax.plot_wireframe(x, y, -z, color="r")
# Can set your view from different angles.
ax.view_init(azim=15, elev=15)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
plt.show()
ax.set_ylabel("y")
ax.set_zlabel("z")
plt.show()
And from my side, the cone looks like:
and hope it helps.

Python Taylor series sin function graph

I'm trying to draw a Taylor series sin(x) graph using python with Jupyter notebook. I created a short function. The graph will appear correctly until y2, but it will fail at y3. It is difficult to draw a graph with a value of x = 2.7 in y3. I don't know how to fix y3.
This is my code:
import numpy as np
import matplotlib.pyplot as plt
import numpy as np
def f(x) :
result = x - x**3/6 + x**5/120
return result
x = np.linspace(0.0, 7.0, 100)
y = np.sin(x)
y2 = x - x**3/6 + x**5/120
y3 = f(2.7)
plt.title("taylor sin graph")
plt.xlim(0, 7+0.2)
plt.ylim(-5, 5+1)
plt.plot(x, y, label='sin(x)')
plt.plot(x, y2, label='x=0')
plt.plot(x, y3, label='x=2.7')
plt.legend()
plt.show()
I want to add y3 here:
After your comment, it got clarified that you do not need a single point but a horizontal line. In that case you can simply input an x-mesh which has the same value 2.7.
To do so, you first define an array containing values 2.7 by using np.ones(100) * 2.7 and then just pass it to the function.
y3 = f(2.7*np.ones(100))
plt.plot(x, y3, label='x=2.7')
For plotting a single point at x=2.7, there are two ways (among possible others).
First option is to just specify the two x-y numbers and plot using a marker as
plt.plot(2.7, y3, 'bo', label='x=2.7')
Second option is to use plt.scatter. s=60 is just to have a big marker.
plt.scatter(2.7, y3, s=60, label='x=2.7')
import numpy as np
import matplotlib.pyplot as plt
import numpy as np
def f(x) :
result = x - x**3/6 + x**5/120
return result
x = np.linspace(0.0, 7.0, 100)
y = np.sin(x)
y2 = x - x**3/6 + x**5/120
y3 = f(2.7)
plt.title("taylor sin graph")
plt.xlim(0, 7+0.2)
plt.ylim(-5, 5+1)
plt.plot(x, y, label='sin(x)')
plt.plot(x, y2, label='x=0')
plt.plot(2.7, y3, label='x=2.7', marker=11)
plt.legend()
plt.show()
You have to add point - not an array in x-axis and scalar on y-axis.
I think
plt.plot([2.7], [y3], '-o', label='x=2.7')
would work. You can't plot(x,y3) when x is a linspace and y3 is just one number.
Also, Taylor approximation of sin function works only in the interval (-pi, pi).

Categories