Repository: scipy/scipy

Issue:
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)


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?

Resolving patch:
diff --git a/scipy/cluster/hierarchy.py b/scipy/cluster/hierarchy.py
--- a/scipy/cluster/hierarchy.py
+++ b/scipy/cluster/hierarchy.py
@@ -3089,10 +3089,10 @@ def dendrogram(Z, p=30, truncate_mode=None, color_threshold=None,
 
     labels : ndarray, optional
         By default, ``labels`` is None so the index of the original observation
-        is used to label the leaf nodes.  Otherwise, this is an :math:`n`
-        -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.
+        is used to label the leaf nodes.  Otherwise, this is an :math:`n`-sized
+        sequence, with ``n == Z.shape[0] + 1``. 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.
     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 +3271,9 @@ def llf(id):
         raise ValueError("orientation must be one of 'top', 'left', "
                          "'bottom', or 'right'")
 
+    if labels and Z.shape[0] + 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


Test patch:
diff --git a/scipy/cluster/tests/test_hierarchy.py b/scipy/cluster/tests/test_hierarchy.py
--- 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']: