Definitiv veiledning til hierarkisk klynging med Python og Scikit-Learn PlatoBlockchain Data Intelligence. Vertikalt søk. Ai.

Definitiv guide til hierarkisk gruppering med Python og Scikit-Learn

Introduksjon

I denne veiledningen vil vi fokusere på å implementere Hierarkisk klyngealgoritme med Scikit-Learn å løse et markedsføringsproblem.

Etter å ha lest veiledningen vil du forstå:

  • Når skal man bruke hierarkisk klynging
  • Hvordan visualisere datasettet for å forstå om det er egnet for gruppering
  • Hvordan forhåndsbehandle funksjoner og konstruere nye funksjoner basert på datasettet
  • Hvordan redusere dimensjonaliteten til datasettet ved hjelp av PCA
  • Hvordan bruke og lese et dendrogram for å skille grupper
  • Hva er de forskjellige koblingsmetodene og avstandsmålingene som brukes på dendrogrammer og klyngealgoritmer
  • Hva er de agglomerative og splittende klyngestrategiene og hvordan de fungerer
  • Hvordan implementere den agglomerative hierarkiske klyngingen med Scikit-Learn
  • Hva er de vanligste problemene når du arbeider med klyngealgoritmer og hvordan de løses

OBS: Du kan laste ned notatboken som inneholder all koden i denne veiledningen her..

Motivasjon

Se for deg et scenario der du er en del av et datavitenskapsteam som kommuniserer med markedsavdelingen. Markedsføring har samlet inn kundekjøpsdata en stund, og de ønsker å forstå, basert på de innsamlede dataene, om det finnes likheter mellom kunder. Disse likhetene deler kunder inn i grupper, og det å ha kundegrupper hjelper med målretting av kampanjer, kampanjer, konverteringer og å bygge bedre kunderelasjoner.

Er det en måte du kan hjelpe deg med å finne ut hvilke kunder som er like? Hvor mange av dem tilhører samme gruppe? Og hvor mange forskjellige grupper er det?

En måte å svare på disse spørsmålene på er å bruke en gruppering algoritmer, slik som K-Means, DBSCAN, Hierarchical Clustering, etc. Generelt sett finner clustering-algoritmer likheter mellom datapunkter og grupperer dem.

I dette tilfellet er markedsføringsdataene våre ganske små. Vi har informasjon om kun 200 kunder. Med tanke på markedsføringsteamet er det viktig at vi tydelig kan forklare dem hvordan beslutningene ble tatt basert på antall klynger, og derfor forklare dem hvordan algoritmen faktisk fungerer.

Siden våre data er små og forklarbarhet er en viktig faktor, kan vi utnytte Hierarkisk klynging for å løse dette problemet. Denne prosessen er også kjent som Hierarkisk klyngeanalyse (HCA).

En av fordelene med HCA er at den er tolkbar og fungerer godt på små datasett.

En annen ting å ta i betraktning i dette scenariet er at HCA er en uten tilsyn algoritme. Når vi grupperer data, vil vi ikke ha en måte å bekrefte at vi identifiserer riktig at en bruker tilhører en bestemt gruppe (vi kjenner ikke gruppene). Det er ingen etiketter vi kan sammenligne resultatene våre med. Hvis vi identifiserte gruppene riktig, vil det senere bli bekreftet av markedsavdelingen på daglig basis (målt ved beregninger som ROI, konverteringsrater osv.).

Nå som vi har forstått problemet vi prøver å løse og hvordan vi skal løse det, kan vi begynne å ta en titt på dataene våre!

Kort utforskende dataanalyse

OBS: Du kan laste ned datasettet som brukes i denne veiledningen her..

Etter å ha lastet ned datasettet, legg merke til at det er en CSV (kommaseparerte verdier) fil kalt shopping-data.csv. For å gjøre det enklere å utforske og manipulere dataene, laster vi dem inn i en DataFrame bruker pandaer:

import pandas as pd


path_to_file = 'home/projects/datasets/shopping-data.csv'
customer_data = pd.read_csv(path_to_file)

Markedsføring sa at den hadde samlet inn 200 kundeposter. Vi kan sjekke om de nedlastede dataene er komplette med 200 rader ved å bruke shape Egenskap. Det vil fortelle oss hvor mange rader og kolonner vi har, henholdsvis:

customer_data.shape

Dette resulterer i:

(200, 5)

Flott! Dataene våre er komplette med 200 rader (klientoppføringer) og vi har også 5 kolonner (funksjoner). For å se hvilke egenskaper markedsavdelingen har samlet inn fra kunder, kan vi se kolonnenavn med columns Egenskap. For å gjøre det, kjør:

customer_data.columns

Skriptet ovenfor returnerer:

Index(['CustomerID', 'Genre', 'Age', 'Annual Income (k$)',
       'Spending Score (1-100)'],
      dtype='object')

Her ser vi at markedsføring har generert en CustomerID, samlet Genre, Age, Annual Income (i tusenvis av dollar), og en Spending Score går fra 1 til 100 for hver av de 200 kundene. På spørsmål om avklaring sa de at verdiene i Spending Score kolonnen angir hvor ofte en person bruker penger i et kjøpesenter på en skala fra 1 til 100. Med andre ord, hvis en kunde har en poengsum på 0, bruker denne personen aldri penger, og hvis poengsummen er 100, har vi nettopp sett høyeste bruker.

