matplotlib collection linewidth mapping? - matplotlib

I'm creating some GIS-style plots in matplotlib of road networks and the like, so I'm using LineCollection to store and represent all of the roads and color accordingly. This is working fine, I color the roads based on a criteria and the following map:
from matplotlib.colors import ListedColormap,BoundaryNorm
from matplotlib.collections import LineCollection
cmap = ListedColormap(['grey','blue','green','yellow','orange','red','black'])
norm = BoundaryNorm([0,0.5,0.75,0.9,0.95,1.0,1.5,100],cmap.N)
roads = LineCollection(road_segments, array=ratios, cmap=cmap, norm=norm)
axes.add_collection(roads)
This works fine, however I would really like to have linewidths defined in a similar manner to the color map - ranging from 0.5 to 5 for each color
Does anyone know of a clever way of doing this?

The linewidths keyword.
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
axes = plt.axes()
roads = LineCollection([
[[0, 0], [1, 1]],
[[0, 1], [1, 0]]
],
colors=['black', 'red'],
linewidths=[3, 8],
)
axes.add_collection(roads)
plt.show()
HTH

Related

Seaborn jointplot link x-axis to Matplotlib subplots

Is there a way to add additional subplots created with vanilla Matplotlib to (below) a Seaborn jointplot, sharing the x-axis? Ideally I'd like to control the ratio between the jointplot and the additional plots (similar to gridspec_kw={'height_ratios':[3, 1, 1]}
I tried to fake it by tuning figsize in the Matplotlib subplots, but obviously it doesn't work well when the KDE curves in the marginal plot change. While I could manually resize the output PNG to shrink/grow one of the figures, I'd like to have everything aligned automatically.
I know this is tricky with the way the joint grid is set up, but maybe it is reasonably simple for someone fluent in the underpinnings of Seaborn.
Here is a minimal working example, but there are two separate figures:
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
Figure 1
diamonds = sns.load_dataset('diamonds')
g = sns.jointplot(
data=diamonds,
x="carat",
y="price",
hue="cut",
xlim=(1, 2),
)
g.ax_marg_x.remove()
Figure 2
fig, (ax1, ax2) = plt.subplots(2,1,sharex=True)
ax1.scatter(x=diamonds["carat"], y=diamonds["depth"], color="gray", edgecolor="black")
ax1.set_xlim([1, 2])
ax1.set_ylabel("depth")
ax2.scatter(x=diamonds["carat"], y=diamonds["table"], color="gray", edgecolor="black")
ax2.set_xlabel("carat")
ax2.set_ylabel("table")
Desired output:
I think this is a case where setting up the figure using matplotlib functions is going to be better than working backwards from a seaborn figure layout that doesn't really match the use-case.
If you have a non-full subplot grid, you'll have to decide whether you want to (A) set up all the subplots and then remove the ones you don't want or (B) explicitly add each of the subplots you do want. Let's go with option A here.
figsize = (6, 8)
gridspec_kw = dict(
nrows=3, ncols=2,
width_ratios=[5, 1],
height_ratios=[4, 1, 1],
)
subplot_kw = dict(sharex="col", sharey="row")
fig = plt.figure(figsize=figsize, constrained_layout=True)
axs = fig.add_gridspec(**gridspec_kw).subplots(**subplot_kw)
sns.kdeplot(data=df, y="price", hue="cut", legend=False, ax=axs[0, 1])
sns.scatterplot(data=df, x="carat", y="price", hue="cut", ax=axs[0, 0])
sns.scatterplot(data=df, x="carat", y="depth", color=".2", ax=axs[1, 0])
sns.scatterplot(data=df, x="carat", y="table", color=".2", ax=axs[2, 0])
axs[0, 0].set(xlim=(1, 2))
axs[1, 1].remove()
axs[2, 1].remove()
BTW, this is almost a bit easier with plt.subplot_mosaic, but it does not yet support axis sharing.
You could take the figure created by jointplot(), move its padding (with subplots_adjust()) and add 2 extra axes.
The example code will need some tweaking for each particular situation.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
import seaborn as sns
diamonds = sns.load_dataset('diamonds')
g = sns.jointplot(data=diamonds, x="carat", y="price", hue="cut",
xlim=(1, 2), height=12)
g.ax_marg_x.remove()
g.fig.subplots_adjust(left=0.08, right=0.97, top=1.05, bottom=0.45)
axins1 = inset_axes(g.ax_joint, width="100%", height="30%",
bbox_to_anchor=(0, -0.4, 1, 1),
bbox_transform=g.ax_joint.transAxes, loc=3, borderpad=0)
axins2 = inset_axes(g.ax_joint, width="100%", height="30%",
bbox_to_anchor=(0, -0.75, 1, 1),
bbox_transform=g.ax_joint.transAxes, loc=3, borderpad=0)
shared_x_group = g.ax_joint.get_shared_x_axes()
shared_x_group.remove(g.ax_marg_x)
shared_x_group.join(g.ax_joint, axins1)
shared_x_group.join(g.ax_joint, axins2)
axins1.scatter(x=diamonds["carat"], y=diamonds["depth"], color="grey", edgecolor="black")
axins1.set_ylabel("depth")
axins2.scatter(x=diamonds["carat"], y=diamonds["table"], color="grey", edgecolor="black")
axins2.set_xlabel("carat")
axins2.set_ylabel("table")
g.ax_joint.set_xlim(1, 2)
plt.setp(axins1.get_xticklabels(), visible=False)
plt.show()
PS: How to share x axes of two subplots after they have been created contains some info about sharing axes (although here you simply get the same effect by setting the xlims for each of the subplots).
The code to position the new axes has been adapted from this tutorial example.

