Visualizzazione per Metodi di Clustering

Clustering Visualization

Nota dell’editore: Evie Fowler è una relatrice per ODSC West. Assicurati di dare un’occhiata al suo intervento, “Colmare il divario di interpretabilità nella segmentazione dei clienti”, lì!

Alla Open Data Science Conference di quest’autunno, parlerò di come portare un approccio sistematico all’interpretazione dei modelli di clustering. Per prepararci a questo, parliamo di visualizzazione dei dati per i modelli di clustering.

Preparazione di un Ambiente di Lavoro

Tutte queste visualizzazioni possono essere create con gli strumenti di base della manipolazione dei dati (pandas e numpy) e i fondamenti della visualizzazione (matplotlib e seaborn).

from matplotlib import colormaps, pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import load_diabetes
from sklearn.preprocessing import MinMaxScaler
import numpy as np
import pandas as pd
import seaborn as sns

Per questo tutorial, userò il dataset di previsione del diabete incorporato in matplotlib. Offrirò molte più informazioni su come addestrare e valutare un modello di clustering efficace a ODSC, ma per ora, mi limiterò ad adattare alcuni semplici modelli di k-means.

# carica i dati sul diabete
diabetesData = load_diabetes(as_frame = True).data

# centra e scala le caratteristiche clusterizzabili
diabetesScaler = MinMaxScaler().fit(diabetesData)
diabetesDataScaled = pd.DataFrame(diabetesScaler.transform(diabetesData)
                                  , columns = diabetesData.columns
                                  , index = diabetesData.index)

# crea tre piccoli modelli di clustering
km3 = KMeans(n_clusters = 3).fit(diabetesDataScaled)
km4 = KMeans(n_clusters = 4).fit(diabetesDataScaled)
km10 = KMeans(n_clusters = 10).fit(diabetesDataScaled)

Scelta di uno Schema di Colori

Il pacchetto matplotlib fornisce una serie di schemi di colori incorporati attraverso il suo registro dei colormap. È conveniente scegliere un colormap per l’intera visualizzazione ed è importante scegliere attentamente. Ciò può significare valutare tutto, dalla sequenzialità della mappa (quando i dati possono essere interpretati lungo una scala da basso ad alto) alla divergenza della mappa (quando i dati sono più rilevanti in uno dei due estremi), fino alla loro adeguatezza tematica per il soggetto (verdi e marroni per un progetto di topografia). Quando non c’è una particolare relazione tra i dati e l’ordine in cui verranno presentati, il colormap nipy_spectral è una buona scelta.

# scegli il colormap nipy_spectral da matplotlib
nps = colormaps['nipy_spectral']

# visualizza l'intero colormap
nps

Ogni colormap di matplotlib è composto da una serie di tuple, ognuna delle quali descrive un colore nel formato RGBA (anche se con componenti scalati tra [0, 1] anziché [0, 255]). I singoli colori dalla mappa possono essere accessibili sia tramite un numero intero (tra 0 e 255) che tramite un numero decimale (tra 0 e 1). I numeri vicini a 0 corrispondono a colori nella parte inferiore della mappa dei colori, mentre i numeri vicini a 255 e i decimali vicini a 1.0 corrispondono a colori nella parte superiore della mappa dei colori. In modo intuitivo, lo stesso colore può essere descritto sia da un numero intero che da un numero decimale che rappresenta quel numero come un quoziente di 255.

# visualizza alcuni colori selezionati dal colormap
print(nps(51))
#(0.0, 0.0, 0.8667, 1.0)

print(nps(0.2))
#(0.0, 0.0, 0.8667, 1.0)

Creazione di Visualizzazioni

Grafici a Scatter

La visualizzazione classica per un modello di clustering è una serie di grafici a scatter che confrontano ogni coppia di caratteristiche utilizzate nel modello di clustering, con l’assegnazione dei cluster indicata dal colore. Ci sono metodi incorporati per ottenere questo risultato, ma un approccio fai da te offre maggior controllo sui dettagli come lo schema di colori.