La oss ta en rask titt på fordelingen av denne poengsummen for å inspisere forbruksvanene til brukerne i datasettet vårt. Det er der pandaene hist() metoden kommer inn for å hjelpe:

customer_data['Spending Score (1-100)'].hist()

img

Ved å se på histogrammet ser vi at mer enn 35 kunder har score mellom 40 og 60, da har mindre enn 25 poeng mellom 70 og 80. Så de fleste av våre kunder er det balanserte brukere, etterfulgt av moderate til høye brukere. Vi kan også se at det er en strek etter 0, til venstre for distribusjonen, og en annen linje før 100, til høyre for distribusjonen. Disse tomme feltene betyr sannsynligvis at distribusjonen ikke inneholder ikke-spenders, som ville ha en score på 0, og at det heller ikke er høye brukere med en score på 100.

For å verifisere om det er sant, kan vi se på minimums- og maksimumsverdiene for distribusjonen. Disse verdiene kan enkelt finnes som en del av den beskrivende statistikken, så vi kan bruke describe() metode for å få en forståelse av andre numeriske verdifordelinger:


customer_data.describe().transpose()

Dette vil gi oss en tabell der vi kan lese distribusjoner av andre verdier i datasettet vårt:

 						count 	mean 	std 		min 	25% 	50% 	75% 	max
CustomerID 				200.0 	100.50 	57.879185 	1.0 	50.75 	100.5 	150.25 	200.0
Age 					200.0 	38.85 	13.969007 	18.0 	28.75 	36.0 	49.00 	70.0
Annual Income (k$) 		200.0 	60.56 	26.264721 	15.0 	41.50 	61.5 	78.00 	137.0
Spending Score (1-100) 	200.0 	50.20 	25.823522 	1.0 	34.75 	50.0 	73.00 	99.0

Vår hypotese er bekreftet. De min verdien av Spending Score is 1 og maks er 99. Så det har vi ikke 0 or 100 score brukere. La oss så ta en titt på de andre kolonnene i den transponerte describe bord. Når man ser på mean og std kolonner, kan vi se det for Age de mean is 38.85 og std er ca 13.97. Det samme skjer for Annual Income, Med en mean of 60.56 og std 26.26, Og for Spending Score med en mean of 50 og std of 25.82. For alle funksjoner mean er langt fra standardavviket, noe som indikerer dataene våre har høy variabilitet.

For å forstå bedre hvordan dataene våre varierer, la oss plotte Annual Income fordeling:

customer_data['Annual Income (k$)'].hist()

Som vil gi oss:

img

Legg merke til i histogrammet at de fleste av våre data, mer enn 35 kunder, er konsentrert nær tallet 60, på vår mean, i den horisontale aksen. Men hva skjer når vi beveger oss mot slutten av distribusjonen? Når vi går mot venstre, fra gjennomsnittet for $60.560, er den neste verdien vi vil møte $34.300 – gjennomsnittet ($60.560) minus standardvariasjonen ($26.260). Hvis vi går lenger bort til venstre for datadistribusjonen vår, gjelder en lignende regel, vi trekker standardvariasjonen ($26.260) fra gjeldende verdi ($34.300). Derfor vil vi møte en verdi på $8.040. Legg merke til hvordan dataene våre raskt gikk fra $60k til $8k. Den "hopper" $26.260 hver gang – det varierer mye, og det er derfor vi har så høy variasjon.

img

Variabiliteten og størrelsen på dataene er viktige i klyngeanalyse fordi avstandsmålinger av de fleste klyngealgoritmer er følsomme for datastørrelser. Forskjellen i størrelse kan endre klyngeresultatene ved å få ett punkt til å virke nærmere eller fjernere fra et annet enn det faktisk er, og forvrenge den faktiske grupperingen av data.

Så langt har vi sett formen på dataene våre, noen av distribusjonene og beskrivende statistikk. Med Pandas kan vi også liste opp datatypene våre og se om alle våre 200 rader er fylte eller har noen null verdier:

customer_data.info()

Dette resulterer i:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 200 entries, 0 to 199
Data columns (total 5 columns):
 #   Column                  Non-Null Count  Dtype 
---  ------                  --------------  ----- 
 0   CustomerID              200 non-null    int64 
 1   Genre                   200 non-null    object
 2   Age                     200 non-null    int64 
 3   Annual Income (k$)      200 non-null    int64 
 4   Spending Score (1-100)  200 non-null    int64 
dtypes: int64(4), object(1)
memory usage: 7.9+ KB

Her kan vi se at det ikke er noen null verdier i dataene og at vi bare har én kategorisk kolonne – Genre. På dette stadiet er det viktig at vi har i bakhodet hvilke funksjoner som virker interessante å legge til klyngemodellen. Hvis vi vil legge til sjangerkolonnen til modellen vår, må vi transformere verdiene fra kategorisk til numerisk.

La oss se hvordan Genre fylles ved å ta en rask titt på de første 5 verdiene av dataene våre:

customer_data.head() 

Dette resulterer i:

    CustomerID 	Genre 	Age 	Annual Income (k$) 	Spending Score (1-100)
0 	1 			Male 	19 		15 					39
1 	2 			Male 	21 		15 					81
2 	3 			Female 	20 		16 					6
3 	4 			Female 	23 		16 					77
4 	5 			Female 	31 		17 					40

Det ser ut til at det bare har Female og Male kategorier. Vi kan være sikre på det ved å ta en titt på dens unike verdier med unique:

