Repository: mwaskom/seaborn

Issue:
longform value error should give the name of the semantic too
e.g.

```python
sns.lineplot(x="time")
```

raises 

> Could not interpret input 'time'`

but something like

> Could not interpret the argument for `x`: 'time'

Would probably be more helpful...

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/mwaskom/seaborn/pull/2205?src=pr&el=h1) Report
> Merging [#2205](https://codecov.io/gh/mwaskom/seaborn/pull/2205?src=pr&el=desc) into [master](https://codecov.io/gh/mwaskom/seaborn/commit/e9550413dc552d65dba8d08040720077f8e610a1&el=desc) will **increase** coverage by `0.04%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/mwaskom/seaborn/pull/2205/graphs/tree.svg?width=650&height=150&src=pr&token=SLuzHnA9Bl)](https://codecov.io/gh/mwaskom/seaborn/pull/2205?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #2205      +/-   ##
==========================================
+ Coverage   96.91%   96.95%   +0.04%     
==========================================
  Files          16       16              
  Lines        5768     5779      +11     
==========================================
+ Hits         5590     5603      +13     
+ Misses        178      176       -2     
```


| [Impacted Files](https://codecov.io/gh/mwaskom/seaborn/pull/2205?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [seaborn/\_core.py](https://codecov.io/gh/mwaskom/seaborn/pull/2205/diff?src=pr&el=tree#diff-c2VhYm9ybi9fY29yZS5weQ==) | `98.43% <100.00%> (+0.02%)` | :arrow_up: |
| [seaborn/categorical.py](https://codecov.io/gh/mwaskom/seaborn/pull/2205/diff?src=pr&el=tree#diff-c2VhYm9ybi9jYXRlZ29yaWNhbC5weQ==) | `97.98% <100.00%> (ø)` | |
| [seaborn/regression.py](https://codecov.io/gh/mwaskom/seaborn/pull/2205/diff?src=pr&el=tree#diff-c2VhYm9ybi9yZWdyZXNzaW9uLnB5) | `97.79% <0.00%> (+0.63%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/mwaskom/seaborn/pull/2205?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/mwaskom/seaborn/pull/2205?src=pr&el=footer). Last update [e955041...5b4d3f5](https://codecov.io/gh/mwaskom/seaborn/pull/2205?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/seaborn/_core.py b/seaborn/_core.py
index 9c59ab04..0bf7b3ff 100644
--- a/seaborn/_core.py
+++ b/seaborn/_core.py
@@ -708,10 +708,15 @@ class VectorPlotter:
             the inputs (or None when no name can be determined).
 
         """
-        # TODO raise here if any kwarg values are not None,
-        # # if we decide for "structure-only" wide API
+        # Raise if semantic or other variables are assigned in wide-form mode
+        assigned = [k for k, v in kwargs.items() if v is not None]
+        if any(assigned):
+            s = "s" if len(assigned) > 1 else ""
+            err = f"The following variable{s} cannot be assigned with wide-form data: "
+            err += ", ".join(f"`{v}`" for v in assigned)
+            raise ValueError(err)
 
-        # First, determine if the data object actually has any data in it
+        # Determine if the data object actually has any data in it
         empty = data is None or not len(data)
 
         # Then, determine if we have "flat" data (a single vector)
@@ -884,14 +889,26 @@ class VectorPlotter:
                     variables[key] = val
                 else:
                     # We don't know what this name means
-                    err = f"Could not interpret input '{val}'"
+                    err = f"Could not interpret value `{val}` for parameter `{key}`"
                     raise ValueError(err)
 
             else:
 
                 # Otherwise, assume the value is itself a vector of data
-                # TODO check for 1D here or let pd.DataFrame raise?
+
+                # Raise when data is present and a vector can't be combined with it
+                if isinstance(data, pd.DataFrame) and not isinstance(val, pd.Series):
+                    if val is not None and len(data) != len(val):
+                        val_cls = val.__class__.__name__
+                        err = (
+                            f"Length of {val_cls} vectors must match length of `data`"
+                            f" when both are used, but `data` has length {len(data)}"
+                            f" and the vector passed to `{key}` has length {len(val)}."
+                        )
+                        raise ValueError(err)
+
                 plot_data[key] = val
+
                 # Try to infer the name of the variable
                 variables[key] = getattr(val, "name", None)
 
diff --git a/seaborn/categorical.py b/seaborn/categorical.py
index 34aa383d..70a7525d 100644
--- a/seaborn/categorical.py
+++ b/seaborn/categorical.py
@@ -45,7 +45,7 @@ class _CategoricalPlotter(object):
 
             # Do a sanity check on the inputs
             if hue is not None:
-                error = "Cannot use `hue` without `x` or `y`"
+                error = "Cannot use `hue` without `x` and `y`"
                 raise ValueError(error)
 
             # No hue grouping with wide inputs


Test patch:
diff --git a/seaborn/conftest.py b/seaborn/conftest.py
index 2630971d..f4809c7c 100644
--- a/seaborn/conftest.py
+++ b/seaborn/conftest.py
@@ -197,6 +197,6 @@ def missing_df(rng, long_df):
 
 
 @pytest.fixture
-def null_series():
+def null_series(flat_series):
 
-    return pd.Series(index=np.arange(20), dtype='float64')
+    return pd.Series(index=flat_series.index, dtype='float64')
diff --git a/seaborn/tests/test_core.py b/seaborn/tests/test_core.py
index 12158470..ac1c90fa 100644
--- a/seaborn/tests/test_core.py
+++ b/seaborn/tests/test_core.py
@@ -78,9 +78,9 @@ class TestHueMapping:
         assert p._hue_map.palette == palette
         assert p._hue_map.levels == hue_order
 
-    def test_hue_map_null(self, long_df, null_series):
+    def test_hue_map_null(self, flat_series, null_series):
 
-        p = VectorPlotter(variables=dict(hue=null_series))
+        p = VectorPlotter(variables=dict(x=flat_series, hue=null_series))
         m = HueMapping(p)
         assert m.levels is None
         assert m.map_type is None
@@ -341,9 +341,9 @@ class TestSizeMapping:
         assert p._size_map.lookup_table == dict(zip(size_order, sizes))
         assert p._size_map.levels == size_order
 
-    def test_size_map_null(self, long_df, null_series):
+    def test_size_map_null(self, flat_series, null_series):
 
-        p = VectorPlotter(variables=dict(size=null_series))
+        p = VectorPlotter(variables=dict(x=flat_series, size=null_series))
         m = HueMapping(p)
         assert m.levels is None
         assert m.map_type is None
@@ -482,9 +482,9 @@ class TestStyleMapping:
         assert p._style_map.levels == style_order
         assert p._style_map(style_order, "marker") == markers
 
-    def test_style_map_null(self, long_df, null_series):
+    def test_style_map_null(self, flat_series, null_series):
 
-        p = VectorPlotter(variables=dict(style=null_series))
+        p = VectorPlotter(variables=dict(x=flat_series, style=null_series))
         m = HueMapping(p)
         assert m.levels is None
         assert m.map_type is None
@@ -596,9 +596,27 @@ class TestVectorPlotter:
         assert p.variables["x"] == expected_x_name
         assert p.variables["y"] == expected_y_name
 
-    # TODO note that most of the other tests that excercise the core
+    # TODO note that most of the other tests that exercise the core
     # variable assignment code still live in test_relational
 
+    def test_wide_semantic_error(self, wide_df):
+
+        err = "The following variable cannot be assigned with wide-form data: `hue`"
+        with pytest.raises(ValueError, match=err):
+            VectorPlotter(data=wide_df, variables={"hue": "a"})
+
+    def test_long_unknown_error(self, long_df):
+
+        err = "Could not interpret value `what` for parameter `hue`"
+        with pytest.raises(ValueError, match=err):
+            VectorPlotter(data=long_df, variables={"x": "x", "hue": "what"})
+
+    def test_long_unmatched_size_error(self, long_df, flat_array):
+
+        err = "Length of ndarray vectors must match length of `data`"
+        with pytest.raises(ValueError, match=err):
+            VectorPlotter(data=long_df, variables={"x": "x", "hue": flat_array})
+
     def test_wide_categorical_columns(self, wide_df):
 
         wide_df.columns = pd.CategoricalIndex(wide_df.columns)
diff --git a/seaborn/tests/test_distributions.py b/seaborn/tests/test_distributions.py
index 2e475f2c..19efc5c9 100644
--- a/seaborn/tests/test_distributions.py
+++ b/seaborn/tests/test_distributions.py
@@ -2018,9 +2018,10 @@ class TestDisPlot:
         if ax.legend_ is not None:
             self.assert_legends_equal(ax.legend_, g._legend)
 
-        long_df["_"] = "_"
-        g2 = displot(long_df, col="_", **kwargs)
-        self.assert_plots_equal(ax, g2.ax)
+        if kwargs:
+            long_df["_"] = "_"
+            g2 = displot(long_df, col="_", **kwargs)
+            self.assert_plots_equal(ax, g2.ax)
 
     @pytest.mark.parametrize(
         "kwargs", [
@@ -2050,9 +2051,10 @@ class TestDisPlot:
         if ax.legend_ is not None:
             self.assert_legends_equal(ax.legend_, g._legend)
 
-        long_df["_"] = "_"
-        g2 = displot(long_df, kind="kde", col="_", **kwargs)
-        self.assert_plots_equal(ax, g2.ax)
+        if kwargs:
+            long_df["_"] = "_"
+            g2 = displot(long_df, kind="kde", col="_", **kwargs)
+            self.assert_plots_equal(ax, g2.ax)
 
     @pytest.mark.parametrize(
         "kwargs", [
@@ -2077,9 +2079,10 @@ class TestDisPlot:
         if ax.legend_ is not None:
             self.assert_legends_equal(ax.legend_, g._legend)
 
-        long_df["_"] = "_"
-        g2 = displot(long_df, kind="ecdf", col="_", **kwargs)
-        self.assert_plots_equal(ax, g2.ax)
+        if kwargs:
+            long_df["_"] = "_"
+            g2 = displot(long_df, kind="ecdf", col="_", **kwargs)
+            self.assert_plots_equal(ax, g2.ax)
 
     @pytest.mark.parametrize(
         "kwargs", [