Matplotlib/pyplot: easy way for conditional formatting of linestyle? - python

Let's say I want to plot two solid lines that cross each other and I want to plot line2 dashed only if it is above line 1. The lines are on the same x-grid. What is the best/simplest way to achieve this? I could split the data of line2 into two corresponding arrays before I plot, but I was wondering if there is a more direct way with some kind of conditional linestyle formatting?
Minimal Example:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0,5,0.1)
y1 = 24-5*x
y2 = x**2
plt.plot(x,y1)
plt.plot(x,y2)#dashed if y2 > y1?!
plt.show()
There were related questions for more complex scenarios, but I am looking for the easiest solution for this standard case. Is there a way to do this directly inside plt.plot()?

You could try something like this:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0,5,0.1)
y1 = 24-5*x
y2 = x**2
xs2=x[y2>y1]
xs1=x[y2<=y1]
plt.plot(x,y1)
plt.plot(xs1,y2[y2<=y1])
plt.plot(xs2,y2[y2>y1],'--')#dashed if y2 > y1?!
plt.show()

#Sameeresque solved it nicely.
This was my take:
import numpy as np
import matplotlib.pyplot as plt
def intersection(list_1, list_2):
shortest = list_1 if len(list_1) < len(list_2) else list_2
indexes = []
for i in range(len(shortest)):
if list_1[i] == list_2[i]:
indexes.append(i)
return indexes
plt.style.use("fivethirtyeight")
x = np.arange(0, 5, 0.1)
y1 = 24 - 5*x
y2 = x**2
intersection_point = intersection(y1, y2)[0] # In your case they only intersect once
plt.plot(x, y1)
x_1 = x[:intersection_point+1]
x_2 = x[intersection_point:]
y2_1 = y2[:intersection_point+1]
y2_2 = y2[intersection_point:]
plt.plot(x_1, y2_1)
plt.plot(x_2, y2_2, linestyle="dashed")
plt.show()
Same princile as #Sammeresque, but I think his solution is simpler.

Related

Color all points between 2 x values in matplotlib efficiently

I wish to color all points in a scatter plot between 2 x values a different color to the rest of the plot. I understand I can do something such as the following:
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(10)
x = np.linspace(1,50,50)
y1 = np.random.rand(50)
y2 = np.random.rand(50)
want_to_color = [20,40]
color_cycle = plt.rcParams['axes.prop_cycle'].by_key()['color']
c_array1 = []
c_array2 = []
for i in range(50):
if i > want_to_color[0] and i < want_to_color[1]:
c_array1.append('r')
c_array2.append('r')
else:
c_array1.append(color_cycle[0])
c_array2.append(color_cycle[1])
plt.scatter(x, y1, c=c_array1)
plt.scatter(x, y2, c=c_array2)
plt.show()
Which produces:
But as you can see, this very memory inefficient, needing a list the size of the data set in order to color all points, when it could simply just be 2 x values. I just wanted to know if there is a more efficient way of doing this.
EDIT:
I just thought of a method of doing this using generators, which would be a really nice clean solution. Unfortauntely:
RuntimeError: matplotlib does not support generators as input
Just because I wrote it anyway, here's the code I tried to use:
import matplotlib.pyplot as plt
import numpy as np
col = lambda x, x1, x2, n : map(lambda v: 'r' if v > x1 and v < x2 else plt.rcParams['axes.prop_cycle'].by_key()['color'][n], x)
np.random.seed(10)
x = np.linspace(1,50,50)
y1 = np.random.rand(50)
y2 = np.random.rand(50)
plt.scatter(x, y1, c=col(x, 20, 40, 0))
plt.scatter(x, y2, c=col(x, 20, 40, 1))
plt.show()
NOTE: The code above actually throws the error TypeError: object of type 'map' has no len(), but even converting this to the less clean iterable equivalent still doesn't work, as matplotlib simply wont accept generators
Here is a more efficient way of creating labels for the scatter plot:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import ListedColormap
np.random.seed(10)
x = np.linspace(1,50,50)
y1 = np.random.rand(50)
y2 = np.random.rand(50)
want_to_color = [20,40]
color_cycle = plt.rcParams['axes.prop_cycle'].by_key()['color']
c = np.zeros(50)
c[want_to_color[0]+1 : want_to_color[1]] = 1
colors1 = ListedColormap([color_cycle[0], 'r'])
colors2 = ListedColormap([color_cycle[1], 'r'])
plt.scatter(x, y1, c=c, cmap=colors1)
plt.scatter(x, y2, c=c, cmap=colors2)
plt.show()
This gives:
If you prefer not to create an array of labels at all then you can try this:
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(10)
x = np.linspace(1,50,50)
y1 = np.random.rand(50)
y2 = np.random.rand(50)
want_to_color = [20,40]
sl = slice(want_to_color[0]+1, want_to_color[1])
color_cycle = plt.rcParams['axes.prop_cycle'].by_key()['color']
plt.scatter(x, y1, c=color_cycle[0])
plt.scatter(x, y2, c=color_cycle[1])
plt.scatter(x[sl], y1[sl], c='r')
plt.scatter(x[sl], y2[sl], c='r')
The resulting image is:

matplotlib fill_between: gaps in fill [duplicate]

I am trying to shade the area before the point of intersection of the two curves produced by this example code:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0,100,10)
y1 = [0,2,4,6,8,5,4,3,2,1]
y2 = [0,1,3,5,6,8,9,12,13,14]
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(t_list,y1,linestyle='-')
ax.plot(t_list,y2,linestyle='--')
plt.show()
Simply using:
ax.fill_between(x,y1,y2,where=y1>=y2,color='grey',alpha='0.5')
Does no work and gives the following error: "ValueError: Argument dimensions are incompatible"
I tried to convert the lists into arrays:
z1 = np.array(y1)
z2 = np.array(y2)
Then:
ax.fill_between(x,y1,y2,where=z1>=z2,color='grey',alpha='0.5')
Not the entire area was shaded.
I know I have to find the point of intersection between the two curves by interpolating but have not seen a simple way to do it.
You are completely right, you need to interpolate. And that is ludicrously complicated, as you need to add the interpolate=True keyword argument to the call to fill_between.
ax.fill_between(x,y1,y2,where=z1>=z2,color='grey', interpolate=True)
Complete code to reproduce:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0,100,10)
y1 = [0,2,4,6,8,5,4,3,2,1]
y2 = [0,1,3,5,6,8,9,12,13,14]
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x,y1,linestyle='-')
ax.plot(x,y2,linestyle='--')
z1 = np.array(y1)
z2 = np.array(y2)
ax.fill_between(x,y1,y2,where=z1>=z2,color='grey',alpha=0.5, interpolate=True)
plt.show()

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

Plotting a list containing tuples to a graph

I have a list of tuples like for example
[("name1",0.05),("name2",0.034),("name3",0.03)....]
I would like to make a graph with a line of these points with
y0 = 0.05
y1 = 0.034
and
x0 = "name1"
x1 = "name2"
and so on.
How would I do this in python/sage ?
import matplotlib.pyplot as plt
import numpy as np
x = np.array([0,1,2,3])
y = np.array([20,21,22,23])
my_xticks = ['John','Arnold','Mavis','Matt']
plt.xticks(x, my_xticks)
plt.plot(x, y)
plt.show()
helped me a lot

How to draw circle by data with matplotlib + python?

I can draw a circle by scatter, which has been shown in the image. But I want to draw them buy a line, because there are many circles in total, I need to link nodes together for a certain circle. Thanks.
I the order of the points is random, you can change X-Y to polar, and sort the data by angle:
create some random order points first:
import pylab as pl
import numpy as np
angle = np.arange(0, np.pi*2, 0.05)
r = 50 + np.random.normal(0, 2, angle.shape)
x = r * np.cos(angle)
y = r * np.sin(angle)
idx = np.random.permutation(angle.shape[0])
x = x[idx]
y = y[idx]
Then use arctan2() to calculate the angle, and sort the data by it:
angle = np.arctan2(x, y)
order = np.argsort(angle)
x = x[order]
y = y[order]
fig, ax = pl.subplots()
ax.set_aspect(1.0)
x2 = np.r_[x, x[0]]
y2 = np.r_[y, y[0]]
ax.plot(x, y, "o")
ax.plot(x2, y2, "r", lw=2)
here is the output:
Here is one way to do it. This answer uses different methods than the linked possible duplicate, so may be worth keeping.
import matplotlib.pyplot as plt
from matplotlib import patches
fig = plt.figure(figsize=plt.figaspect(1.0))
ax = fig.add_subplot(111)
cen = (2.0,1.0); r = 3.0
circle = patches.Circle(cen, r, facecolor='none')
ax.add_patch(circle)
ax.set_xlim(-6.0, 6.0)
ax.set_ylim(-6.0, 6.0)
If all you have are the x and y points, you can use PathPatch. Here's a tutorial
If your data points are already in order, the plot command should work fine. If you're looking to generate a circle from scratch, you can use a parametric equation.
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> t = np.linspace(0,2*np.pi, 100)
>>> x = np.cos(t)
>>> y = np.sin(t)
>>> plt.plot(x,y)

Categories