customer_data['Genre'].unique()

Dette bekrefter vår antagelse:

array(['Male', 'Female'], dtype=object)

Så langt vet vi at vi bare har to sjangere, hvis vi planlegger å bruke denne funksjonen på modellen vår, Male kunne forvandles til 0 og Female til 1. Det er også viktig å sjekke proporsjonen mellom sjangere, for å se om de er balanserte. Det kan vi gjøre med value_counts() metoden og dens argumentasjon normalize=True for å vise prosenten mellom Male og Female:

customer_data['Genre'].value_counts(normalize=True)

Dette gir utganger:

Female    0.56
Male      0.44
Name: Genre, dtype: float64

Vi har 56 % av kvinnene i datasettet og 44 % av mennene. Forskjellen mellom dem er bare 16 %, og våre data er ikke 50/50, men er det balansert nok ikke å skape problemer. Hvis resultatene var 70/30, 60/40, kan det ha vært nødvendig enten å samle inn mer data eller å bruke en slags dataforsterkningsteknikk for å gjøre forholdet mer balansert.

Inntil nå, alle funksjoner men Age, har blitt kort utforsket. I det som angår Age, er det vanligvis interessant å dele det inn i søppelkasser for å kunne segmentere kunder basert på deres aldersgrupper. Hvis vi gjør det, må vi transformere alderskategoriene til ett tall før vi legger dem til modellen vår. På den måten, i stedet for å bruke kategorien 15-20 år, vil vi telle hvor mange kunder det er i 15-20 kategori, og det ville være et tall i en ny kolonne kalt 15-20.

Råd: I denne veiledningen presenterer vi kun kort utforskende dataanalyse. Men du kan gå lenger, og du bør gå lenger. Du kan se om det er inntektsforskjeller og poengforskjeller basert på sjanger og alder. Dette beriker ikke bare analysen, men fører til bedre modellresultater. For å gå dypere inn i Exploratory Data Analysis, sjekk ut EDA kapittel i "Hands-on husprisprediksjon – maskinlæring i Python" Guidet prosjekt.

Etter å ha antatt hva som kan gjøres med både kategorisk – eller kategorisk å være – Genre og Age kolonner, la oss bruke det som er diskutert.

Kodingsvariabler og funksjonsteknikk

La oss starte med å dele Age i grupper som varierer i 10, slik at vi har 20-30, 30-40, 40-50, og så videre. Siden vår yngste kunde er 15, kan vi begynne på 15 og slutte på 70, som er alderen til den eldste kunden i dataene. Fra 15 og slutter ved 70, ville vi ha 15-20, 20-30, 30-40, 40-50, 50-60 og 60-70 intervaller.

Å gruppere eller bin Age verdier inn i disse intervallene, kan vi bruke pandaene cut() metode for å kutte dem i binger og deretter tilordne hyllene til en ny Age Groups kolonne:

intervals = [15, 20, 30, 40, 50, 60, 70]
col = customer_data['Age']
customer_data['Age Groups'] = pd.cut(x=col, bins=intervals)


customer_data['Age Groups'] 

Dette resulterer i:

0      (15, 20]
1      (20, 30]
2      (15, 20]
3      (20, 30]
4      (30, 40]
         ...   
195    (30, 40]
196    (40, 50]
197    (30, 40]
198    (30, 40]
199    (20, 30]
Name: Age Groups, Length: 200, dtype: category
Categories (6, interval[int64, right]): [(15, 20] < (20, 30] < (30, 40] < (40, 50] < (50, 60] < (60, 70]]

Legg merke til at når du ser på kolonneverdiene, er det også en linje som spesifiserer at vi har 6 kategorier og viser alle innlagte dataintervaller. På denne måten har vi kategorisert våre tidligere numeriske data og opprettet en ny Age Groups funksjonen.

Og hvor mange kunder har vi i hver kategori? Det kan vi raskt vite ved å gruppere kolonnen og telle verdiene med groupby() og count():

customer_data.groupby('Age Groups')['Age Groups'].count()

Dette resulterer i:

Age Groups
(15, 20]    17
(20, 30]    45
(30, 40]    60
(40, 50]    38
(50, 60]    23
(60, 70]    17
Name: Age Groups, dtype: int64

Det er lett å få øye på at de fleste kundene er mellom 30 og 40 år, etterfulgt av kunder mellom 20 og 30 og deretter kunder mellom 40 og 50. Dette er også god informasjon for Markedsavdelingen.

For øyeblikket har vi to kategoriske variabler, Age og Genre, som vi må transformere til tall for å kunne bruke i vår modell. Det er mange forskjellige måter å gjøre den transformasjonen på – vi vil bruke pandaene get_dummies() metode som oppretter en ny kolonne for hvert intervall og sjanger og deretter fyller verdiene med 0-er og 1-er - denne typen operasjon kalles one-hot-koding. La oss se hvordan det ser ut:


customer_data_oh = pd.get_dummies(customer_data)

customer_data_oh 

Dette vil gi oss en forhåndsvisning av den resulterende tabellen:

img

Med utgangen er det lett å se at kolonnen Genre ble delt inn i kolonner - Genre_Female og Genre_Male. Når kunden er kvinne, Genre_Female er lik 1, og når kunden er mann, er det lik 0.

Også Age Groups kolonne ble delt inn i 6 kolonner, en for hvert intervall, som f.eks Age Groups_(15, 20], Age Groups_(20, 30], og så videre. På samme måte som Genre, når kunden er 18 år gammel Age Groups_(15, 20] verdien er 1 og verdien av alle andre kolonner er 0.

