Repository: scipy/scipy
Pull request: ENH: add input validation for dendrogram and a test.
#### Reference issue
To fix #11492 

#### What does this implement/fix?
add input validation of Z and labels dimension consistency for dendrogram and a test.

Problem statement:
Confusing dendrogram labelling
<!-- 
Thank you for taking the time to file a bug report. 
Please fill in the fields below, deleting the sections that 
don't apply to your issue. You can view the final output
by clicking the preview button above.
Note: This is a comment, and won't appear in the output.
-->

`scipy.cluster.hierarchy.dendrogram` has undesirable behavior when `labels` specified

#### Reproducing code example:
<!-- 
If you place your code between the triple backticks below, 
it will be rendered as a code block. 
-->

```
link = np.array([
    [0, 1, 1.0, 4],
    [2, 3, 1.0, 5],
    [4, 5, 2.0, 6],    
])
plt.figure()
dendrogram(link, labels=list(range(100)))
```

Though there are only 4 linked objects, no error raised. Labels on plot are 96, 97, 98, 99.

#### Scipy/Numpy/Python version information:
<!-- You can simply run the following and paste the result in a code block
```
import sys, scipy, numpy; print(scipy.__version__, numpy.__version__, sys.version_info)
```
-->
1.4.1 / 1.18.1 / sys.version_info(major=3, minor=7, micro=4, releaselevel='final', serial=0)

Hints from issue discussion:
This is a small issue - it would be nice to have input validation for the `labels` keyword (as well as for other keywords probably), but basically this is user error (providing the wrong length input for `labels`).

EDIT: it's not even a bug, because the needed length for `labels` is clearly specified in the docstring
Don't think so.
- Docstring says that `labels` is n-sized list/tuple, but doesn't clearly define what is `n` (it should be defined in `Z` parameter description)
- Suppose `matplotlib.pyplot.plot` would always plot `x` vs `y[-len(x):]` instead of checking their consistency. It would obviously be a big problem.
A fix is very welcome of course. Both your points make sense. Would you like to open a PR?

Commit message: ENH: add input validation for dendrogram and a test.

Patch to review:
diff --git a/scipy/cluster/hierarchy.py b/scipy/cluster/hierarchy.py
index fc4bec5e93ae..0f64927c41ba 100644
--- a/scipy/cluster/hierarchy.py
+++ b/scipy/cluster/hierarchy.py
@@ -3093,6 +3093,8 @@ def dendrogram(Z, p=30, truncate_mode=None, color_threshold=None,
         -sized list (or tuple). The ``labels[i]`` value is the text to put
         under the :math:`i` th leaf node only if it corresponds to an original
         observation and not a non-singleton cluster.
+
+        Note, Dimensions of ``Z`` and ``labels`` must be consistent.
     count_sort : str or bool, optional
         For each node n, the order (visually, from left-to-right) n's
         two descendent links are plotted is determined by this
@@ -3271,6 +3273,9 @@ def llf(id):
         raise ValueError("orientation must be one of 'top', 'left', "
                          "'bottom', or 'right'")
 
+    if labels and  Z.shape[1] != len(labels):
+        raise ValueError("Dimensions of Z and labels must be consistent.")
+
     is_valid_linkage(Z, throw=True, name='Z')
     Zs = Z.shape
     n = Zs[0] + 1
diff --git a/scipy/cluster/tests/test_hierarchy.py b/scipy/cluster/tests/test_hierarchy.py
index b5fe3f87c187..b8373fb2cde7 100644
--- a/scipy/cluster/tests/test_hierarchy.py
+++ b/scipy/cluster/tests/test_hierarchy.py
@@ -812,6 +812,20 @@ def test_valid_orientation(self):
         Z = linkage(hierarchy_test_data.ytdist, 'single')
         assert_raises(ValueError, dendrogram, Z, orientation="foo")
 
+    @pytest.mark.skipif(not have_matplotlib, reason="no matplotlib")
+    def test_valid_label_size(self):
+        link = np.array([
+            [0, 1, 1.0, 4],
+            [2, 3, 1.0, 5],
+            [4, 5, 2.0, 6],
+        ])
+        plt.figure()
+        with pytest.raises(ValueError) as exc_info:
+            dendrogram(link, labels=list(range(100)))
+        assert "Dimensions of Z and labels must be consistent."\
+               in str(exc_info.value)
+        plt.close()
+
     @pytest.mark.skipif(not have_matplotlib, reason="no matplotlib")
     def test_dendrogram_plot(self):
         for orientation in ['top', 'bottom', 'left', 'right']:


Code review comments:
diff_hunk: @@ -3093,6 +3093,8 @@ def dendrogram(Z, p=30, truncate_mode=None, color_threshold=None,
         -sized list (or tuple). The ``labels[i]`` value is the text to put
         under the :math:`i` th leaf node only if it corresponds to an original
         observation and not a non-singleton cluster.
+
+        Note, Dimensions of ``Z`` and ``labels`` must be consistent.
path: scipy/cluster/hierarchy.py
text: @user1:
minor: could you spell dimensions with a lower-case D?

@user1:
Actually, it would be better to spell out what `n` is in the paragraph above, instead of adding this sentence. E.g. change `"n-sized list (or tuple)"` to
```
`n`-sized sequence, with ``n == Z.shape[1]``
```