Matplotlib iterate to combine legend handles and labels - python

If I have the following plotting routine that plots a scatter plot and corresponding linear regression and combines the legend handles:
import pandas as pd
from scipy.stats import linregress
import numpy as np
import matplotlib.pyplot as plt
#data and Regression
x = np.arange(0,5,1)
y = np.arange(0,10,2)
df = pd.DataFrame(data = {'x':x,'y':y})
s, intcpt, r, p, serr = linregress(df.x, df.y)
xHat = np.linspace(0,5,100)
# do the plotting
fig,ax = plt.subplots()
df.plot(x='x',y='y',ax=ax,label='series1',ls=' ',marker='x',c='blue')
ls_handle, = ax.plot(xHat, s*xHat + intcpt, linestyle='-', marker=None, c='blue')
handle2merge = [ls_handle]
handles, labels = ax.get_legend_handles_labels()
handle_combined = zip(handles, handle2merge)
ax.legend(handle_combined, labels)
Which returns the where the marker and line handles are merged looking like:
Now I want to plot another dataset in a similar fashion:
#get current axis handles and labels
handle_start, label_start = ax.get_legend_handles_labels()
#second dataset and regression
x1 = np.arange(0,5,1)
y1 = np.arange(0,2.5,0.5)
df1 = pd.DataFrame(data = {'x':x1,'y':y1})
s1, intcpt1, r1, p1, serr1 = linregress(df1.x, df1.y)
xHat1 = np.linspace(0,5,100)
#plot second data set on same figure
marker_handle2, = ax.plot(df1.x, df1.y, marker = 'x', zorder=10,c='k', linestyle=' ')
line_handle2, = ax.plot(xHat, s1*xHat1 + intcpt1, linestyle='--', marker=None, c='k')
new_line_handles = [line_handle2]
new_marker_handles= [marker_handle2]
ax.legend(handle_start + zip(new_marker_handles,new_line_handles), label_start + ['series2'])
This returns a plot where the handles for series1 legend handle only contains the marker.
Why is len(handle_start)=1 when I constructed the handle with handle_combined = zip(handles, handle2merge)?

I have poked around the code a little. What you are doing is passing a list of tuples to ax.legend, which apparently draws each Artist in each tuple as one entry in the legend. I have actually not come across this behaviour before; it could be a bug, or unintended use of ax.legend.
Nevertheless, I think that in this case, since you know what your lines should look like beforehand, instead of going the roundabout way with zip and stuff, you could just pass a custom Line2D to legend directly:
import numpy as np
from scipy.stats import linregress
from matplotlib import pyplot as plt
from matplotlib import lines
x1 = np.arange(0, 5, 1)
y1 = np.arange(0, 10, 2)
x2 = np.arange(0, 5, 1)
y2 = np.arange(0, 2.5, 0.5)
m1, c1, r1, p1, serr1 = linregress(x1, y1)
m2, c2, r2, p2, serr2 = linregress(x2, y2)
x_pred = np.linspace(0,5,100)
fig, ax = plt.subplots()
first_line, = ax.plot(x_pred, x_pred * m1 + c1, ls='-', c='blue')
first_scatter = ax.scatter(x1, y1, marker='x', c='blue')
second_line, = ax.plot(x_pred, x_pred * m2 + c2, ls='--', c='black')
second_scatter = ax.scatter(x2, y2, marker='x', c='black')
ax.legend([lines.Line2D([0], [0], marker='x', ls='-', c='blue'),
lines.Line2D([0], [0], marker='x', ls='--', c='black')],
['series_1', 'series_2'])
I cleaned up your code a little, but feel free to take only the last line and the necessary import.

In the last line, just use the already created merged handle handle_combined instead of the handle_start.
ax.legend(handle_combined + list(zip(new_marker_handles,new_line_handles)),
label_start + ['series2'])
The length is 1 but if you look into the contents of the list, it is a tuple consisting of two objects. If you print handle_combined, you get a list of two Line2D objects, one of which is marker and the other is the line.
print (handle_combined)
# [(<matplotlib.lines.Line2D object at xxxxxxxxx>, <matplotlib.lines.Line2D object at xxxxxxxxx>)]
However, if you print handle_start, it returns just a single Line2D object
print (handle_start)
# [<matplotlib.lines.Line2D object at xxxxxxxxx>]

Related

The Matplotlib Result is Different From WolfarmAlpha