De fordel av en-hot-koding er enkelheten i å representere kolonneverdiene, det er enkelt å forstå hva som skjer – mens ulempe er at vi nå har laget 8 ekstra kolonner, for å oppsummere med kolonnene vi allerede hadde.

Advarsel: Hvis du har et datasett der antallet one-hot-kodede kolonner overstiger antall rader, er det best å bruke en annen kodingsmetode for å unngå problemer med datadimensjonalitet.

One-hot-koding legger også til 0-er til dataene våre, noe som gjør dem mer sparsommelige, noe som kan være et problem for noen algoritmer som er sensitive for datasparsomhet.

For våre klyngebehov ser det ut til at one-hot-koding fungerer. Men vi kan plotte dataene for å se om det virkelig er forskjellige grupper som vi kan gruppere.

Grunnleggende plotting og dimensjonsreduksjon

Datasettet vårt har 11 kolonner, og det er noen måter vi kan visualisere disse dataene på. Den første er ved å plotte den i 10-dimensjoner (lykke til med det). Ti fordi Customer_ID kolonne vurderes ikke. Den andre er ved å plotte våre innledende numeriske funksjoner, og den tredje er ved å transformere våre 10 funksjoner til 2 – og derfor utføre en dimensjonalitetsreduksjon.

Plotte hvert datapar

Siden det er litt umulig å plotte 10 dimensjoner, velger vi å gå med den andre tilnærmingen – vi vil plotte våre første funksjoner. Vi kan velge to av dem for vår klyngeanalyse. En måte vi kan se alle dataparene våre kombinert er med en Seaborn pairplot():

import seaborn as sns


customer_data = customer_data.drop('CustomerID', axis=1)

sns.pairplot(customer_data)

Som viser:

img

Med et øyeblikk kan vi se spredningsplottene som ser ut til å ha grupper av data. En som virker interessant er scatterplot som kombinerer Annual Income og Spending Score. Legg merke til at det ikke er noe klart skille mellom andre variable spredningsplott. På det meste kan vi kanskje fortelle at det er to distinkte konsentrasjoner av punkter i Spending Score vs Age scatterplot.

Begge scatterplots bestående av Annual Income og Spending Score er i hovedsak de samme. Vi kan se det to ganger fordi x- og y-aksen ble byttet ut. Ved å ta en titt på noen av dem kan vi se det som ser ut til å være fem forskjellige grupper. La oss plotte bare de to funksjonene med en Seaborn scatterplot() for å se nærmere:

sns.scatterplot(x=customer_data['Annual Income (k$)'],
                y=customer_data['Spending Score (1-100)'])

img

Ved å se nærmere kan vi definitivt skille 5 forskjellige grupper av data. Det ser ut til at kundene våre kan grupperes basert på hvor mye de tjener på et år og hvor mye de bruker. Dette er et annet relevant punkt i vår analyse. Det er viktig at vi kun tar to funksjoner i betraktning for å gruppere våre kunder. All annen informasjon vi har om dem kommer ikke inn i ligningen. Dette gir analysen mening – hvis vi vet hvor mye en klient tjener og bruker, kan vi enkelt finne likhetene vi trenger.

img

Det er flott! Så langt har vi allerede to variabler for å bygge vår modell. I tillegg til hva dette representerer, gjør det også modellen enklere, sparsommelig og mer forklarlig.

Sjekk ut vår praktiske, praktiske guide for å lære Git, med beste praksis, bransjeaksepterte standarder og inkludert jukseark. Slutt å google Git-kommandoer og faktisk lære den!

OBS: Data Science favoriserer vanligvis så enkle tilnærminger som mulig. Ikke bare fordi det er lettere å forklare for virksomheten, men også fordi det er mer direkte – med 2 funksjoner og en forklarbar modell er det tydelig hva modellen gjør og hvordan den fungerer.

Plotte data etter bruk av PCA

Det ser ut til at vår andre tilnærming sannsynligvis er den beste, men la oss også ta en titt på vår tredje tilnærming. Det kan være nyttig når vi ikke kan plotte dataene fordi de har for mange dimensjoner, eller når det ikke er datakonsentrasjoner eller tydelig skille i grupper. Når slike situasjoner oppstår, anbefales det å prøve å redusere datadimensjonene med en metode som kalles Hovedkomponentanalyse (PCA).

OBS: De fleste bruker PCA for dimensjonalitetsreduksjon før visualisering. Det finnes andre metoder som hjelper til med datavisualisering før klynging, som f.eks Tetthetsbasert romlig klynging av applikasjoner med støy (DBSCAN) og Selvorganiserende kart (SOM) gruppering. Begge er klyngealgoritmer, men kan også brukes til datavisualisering. Siden klyngeanalyse ikke har noen gylden standard, er det viktig å sammenligne ulike visualiseringer og ulike algoritmer.

PCA vil redusere dimensjonene til dataene våre samtidig som vi prøver å bevare så mye av informasjonen som mulig. La oss først få en idé om hvordan PCA fungerer, og deretter kan vi velge hvor mange datadimensjoner vi skal redusere dataene våre til.

