
.. DO NOT EDIT.
.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.
.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE:
.. "auto_examples/cluster/plot_ward_structured_vs_unstructured.py"
.. LINE NUMBERS ARE GIVEN BELOW.

.. only:: html

    .. note::
        :class: sphx-glr-download-link-note

        :ref:`Go to the end <sphx_glr_download_auto_examples_cluster_plot_ward_structured_vs_unstructured.py>`
        to download the full example code or to run this example in your browser via JupyterLite or Binder.

.. rst-class:: sphx-glr-example-title

.. _sphx_glr_auto_examples_cluster_plot_ward_structured_vs_unstructured.py:


===================================================
Hierarchical clustering with and without structure
===================================================

This example demonstrates hierarchical clustering with and without
connectivity constraints. It shows the effect of imposing a connectivity
graph to capture local structure in the data. Without connectivity constraints,
the clustering is based purely on distance, while with constraints, the
clustering respects local structure.

For more information, see :ref:`hierarchical_clustering`.

There are two advantages of imposing connectivity. First, clustering
with sparse connectivity matrices is faster in general.

Second, when using a connectivity matrix, single, average and complete
linkage are unstable and tend to create a few clusters that grow very
quickly. Indeed, average and complete linkage fight this percolation behavior
by considering all the distances between two clusters when merging them
(while single linkage exaggerates the behaviour by considering only the
shortest distance between clusters). The connectivity graph breaks this
mechanism for average and complete linkage, making them resemble the more
brittle single linkage. This effect is more pronounced for very sparse graphs
(try decreasing the number of neighbors in `kneighbors_graph`) and with
complete linkage. In particular, having a very small number of neighbors in
the graph, imposes a geometry that is close to that of single linkage,
which is well known to have this percolation instability.

The effect of imposing connectivity is illustrated on two different but
similar datasets which show a spiral structure. In the first example we
build a Swiss roll dataset and run hierarchical clustering on the position
of the data. Here, we compare unstructured Ward clustering with a
structured variant that enforces k-Nearest Neighbors connectivity. In the
second example we include the effects of applying a such a connectivity graph
to single, average and complete linkage.

.. GENERATED FROM PYTHON SOURCE LINES 38-42

.. code-block:: Python


    # Authors: The scikit-learn developers
    # SPDX-License-Identifier: BSD-3-Clause








.. GENERATED FROM PYTHON SOURCE LINES 43-45

Generate the Swiss Roll dataset.
--------------------------------

.. GENERATED FROM PYTHON SOURCE LINES 45-55

.. code-block:: Python

    import time

    from sklearn.cluster import AgglomerativeClustering
    from sklearn.datasets import make_swiss_roll

    n_samples = 1500
    noise = 0.05
    X1, _ = make_swiss_roll(n_samples, noise=noise)
    X1[:, 1] *= 0.5  # Make the roll thinner








.. GENERATED FROM PYTHON SOURCE LINES 56-58

Compute clustering without connectivity constraints
---------------------------------------------------

.. GENERATED FROM PYTHON SOURCE LINES 58-66

.. code-block:: Python

    print("Compute unstructured hierarchical clustering...")
    st = time.time()
    ward_unstructured = AgglomerativeClustering(n_clusters=6, linkage="ward").fit(X1)
    elapsed_time_unstructured = time.time() - st
    label_unstructured = ward_unstructured.labels_
    print(f"Elapsed time: {elapsed_time_unstructured:.2f}s")
    print(f"Number of points: {label_unstructured.size}")





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    Compute unstructured hierarchical clustering...
    Elapsed time: 0.04s
    Number of points: 1500




.. GENERATED FROM PYTHON SOURCE LINES 67-68

Plot unstructured clustering result

.. GENERATED FROM PYTHON SOURCE LINES 68-87

