mwaskom / seaborn

Statistical data visualization in Python
https://seaborn.pydata.org
BSD 3-Clause "New" or "Revised" License
12.55k stars 1.92k forks source link

`PlotSpecError` after setting color parameter on so.Plot.scale #3375

Open joaofauvel opened 1 year ago

joaofauvel commented 1 year ago

Setting the color param with an integer series on so.Plot.add and then setting the color param on so.Plot.scale to a qualitative palette raises PlotSpecError: Scaling operation failed for the color variable. If the color palette is sequential, no error is raised. I don't believe this is intended, given that it works when the series is casted to a str, category, or float.

Example:

import seaborn as sns
import seaborn.objects as so

# loading example dataset and splitting subject column into its number
fmri = sns.load_dataset('fmri').assign(subject=lambda df: df.subject.str.split('s', expand=True).iloc[:,1].astype(int))

(
    so.Plot(fmri, x='timepoint', y='signal')
    .add(so.Lines(alpha=.7), so.Agg(), color='subject')
    .scale(color='deep')
    .add(so.Line(linewidth=3), so.Agg())
)
Traceback ``` IndexError Traceback (most recent call last) File /opt/conda/lib/python3.10/site-packages/seaborn/_marks/base.py:179, in Mark._resolve(self, data, name, scales) 178 try: --> 179 feature = scale(value) 180 except Exception as err: File /opt/conda/lib/python3.10/site-packages/seaborn/_core/scales.py:129, in Scale.__call__(self, data) 128 if func is not None: --> 129 trans_data = func(trans_data) 131 if scalar_data: File /opt/conda/lib/python3.10/site-packages/seaborn/_core/properties.py:682, in Color._get_nominal_mapping..mapping(x) 681 out = np.full((len(ixs), colors.shape[1]), np.nan) --> 682 out[use] = np.take(colors, ixs[use], axis=0) 683 return out File <__array_function__ internals>:180, in take(*args, **kwargs) File /opt/conda/lib/python3.10/site-packages/numpy/core/fromnumeric.py:190, in take(a, indices, axis, out, mode) 95 """ 96 Take elements from an array along an axis. 97 (...) 188 [5, 7]]) 189 """ --> 190 return _wrapfunc(a, 'take', indices, axis=axis, out=out, mode=mode) File /opt/conda/lib/python3.10/site-packages/numpy/core/fromnumeric.py:57, in _wrapfunc(obj, method, *args, **kwds) 56 try: ---> 57 return bound(*args, **kwds) 58 except TypeError: 59 # A TypeError occurs if the object does have such a method in its 60 # class, but its signature is not identical to that of NumPy's. This (...) 64 # Call _wrapit from within the except clause to ensure a potential 65 # exception has a traceback chain. IndexError: index 14 is out of bounds for axis 0 with size 14 The above exception was the direct cause of the following exception: PlotSpecError Traceback (most recent call last) File /opt/conda/lib/python3.10/site-packages/IPython/core/formatters.py:344, in BaseFormatter.__call__(self, obj) 342 method = get_real_method(obj, self.print_method) 343 if method is not None: --> 344 return method() 345 return None 346 else: File /opt/conda/lib/python3.10/site-packages/seaborn/_core/plot.py:279, in Plot._repr_png_(self) 277 def _repr_png_(self) -> tuple[bytes, dict[str, float]]: --> 279 return self.plot()._repr_png_() File /opt/conda/lib/python3.10/site-packages/seaborn/_core/plot.py:821, in Plot.plot(self, pyplot) 817 """ 818 Compile the plot spec and return the Plotter object. 819 """ 820 with theme_context(self._theme_with_defaults()): --> 821 return self._plot(pyplot) File /opt/conda/lib/python3.10/site-packages/seaborn/_core/plot.py:851, in Plot._plot(self, pyplot) 849 # Process the data for each layer and add matplotlib artists 850 for layer in layers: --> 851 plotter._plot_layer(self, layer) 853 # Add various figure decorations 854 plotter._make_legend(self) File /opt/conda/lib/python3.10/site-packages/seaborn/_core/plot.py:1366, in Plotter._plot_layer(self, p, layer) 1363 grouping_vars = mark._grouping_props + default_grouping_vars 1364 split_generator = self._setup_split_generator(grouping_vars, df, subplots) -> 1366 mark._plot(split_generator, scales, orient) 1368 # TODO is this the right place for this? 1369 for view in self._subplots: File /opt/conda/lib/python3.10/site-packages/seaborn/_marks/line.py:186, in Paths._plot(self, split_gen, scales, orient) 183 line_data[ax]["segments"].extend(segments) 184 n = len(segments) --> 186 vals = resolve_properties(self, keys, scales) 187 vals["color"] = resolve_color(self, keys, scales=scales) 189 line_data[ax]["colors"].extend([vals["color"]] * n) File /opt/conda/lib/python3.10/site-packages/seaborn/_marks/base.py:235, in resolve_properties(mark, data, scales) 231 def resolve_properties( 232 mark: Mark, data: DataFrame, scales: dict[str, Scale] 233 ) -> dict[str, Any]: --> 235 props = { 236 name: mark._resolve(data, name, scales) for name in mark._mappable_props 237 } 238 return props File /opt/conda/lib/python3.10/site-packages/seaborn/_marks/base.py:236, in (.0) 231 def resolve_properties( 232 mark: Mark, data: DataFrame, scales: dict[str, Scale] 233 ) -> dict[str, Any]: 235 props = { --> 236 name: mark._resolve(data, name, scales) for name in mark._mappable_props 237 } 238 return props File /opt/conda/lib/python3.10/site-packages/seaborn/_marks/base.py:181, in Mark._resolve(self, data, name, scales) 179 feature = scale(value) 180 except Exception as err: --> 181 raise PlotSpecError._during("Scaling operation", name) from err 183 if return_array: 184 feature = np.asarray(feature) PlotSpecError: Scaling operation failed for the `color` variable. See the traceback above for more information. ```
mwaskom commented 1 year ago