For hvert funksjonspar ser PCA om de større verdiene til en variabel samsvarer med de større verdiene til den andre variabelen, og den gjør det samme for de mindre verdiene. Så den beregner i hovedsak hvor mye funksjonsverdiene varierer i forhold til hverandre - vi kaller det deres kovarians. Disse resultatene blir deretter organisert i en matrise, og oppnår en samvariasjonsmatrise.

Etter å ha fått kovariansmatrisen, prøver PCA å finne en lineær kombinasjon av funksjoner som best forklarer den – den passer til lineære modeller til den identifiserer den som forklarer maksimal mengde variasjon.

Merknader: PCA er en lineær transformasjon, og linearitet er sensitiv for dataskalaen. Derfor fungerer PCA best når alle dataverdier er på samme skala. Dette kan gjøres ved å trekke fra kolonnen bety fra verdiene og dividere resultatet med standardavviket. Det heter datastandardisering. Før du bruker PCA, sørg for at dataene er skalert! Hvis du ikke er sikker på hvordan, les vår "Funksjonsskaleringsdata med Scikit-Learn for maskinlæring i Python"!

Med den beste linjen (lineær kombinasjon) funnet, får PCA retningene til sine akser, kalt egenvektorer, og dens lineære koeffisienter, den egenverdier. Kombinasjonen av egenvektorene og egenverdiene – eller akseretningene og koeffisientene – er Hovedkomponenter av PCA. Og det er da vi kan velge antall dimensjoner basert på den forklarte variansen til hver funksjon, ved å forstå hvilke hovedkomponenter vi ønsker å beholde eller forkaste basert på hvor mye variasjon de forklarer.

Etter å ha oppnådd hovedkomponentene, bruker PCA egenvektorene til å danne en vektor av funksjoner som reorienterer dataene fra de opprinnelige aksene til de som er representert av hovedkomponentene – det er slik datadimensjonene reduseres.

OBS: En viktig detalj å ta i betraktning her er at på grunn av sin lineære natur, vil PCA konsentrere det meste av den forklarte variansen i de første hovedkomponentene. Så når man ser på den forklarte variansen, vil vanligvis de to første komponentene våre være tilstrekkelige. Men det kan være misvisende i noen tilfeller - så prøv å fortsette å sammenligne forskjellige plott og algoritmer når du grupperer for å se om de har lignende resultater.

Før vi bruker PCA, må vi velge mellom Age kolonne eller Age Groups kolonner i våre tidligere one-hot-kodede data. Siden begge kolonnene representerer den samme informasjonen, vil det å introdusere den to ganger påvirke dataavviket vårt. Hvis Age Groups kolonnen er valgt, fjern ganske enkelt Age kolonne ved hjelp av pandaene drop() metoden og tilordne den på nytt til customer_data_oh variabel:

customer_data_oh = customer_data_oh.drop(['Age'], axis=1)
customer_data_oh.shape 

Nå har dataene våre 10 kolonner, noe som betyr at vi kan få en hovedkomponent for kolonne og velge hvor mange av dem vi vil bruke ved å måle hvor mye introduksjonen av en ny dimensjon forklarer mer av datavariasjonen vår.

La oss gjøre det med Scikit-Learn PCA. Vi vil beregne den forklarte variansen for hver dimensjon, gitt av explained_variance_ratio_ , og se deretter på deres kumulative sum med cumsum() :

from sklearn.decomposition import PCA

pca = PCA(n_components=10)
pca.fit_transform(customer_data_oh)
pca.explained_variance_ratio_.cumsum()

Våre kumulative forklarte avvik er:

array([0.509337  , 0.99909504, 0.99946364, 0.99965506, 0.99977937,
       0.99986848, 0.99993716, 1.        , 1.        , 1.        ])

Vi kan se at den første dimensjonen forklarer 50 % av dataene, og når de kombineres med den andre dimensjonen, forklarer de 99 %. Dette betyr at de to første dimensjonene allerede forklarer 2 % av dataene våre. Så vi kan bruke en PCA med 99 komponenter, skaffe våre hovedkomponenter og plotte dem:

from sklearn.decomposition import PCA

pca = PCA(n_components=2)
pcs = pca.fit_transform(customer_data_oh)

pc1_values = pcs[:,0]
pc2_values = pcs[:,1]
sns.scatterplot(x=pc1_values, y=pc2_values)

img

Dataplotten etter PCA er veldig lik plottet som bare bruker to kolonner av dataene uten PCA. Legg merke til at punktene som danner grupper er nærmere, og litt mer konsentrert etter PCA enn før.

img

Visualisere hierarkisk struktur med dendrogrammer

Så langt har vi utforsket dataene, one-hot-kodede kategoriske kolonner, bestemt hvilke kolonner som var egnet for clustering, og redusert datadimensjonalitet. Plottene indikerer at vi har 5 klynger i dataene våre, men det er også en annen måte å visualisere relasjonene mellom punktene våre og hjelpe med å bestemme antall klynger – ved å lage en dendrogram (ofte feilstavet som dendogram). dendro midler Treet på latin.

De dendrogram er et resultat av koblingen av punkter i et datasett. Det er en visuell representasjon av den hierarkiske klyngeprosessen. Og hvordan fungerer den hierarkiske klyngeprosessen? Vel ... det kommer an på - sannsynligvis et svar du allerede har hørt mye i Data Science.

Forstå hierarkisk klynging

når Hierarkisk clustering Algorithm (HCA) begynner å koble sammen punktene og finne klynger, kan den først dele opp punkter i 2 store grupper, og deretter dele hver av disse to gruppene i mindre 2 grupper, med totalt 4 grupper, som er splittende og top-down nærme seg.