.. code-block:: Python

    import matplotlib.pyplot as plt
    import numpy as np

    fig1 = plt.figure()
    ax1 = fig1.add_subplot(111, projection="3d", elev=7, azim=-80)
    ax1.set_position([0, 0, 0.95, 1])
    for l in np.unique(label_unstructured):
        ax1.scatter(
            X1[label_unstructured == l, 0],
            X1[label_unstructured == l, 1],
            X1[label_unstructured == l, 2],
            color=plt.cm.jet(float(l) / np.max(label_unstructured + 1)),
            s=20,
            edgecolor="k",
        )
    _ = fig1.suptitle(
        f"Without connectivity constraints (time {elapsed_time_unstructured:.2f}s)"
    )




.. image-sg:: /auto_examples/cluster/images/sphx_glr_plot_ward_structured_vs_unstructured_001.png
   :alt: Without connectivity constraints (time 0.04s)
   :srcset: /auto_examples/cluster/images/sphx_glr_plot_ward_structured_vs_unstructured_001.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 88-90

Compute clustering with connectivity constraints
------------------------------------------------

.. GENERATED FROM PYTHON SOURCE LINES 90-104

.. code-block:: Python

    from sklearn.neighbors import kneighbors_graph

    connectivity = kneighbors_graph(X1, n_neighbors=10, include_self=False)

    print("Compute structured hierarchical clustering...")
    st = time.time()
    ward_structured = AgglomerativeClustering(
        n_clusters=6, connectivity=connectivity, linkage="ward"
    ).fit(X1)
    elapsed_time_structured = time.time() - st
    label_structured = ward_structured.labels_
    print(f"Elapsed time: {elapsed_time_structured:.2f}s")
    print(f"Number of points: {label_structured.size}")





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    Compute structured hierarchical clustering...
    Elapsed time: 0.06s
    Number of points: 1500




.. GENERATED FROM PYTHON SOURCE LINES 105-106

Plot structured clustering result

.. GENERATED FROM PYTHON SOURCE LINES 106-122

.. code-block:: Python

    fig2 = plt.figure()
    ax2 = fig2.add_subplot(111, projection="3d", elev=7, azim=-80)
    ax2.set_position([0, 0, 0.95, 1])
    for l in np.unique(label_structured):
        ax2.scatter(
            X1[label_structured == l, 0],
            X1[label_structured == l, 1],
            X1[label_structured == l, 2],
            color=plt.cm.jet(float(l) / np.max(label_structured + 1)),
            s=20,
            edgecolor="k",
        )
    _ = fig2.suptitle(
        f"With connectivity constraints (time {elapsed_time_structured:.2f}s)"
    )




.. image-sg:: /auto_examples/cluster/images/sphx_glr_plot_ward_structured_vs_unstructured_002.png
   :alt: With connectivity constraints (time 0.06s)
   :srcset: /auto_examples/cluster/images/sphx_glr_plot_ward_structured_vs_unstructured_002.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 123-125

Generate 2D spiral dataset.
---------------------------

.. GENERATED FROM PYTHON SOURCE LINES 125-135

.. code-block:: Python

    n_samples = 1500
    np.random.seed(0)
    t = 1.5 * np.pi * (1 + 3 * np.random.rand(1, n_samples))
    x = t * np.cos(t)
    y = t * np.sin(t)

    X2 = np.concatenate((x, y))
    X2 += 0.7 * np.random.randn(2, n_samples)
    X2 = X2.T








.. GENERATED FROM PYTHON SOURCE LINES 136-142

Capture local connectivity using a graph
----------------------------------------
Larger number of neighbors will give more homogeneous clusters to
the cost of computation time. A very large number of neighbors gives
more evenly distributed cluster sizes, but may not impose the local
manifold structure of the data.

.. GENERATED FROM PYTHON SOURCE LINES 142-144

.. code-block:: Python

    knn_graph = kneighbors_graph(X2, 30, include_self=False)








.. GENERATED FROM PYTHON SOURCE LINES 145-147

Plot clustering with and without structure
******************************************

.. GENERATED FROM PYTHON SOURCE LINES 147-182