Thanks for the clear report. Interestingly, when I tried to reduce it to an even simpler example that should have the same characteristics (integer type data with a nominal scale) I'm getting the expected behavior:

import seaborn as sns
import seaborn.objects as so

(
    so.Plot(x=[1, 2, 3], y=[1, 2, 3], color=[1, 2, 3])
    .add(so.Dot())
    .scale(color=so.Nominal())
)

So something complicated is happening here...

frfeng commented 12 months ago

I ran into the same error. It seems it relates to using multiple add method. The code below reproduces the error.

Seaborn version: 0.13.0

import seaborn.objects as so

(
    so.Plot(x=[1, 2, 3], y=[1, 2, 3], color=[1, 2, 3])
    .add(so.Dot())
    .add(so.Dot(), x=[1], y=[1], color=[1])
    .scale(color=so.Nominal())
)
Traceback: ``` --------------------------------------------------------------------------- IndexError Traceback (most recent call last) File /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/seaborn/_marks/base.py:180, in Mark._resolve(self, data, name, scales) 179 try: --> 180 feature = scale(value) 181 except Exception as err: File /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/seaborn/_core/scales.py:129, in Scale.__call__(self, data) 128 if func is not None: --> 129 trans_data = func(trans_data) 131 if scalar_data: File /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/seaborn/_core/properties.py:682, in Color._get_nominal_mapping..mapping(x) 681 out = np.full((len(ixs), colors.shape[1]), np.nan) --> 682 out[use] = np.take(colors, ixs[use], axis=0) 683 return out File /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/numpy/core/fromnumeric.py:192, in take(a, indices, axis, out, mode) 97 """ 98 Take elements from an array along an axis. 99 (...) 190 [5, 7]]) 191 """ --> 192 return _wrapfunc(a, 'take', indices, axis=axis, out=out, mode=mode) File /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/numpy/core/fromnumeric.py:59, in _wrapfunc(obj, method, *args, **kwds) 58 try: ---> 59 return bound(*args, **kwds) 60 except TypeError: 61 # A TypeError occurs if the object does have such a method in its 62 # class, but its signature is not identical to that of NumPy's. This (...) 66 # Call _wrapit from within the except clause to ensure a potential 67 # exception has a traceback chain. IndexError: index 3 is out of bounds for axis 0 with size 3 The above exception was the direct cause of the following exception: PlotSpecError Traceback (most recent call last) File /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/IPython/core/formatters.py:344, in BaseFormatter.__call__(self, obj) 342 method = get_real_method(obj, self.print_method) 343 if method is not None: --> 344 return method() 345 return None 346 else: File /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/seaborn/_core/plot.py:387, in Plot._repr_png_(self) 385 if Plot.config.display["format"] != "png": 386 return None --> 387 return self.plot()._repr_png_() File /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/seaborn/_core/plot.py:934, in Plot.plot(self, pyplot) 930 """ 931 Compile the plot spec and return the Plotter object. 932 """ 933 with theme_context(self._theme_with_defaults()): --> 934 return self._plot(pyplot) File /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/seaborn/_core/plot.py:964, in Plot._plot(self, pyplot) 962 # Process the data for each layer and add matplotlib artists 963 for layer in layers: --> 964 plotter._plot_layer(self, layer) 966 # Add various figure decorations 967 plotter._make_legend(self) File /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/seaborn/_core/plot.py:1501, in Plotter._plot_layer(self, p, layer) 1498 grouping_vars = mark._grouping_props + default_grouping_vars 1499 split_generator = self._setup_split_generator(grouping_vars, df, subplots) -> 1501 mark._plot(split_generator, scales, orient) 1503 # TODO is this the right place for this? 1504 for view in self._subplots: File /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/seaborn/_marks/dot.py:71, in DotBase._plot(self, split_gen, scales, orient) 68 for _, data, ax in split_gen(): 70 offsets = np.column_stack([data["x"], data["y"]]) ---> 71 data = self._resolve_properties(data, scales) 73 points = mpl.collections.PathCollection( 74 offsets=offsets, 75 paths=data["path"], (...) 83 **self.artist_kws, 84 ) 85 ax.add_collection(points) File /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/seaborn/_marks/dot.py:134, in Dot._resolve_properties(self, data, scales) 132 def _resolve_properties(self, data, scales): --> 134 resolved = super()._resolve_properties(data, scales) 135 filled = resolved["fill"] 137 main_stroke = resolved["stroke"] File /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/seaborn/_marks/dot.py:49, in DotBase._resolve_properties(self, data, scales) 47 def _resolve_properties(self, data, scales): ---> 49 resolved = resolve_properties(self, data, scales) 50 resolved["path"] = self._resolve_paths(resolved) 51 resolved["size"] = resolved["pointsize"] ** 2 File /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/seaborn/_marks/base.py:237, in resolve_properties(mark, data, scales) 232 def resolve_properties( 233 mark: Mark, data: DataFrame, scales: dict[str, Scale] 234 ) -> dict[str, Any]: 236 props = { --> 237 name: mark._resolve(data, name, scales) for name in mark._mappable_props 238 } 239 return props File /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/seaborn/_marks/base.py:182, in Mark._resolve(self, data, name, scales) 180 feature = scale(value) 181 except Exception as err: --> 182 raise PlotSpecError._during("Scaling operation", name) from err 184 if return_array: 185 feature = np.asarray(feature) PlotSpecError: Scaling operation failed for the `color` variable. See the traceback above for more information. ```
akartapanis commented 4 months ago

Any updates with regards to this issue?