Alternativt kan den gjøre det motsatte – den kan se på alle datapunktene, finne 2 punkter som er nærmere hverandre, koble dem, og deretter finne andre punkter som er nærmest de koblede punktene og fortsette å bygge de 2 gruppene fra opp ned. Hvilken er den agglomerativ tilnærmingen vi vil utvikle.

Trinn for å utføre agglomerativ hierarkisk gruppering

For å gjøre den agglomerative tilnærmingen enda tydelig, er det trinn i Agglomerative Hierarchical Clustering (AHC) algoritme:

  1. Ved starten behandler du hvert datapunkt som én klynge. Derfor vil antallet klynger ved starten være K – mens K er et heltall som representerer antall datapunkter.
  2. Dann en klynge ved å slå sammen de to nærmeste datapunktene, noe som resulterer i K-1-klynger.
  3. Dann flere klynger ved å slå sammen de to nærmeste klynger som resulterer i K-2 klynger.
  4. Gjenta de tre ovennevnte trinnene til en stor klynge er dannet.

Merknader: For forenkling sier vi "to nærmeste" datapunkter i trinn 2 og 3. Men det er flere måter å koble punkter på som vi vil se om litt.

Hvis du inverterer trinnene til ACH-algoritmen, går fra 4 til 1 – det vil være trinnene til *Divisive Hierarchical Clustering (DHC)*.

Legg merke til at HCA-er kan være enten splittende og ovenfra og ned, eller agglomerative og nedenfra og opp. Top-down DHC-tilnærmingen fungerer best når du har færre, men større klynger, og derfor er den dyrere beregningsmessig. På den annen side er bottom-up AHC-tilnærmingen tilpasset når du har mange mindre klynger. Det er beregningsmessig enklere, mer brukt og mer tilgjengelig.

OBS: Enten ovenfra og ned eller nedenfra og opp, vil dendrogramrepresentasjonen av klyngeprosessen alltid starte med en deling i to og ende opp med at hvert enkelt punkt blir diskriminert når den underliggende strukturen er av et binært tre.

La oss plotte vårt kundedatadendrogram for å visualisere de hierarkiske relasjonene til dataene. Denne gangen vil vi bruke scipy bibliotek for å lage dendrogrammet for datasettet vårt:

import scipy.cluster.hierarchy as shc
import matplotlib.pyplot as plt

plt.figure(figsize=(10, 7))
plt.title("Customers Dendrogram")


selected_data = customer_data_oh.iloc[:, 1:3]
clusters = shc.linkage(selected_data, 
            method='ward', 
            metric="euclidean")
shc.dendrogram(Z=clusters)
plt.show()

Utgangen av skriptet ser slik ut:

img

I skriptet ovenfor har vi generert klyngene og underklyngene med punktene våre, definert hvordan punktene våre skal kobles sammen (ved å bruke ward metode), og hvordan måle avstanden mellom punktene (ved å bruke euclidean metrisk).

Med plottet av dendrogrammet kan de beskrevne prosessene til DHC og AHC visualiseres. For å visualisere top-down-tilnærmingen, start fra toppen av dendrogrammet og gå ned, og gjør det motsatte, start ned og bevege deg oppover for å visualisere bottom-up-tilnærmingen.

Koblingsmetoder

Det er mange andre koblingsmetoder, ved å forstå mer om hvordan de fungerer, vil du kunne velge den passende for dine behov. I tillegg vil hver av dem gi forskjellige resultater når de brukes. Det er ingen fast regel i klyngeanalyse, hvis mulig, studer problemets natur for å se hvilken som passer best, test forskjellige metoder og inspiser resultatene.

Noen av koblingsmetodene er:

  • Enkel kobling: også referert til som Nærmeste nabo (NN). Avstanden mellom klynger er definert av avstanden mellom deres nærmeste medlemmer.

img

  • Komplett kobling: også referert til som Ytterste nabo (FN), Algoritmen for det fjerneste punkteller For Hees-algoritmen. Avstanden mellom klynger er definert av avstanden mellom deres lengste medlemmer. Denne metoden er beregningsmessig dyr.

img

  • Gjennomsnittlig kobling: også kjent som UPGMA (Uvektet pargruppemetode med aritmetisk gjennomsnitt). Prosentandelen av antall poeng for hver klynge beregnes med hensyn til antall poeng for de to klyngene hvis de ble slått sammen.

img

  • Vektet kobling: også kjent som WPGMA (Vektet pargruppemetode med aritmetisk gjennomsnitt). De individuelle punktene til de to klyngene bidrar til den aggregerte avstanden mellom en mindre og en større klynge.
  • Centroid kobling: også referert til som UPGMC (Uvektet pargruppemetode ved bruk av Centroids). Et punkt definert av gjennomsnittet av alle punkter (tyngdepunkt) beregnes for hver klynge, og avstanden mellom klynger er avstanden mellom deres respektive tyngdepunkt.

img

  • Avdelingskobling: Også kjent som MISSQ (Minimal økning av sum-of-kvadrater). Den spesifiserer avstanden mellom to klynger, beregner summen av kvadratfeil (ESS), og velger suksessivt de neste klynger basert på den mindre ESS. Wards metode søker å minimere økningen av ESS ved hvert trinn. Derfor minimerer feil.

img

Avstandsberegninger