def plotScatters(df, model):
    """ Crea grafici a scatter basati su ogni coppia di colonne in un dataframe.
    Utilizza il colore per indicare l'etichetta del modello.
    """

    # crea una figura e degli assi
    righeGrafico = df.shape[1]
    colonneGrafico = df.shape[1]
    fig, axes = plt.subplots(
        # crea una riga e una colonna per ogni caratteristica nel dataframe
        righeGrafico, colonneGrafico
        # ingrandisci le dimensioni della figura per una visualizzazione più semplice
        , figsize = ((colonneGrafico * 3), (righeGrafico * 3))
    )   
    # itera attraverso gli assi per creare i grafici a scatter
    indiceGrafico = 0
    for i in np.arange(0, righeGrafico):
        for j in np.arange(0, colonneGrafico):
            indiceGrafico += 1
            # identifica il sottografo corrente
            plt.subplot(righeGrafico, colonneGrafico, indiceGrafico)
            plt.scatter(
                # confronta la i-esima e la j-esima caratteristica del dataframe
                df.iloc[:, j], df.iloc[:, i]
                # utilizza etichette di cluster intere e un colormap per unificare la selezione dei colori
                , c = model.labels_, cmap = nps
                # scegli una dimensione di marker piccola per ridurre la sovrapposizione
                , s = 1)
            # etichetta l'asse x sull'ultima riga dei sottografi
            if i == df.shape[1] - 1:
                plt.xlabel(df.columns[j])
            # etichetta l'asse y sulla prima colonna dei sottografi
            if j == 0:
                plt.ylabel(df.columns[i])           

    plt.show()

Questi grafici svolgono una doppia funzione, mostrando la relazione tra una coppia di caratteristiche e la relazione tra tali caratteristiche e l’assegnazione al cluster.

plotScatters(diabetesDataScaled, km3)

Man mano che l’analisi avanza, è facile concentrarsi su un subset più piccolo di caratteristiche.

plotScatters(diabetesDataScaled.iloc[:, 2:7], km4)

Violin Plots

Per avere una migliore comprensione della distribuzione di ciascuna caratteristica all’interno di ogni cluster, possiamo anche guardare i violin plots. Se non sei familiare con i violin plots, pensa a loro come al cugino adulto del classico box plot. Mentre i box plot identificano solo alcuni descrittori chiave di una distribuzione, i violin plots sono contornati per illustrare l’intera funzione di densità di probabilità.

def plotViolins(df, model, plotCols = 5):
    """ Crea violin plots di ogni caratteristica in un dataframe
    Usa le etichette del modello per raggruppare.
    """  

    # calcola il numero di righe necessarie per la griglia del plot
    plotRows = df.shape[1] // plotCols
    while plotRows * plotCols < df.shape[1]:
        plotRows += 1      

    # crea una figura e degli assi
    fig, axes = plt.subplots(plotRows, plotCols
                             # ingrandisci la dimensione della figura per una visualizzazione facile
                             , figsize = ((plotCols * 3), (plotRows * 3))
                            )  

    # identifica le etichette dei cluster uniche dal modello
    uniqueLabels = sorted(np.unique(model.labels_))    

    # crea una sottopalette personalizzata dalle etichette uniche
    # questo restituirà
    npsTemp = nps([x / max(uniqueLabels) for x in uniqueLabels])  

    # aggiungi le etichette dei cluster interi al dataframe di input
    df2 = df.assign(cluster = model.labels_)  

    # itera attraverso i subplots per creare i violin plots
    pltindex = 0
    for col in df.columns:
        pltindex += 1
        plt.subplot(plotRows, plotCols, pltindex)
        sns.violinplot(
            data = df2
            # usa le etichette dei cluster come raggruppatore x
            , x = 'cluster'
            # usa la caratteristica corrente come valori y
            , y = col
            # usa le etichette dei cluster e la palette personalizzata per unificare la selezione del colore
            , hue = model.labels_
            , palette = npsTemp
        ).legend_.remove()
        # etichetta l'asse y con il nome della caratteristica
        plt.ylabel(col)   

    plt.show()

plotViolins(diabetesDataScaled, km3, plotCols = 5)

Istogrammi

