Seaborn stripplot set edgecolor based on hue/palette - matplotlib

I'm trying to create a figure like this one from the seaborn documentation but with the edgecolor of the stripplot determined by the hue. This is my attempt:
import seaborn as sns
df = sns.load_dataset("tips")
ax = sns.stripplot(x="sex", y="tip", hue="day", data=df, jitter=True,
edgecolor=sns.color_palette("hls", 4),
facecolors="none", split=False, alpha=0.7)
But the color palettes for male and female appear to be different. How do I use the same color palette for both categories?
I'm using seaborn 0.6.dev

The edgecolor parameter is just passed straight through to plt.scatter. Currently you're giving it a list of 4 colors. I'm not exactly sure what I would expect it to do in that case (and I am not exactly sure why you end up with what you're seeing here), but I would not have expected it to "work".
The ideal way to have this work would be to have a "hollow circle" marker glyph that colors the edges based on the color (or facecolor) attribute rather than the edges. While it would be nice to have this as an option in core matplotlib, there are some inconsistencies that might make that unworkable. However, it's possible to hack together a custom glyph that will do the trick:
import numpy as np
import matplotlib as mpl
import seaborn as sns
sns.set_style("whitegrid")
df = sns.load_dataset("tips")
pnts = np.linspace(0, np.pi * 2, 24)
circ = np.c_[np.sin(pts) / 2, -np.cos(pts) / 2]
vert = np.r_[circ, circ[::-1] * .7]
open_circle = mpl.path.Path(vert)
sns.stripplot(x="sex", y="tip", hue="day", data=df,
jitter=True, split=False,
palette="hls", marker=open_circle, linewidth=0)
FWIW I should also mention that it's important to be careful when using this approach because the colors become much harder to distinguish. The hls palette exacerbates the problem as the lime green and cyan middle colors end up quite similar. I can imagine situations where this would work nicely, though, for instance a hue variable with two levels represented by gray and a bright color, where you want to emphasize the latter.

Related

How to se BG color over an Histogram graph in matplotlb [duplicate]

I am making a scatter plot in matplotlib and need to change the background of the actual plot to black. I know how to change the face color of the plot using:
fig = plt.figure()
fig.patch.set_facecolor('xkcd:mint green')
My issue is that this changes the color of the space around the plot. How to I change the actual background color of the plot?
Use the set_facecolor(color) method of the axes object, which you've created one of the following ways:
You created a figure and axis/es together
fig, ax = plt.subplots(nrows=1, ncols=1)
You created a figure, then axis/es later
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1) # nrows, ncols, index
You used the stateful API (if you're doing anything more than a few lines, and especially if you have multiple plots, the object-oriented methods above make life easier because you can refer to specific figures, plot on certain axes, and customize either)
plt.plot(...)
ax = plt.gca()
Then you can use set_facecolor:
ax.set_facecolor('xkcd:salmon')
ax.set_facecolor((1.0, 0.47, 0.42))
As a refresher for what colors can be:
matplotlib.colors
Matplotlib recognizes the following formats to specify a color:
an RGB or RGBA tuple of float values in [0, 1] (e.g., (0.1, 0.2, 0.5) or (0.1, 0.2, 0.5, 0.3));
a hex RGB or RGBA string (e.g., '#0F0F0F' or '#0F0F0F0F');
a string representation of a float value in [0, 1] inclusive for gray level (e.g., '0.5');
one of {'b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'};
a X11/CSS4 color name;
a name from the xkcd color survey; prefixed with 'xkcd:' (e.g., 'xkcd:sky blue');
one of {'tab:blue', 'tab:orange', 'tab:green', 'tab:red', 'tab:purple', 'tab:brown', 'tab:pink', 'tab:gray', 'tab:olive', 'tab:cyan'} which are the Tableau Colors from the ‘T10’ categorical palette (which is the default color cycle);
a “CN” color spec, i.e. 'C' followed by a single digit, which is an index into the default property cycle (matplotlib.rcParams['axes.prop_cycle']); the indexing occurs at artist creation time and defaults to black if the cycle does not include color.
All string specifications of color, other than “CN”, are case-insensitive.
One method is to manually set the default for the axis background color within your script (see Customizing matplotlib):
import matplotlib.pyplot as plt
plt.rcParams['axes.facecolor'] = 'black'
This is in contrast to Nick T's method which changes the background color for a specific axes object. Resetting the defaults is useful if you're making multiple different plots with similar styles and don't want to keep changing different axes objects.
Note: The equivalent for
fig = plt.figure()
fig.patch.set_facecolor('black')
from your question is:
plt.rcParams['figure.facecolor'] = 'black'
Something like this? Use the axisbg keyword to subplot:
>>> from matplotlib.figure import Figure
>>> from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
>>> figure = Figure()
>>> canvas = FigureCanvas(figure)
>>> axes = figure.add_subplot(1, 1, 1, axisbg='red')
>>> axes.plot([1,2,3])
[<matplotlib.lines.Line2D object at 0x2827e50>]
>>> canvas.print_figure('red-bg.png')
(Granted, not a scatter plot, and not a black background.)
Simpler answer:
ax = plt.axes()
ax.set_facecolor('silver')
If you already have axes object, just like in Nick T's answer, you can also use
ax.patch.set_facecolor('black')
The easiest thing is probably to provide the color when you create the plot :
fig1 = plt.figure(facecolor=(1, 1, 1))
or
fig1, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, facecolor=(1, 1, 1))
One suggestion in other answers is to use ax.set_axis_bgcolor("red"). This however is deprecated, and doesn't work on MatPlotLib >= v2.0.
There is also the suggestion to use ax.patch.set_facecolor("red") (works on both MatPlotLib v1.5 & v2.2). While this works fine, an even easier solution for v2.0+ is to use
ax.set_facecolor("red")
In addition to the answer of NickT, you can also delete the background frame by setting it to "none" as explain here: https://stackoverflow.com/a/67126649/8669161
import matplotlib.pyplot as plt
plt.rcParams['axes.facecolor'] = 'none'
I think this might be useful for some people:
If you want to change the color of the background that surrounds the figure, you can use this:
fig.patch.set_facecolor('white')
So instead of this:
you get this:
Obviously you can set any color you'd want.
P.S. In case you accidentally don't see any difference between the two plots, try looking at StackOverflow using darkmode.

