Drawing a righthand coordinate system in mplot3d - matplotlib

I want to create plots from Python of 3D coordinate transformations. For example, I want to create the following image (generated statically by TikZ):
A bit of searching led me to the following program:
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.patches import FancyArrowPatch
from mpl_toolkits.mplot3d import proj3d
class Arrow3D(FancyArrowPatch):
def __init__(self, xs, ys, zs, *args, **kwargs):
FancyArrowPatch.__init__(self, (0, 0), (0, 0), *args, **kwargs)
self._verts3d = xs, ys, zs
def draw(self, renderer):
xs3d, ys3d, zs3d = self._verts3d
xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M)
self.set_positions((xs[0], ys[0]), (xs[1], ys[1]))
FancyArrowPatch.draw(self, renderer)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
arrow_prop_dict = dict(mutation_scale=20, arrowstyle='->', shrinkA=0, shrinkB=0)
a = Arrow3D([0, 1], [0, 0], [0, 0], **arrow_prop_dict, color='r')
ax.add_artist(a)
a = Arrow3D([0, 0], [0, 1], [0, 0], **arrow_prop_dict, color='b')
ax.add_artist(a)
a = Arrow3D([0, 0], [0, 0], [0, 1], **arrow_prop_dict, color='g')
ax.add_artist(a)
ax.text(0.0, 0.0, -0.1, r'$o$')
ax.text(1.1, 0, 0, r'$x$')
ax.text(0, 1.1, 0, r'$y$')
ax.text(0, 0, 1.1, r'$z$')
ax.view_init(azim=-90, elev=90)
ax.set_axis_off()
plt.show()
The result doesn't look like what one normally sees in books:
Furthermore, when I include the axes, the origin is not at the intersection of the three planes, which is where I expect it to be.

You may change the view of your 3D axes by changing the following line :
ax.view_init(azim=-90, elev=90)
to
ax.view_init(azim=20, elev=10)
to reproduce your TikZ plot, or change the view angles to any other values as you prefer.

Related

SHAP Partial Dependence Subplots Issue

I am trying to plot multiple subplots using SHAP but for some reasons all the plot end up in the bottom right corner when I run plt.show(). Here is a snippet of my code below to prepare the shap and train the model:
X = df.loc[:, features]
X = X.fillna(X.median())
y_test = df.loc[:, 'y_test'].values
model = lgbm.LGBMRegressor()
model.fit(X, y_test)
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X)
Here is the snippet to get the graphs:
fig, axes = plt.subplots(nrows=3, ncols=3, figsize=(16, 16))
axes_dic = {
0: axes[0, 0],
1: axes[0, 1],
2: axes[0, 2],
3: axes[1, 0],
4: axes[1, 1],
5: axes[1, 2],
6: axes[2, 0],
7: axes[2, 1],
8: axes[2, 2]
}
fig, axes = plt.subplots(nrows=3, ncols=3, figsize=(16, 16))
fig.suptitle('Partial Dependence SHAP plots', fontsize=20)
for i in range(len(axes_dic)):
shap.partial_dependence_plot(
features[i], model.predict, X, model_expected_value=True,
feature_expected_value=True, ice=False, show=False, ax=axes_dic[i]
)
plt.tight_layout()
plt.show()
While this doesn't work, this works for the Dependence plot
fig, axes = plt.subplots(nrows=3, ncols=3, figsize=(16, 16))
fig.suptitle('Dependence SHAP plots', fontsize=20)
for i in range(len(axes_dic)):
shap.dependence_plot(
features[i], shap_values, X, ax=axes_dic[i], show=False,
interaction_index="uprc"
)
plt.tight_layout()
plt.show()
Anyone can help me figure out what is going on? Why does dependence_plot works well but not the partial_dependence_plot.
print(matplotlib.__version__)
3.6.1
print(shap.__version__)
0.41.0
I tried multiple alternatives to get it to plot on the right axe but it doesn't seem to be doing what I would expect.

In matplotlib, how to set ylim independently while setting sharey=True

I want to replicate seaborn pairplot function but I need increased flexibility to plot extra things.
I have the following minimal example, but I need that the uppermost labels show the range [10, 20] instead of [0, 1] without changing the displayed range of the first plot, and that the grid for the right plots shows horizontal bars.
How can I keep x and y shared and displayed for all plots but exclude the y axis of diagonal plots as in seaborn?
import matplotlib.pyplot as plt
fig, axes = plt.subplots(nrows=2, ncols=2, sharex='col',
sharey='row')
axes[0, 0]._shared_axes['y'].remove(axes[0, 0])
axes[1, 1]._shared_axes['y'].remove(axes[1, 1])
axes[0, 0].plot([0, 1])
axes[0, 1].scatter([0, 1], [10, 20])
axes[1, 0].scatter([0, 1], [100, 200])
axes[1, 1].plot([0, 1])
axes[0, 0].grid(color='lightgray', linestyle='--')
axes[0, 1].grid(color='lightgray', linestyle='--')
axes[1, 0].grid(color='lightgray', linestyle='--')
axes[1, 1].grid(color='lightgray', linestyle='--')
plt.show()
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
df = pd.DataFrame([[10, 100], [20, 200]])
sns.pairplot(df, diag_kind='kde')
plt.show()