I tillegg til koblingen, kan vi også spesifisere noen av de mest brukte avstandsmålene:

  • euklidsk: også referert til som Pythagoras eller rettlinjet avstand. Den beregner avstanden mellom to punkter i rommet ved å måle lengden på et linjestykke som går mellom dem. Den bruker Pythagoras teorem og avstandsverdien er resultatet (C) av ligningen:

$$
c^2 = a^2 + b^2
$$

  • Manhattan: også kalt Byblokk, Taxicab avstand. Det er summen av absolutte forskjeller mellom målene i alle dimensjoner av to punkter. Hvis disse dimensjonene er to, er det analogt med å lage en høyre og deretter venstre når du går en blokk.

img

  • Minkowski: det er en generalisering av både euklidiske og Manhattan-avstander. Det er en måte å beregne avstander basert på de absolutte forskjellene til rekkefølgen til Minkowski-metrikken p. Selv om det er definert for evt p> 0, brukes den sjelden for andre verdier enn 1, 2 og ∞ (uendelig). Minkowski-avstanden er den samme som Manhattan-avstanden når p = 1, og det samme som euklidisk avstand når p = 2.

$$
Dleft(X,Yright) = left(sum_{i=1}^n |x_i-y_i|^pright)^{frac{1}{p}}
$$

img

  • Chebyshev: også kjent som Sjakkbrett avstand. Det er det ekstreme tilfellet med Minkowski-avstand. Når vi bruker uendelig som verdien av parameteren p (p = ∞), ender vi opp med en metrikk som definerer avstand som den maksimale absolutte forskjellen mellom koordinatene.
  • cosinus: det er vinkelcosinusavstanden mellom to sekvenser av punkter, eller vektorer. Cosinuslikheten er punktproduktet til vektorene delt på produktet av lengdene deres.
  • Jaccard: måler likheten mellom endelige sett med punkter. Det er definert som det totale antallet poeng (kardinalitet) i fellespunktene i hvert sett (kryss), delt på det totale antallet poeng (kardinalitet) av de totale poengene for begge sett (union).
  • Jensen-Shannon: basert på Kullback-Leibler-divergensen. Den vurderer poengenes sannsynlighetsfordelinger og måler likheten mellom disse fordelingene. Det er en populær metode for sannsynlighetsteori og statistikk.

Vi har valgt Avdeling og euklidsk for dendrogrammet fordi de er den mest brukte metoden og metrikken. De gir vanligvis gode resultater siden Ward kobler punkter basert på å minimere feilene, og Euclide fungerer bra i lavere dimensjoner.

I dette eksemplet jobber vi med to funksjoner (kolonner) i markedsføringsdataene og 200 observasjoner eller rader. Siden antall observasjoner er større enn antall funksjoner (200 > 2), arbeider vi i et lavdimensjonalt rom.

Når antall funksjoner (F) er større enn antall observasjoner (N) – for det meste skrevet som f >> N, betyr det at vi har en høy dimensjonal plass.

Hvis vi skulle inkludere flere attributter, slik at vi har mer enn 200 funksjoner, vil den euklidiske avstanden kanskje ikke fungere veldig bra, siden den ville ha vanskeligheter med å måle alle de små avstandene i et veldig stort rom som bare blir større. Den euklidiske avstandstilnærmingen har med andre ord vanskeligheter med å jobbe med dataene sparsitet. Dette er en sak som kalles dimensjonalitetens forbannelse. Avstandsverdiene ville bli så små, som om de ble "fortynnet" i det større rommet, forvrengt til de ble 0.

OBS: Hvis du noen gang støter på et datasett med f >> s, vil du sannsynligvis bruke andre avstandsmålinger, for eksempel Mahalanobis avstand. Alternativt kan du også redusere datasettdimensjonene ved å bruke Hovedkomponentanalyse (PCA). Dette problemet er hyppig, spesielt ved gruppering av biologiske sekvenseringsdata.

Vi har allerede diskutert beregninger, koblinger og hvordan hver enkelt av dem kan påvirke resultatene våre. La oss nå fortsette dendrogramanalysen og se hvordan den kan gi oss en indikasjon på antall klynger i datasettet vårt.

Å finne et interessant antall klynger i et dendrogram er det samme som å finne det største horisontale rommet som ikke har noen vertikale linjer (rommet med de lengste vertikale linjene). Dette betyr at det er mer skille mellom klyngene.

Vi kan tegne en horisontal linje som går gjennom den lengste avstanden:

plt.figure(figsize=(10, 7))
plt.title("Customers Dendogram with line")
clusters = shc.linkage(selected_data, 
            method='ward', 
            metric="euclidean")
shc.dendrogram(clusters)
plt.axhline(y = 125, color = 'r', linestyle = '-')

img

Etter å ha lokalisert den horisontale linjen, teller vi hvor mange ganger de vertikale linjene våre ble krysset av den – i dette eksemplet, 5 ganger. Så 5 virker som en god indikasjon på antall klynger som har størst avstand mellom seg.

Merknader: Dendrogrammet bør kun betraktes som en referanse når det brukes til å velge antall klynger. Det kan lett få det tallet langt unna og er fullstendig påvirket av typen kobling og avstandsmålinger. Når du utfører en dyptgående klyngeanalyse, anbefales det å se på dendrogrammer med forskjellige koblinger og metrikker og å se på resultatene generert med de tre første linjene der klyngene har størst avstand mellom seg.

Implementering av en agglomerativ hierarkisk gruppering