Geopandas & Mapplotlib, how do I plot without an outline around any shape?

When I run the code below in a Jupyter Notebook,
I get a map of the world, colored in red.
There are fine white-ish lines between the countries.
Is there a way to plot the world so that all countries
are solid and there's no line in between?
I'm asking, because my real world usecase is a fine grid that
behaves just like the world map: Each grid shape has a fine outline
which I do not want to have in the plot. (Update, since this was asked: The grid shapes will not have the same fill color.
)
import geopandas as gpd
import geoplot as gplt
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
world['total'] = 1
world.plot(column='total', cmap='Set1')
For the grid example, the grid files are at https://opendata-esri-de.opendata.arcgis.com/datasets/3c1f46241cbb4b669e18b002e4893711_0
A simplified example that shows the problem.
sf = 'Hexagone_125_km/Hexagone_125_km.shp'
shp = gpd.read_file(sf)
shp.crs = {'init': 'epsg:4326'}
shp['sum'] = 1 # for example, fill sum with something
shp.plot(figsize=(20,20), column='sum', cmap='gnuplot', alpha=1, legend=True)
The white lines are due to antialiasing. This usually makes the visual more smooth, but leads to white lines in between different shapes. You can turn off anialiasing via
antialiased=False
That has the inevitable drawback of the plot looking pixelated.
An alternative is to give the patches an edge with a certain linewidth. The edges should probably have the same color as the faces, so
edgecolor="face", linewidth=0.4
would be an option. This removes the white lines, but introduces a slight "searing" effect (You'll notice mainly looking at islands like Indonesia or Japan). This will be the more noticable, the smaller the features, so it may be irrelevant for showing a hexbin plot. Still, playing a bit with the linewidth might improve the result further.
Code for reproduction:
import numpy as np; np.random.seed(42)
import geopandas as gpd
import matplotlib.pyplot as plt
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
world['total'] = np.random.randint(0,10, size=len(world))
fig, (ax1, ax2, ax3) = plt.subplots(nrows=3, figsize=(7,10))
world.plot(column='total', cmap='Set1', ax=ax1)
world.plot(column='total', cmap='Set1', ax=ax2, antialiased=False)
world.plot(column='total', cmap='Set1', ax=ax3, edgecolor="face", linewidth=0.4)
ax1.set_title("original")
ax2.set_title("antialiased=False")
ax3.set_title("edgecolor='face', linewidth=0.4")
plt.tight_layout()
plt.savefig("world.png")
plt.show()

Periodic grayscale colormap

To plot an angle, I need an intuitive periodic colormap. I found the 'hsv' colormap (https://matplotlib.org/users/colormaps.html) which is periodic, but very not intuitive because to me green is not further away from blue than from yellow for example.
I think a periodic grayscale colormap is exactly what I need: changing smoothly from black in both edges to white in the middle (or the other way around).
I could not find this colormap in the built-in ones. Does anyone know of such existing colormap or a way to manually define it?
The easiest way to create a the desired black-white-black colormap is to use matplotlib.colors.LinearSegmentedColormap.from_list()
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
colors = ["black", "white", "black"]
cmap=LinearSegmentedColormap.from_list("", colors)
Then use it as any usual colormap:
import numpy as np
r = np.linspace(0,1)
t = np.linspace(0,2*np.pi, 360)
R,T = np.meshgrid(r,t)
fig, ax = plt.subplots(subplot_kw=dict(projection="polar"))
ax.pcolormesh(T,R,T, cmap=cmap)
plt.show()

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()