Matplotlib inconvenient "reset to original view" while previously zoomed

Updated:
After having zoomed to a region of interest, I would like to add a scatter point without having a reset to the original view.
It occurs when I double click after having zoomed to rectangle.
Of course this is a simplification of a problem encountered as I wanted to add markers to a large image after having properly zoomed to a region of interest.
Any help welcomed
import matplotlib.pyplot as plt
fig, ax = plt.subplots(nrows=1, figsize=(4,4))
plt.plot([0, 1, 2, 3], [10, 20, 30, 40])
def onclick(event):
if event.dblclick:
plt.scatter(event.xdata, event.ydata, c='r')
fig.canvas.mpl_connect('button_press_event', onclick)
plt.get_current_fig_manager().toolbar.zoom()
plt.show()
Answer:
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots(nrows=1, figsize=(4,4))
ax.imshow([[1, 2], [5, 6]])
ax.autoscale(False) # disable autoscaling for all future plotting functions.
def onclick(event):
if event.dblclick:
plt.scatter(event.xdata, event.ydata, c='r')
fig.canvas.mpl_connect('button_press_event', onclick)
plt.get_current_fig_manager().toolbar.zoom()
plt.show()
I believe the problem comes from the autoscale features that kicks in whenever you call plt.scatter(). The solution is simply to disable autoscale (but draw the initial plot beforehand):
import matplotlib.pyplot as plt
fig, ax = plt.subplots(nrows=1, figsize=(4, 4))
plt.plot([0, 1, 2, 3], [10, 20, 30, 40])
fig.canvas.draw() # force draw so that the axes are autoscaled here
ax.autoscale(False) # disable autoscaling for all future plotting functions.
def onclick(event):
if event.inaxes and event.dblclick:
plt.scatter(event.xdata, event.ydata, marker='o', s=10, c='r')
fig.canvas.mpl_connect('button_press_event', onclick)
plt.show()

Changing color in Scikit's example for plotting decision boundaries of a VotingClassifier?