Seaborn: annotate missing values on the heatmap

I am plotting a heatmap in python with the seaborn library. The dataframe contains some missing values (NaN). I wish that the heatmap cells corresponding to these fields are white (by default) and also annotated with a string NA. However, if I see it correctly, annotation does not work with missing values. Is there any hack around it?
My code:
sns.heatmap(
df,
ax=ax[0, 0],
cbar=False,
annot=annot_df,
fmt="",
annot_kws={"size": annot_size, "va": "center_baseline"},
cmap="coolwarm",
linewidth=0.5,
linecolor="black",
vmin=-max_value,
vmax=max_value,
xticklabels=True,
yticklabels=True,
)
An idea is to draw another heatmap, with a transparent color and with only values where the original dataframe is NaN. To control the axis labels, the "real" heatmap should be drawn last. Note that the color for the NaN cells is the background color of the plot.
import seaborn as sns
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
data = np.where(np.random.rand(7, 10) < 0.2, np.nan, np.random.rand(7, 10) * 2 - 1)
df = pd.DataFrame(data)
annot_df = df.applymap(lambda f: f'{f:.1f}')
fig, ax = plt.subplots(squeeze=False)
sns.heatmap(
np.where(df.isna(), 0, np.nan),
ax=ax[0, 0],
cbar=False,
annot=np.full_like(df, "NA", dtype=object),
fmt="",
annot_kws={"size": 10, "va": "center_baseline", "color": "black"},
cmap=ListedColormap(['none']),
linewidth=0)
sns.heatmap(
df,
ax=ax[0, 0],
cbar=False,
annot=annot_df,
fmt="",
annot_kws={"size": 10, "va": "center_baseline"},
cmap="coolwarm",
linewidth=0.5,
linecolor="black",
vmin=-1,
vmax=1,
xticklabels=True,
yticklabels=True)
plt.show()
PS: To explicitly color the 'NA' cells, e.g. cmap=ListedColormap(['yellow']) could be used.

Errorbar plot transparency overlapping