Bruke originaldata

Så langt har vi beregnet det foreslåtte antallet klynger for datasettet vårt som bekrefter vår første analyse og vår PCA-analyse. Nå kan vi lage vår agglomerative hierarkiske klyngemodell ved hjelp av Scikit-Learn AgglomerativeClustering og finn ut etikettene til markedsføringspoeng med labels_:

from sklearn.cluster import AgglomerativeClustering

clustering_model = AgglomerativeClustering(n_clusters=5, affinity='euclidean', linkage='ward')
clustering_model.fit(selected_data)
clustering_model.labels_

Dette resulterer i:

array([4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3,
       4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 1,
       4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 0, 2, 0, 2,
       1, 2, 0, 2, 0, 2, 0, 2, 0, 2, 1, 2, 0, 2, 1, 2, 0, 2, 0, 2, 0, 2,
       0, 2, 0, 2, 0, 2, 1, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2,
       0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2,
       0, 2])

Vi har undersøkt mye for å komme til dette punktet. Og hva betyr disse etikettene? Her har vi hvert punkt i dataene våre merket som en gruppe fra 0 til 4:

data_labels = clustering_model.labels_
sns.scatterplot(x='Annual Income (k$)', 
                y='Spending Score (1-100)', 
                data=selected_data, 
                hue=data_labels,
                pallete="rainbow").set_title('Labeled Customer Data')

img

Dette er våre siste grupperte data. Du kan se de fargekodede datapunktene i form av fem klynger.

Datapunktene nederst til høyre (etikett: 0, lilla datapunkter) tilhører kundene med høy lønn, men lavt forbruk. Dette er kundene som bruker pengene sine forsiktig.

På samme måte er kundene øverst til høyre (etikett: 2, grønne datapunkter), er kundene med høye lønninger og høye utgifter. Dette er den typen kunder som selskaper retter seg mot.

Kundene i midten (etikett: 1, blå datapunkter) er de med gjennomsnittlig inntekt og gjennomsnittlig forbruk. Det høyeste antallet kunder tilhører denne kategorien. Bedrifter kan også målrette mot disse kundene gitt det faktum at de er i enorme antall.

Kundene nederst til venstre (etikett: 4, rød) er kundene som har lav lønn og lavt forbruk, kan de tiltrekkes ved å tilby kampanjer.

Og til slutt, kundene øverst til venstre (etikett: 3, oransje datapunkter) er de med høy inntekt og lavt forbruk, som er ideelt målrettet av markedsføring.

Bruke resultatet fra PCA

Hvis vi var i et annet scenario, der vi måtte redusere dimensjonaliteten til data. Vi kan også enkelt plotte de grupperte PCA-resultatene. Det kan gjøres ved å lage en annen agglomerativ klyngemodell og skaffe en dataetikett for hver hovedkomponent:

clustering_model_pca = AgglomerativeClustering(n_clusters=5, affinity='euclidean', linkage='ward')
clustering_model_pca.fit(pcs)

data_labels_pca = clustering_model_pca.labels_

sns.scatterplot(x=pc1_values, 
                y=pc2_values,
                hue=data_labels_pca,
                palette="rainbow").set_title('Labeled Customer Data Reduced with PCA')

img

Legg merke til at begge resultatene er svært like. Hovedforskjellen er at det første resultatet med de originale dataene er mye lettere å forklare. Det er tydelig å se at kunder kan deles inn i fem grupper etter årlig inntekt og forbruksscore. Mens vi i PCA-tilnærmingen tar alle funksjonene våre i betraktning, så mye som vi kan se på variansen som er forklart av hver av dem, er dette et vanskeligere konsept å forstå, spesielt når du rapporterer til en markedsavdeling.

Jo minst vi har for å transformere dataene våre, jo bedre.

Hvis du har et veldig stort og komplekst datasett der du må utføre en dimensjonalitetsreduksjon før klynging – prøv å analysere de lineære relasjonene mellom hver av funksjonene og deres residualer for å sikkerhetskopiere bruken av PCA og forbedre forklarbarheten til prosessen. Ved å lage en lineær modell per funksjonspar vil du kunne forstå hvordan funksjonene samhandler.

Hvis datavolumet er så stort, blir det umulig å plotte funksjonsparene, velge et utvalg av dataene dine, så balansert og nær normalfordelingen som mulig og utføre analysen på prøven først, forstå den, finjustere det – og bruk det senere på hele datasettet.

Du kan alltid velge forskjellige klyngevisualiseringsteknikker i henhold til arten av dataene dine (lineære, ikke-lineære) og kombinere eller teste dem alle om nødvendig.

konklusjonen

Klyngeteknikken kan være veldig nyttig når det kommer til umerkede data. Siden de fleste dataene i den virkelige verden er umerket og det å kommentere dataene har høyere kostnader, kan klyngeteknikker brukes til å merke umerkede data.

I denne guiden har vi brakt et reelt datavitenskapelig problem, siden klyngeteknikker i stor grad brukes i markedsanalyse (og også i biologisk analyse). Vi har også forklart mange av undersøkelsestrinnene for å komme til en god hierarkisk klyngemodell og hvordan man leser dendrogrammer og stilt spørsmål ved om PCA er et nødvendig trinn. Vårt hovedmål er at noen av fallgruvene og ulike scenarier der vi kan finne hierarkisk klynging dekkes.

Lykke til med klynging!

Tidstempel:

Mer fra Stackabuse