Specify Matplotlib's kwargs to Seaborn's displot when hue is used - matplotlib

Suppose we have this:
import seaborn as sns
import pandas as pd
import numpy as np
samples = 2**13
data = pd.DataFrame({'Values': list(np.random.normal(size=samples)) + list(np.random.uniform(size=samples)),
'Kind': ['Normal'] * samples + ['Uniform'] * samples})
sns.displot(data, hue='Kind', x='Values', fill=True)
I want my Normal's histogram (or KDE) emphasized. I'd like it in red and non transparent in the background. Uniform should have alpha = .5.
How do I specify these style parameters in a "per hue" manner?

It's possible to do it with two separate histplots on the same Axes, as #Redox suggested. We can basically recreate the same plot, but with fine-grade control over colours and alpha. However I had to explicitly pass the number of bins in to get the same plot as yours. I also needed to define the colour for Uniform otherwise a ghost element would be added to the legend! I used C1, meaning the first default colour.
_, ax = plt.subplots()
sns.histplot(data=data[data.Kind=='Normal'], x="Values", ax=ax, label='Normal', color='tab:red',bins=130,alpha=1)
sns.histplot(data=data[data.Kind=='Uniform'], x="Values", ax=ax, label='Uniform', color='C1',bins=17, alpha=.5)
ax.set_xlabel('')
ax.legend()
Note that if you just want to set the colour without alpha you can already do this on a displot via the palette argument - pass in a dictionary of your unique hue values to colour names. However, the alpha that you pass in must be a scalar. I tried to use this clever answer to set colours as RGBA colours which include alpha, which seems to work with other figure level plots in Seaborn. However, displot overrides this and sets the alpha separately!

Related

add text on plt.imshow

I have two ndarrays: Mat, labels
Currently I display Mat:
plt.imshow(Mat, cmap='gray', vmin=0, vmax=1, interpolation='None')
labels has the same shape as Mat, and lables[(i,j)] contains a label of Mat[(i,j)].
How can I show the label on each pixel?
The easiest approach uses Seaborn's heatmap. When annot=True it prints the data values into the cells. But annot= can also be a matrix of labels. In that case it is important to set the print format to string (fmt='s'). annot_kws= can set additional keywords, such as fontsize or color. x and yticklabels can be incorporated in the call to heatmap(), or be set afterwards using matplotlib.
An important benefit of the default coloring is that Sorn uses black on the light colored cells and white on the dark cells.
Here is an example that uses some utf8 characters as labels.
from matplotlib import pyplot as plt
import numpy as np
import seaborn as sns
M, N = 5, 10
mat = np.random.rand(M, N)
labels = np.random.choice(['X', '☀', '★', '♛'], size=(M, N))
ax = sns.heatmap(mat, cmap="inferno", annot=labels, annot_kws={'fontsize': 16}, fmt='s')
plt.show()
PS: There is a matplotlib example in the documentation to create something similar without Seaborn. It can be easily adapted to print strings from a different matrix, and also a test can be added to change the color depending on the cell darkness.

How do I use colourmaps with variable alpha in a Seaborn kdeplot without seeing the contour lines?