In an errorbar matplotlib plot, the main line, the markers and the errorbars of a same color overlap each other on their countour when I use the alpha parameter. Although my goal was to have a transparency between the two different colors, but not within the same color, as if same color lines, markers and errorbars were only one object. Is that possible?
import matplotlib.pyplot as plt
import numpy as np
Time = np.array([1, 2, 3])
Green = np.array([3, 5, 9])
Blue = np.array([4, 7, 13])
Green_StDev = np.array([0.6, 0.6, 0.7])
Blue_StDev = np.array([0.5, 0.5, 0.6])
plt.errorbar(Time, Green, Green_StDev, marker='o', c='green', alpha=0.5)
plt.errorbar(Time, Blue, Blue_StDev, marker='o', c='blue', alpha=0.5)
plt.show()
Like the example below, but with transparency only between different color objects, differently of the example above.
I think you cannot draw them as one single object since they (marker and error bar) are drawn individually. However, to make it more 'aesthetic', you could redraw a non-transparent marker:
import matplotlib.pyplot as plt
import numpy as np
Time = np.array([1, 2, 3])
Green = np.array([3, 5, 9])
Blue = np.array([4, 7, 13])
Green_StDev = np.array([0.6, 0.6, 0.7])
Blue_StDev = np.array([0.5, 0.5, 0.6])
plt.errorbar(Time, Green, Green_StDev, marker='o', c='green', alpha=0.5)
# Add additional marker
plt.scatter(Time, Green,marker='o', c='green')
plt.errorbar(Time, Blue, Blue_StDev, marker='o', c='blue', alpha=0.5)
# Add additional marker
plt.scatter(Time, Blue, marker='o', c='blue')
plt.show()

Matplotlib `fill_between`: Remove thin boundary

Consider the following code:
import matplotlib.pyplot as plt
import numpy as np
from pylab import *
graph_data = [[0, 1, 2, 3], [5, 8, 7, 9]]
x = range(len(graph_data[0]))
y = graph_data[1]
fig, ax = plt.subplots()
alpha = 0.5
plt.plot(x, y, '-o',markersize=3, color=[1., alpha, alpha], markeredgewidth=0.0)
ax.fill_between(x, 0, y, facecolor=[1., alpha, alpha], interpolate=False)
plt.show()
filename = 'test1.pdf'
fig.savefig(filename, bbox_inches='tight')
It works fine. However, when zoomed in the generated PDF, I can see two thin gray/black boundaries that separate the line:
I can see this when viewing in both Edge and Chrome. My question is, how can I get rid of the boundaries?
UPDATE I forgot to mention, I was using Sage to generate the graph. Now it seems a problem specific to Sage (and not to Python in general). This time I used native Python, and got correct result.
I could not reproduce it but maybe you can try to not plot the line.
import matplotlib.pyplot as plt
import numpy as np
from pylab import *
graph_data = [[0, 1, 2, 3], [5, 8, 7, 9]]
x = range(len(graph_data[0]))
y = graph_data[1]
fig, ax = plt.subplots()
alpha = 0.5
plt.plot(x, y, 'o',markersize=3, color=[1., alpha, alpha])
ax.fill_between(x, 0, y, facecolor=[1., alpha, alpha], interpolate=False)
plt.show()
filename = 'test1.pdf'
fig.savefig(filename, bbox_inches='tight')

How to turn off matplotlib quiver scaling?

The matplotlib.pyplot.quiver function takes a set of "origin" points and a set of "destination" points and the plots a bunch of arrows starting at the "origin" points headed in the direction of the "destination" points. However, there is a scaling factor so that the arrows don't necessarily end AT the "destination" points, they simply point in that direction.
e.g.
import matplotlib.pyplot as plt
import numpy as np
pts = np.array([[1, 2], [3, 4]])
end_pts = np.array([[2, 4], [6, 8]])
plt.quiver(pts[:,0], pts[:,1], end_pts[:,0], end_pts[:,1])
Note that the vector in the bottom left starts at (1,2) (which I want), but does not end at (2,4). This is governed by a scale parameter to the quiver function that makes the arrow longer or shorter. How do I get the arrow to end at EXACTLY (2,4)?
The quiver documentation states
To plot vectors in the x-y plane, with u and v having the same units as x and y, use angles='xy', scale_units='xy', scale=1.
Note however that u and v are understood relative to the position. Hence you would need to take the difference first.
import matplotlib.pyplot as plt
import numpy as np
pts = np.array([[1, 2], [3, 4]])
end_pts = np.array([[2, 4], [6, 8]])
diff = end_pts - pts
plt.quiver(pts[:,0], pts[:,1], diff[:,0], diff[:,1],
angles='xy', scale_units='xy', scale=1.)
plt.show()