I want to plot some equation in Matplotlib. But it has different result from Wolframalpha.
This is the equation:
y = 10yt + y^2t + 20
The plot result in wolframalpha is:
But when I want to plot it in the matplotlib with these code
# Creating vectors X and Y
x = np.linspace(-2, 2, 100)
# Assuming α is 10
y = ((10*y*x)+((y**2)*x)+20)
# Create the plot
fig = plt.figure(figsize = (10, 5))
plt.plot(x, y)
The result is:
Any suggestion to modify to code so it has similar plot result as wolframalpha? Thank you
As #Him has suggested in the comments, y = ((10*y*x)+((y**2)*x)+20) won't describe a relationship, so much as make an assignment, so the fact that y appears on both sides of the equation makes this difficult.
It's not trivial to express y cleanly in terms of x, but it's relatively easy to express x in terms of y, and then graph that relationship, like so:
import numpy as np
import matplotlib.pyplot as plt
y = np.linspace(-40, 40, 2000)
x = (y-20)*(((10*y)+(y**2))**-1)
fig, ax = plt.subplots()
ax.plot(x, y, linestyle = 'None', marker = '.')
ax.set_xlim(left = -4, right = 4)
ax.grid()
ax.set_xlabel('x')
ax.set_ylabel('y')
Which produces the following result:
If you tried to plot this with a line instead of points, you'll get a big discontinuity as the asymptotic limbs try to join up
So you'd have to define the same function and evaluate it in three different ranges and plot them all so you don't get any crossovers.
import numpy as np
import matplotlib.pyplot as plt
y1 = np.linspace(-40, -10, 2000)
y2 = np.linspace(-10, 0, 2000)
y3 = np.linspace(0, 40, 2000)
x = lambda y: (y-20)*(((10*y)+(y**2))**-1)
y = np.hstack([y1, y2, y3])
fig, ax = plt.subplots()
ax.plot(x(y), y, linestyle = '-', color = 'b')
ax.set_xlim(left = -4, right = 4)
ax.grid()
ax.set_xlabel('x')
ax.set_ylabel('y')
Which produces this result, that you were after:

Colored gradient on multiple lines from a csv in 3D plot?

Disclaimer: I'm a total newb to this, 2nd day so pls bear with me, thank you in advance!
So, I managed to get my 3D plot to have multiple lines, but I would like to give them some color gradients. I've managed to get it onto one example line, but I cannot convert it to my own plots.
My plots come from a .csv
I followed this question for the gradients: https://stackoverflow.com/a/8505774/20387853 (Answer by Yann) but I can't seem to understand how to merge the two for i in range bits (one from my old code with the new code) (if it even can be?)
I also dont understand ax.plot(x[i:i+2],y[i:i+2]) so I couldn't adjust this like I thought I could.
SO ATM i have two scripts
Script 1 - in which I'm trying to merge my two data sets.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import sys
import pandas
points = pandas.read_csv('D:Documents\PYTHON_FILES/test3d.csv')
def highResPoints(x,y,factor=10):
# r is the distance spanned between pairs of points
r = [0]
for i in range(1,len(x)):
dx = x[i]-x[i-1]
dy = y[i]-y[i-1]
r.append(np.sqrt(dx*dx+dy*dy))
r = np.array(r)
# rtot is a cumulative sum of r, it's used to save time
rtot = []
for i in range(len(r)):
rtot.append(r[0:i].sum())
rtot.append(r.sum())
dr = rtot[-1]/(NPOINTS*RESFACT-1)
xmod=[x[0]]
ymod=[y[0]]
rPos = 0 # current point on walk along data
rcount = 1
while rPos < r.sum():
x1,x2 = x[rcount-1],x[rcount]
y1,y2 = y[rcount-1],y[rcount]
dpos = rPos-rtot[rcount]
theta = np.arctan2((x2-x1),(y2-y1))
rx = np.sin(theta)*dpos+x1
ry = np.cos(theta)*dpos+y1
xmod.append(rx)
ymod.append(ry)
rPos+=dr
while rPos > rtot[rcount+1]:
rPos = rtot[rcount+1]
rcount+=1
if rcount>rtot[-1]:
break
return xmod,ymod
#CONSTANTS
NPOINTS = 10
COLOR='red'
RESFACT=10
MAP='winter' # choose carefully, or color transitions will not appear smoooth
cm = plt.get_cmap(MAP)
################ These are old data sets, just to use for this example
x = points['x'].values
y = points['y'].values
z = points['z'].values
x2 = points['x2'].values
y2 = points['y2'].values
z2 = points['z2'].values
fig = plt.figure()
#ax1 = fig.add_subplot(111,projection='3d') # regular resolution color map
ax = fig.add_subplot(111, projection='3d')
ax.plot(x, y, z, c='red',marker='v', linewidth=1.0, markersize=2)
ax.plot(x2, y2, z2, c='blue', marker='o', linewidth=1.0, markersize=2)
ax.set_prop_cycle(color=[cm(1.*i/(NPOINTS-1)) for i in range(NPOINTS-1)])
for i in range(NPOINTS-1):
#ax1.plot(x[i:i+2],y[i:i+2])
ax.plot(x[i:i+2],y[i:i+2])
########################The part I want to merge in
#for i in range(1, 5):
#if i == 1: i = '' #x is your first value not x1
#ax.plot(points[f"x{i}"], points[f"y{i}"], points[f"z{i}"], c='red', marker='o', linewidth=1.0, markersize=2)
#########################
fig.savefig('colorgradienttest.png')
plt.show()
[Link to Image]
I want to make the blue and red lines have a color gradient like the example 3rd line (markers are not important)
Script 2 - to which I want to apply the gradient (the one with the .csv)
from mpl_toolkits.mplot3d import Axes3D
import sys
import matplotlib.pyplot as plt
import pandas
import numpy as np
points = pandas.read_csv('D:Documents\PYTHON_FILES/test3d.csv')
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
#OPTION 1 - not sure why this isn't working for me so Im not using it yet
#for idx in range(29):
# suffix = '' if idx == 0 else str(idx + 1) # ranges start at 0
# x = points[f"x{suffix}"].values
# y = points[f"y{suffix}"].values
# z = points[f"z{suffix}"].values
#ax.plot(x, y, z, c='red', marker='o', linewidth=1.0, markersize=2)
#OPTION 2 - current approach <<<<<<<<<<<<<<<< want to apply gradient to this segment
for i in range(1, 5):
if i == 1: i = '' #x is your first value not x1
ax.plot(points[f"x{i}"], points[f"y{i}"], points[f"z{i}"], c='red', marker='o', linewidth=1.0, markersize=2)
plt.show()