Hi I am trying to reproduce Scikit's example for plotting decision boundaries Voting Classifiers.
The classification part is rather straight forward, and the neat way of plotting several plots in a single figure is intruiging. However, I have trouble with altering the coloring scheme.
This is the straight forward classification part:
from itertools import product
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.ensemble import VotingClassifier
# Loading some example data
iris = datasets.load_iris()
X = iris.data[:, [0, 2]]
y = iris.target
# Training classifiers
clf1 = DecisionTreeClassifier(max_depth=4)
clf2 = KNeighborsClassifier(n_neighbors=7)
clf3 = SVC(kernel='rbf', probability=True)
eclf = VotingClassifier(estimators=[('dt', clf1), ('knn', clf2),
('svc', clf3)],
voting='soft', weights=[2, 1, 2])
clf1.fit(X, y)
clf2.fit(X, y)
clf3.fit(X, y)
eclf.fit(X, y)
The example uses the following code to create the figure:
# Plotting decision regions
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
np.arange(y_min, y_max, 0.1))
f, axarr = plt.subplots(2, 2, sharex='col', sharey='row', figsize=(10, 8))
for idx, clf, tt in zip(product([0, 1], [0, 1]),
[clf1, clf2, clf3, eclf],
['Decision Tree (depth=4)', 'KNN (k=7)',
'Kernel SVM', 'Soft Voting']):
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
axarr[idx[0], idx[1]].contourf(xx, yy, Z, alpha=0.4)
axarr[idx[0], idx[1]].scatter(X[:, 0], X[:, 1], c=y,
s=20, edgecolor='k')
axarr[idx[0], idx[1]].set_title(tt)
plt.show()
It seems that matplotlib somehow uses a default coloring scheme. Is there a way to pass other colors? I tried to fiddle arround with c=y (e.g c = ['y', 'b']) but that does not do the trick.
I would like to alter both the background coloring and the scatter coloring. Any ideas?
The colors are chosen according to the values in y and Z for the respective plots. y has as many entries as there are points and it has 3 unique values. Z has 3 levels as well. They are colormapped according to matplotlib
colormaps.
You may choose a different colormap, e.g. cmap="brg":
axarr[idx].contourf(xx, yy, Z, alpha=0.4, cmap="brg")
axarr[idx].scatter(X[:, 0], X[:, 1], c=y, cmap="brg",
s=20, edgecolor='w')
Complete code:
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
iris = datasets.load_iris()
X = iris.data[:, [0, 2]]
y = iris.target
clf1 = DecisionTreeClassifier(max_depth=4)
clf2 = KNeighborsClassifier(n_neighbors=7)
clf1.fit(X, y)
clf2.fit(X, y)
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
np.arange(y_min, y_max, 0.1))
f, axarr = plt.subplots(1,2, sharex='col', sharey='row', figsize=(5,3))
for idx, clf, tt in zip([0, 1],[clf1, clf2],
['Decision Tree (depth=4)', 'KNN (k=7)']):
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
axarr[idx].contourf(xx, yy, Z, alpha=0.4, cmap="brg")
axarr[idx].scatter(X[:, 0], X[:, 1], c=y, cmap="brg",
s=20, edgecolor='w')
axarr[idx].set_title(tt)
plt.show()
You may also create your custom colormap. E.g. to use gold, crimson and indigo as colors,
import matplotlib.colors
cmap = matplotlib.colors.ListedColormap(["gold", "crimson", "indigo"])
axarr[idx].contourf(xx, yy, Z, alpha=0.4, cmap=cmap)
axarr[idx].scatter(X[:, 0], X[:, 1], c=y, cmap=cmap,
s=20, edgecolor='w')
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors
from sklearn import datasets
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
iris = datasets.load_iris()
X = iris.data[:, [0, 2]]
y = iris.target
clf1 = DecisionTreeClassifier(max_depth=4)
clf2 = KNeighborsClassifier(n_neighbors=7)
clf1.fit(X, y)
clf2.fit(X, y)
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
np.arange(y_min, y_max, 0.1))
f, axarr = plt.subplots(1,2, sharex='col', sharey='row', figsize=(5,3))
cmap = matplotlib.colors.ListedColormap(["gold", "crimson", "indigo"])
for idx, clf, tt in zip([0, 1],[clf1, clf2],
['Decision Tree (depth=4)', 'KNN (k=7)']):
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
axarr[idx].contourf(xx, yy, Z, alpha=0.4, cmap=cmap)
axarr[idx].scatter(X[:, 0], X[:, 1], c=y, cmap=cmap,
s=20, edgecolor='w')
axarr[idx].set_title(tt)
plt.show()

Label is Missing from matplotlib legend

I'm plotting subplots with matplotlib and the legend does not show up for some plots.
In this example, the scatter plot legend does not show up.
import numpy as np
import matplotlib
from matplotlib import pyplot as plt
from matplotlib.legend_handler import HandlerLine2D
from matplotlib.patches import Rectangle, Circle
fig = plt.figure()
plt.cla()
plt.clf()
x = np.arange(5) + 1
y = np.full(5, 10)
fig, subplots = plt.subplots(2, sharex=False, sharey=False)
subplots[0].bar(x, y, color='r', alpha=0.5, label='a')
scat = subplots[0].scatter(x, y-1, color='g', label='c')
subplots[0].set_yscale('log')
subplots[1].bar(x, y, color='r', alpha=0.5, label='a')
x = [2, 3]
y = [4, 4]
subplots[1].bar(x, y, color='b', alpha=1, label='b')
subplots[1].set_yscale('log')
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5), handler_map={scat: HandlerLine2D(numpoints=4)})
plt.show()
Here is what I tried as a workaround:
p1 = Rectangle((0, 0), 1, 1, fc="r", alpha=0.5)
p2 = Rectangle((0, 0), 1, 1, fc="b")
p3 = Circle((0, 0), 1, fc="g")
legend([p1, p2, p3], ['a', 'b', 'c'], loc='center left', bbox_to_anchor=(1, 0.5))
I really prefer to fix this without the workaround so if anyone knows how to fix it please let me know.
Also, an issue with the workaround is that the Circle object still appears as a bar on the legend.
plt.legend starts with a gca() (which returns the current axes):
# from pyplot.py:
def legend(*args, **kwargs):
ret = gca().legend(*args, **kwargs)
So calling plt.legend will only get you a legend on your last subplot. But it is also possible to call e.g. ax.legend(), or in your case subplots[0].legend(). Adding that to the end of your code gives me a legend for both subplots.
Sample:
for subplot in subplots:
subplot.legend(loc='center left', bbox_to_anchor=(1, 0.5))