Python version: 3.6.4 (Anaconda on Windows)
Seaborn: 0.8.1
Matplotlib: 2.1.2
I'm trying to create a 2D Kernel Density plot using Seaborn but I want each step in the colourmap to have a different alpha value. I had a look at this question to create a matplotlib colourmap with alpha values: Add alpha to an existing matplotlib colormap.
I have a problem in that the lines between contours are visible. The result I get is here:
I thought that I had found the answer when I found this question: Hide contour linestroke on pyplot.contourf to get only fills. I tried the method outlined in the answer (using set_edgecolor("face") but it did not work in this case. That question also seemed to be related to vector graphics formats and I am just writing out a PNG.
Here is my script:
import numpy as np
import seaborn as sns
import matplotlib.colors as cols
import matplotlib.pyplot as plt
def alpha_cmap(cmap):
my_cmap = cmap(np.arange(cmap.N))
# Set a square root alpha.
x = np.linspace(0, 1, cmap.N)
my_cmap[:,-1] = x ** (0.5)
my_cmap = cols.ListedColormap(my_cmap)
return my_cmap
xs = np.random.uniform(size=100)
ys = np.random.uniform(size=100)
kplot = sns.kdeplot(data=xs, data2=ys,
cmap=alpha_cmap(plt.cm.viridis),
shade=True,
shade_lowest=False,
n_levels=30)
plt.savefig("example_plot.png")
Guided by some comments on this question I have tried some other methods that have been successful when this problem has come up. Based on this question (Matplotlib Contourf Plots Unwanted Outlines when Alpha < 1) I have tried altering the plot call to:
sns.kdeplot(data=xs, data2=ys,
cmap=alpha_cmap(plt.cm.viridis),
shade=True,
shade_lowest=False,
n_levels=30,
antialiased=True)
With antialiased=True the lines between contours are replaced by a narrow white line:
I have also tried an approach similar to this question - Pyplot pcolormesh confused when alpha not 1. This approach is based on looping over the PathCollections in kplot.collections and tuning the parameters of the edges so that they become invisible. I have tried adding this code and tweaking the linewidth -
for thing in kplot.collections:
thing.set_edgecolor("face")
thing.set_linewidth(0.01)
fig.canvas.draw()
This results in a mix of white and dark lines - .
I believe that I will not be able to tune the line width to make the lines disappear because of the variable width of the contour bands.
Using both methods (antialiasing + linewidth) makes this version, which looks cool but isn't quite what I want:
I also found this question - Changing Transparency of/Remove Contour Lines in Matplotlib
This one suggests overplotting a second plot with a different number of contour levels on the same axis, like:
kplot = sns.kdeplot(data=xs, data2=ys,
ax=ax,
cmap=alpha_cmap(plt.cm.viridis),
shade=True,
shade_lowest=False,
n_levels=30,
antialiased=True)
kplot = sns.kdeplot(data=xs, data2=ys,
ax=ax,
cmap=alpha_cmap(plt.cm.viridis),
shade=True,
shade_lowest=False,
n_levels=35,
antialiased=True)
This results in:
This is better, and almost works. The problem here is I need variable (and non-linear) alpha throughout the colourmap. The variable banding and lines seem to be a result of the combinations of alpha when contours are plotted over each other. I also still see some clear/white lines in the result.

Different level of transparency for edgeline and fill in matplotlib or seaborn distribution plot

I would like to set different levels of transparency (= alpha) for the edge line and fill of a distribution plot that I created in matplotlib/seaborn. For example:
ax1 = sns.distplot(BSRDI_DF, label="BsrDI", bins=newBins, kde=False,
hist_kws={"edgecolor": (1,0,0,1), "color":(1,0,0,0.25)})
The above approach does not work, unfortunately. Does anybody have any idea how I could accomplish this?
The problem seems to be that seaborn sets an alpha parameter for the histogram. While alpha defaults to None for a usual histogram, such that something like
plt.hist(x, lw=3, edgecolor=(1,0,0,0.75), color=(1,0,0,0.25))
works as expected, seaborn sets this alpha to some given value. This overwrites the alpha that is set in the RGBA tuples.
The solution is to set alpha explicitely to None:
ax = sns.distplot(x, kde=False, hist_kws={"lw":3, "edgecolor": (1,0,0,0.75),
"color":(1,0,0,0.25),"alpha":None})
A complete example:
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
x = np.random.randn(60)
ax = sns.distplot(x, label="BsrDI", bins=np.linspace(-3,3,10), kde=False,
hist_kws={"lw":3, "edgecolor": (1,0,0,0.75),
"color":(1,0,0,0.25),"alpha":None})
plt.show()
EDIT Nevermind, I thought using color instead of facecolor was causing the problem but it seems the output that I got only looked right because the patches were overlapping, giving seemingly darker edges.
After investigating the issue further, it looks like seaborn is hard-setting the alpha level at 0.4, which supersedes the arguments passed to hist_kws=
sns.distplot(x, kde=False, hist_kws={"edgecolor": (1,0,0,1), "lw":5, "facecolor":(0,1,0,0.1), "rwidth":0.8})
While using the same parameters to plt.hist() gives:
plt.hist(x, edgecolor=(1,0,0,1), lw=5, facecolor=(0,1,0,0.1), rwidth=0.8)
Conclusion: if you want different alpha levels for edges and face colors, you'll have to use matplotlib directly, and not seaborn.

Coloring Intersection of Circles/Patches in Matplotlib

The following code:
# in ipython notebook, enable inline plotting with:
# %pylab inline --no-import-all
import matplotlib.pyplot as plt
# create some circles
circle1 = plt.Circle((-.5,0), 1, color='r', alpha=.2)
circle2 = plt.Circle(( .5,0), 1, color='b', alpha=.2)
# add them to the plot (bad form to use ;, but saving space)
# and control the display a bit
ax = plt.gca()
ax.add_artist(circle1); ax.add_artist(circle2)
ax.set_xlim(-2, 2); ax.set_ylim(-2, 2)
ax.set_aspect('equal')
# display it
plt.plot()
Produces the following plot:
I would like to specify the colors of the four regions (1) the background (currently white), (2 and 3) each individual event (the non-overlapping areas, currently blue and red), and (4) the intersection event (currently blended to purple). For example, I might color them red, green, blue, yellow -or- I might give them four different, precisely specified grayscale values (the later is more likely). [The colors will be generated based on characteristics of the underlying data.]
I specifically do not want to use alpha blending to "infer" a color in the intersection. I need to explicitly control the colors of all four regions.
I can think of a few strategies to solve this:
Ask mpl to extract the "primitive" patch objects that make up the three distinctly colored graphical regions (and do something similar to operate on the background) and then color them.
Given the circles, manually compute their intersections and color that intersection (somehow). Going point by point seems ugly.
Thanks!
I'm not 100% sure but I think matplotlib does not have the functionality to intersect polygons. But you could use shapely:
import shapely.geometry as sg
import matplotlib.pyplot as plt
import descartes
# create the circles with shapely
a = sg.Point(-.5,0).buffer(1.)
b = sg.Point(0.5,0).buffer(1.)
# compute the 3 parts
left = a.difference(b)
right = b.difference(a)
middle = a.intersection(b)
# use descartes to create the matplotlib patches
ax = plt.gca()
ax.add_patch(descartes.PolygonPatch(left, fc='b', ec='k', alpha=0.2))
ax.add_patch(descartes.PolygonPatch(right, fc='r', ec='k', alpha=0.2))
ax.add_patch(descartes.PolygonPatch(middle, fc='g', ec='k', alpha=0.2))
# control display
ax.set_xlim(-2, 2); ax.set_ylim(-2, 2)
ax.set_aspect('equal')
plt.show()

Problems with zeros in matplotlib.colors.LogNorm

I am plotting a histogram using
plt.imshow(hist2d, norm = LogNorm(), cmap = gray)
where hist2d is a matrix of histogram values. This works fine except for elements in hist2d that are zero. In particular, I obtain the following image
but would like the white patches to be black.
Thank you!
Here's an alternative method that does not require you to muck with your data by setting a rgb value for bad pixels.
import copy
data = np.arange(25).reshape((5,5))
my_cmap = copy.copy(matplotlib.cm.get_cmap('gray')) # copy the default cmap
my_cmap.set_bad((0,0,0))
plt.imshow(data,
norm=matplotlib.colors.LogNorm(),
interpolation='nearest',
cmap=my_cmap)
The problem is that bins with 0 can not be properly log normalized so they are flagged as 'bad', which are mapped to differently. The default behavior is to not draw anything on those pixels. You can also specify what color to draw pixels that are over or under the limits of the color map (the default is to draw them as the highest/lowest color).
If you're happy with the colour scaling as is, and simply want the 0 values to be black, I'd simply change the input matrix so that the 0s are replaced by the next smallest value:
import matplotlib.pyplot as plt
import matplotlib.cm, matplotlib.colors
import numpy
hist2d = numpy.arange(9).reshape(3,3)
plt.imshow(numpy.maximum(hist2d, sorted(hist2d.flat)[1]),
interpolation='nearest',
norm = matplotlib.colors.LogNorm(),
cmap = matplotlib.cm.gray)
produces
Was this generated with the matplotlib hist2d function?
All you need to do is go through the matrix and set some arbitrary floor value, then make sure to plot this with fixed limits
for f in hist2d:
f += 1e-3
then when you show the figure, all of the whitespace will now be at the floor value, and will show up on the lognormal plot . However, if you are letting hist2d automatically pick the scaling for you, it will want to use the 1e-3 floor value as the minimum. To avoid this, you need to set vmin and vmax values in hist2d
hist2d(x,y,bins=40, norm=LogNorm(), vmin=1, vmax=1e4)