Open bschweigert opened 6 months ago
hm this smells like something between matplotlib and cartopy, that's not a function seaborn is calling directly. can you share the full traceback?
Here is the full traceback:
File ~\Miniconda3\envs\figs\Lib\site-packages\spyder_kernels\py3compat.py:356 in compat_exec exec(code, globals, locals)
File c:\users\bschweigert2\desktop\lm clim\error_ex.py:28 kde = sns.kdeplot(x = lons, y = lats, fill = True, transform = ccrs.PlateCarree(), zorder = 1)
File ~\Miniconda3\envs\figs\Lib\site-packages\seaborn\distributions.py:1682 in kdeplot color = _default_color(method, hue, color, kwargs)
File ~\Miniconda3\envs\figs\Lib\site-packages\seaborn\utils.py:136 in _default_color scout = method([], [], **kws)
File ~\Miniconda3\envs\figs\Lib\site-packages\matplotlib__init__.py:1478 in inner return func(ax, *map(sanitize_sequence, args), **kwargs)
File ~\Miniconda3\envs\figs\Lib\site-packages\matplotlib\axes_axes.py:5509 in fill_between return self._fill_between_x_or_y(
File ~\Miniconda3\envs\figs\Lib\site-packages\matplotlib\axes_axes.py:5500 in _fill_between_x_or_y up_x, up_y = kwargs["transform"].contains_branch_seperately(self.transData)
AttributeError: 'PlateCarree' object has no attribute 'contains_branch_seperately'
That would be intriguing, because I have done plenty of plotting recently with matplotlib and cartopy with no issues thus far. Thanks for the help!
Based on that it seems like you could reproduce this with something like
f, ax = plt.subplots()
ax.fill_between([], [], transform=ccrs.PlateCarree())
(untested as I don't have the relevant geographic dependencies)
You may need to set up the subplot with the same projection too, I am not sure.
@bschweigert , I've been looking into this one too.
I compared to code from 0.9 branch (I saw it was working from someone on stack exchange in 2020 so picked the branch of that year).
Back then the kwargs
being fed into seaborn.distributions.kdeplot
went straight to _bivariate_kdeplot_
or _univariate_kdeplot
, which later feeds the kwargs into ax.contourf
or ax.contour
which I believe is managed by matplotlib.
However, somewhere along the way (about 3 years ago), this was added before the kwargs can get to plot_univariate_density
or plot_bivariate_density
color = _default_color(method, hue, color, kwargs)
It consequently tries to pass in the transform
kwarg into the fill_between
method in matplotlib - the tricky bit is that fill_between
does accept the transform kwarg, except it must be a matplotlib.transform type!
I am not sure if you might still encounter the error with a univariate density plot with fill=True
because it does look like it can calll fill_between
. I haven't tested/looked further into this.
But it seems to work fine on a bivariate density maybe because contourf
supports the cartopy projection? transform
is an argument for matplotlib.contour.ContourSet
and seems to accept a wide range of transformation types based on the get_transform
function. This function converts the transform variable to a matplotlib.Transform
type.
A workaround for you to continue forward is to do this:
kde = sns.kdeplot(x = lons, y = lats, fill = True, transform = ccrs.PlateCarree()._as_mpl_transform(ax), zorder = 1)
as cartopy projections have a (private) method to convert to matplotlib transform types.
Whether this is a bug by seaborn, or by matplotlib - I don't know. Maybe matplotlib should convert all transforms to a matplotlib type first in fill_between
, or maybe seaborn should be doing the conversion - or maybe transform
type should be first checked to prevent passing into _default_color()
as I don't think it's doing much with that information anyway.
@mwaskom , let me know if you would like to me to raise a PR to address this if one of the suggestions is something you would want to implement in seaborn.
I am plotting lat/lon points on a geographic map, and I would like to underlay a KDE plot beneath scatter points. Because they are lat/lon points, the plotting will use a cartopy transform (in my case PlateCarree). When calling the kdeplot function with fill=False, everything works fine. However, with fill = True it returns the error:
AttributeError: 'PlateCarree' object has no attribute 'contains_branch_seperately'
Here is reproducible code:
import numpy as np import seaborn as sns import cartopy.crs as ccrs import matplotlib.pyplot as plt import cartopy.feature as cfeature
Create example lat/lon points
lons = np.random.uniform(low = -100, high = -90, size = 20) lats = np.random.uniform(low = 30, high = 40, size = 20)
Create the figure
fig = plt.figure(figsize = (6, 4), dpi = 300) ax = fig.add_subplot(1, 1, 1, projection = ccrs.LambertConformal())
Add features
ax.add_feature(cfeature.STATES.with_scale('50m'), zorder = 1) ax.set_extent((-103, -88, 22.5, 42))
Scatter plot the lat/lon points
ax.scatter(lons, lats, c = 'black', marker = 'x', transform = ccrs.PlateCarree(), zorder = 2)
Add a KDE underlay
kde = sns.kdeplot(x = lons, y = lats, fill = True, transform = ccrs.PlateCarree(), zorder = 1)