Choosing a specific contour in oscillatory data to plot with matplotlib

I have oscillatory data to which I would like to add a specific contour line. For example, the data pass through a value several times, and I would like to pick a specific instance of that value to highlight with a contour. As an example, consider a Bessel function. Below, I plot the contours with a single level, 0.2. I would like to choose to show only the outer contour, however, and not the other interior ones.
from scipy.special import jv
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-20,20,num=500)
y = np.linspace(-20,20,num=500)
[X,Y] = np.meshgrid(x,y)
Z = jv(1,np.sqrt(X**2.+Y**2.))
fig = plt.figure()
ax = fig.add_subplot(111)
cb = ax.pcolormesh(X,Y,Z)
ax.contour(X,Y,Z,[.2],linestyles='dashed')
cbar = fig.colorbar(cb)
plt.show()
If helpful, this is a plot of my actual data (the code used to create is far too long to include here). I would only like to plot the outermost purple contour:
Thank you
Let's see how you like this ;) ... I plot all contour lines invisibly, but extract the contour object and replot the first one (that I just figured out by trial and error, and might be different in your case).
from scipy.special import jv
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-20, 20, num=500)
y = np.linspace(-20, 20, num=500)
[X, Y] = np.meshgrid(x, y)
Z = jv(1, np.sqrt(X**2. + Y**2.))
fig = plt.figure()
ax = fig.add_subplot(111)
cb = ax.pcolormesh(X, Y, Z)
cont = ax.contour(X, Y, Z, [.2], alpha=0) # alpha = 0 -> invisible
the_interesting_one = cont.allsegs[0][0]
plt.plot(the_interesting_one[:, 0], the_interesting_one[:, 1], "k--")
cbar = fig.colorbar(cb)
plt.show()

Reorient Histogram and Scatterplot with Trend Line

