Repository: mwaskom/seaborn

Issue:
seaborn violinplot does not work right for 'inner=points' and 'inner=box' options 
When plotting a violinplot I can't get the inner='points' and inner='box' options work for the left part (Hue='Before').
![points](https://user-images.githubusercontent.com/6062179/153753897-3c4e36b3-149e-4d6b-80d5-7ab7897e5100.png)
![box](https://user-images.githubusercontent.com/6062179/153753900-11015208-b2fd-47b5-9df5-bacf39af96a8.png)


Nevertheless, for inner='quartile' and inner='stick' this works well.
![quartile](https://user-images.githubusercontent.com/6062179/153753908-a77874c7-438c-4c79-9fa3-33318bfd52f8.png)
![stick](https://user-images.githubusercontent.com/6062179/153753914-40e4d356-d715-47eb-bacf-d87b2eec44c5.png)


Here goes the code used to reproduce it:

    
    N = 30
    np.random.seed(3)
    data = np.random.normal(size=(N,))
    df = pd.DataFrame({'Before': data,
                       'After': data + 1})
    df_long = pd.melt(df, value_vars=['Before', 'After'])
    sns.violinplot(x='variable', y='value', data=df_long, hue='variable', split=True, inner='box', cut=1)

I'm using seaborn 0.11.2.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/mwaskom/seaborn/pull/2814?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) Report
> Merging [#2814](https://codecov.io/gh/mwaskom/seaborn/pull/2814?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) (d4fceee) into [master](https://codecov.io/gh/mwaskom/seaborn/commit/34b887325ca01033d39698c18a1d14941ef574f0?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) (34b8873) will **decrease** coverage by `0.16%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##           master    #2814      +/-   ##
==========================================
- Coverage   97.96%   97.79%   -0.17%     
==========================================
  Files          67       67              
  Lines       21756    21760       +4     
==========================================
- Hits        21313    21280      -33     
- Misses        443      480      +37     
```


| [Impacted Files](https://codecov.io/gh/mwaskom/seaborn/pull/2814?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) | Coverage Δ | |
|---|---|---|
| [seaborn/categorical.py](https://codecov.io/gh/mwaskom/seaborn/pull/2814/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9jYXRlZ29yaWNhbC5weQ==) | `98.24% <100.00%> (ø)` | |
| [seaborn/tests/test\_categorical.py](https://codecov.io/gh/mwaskom/seaborn/pull/2814/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi90ZXN0cy90ZXN0X2NhdGVnb3JpY2FsLnB5) | `99.72% <100.00%> (-0.05%)` | :arrow_down: |
| [seaborn/\_compat.py](https://codecov.io/gh/mwaskom/seaborn/pull/2814/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9fY29tcGF0LnB5) | `17.56% <0.00%> (-10.82%)` | :arrow_down: |
| [seaborn/\_docstrings.py](https://codecov.io/gh/mwaskom/seaborn/pull/2814/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9fZG9jc3RyaW5ncy5weQ==) | `97.50% <0.00%> (-2.50%)` | :arrow_down: |
| [seaborn/\_core/subplots.py](https://codecov.io/gh/mwaskom/seaborn/pull/2814/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9fY29yZS9zdWJwbG90cy5weQ==) | `98.61% <0.00%> (-1.39%)` | :arrow_down: |
| [seaborn/\_core/plot.py](https://codecov.io/gh/mwaskom/seaborn/pull/2814/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9fY29yZS9wbG90LnB5) | `93.78% <0.00%> (-1.31%)` | :arrow_down: |
| [seaborn/utils.py](https://codecov.io/gh/mwaskom/seaborn/pull/2814/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi91dGlscy5weQ==) | `94.08% <0.00%> (-0.89%)` | :arrow_down: |
| [seaborn/\_core/scales.py](https://codecov.io/gh/mwaskom/seaborn/pull/2814/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9fY29yZS9zY2FsZXMucHk=) | `86.24% <0.00%> (-0.58%)` | :arrow_down: |
| [seaborn/\_core/properties.py](https://codecov.io/gh/mwaskom/seaborn/pull/2814/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi9fY29yZS9wcm9wZXJ0aWVzLnB5) | `98.58% <0.00%> (-0.57%)` | :arrow_down: |
| [seaborn/tests/test\_distributions.py](https://codecov.io/gh/mwaskom/seaborn/pull/2814/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom#diff-c2VhYm9ybi90ZXN0cy90ZXN0X2Rpc3RyaWJ1dGlvbnMucHk=) | `99.52% <0.00%> (-0.28%)` | :arrow_down: |
| ... and [4 more](https://codecov.io/gh/mwaskom/seaborn/pull/2814/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/mwaskom/seaborn/pull/2814?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/mwaskom/seaborn/pull/2814?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom). Last update [34b8873...d4fceee](https://codecov.io/gh/mwaskom/seaborn/pull/2814?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Michael+Waskom).


Patch:
diff --git a/doc/releases/v0.12.0.txt b/doc/releases/v0.12.0.txt
index 8c4d37b7..1ce2c782 100644
--- a/doc/releases/v0.12.0.txt
+++ b/doc/releases/v0.12.0.txt
@@ -57,10 +57,13 @@ Other updates
 
 - |Fix| Fixed two edgecases in :func:`histplot` when only `binwidth` was provided (:pr:`2813').
 
-- |Fix| FacetGrid subplot titles will no longer be reset when calling :meth:`FacetGrid.map` or :meth:`FacetGrid.map_dataframe` after :meth:`FacetGrid.set_titles` (:pr:`2705`).
+- |Fix| Fixed a bug in :func:`violinplot` where inner boxes/points could be missing with unpaired split violins (:pr:`2814`).
+
+- |Fix| Subplot titles will no longer be reset when calling :meth:`FacetGrid.map` or :meth:`FacetGrid.map_dataframe` (:pr:`2705`).
 
 - |Fix| In :func:`lineplot`, allowed the `dashes` keyword to set the style of a line without mapping a `style` variable (:pr:`2449`).
 
+
 - |Dependencies| Made `scipy` an optional dependency and added `pip install seaborn[all]` as a method for ensuring the availability of compatible `scipy` and `statsmodels` libraries at install time. This has a few minor implications for existing code, which are explained in the Github pull request (:pr:`2398`).
 
 - |Dependencies| Following `NEP29 <https://numpy.org/neps/nep-0029-deprecation_policy.html>`_, dropped support for Python 3.6 and bumped the minimally-supported versions of the library dependencies.
diff --git a/seaborn/_core/plot.py b/seaborn/_core/plot.py
index 2abd64e7..2c735a92 100644
--- a/seaborn/_core/plot.py
+++ b/seaborn/_core/plot.py
@@ -1263,12 +1263,16 @@ class Plotter:
                 order = categorical_order(df[var])
             grouping_keys.append(order)
 
-        def split_generator() -> Generator:
+        def split_generator(dropna=True) -> Generator:
 
             for view in subplots:
 
                 axes_df = self._filter_subplot_data(df, view)
 
+                if dropna:
+                    with pd.option_context("mode.use_inf_as_null", True):
+                        axes_df = axes_df.dropna()
+
                 subplot_keys = {}
                 for dim in ["col", "row"]:
                     if view[dim] is not None:
diff --git a/seaborn/_core/properties.py b/seaborn/_core/properties.py
index 68836cd0..95539dc6 100644
--- a/seaborn/_core/properties.py
+++ b/seaborn/_core/properties.py
@@ -312,6 +312,7 @@ class ObjectProperty(Property):
     normed = False
 
     # Object representing null data, should appear invisible when drawn by matplotlib
+    # Note that we now drop nulls in Plot._plot_layer and thus may not need this
     null_value: Any = None
 
     def _default_values(self, n: int) -> list:
@@ -720,7 +721,11 @@ class Fill(Property):
             raise TypeError(msg)
 
         def mapping(x):
-            return np.take(values, np.asarray(x, np.intp))
+            ixs = np.asarray(x, np.intp)
+            return [
+                values[ix] if np.isfinite(x_i) else False
+                for x_i, ix in zip(x, ixs)
+            ]
 
         return mapping
 
diff --git a/seaborn/_marks/basic.py b/seaborn/_marks/basic.py
index 2d5d8d38..a79e99bd 100644
--- a/seaborn/_marks/basic.py
+++ b/seaborn/_marks/basic.py
@@ -1,6 +1,7 @@
 from __future__ import annotations
 from dataclasses import dataclass
 
+import numpy as np
 import matplotlib as mpl
 
 from seaborn._marks.base import (
@@ -35,13 +36,15 @@ class Line(Mark):
 
     def _plot(self, split_gen, scales, orient):
 
-        for keys, data, ax in split_gen():
+        for keys, data, ax in split_gen(dropna=False):
 
             keys = resolve_properties(self, keys, scales)
 
             if self.sort:
                 # TODO where to dropna?
-                data = data.dropna().sort_values(orient)
+                data = data.sort_values(orient)
+            else:
+                data.loc[data.isna().any(axis=1), ["x", "y"]] = np.nan
 
             line = mpl.lines.Line2D(
                 data["x"].to_numpy(),
diff --git a/seaborn/_marks/scatter.py b/seaborn/_marks/scatter.py
index b5d4a0bc..85642ff3 100644
--- a/seaborn/_marks/scatter.py
+++ b/seaborn/_marks/scatter.py
@@ -67,7 +67,7 @@ class Scatter(Mark):
             filled_marker = [m.is_filled() for m in resolved["marker"]]
 
         resolved["linewidth"] = resolved["stroke"]
-        resolved["fill"] = resolved["fill"] & filled_marker
+        resolved["fill"] = resolved["fill"] * filled_marker
         resolved["size"] = resolved["pointsize"] ** 2
 
         resolved["edgecolor"] = resolve_color(self, data, "", scales)
@@ -91,7 +91,6 @@ class Scatter(Mark):
         # (That should be solved upstream by defaulting to "" for unset x/y?)
         # (Be mindful of xmin/xmax, etc!)
 
-        # TODO pass scales *into* split_gen?
         for keys, data, ax in split_gen():
 
             offsets = np.column_stack([data["x"], data["y"]])
diff --git a/seaborn/categorical.py b/seaborn/categorical.py
index 3964ab74..15c3e8b0 100644
--- a/seaborn/categorical.py
+++ b/seaborn/categorical.py
@@ -1136,7 +1136,7 @@ class _ViolinPlotter(_CategoricalPlotter):
 
                 # Draw box and whisker information
                 if self.inner.startswith("box"):
-                    self.draw_box_lines(ax, violin_data, support, density, i)
+                    self.draw_box_lines(ax, violin_data, i)
 
                 # Draw quartile lines
                 elif self.inner.startswith("quart"):
@@ -1217,7 +1217,7 @@ class _ViolinPlotter(_CategoricalPlotter):
 
                         # The box and point interior plots are drawn for
                         # all data at the group level, so we just do that once
-                        if not j:
+                        if j and any(self.plot_hues[0] == hue_level):
                             continue
 
                         # Get the whole vector for this group level
@@ -1225,8 +1225,7 @@ class _ViolinPlotter(_CategoricalPlotter):
 
                         # Draw box and whisker information
                         if self.inner.startswith("box"):
-                            self.draw_box_lines(ax, violin_data,
-                                                support, density, i)
+                            self.draw_box_lines(ax, violin_data, i)
 
                         # Draw point observations
                         elif self.inner.startswith("point"):
@@ -1252,9 +1251,7 @@ class _ViolinPlotter(_CategoricalPlotter):
 
                         # Draw box and whisker information
                         if self.inner.startswith("box"):
-                            self.draw_box_lines(ax, violin_data,
-                                                support, density,
-                                                i + offsets[j])
+                            self.draw_box_lines(ax, violin_data, i + offsets[j])
 
                         # Draw quartile lines
                         elif self.inner.startswith("quart"):
@@ -1286,7 +1283,7 @@ class _ViolinPlotter(_CategoricalPlotter):
                     color=self.gray,
                     linewidth=self.linewidth)
 
-    def draw_box_lines(self, ax, data, support, density, center):
+    def draw_box_lines(self, ax, data, center):
         """Draw boxplot information at center of the density."""
         # Compute the boxplot statistics
         q25, q50, q75 = np.percentile(data, [25, 50, 75])


Test patch:
diff --git a/seaborn/tests/_marks/test_scatter.py b/seaborn/tests/_marks/test_scatter.py
index e5f0a6b8..9f751f95 100644
--- a/seaborn/tests/_marks/test_scatter.py
+++ b/seaborn/tests/_marks/test_scatter.py
@@ -1,5 +1,6 @@
 from matplotlib.colors import to_rgba, to_rgba_array
 
+import pytest
 from numpy.testing import assert_array_equal
 
 from seaborn._core.plot import Plot
@@ -139,3 +140,15 @@ class TestDot(ScatterBase):
 
         expected = [mark.edgewidth, mark.stroke]
         assert_array_equal(points.get_linewidths(), expected)
+
+    @pytest.mark.parametrize("prop", ["color", "fill", "marker", "pointsize"])
+    def test_missing_semantic_data(self, prop):
+
+        x = [1, 2, 3]
+        y = [5, 3, 4]
+        z = ["a", float("nan"), "b"]
+
+        p = Plot(x=x, y=y, **{prop: z}).add(Dot()).plot()
+        ax = p._figure.axes[0]
+        points, = ax.collections
+        self.check_offsets(points, [1, 3], [5, 4])
diff --git a/seaborn/tests/test_categorical.py b/seaborn/tests/test_categorical.py
index fd37b809..fc7fc5e5 100644
--- a/seaborn/tests/test_categorical.py
+++ b/seaborn/tests/test_categorical.py
@@ -1324,7 +1324,7 @@ class TestViolinPlotter(CategoricalFixture):
         p = cat._ViolinPlotter(**kws)
 
         _, ax = plt.subplots()
-        p.draw_box_lines(ax, self.y, p.support[0], p.density[0], 0)
+        p.draw_box_lines(ax, self.y, 0)
         assert len(ax.lines) == 2
 
         q25, q50, q75 = np.percentile(self.y, [25, 50, 75])
@@ -1342,7 +1342,7 @@ class TestViolinPlotter(CategoricalFixture):
         p = cat._ViolinPlotter(**kws)
 
         _, ax = plt.subplots()
-        p.draw_box_lines(ax, self.y, p.support[0], p.density[0], 0)
+        p.draw_box_lines(ax, self.y, 0)
         assert len(ax.lines) == 2
 
         q25, q50, q75 = np.percentile(self.y, [25, 50, 75])
@@ -1592,6 +1592,13 @@ class TestViolinPlotter(CategoricalFixture):
                            inner=inner, split=True)
             plt.close("all")
 
+    def test_split_one_each(self, rng):
+
+        x = np.repeat([0, 1], 5)
+        y = rng.normal(0, 1, 10)
+        ax = cat.violinplot(x=x, y=y, hue=x, split=True, inner="box")
+        assert len(ax.lines) == 4
+
 
 # ====================================================================================
 # ====================================================================================