.. code-block:: Python

    fig3 = plt.figure(figsize=(8, 12))
    subfigs = fig3.subfigures(4, 1)
    params = [
        (None, 30),
        (None, 3),
        (knn_graph, 30),
        (knn_graph, 3),
    ]

    for subfig, (connectivity, n_clusters) in zip(subfigs, params):
        axs = subfig.subplots(1, 4, sharey=True)
        for index, linkage in enumerate(("average", "complete", "ward", "single")):
            model = AgglomerativeClustering(
                linkage=linkage, connectivity=connectivity, n_clusters=n_clusters
            )
            t0 = time.time()
            model.fit(X2)
            elapsed_time = time.time() - t0
            axs[index].scatter(
                X2[:, 0], X2[:, 1], c=model.labels_, cmap=plt.cm.nipy_spectral
            )
            axs[index].set_title(
                "linkage=%s\n(time %.2fs)" % (linkage, elapsed_time),
                fontdict=dict(verticalalignment="top"),
            )
            axs[index].set_aspect("equal")
            axs[index].axis("off")

            subfig.subplots_adjust(bottom=0, top=0.83, wspace=0, left=0, right=1)
            subfig.suptitle(
                "n_cluster=%i, connectivity=%r" % (n_clusters, connectivity is not None),
                size=17,
            )

    plt.show()



.. image-sg:: /auto_examples/cluster/images/sphx_glr_plot_ward_structured_vs_unstructured_003.png
   :alt: linkage=average (time 0.04s), linkage=complete (time 0.03s), linkage=ward (time 0.04s), linkage=single (time 0.01s), linkage=average (time 0.04s), linkage=complete (time 0.03s), linkage=ward (time 0.04s), linkage=single (time 0.01s), linkage=average (time 0.10s), linkage=complete (time 0.11s), linkage=ward (time 0.15s), linkage=single (time 0.02s), linkage=average (time 0.10s), linkage=complete (time 0.11s), linkage=ward (time 0.15s), linkage=single (time 0.02s)
   :srcset: /auto_examples/cluster/images/sphx_glr_plot_ward_structured_vs_unstructured_003.png
   :class: sphx-glr-single-img






.. rst-class:: sphx-glr-timing

   **Total running time of the script:** (0 minutes 2.094 seconds)


.. _sphx_glr_download_auto_examples_cluster_plot_ward_structured_vs_unstructured.py:

.. only:: html

  .. container:: sphx-glr-footer sphx-glr-footer-example

    .. container:: binder-badge

      .. image:: images/binder_badge_logo.svg
        :target: https://mybinder.org/v2/gh/scikit-learn/scikit-learn/1.8.X?urlpath=lab/tree/notebooks/auto_examples/cluster/plot_ward_structured_vs_unstructured.ipynb
        :alt: Launch binder
        :width: 150 px

    .. container:: lite-badge

      .. image:: images/jupyterlite_badge_logo.svg
        :target: ../../lite/lab/index.html?path=auto_examples/cluster/plot_ward_structured_vs_unstructured.ipynb
        :alt: Launch JupyterLite
        :width: 150 px

    .. container:: sphx-glr-download sphx-glr-download-jupyter

      :download:`Download Jupyter notebook: plot_ward_structured_vs_unstructured.ipynb <plot_ward_structured_vs_unstructured.ipynb>`

    .. container:: sphx-glr-download sphx-glr-download-python

      :download:`Download Python source code: plot_ward_structured_vs_unstructured.py <plot_ward_structured_vs_unstructured.py>`

    .. container:: sphx-glr-download sphx-glr-download-zip

      :download:`Download zipped: plot_ward_structured_vs_unstructured.zip <plot_ward_structured_vs_unstructured.zip>`


.. include:: plot_ward_structured_vs_unstructured.recommendations


.. only:: html

 .. rst-class:: sphx-glr-signature

    `Gallery generated by Sphinx-Gallery <https://sphinx-gallery.github.io>`_