I violin plots mostrano la distribuzione di ciascuna caratteristica all’interno di ogni cluster, ma è anche utile guardare come ogni cluster è rappresentato nella distribuzione più ampia di ciascuna caratteristica. Un istogramma modificato può illustrare bene questo concetto.

def histogramByCluster(df, labels, plotCols = 5, nbins = 30, legend = False, vlines = False):
    """ Crea un istogramma di ciascuna caratteristica.
    Usa le etichette del modello per codificare a colori.
    """
 
    # calcola il numero di righe necessarie per la griglia del plot
    plotRows = df.shape[1] // plotCols
    while plotRows * plotCols < df.shape[1]:
        plotRows += 1

    # identifica le etichette dei cluster uniche
    uniqueLabels = sorted(np.unique(labels))
  
    # crea una figura e degli assi
    fig, axes = plt.subplots(plotRows, plotCols
                             # ingrandisci la dimensione della figura per una visualizzazione facile
                             , figsize = ((plotCols * 3), (plotRows * 3))
                            )
    pltindex = 0
    # loop attraverso le caratteristiche nel dataframe di input
    for col in df.columns:
        # discretizza la caratteristica in un numero specificato di bin
        tempBins = np.trunc(nbins * df[col]) / nbins
        # combina la caratteristica discretizzata con le etichette dei cluster
        tempComb = pd.crosstab(tempBins, labels)
        # crea un indice delle stesse dimensioni della cross tab
        # questo aiuterà con l'allineamento
        ind = np.arange(tempComb.shape[0])

        # identifica il subplot rilevante
        pltindex += 1
        plt.subplot(plotRows, plotCols, pltindex)
        # crea i dati dell'istogramma raggruppati
        histPrep = {}
        # lavora un cluster alla volta
        for lbl in uniqueLabels:
            histPrep.update(
                {
                    # associa l'etichetta del cluster...
                    lbl:
                    # ... a un istogramma a barre
                    plt.bar(
                        # usa l'indice specifico della caratteristica per impostare le posizioni x
                        x = ind
                        # usa le conte di questo cluster come altezza della barra
                        , height = tempComb[lbl]
                        # sovrappone questa barra sulle barre precedenti dei cluster
                        , bottom = tempComb[[x for x in uniqueLabels if x < lbl]].sum(axis = 1)
                        # elimina gli spazi tra le barre
                        , width = 1
                        , color = nps(lbl / max(uniqueLabels))
                    )
                }
            )
       
        # usa il nome della caratteristica per etichettare l'asse x di ogni plot
        plt.xlabel(col)
    
        # etichetta l'asse y dei plot nella prima colonna
        if pltindex % plotCols == 1:
            plt.ylabel('Frequenza')
        plt.xticks(ind[0::5], np.round(tempComb.index[0::5], 2))
     
        # se desiderato, sovrappone linee verticali
        if vlines:
            for vline in vlines:
                plt.axvline(x = vline * ind[-1], lw = 0.5, color = 'red')
    
    if legend:
        leg1 = []; leg2 = []
        for key in histPrep:
            leg1 += [histPrep[key]]
            leg2 += [str(key)]
        plt.legend(leg1, leg2)

    plt.show()
histogramByCluster(diabetesDataScaled, km4.labels_)

Questo processo si scala facilmente quando sono necessarie più categorie di cluster.

istogrammaPerCluster(diabetesDataScaled, km10.labels_)

Conclusioni

Queste visualizzazioni forniranno una solida base per valutare i modelli di clustering. Per saperne di più su come farlo in modo sistematico, assicurati di venire al mio intervento alla prossima Conferenza di Scienza dei Dati Aperti in autunno a San Francisco!

Sull’autore:

Evie Fowler è una data scientist con sede a Pittsburgh, Pennsylvania. Attualmente lavora nel settore sanitario guidando un team di data scientist che sviluppano modelli predittivi incentrati sull’esperienza di cura del paziente. Ha un particolare interesse nell’applicazione etica dell’analisi predittiva e nell’esplorazione di come i metodi qualitativi possano informare il lavoro della scienza dei dati. Ha una laurea triennale all’Università di Brown e una laurea magistrale alla Carnegie Mellon.