I have a dataset that looks similar to the one simulated in the code below. There are two sets of observations, one for those at X=0 and another for those at X>0.
import numpy as np
import seaborn as sns; sns.set()
import matplotlib.pyplot as plt
X1 = np.random.normal(0, 1, 100)
X1 = X1 - np.min(X1)
Y1 = X1 + np.random.normal(0, 1, 100)
X0 = np.zeros(100)
Y0 = np.random.normal(0, 1.2, 100) + 2
X = np.concatenate((X1, X0))
Y = np.concatenate((Y1, Y0))
sns.distplot(Y0, color="orange")
plt.show()
sns.scatterplot(X, Y, hue = (X == 0), legend=False)
plt.show()
There are two plots: a histogram with KDE and a scatterplot.
I want to take the histogram with KDE, rotate it, and orient it appropriately with respect to the scatter plot. I would also like to add a trend line for each respective set of observations.
The ideal result would look something like this:
How do you do this in python, either using seaborn or matplotlib?
This can be done by combining plt.subplots with shared y-axis to keep the scale and sns plots. For trend line you need some additional computation, but you can use np for quick fitting. Here is an example how to achieve your goal, and here is jupyter notebook to play with.
import numpy as np
import seaborn as sns; sns.set()
import matplotlib.pyplot as plt
# Prepare some data
np.random.seed(2020)
mean_Y1 = 0
std_Y1 = 1
size_Y1 = 100
X1 = np.random.normal(mean_Y1, std_Y1, size_Y1)
X1 = X1 - np.min(X1)
Y1 = X1 + np.random.normal(mean_Y1, std_Y1, size_Y1)
# this for computing trend line
Z = np.polyfit(X1, Y1, 1)
Y_ = np.poly1d(Z)(X1)
mean_Y0 = 2
std_Y0 = 1.2
size_Y0 = 100
X0 = np.zeros(100)
Y0 = np.random.normal(mean_Y0, std_Y0, size_Y0)
X = np.concatenate((X1, X0))
Y = np.concatenate((Y1, Y0))
# Now time for plotting
fig, axs = plt.subplots(1, 2,
sharey=True,
figsize=(10, 5),
gridspec_kw={'width_ratios': (1, 2)}
)
# control space between plots
fig.subplots_adjust(wspace=0.1)
# set the ticks for y-axis:
axs[0].yaxis.set_tick_params(left=False, labelleft=False, labelright=True)
# if you wish you can rotate xticks on the histogram with:
axs[0].xaxis.set_tick_params(rotation=90)
# plot histogram
dist = sns.distplot(Y0, color="orange", vertical=True, ax=axs[0])
# now we need to get the coordinate of the peak, we need this for mean line
line_data = dist.get_lines()[0].get_data()
max_Y0 = np.max(line_data[0])
# plotting the mean line
axs[0].plot([0, max_Y0], [mean_Y0, mean_Y0], '--', c='orange')
# inverting xaxis
axs[0].invert_xaxis()
# Plotting scatterpot
sns.scatterplot(X, Y, hue = (X == 0), legend=False, ax=axs[1])
# Plotting trend line
sns.lineplot(X1, Y_, ax=axs[1])
# Plotting mean again
axs[1].plot([0, max(X1)], [mean_Y0, mean_Y0], '--', c='orange')
plt.show()
Out:

Interactively changing the alpha value of matplotlib plots

I've looked at the documentation, but I can't seem to figure out if this is possible -
I have a dataset, with x and y values and discrete z values. Multiple pairs of (x,y) share the same z value. What I want to do is when I mouseover one point with a particular z value, the alpha of all the points with the same z values goes to 1 - i.e., If all the alpha values are initially 0.5, I'd like only the points with the same z value to go to 1.
Here's a minimal working example to illustrate what I'm talking about :
#! /usr/bin/env python
import numpy as np
import matplotlib.pyplot as plt
x = np.random.randn(100)
y = np.random.randn(100)
z = np.arange(0, 10, 1)
z = np.repeat(z, 10)
im = plt.scatter(x, y, c=z, alpha = 0.5)
plt.colorbar(im)
plt.show()
You can probably fake what you want to achieve using a second plot:
import numpy as np
import matplotlib.pyplot as plt
Z = np.zeros(1000, dtype = [("Z", int), ("P", float, 2)])
Z["P"] = np.random.uniform(0.0,1.0,(len(Z),2))
Z["Z"] = np.random.randint(0,50,len(Z))
def on_pick(event):
z = Z[event.ind[0]]['Z']
P = Z[np.where(Z["Z"] == z)]["P"]
selection_plot.set_data(P[:,0],P[:,1])
plt.draw()
fig = plt.figure(figsize=(10,10), facecolor='white')
fig.canvas.mpl_connect('pick_event', on_pick)
ax = plt.subplot(111, aspect=1)
ax.plot(Z['P'][:,0], Z['P'][:,1], 'o', color='k', alpha=0.1, picker=5)
selection_plot, = ax.plot([],[], 'o', color='black', alpha=1.0, zorder=10)
plt.show()

Categories

Resources