Chapter 9 Clustering
9.1 Indledning og læringsmålene
9.1.1 Læringsmålene
Du skal være i stand til at
- Beskrive hvad k-means clustering går ud på
- Anvende
kmeans
og output resultatet på en tidy måde - Anvende
map
over forskellige antal clusters og vælge antallet som passer til de data - Anvende funktionen
hclust
for at lave et simpel hierarchical clustering
9.1.2 Inledning til chapter
I clustering er det til formål at dele observationerne i et datasæt op i forskellige grupper (clusters eller klynge på dansk), således at observationerne i sammen cluster ligner hinanden. Det øger indsigten i datasættet ved at fk. bedre forstår strukturen. Fk. hvor mange forskellige clusters er repræsenteret i mit datasæt? Og hvilke individuelle observationer tilhører hvilken cluster?
I dette kapitel ser vi hvordan vi kan implementere både k-means clustering og hierarchical clustering indenfor den tidyverse ramme.
9.1.3 Video ressourcer
- Video 1: K-means clustering
Link her hvis det ikke virker nedenunder: https://player.vimeo.com/video/553656150
- Video 2: augment, glanced og tidy med K-means. OBS der er en lille fejl i koden omkring 6:00 - den anden
geom_point
skal væregeom_point(data = kclust_tidy,aes(x=bill_length,y=bill_depth),shape="x,colour="black")
fordi tallerne er allerede basaserede på “scaled” data ikclust_tidy
- se sektion 9.2.5 for uddybelse.
Link her hvis det ikke virker nedenunder: https://player.vimeo.com/video/553656139
- Video 3: Hvor mange clusters skal man vælge?
Link her hvis det ikke virker nedenunder: https://player.vimeo.com/video/553656129
- Video 4: Hierarchical clustering
(OBS Video 4 mangler: se gerne kursusnotaterne og jeg laver videoen ASAP)
9.2 Method 1: K-means clustering
library(palmerpenguins)
library(tidyverse)
library(broom)
I k-means clustering bliver samtlige observationer tilknyttet den nærmeste cluster “centroid” (se “hvordan fungerer kmeans?” nedenunder). I k-means er man nødt til at specificere antallet af clusters, som observationerne skal være delt op i, i forvejen. Derfor skal der være nogle undersøgelsesarbejde for at vælge den bedste antal clusters som passer til problemstillingen, eller som bedste repræsenterer datasættet.
Lad os tage udgangspunkt i datasættet penguins
. Vi begynder med at få fjernet observationerne med NA
i mindst én variable med funktionen drop_na
og ved at specificere at year
skal være en faktor (for at skelne den fra de andre numeriske kolonner):
data(penguins)
<- penguins %>%
penguins mutate(year=as.factor(year)) %>%
drop_na()
Vi vide allerede i forvejen, at der er 3 species
med i de data, som vi plotter her med forskellige farver.
%>% ggplot(aes(x=bill_length_mm,y=body_mass_g,colour=species)) +
penguins geom_point() +
theme_classic()
Vi vil gerne bruge k-means clustering på de numeriske variabler i datasættet, og beregne 3 clusters ud fra dem. Derefter kan det være interessant at sammenligne de clusters vi få med de tre arter af pingvin - hvor gode er de clusters til at skelne i mellem de forskellige species, eller fanger de noget anden struktur i datasættet (for eksempel kønnet eller øen, de bor på)?
9.2.1 Hvordan fungere kmeans?
K-means er en iterativ process. Lad os forestille os at vi gerne vil have tre clusters i de data. Man starter med tre tilfældige observationer og kalder dem for de cluster middelværdier eller “centroids”. Man tilknytter alle observationer til én af de tre clusters (efter den nærmeste af de tre centroids), og så beregner en ny middelværdi/centroid for at hver cluster. Man tilknytter samtlige observationerne igen eften den nærmeste af de tre nye cluster centroids, og så gentager man processen flere gange. Efter flere gange konvergere de tre centroids til nogle faste værdier, der ikke længere ændre sig meget hver gange med gentager processen. Disse tre centroids defininere de tre endelige clusters og samtlige observationer er tilknyttet én af de tre.
Jeg spørger ikke efter detaljerne i metoden men der er mange videoer på Youtube som bedre foreklarer hvordan k-means fungerer, for eksempel: https://www.youtube.com/watch?v=4b5d3muPQmA
Bemærk, at der er noget tilfældighed indbygget i algoritmen. Det betyder, at hver gang man anvende k-means, få man en lidt anderledes resultat.
9.2.2 Within/between sum of squares
Man kan forestille sig, at hvis man lave en gode clustering af datasæt, så ligner observationerne indenfor den sammen cluster hinanden meget, og til gengæld er observationerne i forskellige clusters meget forskellige fra hinanden. Med andre ord, er afstanden mellem observationerne i samme cluster så mindre som muligt og afstanden mellem observationerne i forskellige clusters er så stor som muligt. For at måle det kan man beregne følgende:
- total within sum of squares - den totale squared afstand af observationerne fra deres nærmeste centroid.
- total between sum of squares - den totale afstand af centroids til samtlige andre centroids. Det skal være så stor som muligt.
9.2.3 Run k-means i R
K-means fungerer kun på numeriske data, som vi kan vælge fra datasættet med select()
sammen med hjælper where(is.numeric)
. Vi bruger også scale()
, som betyder, at alle variabler få den samme skala og det undgår, at der er nogle som få mere indflydelse end andre i de færdige resultater.
<- penguins %>%
penguins_scaled select(where(is.numeric)) %>%
scale()
Man er også nødt til at fortælle i forvejen hvor mange clusters at opdele datasættet i, så lad os sige centers=3
indenfor funktionen kmeans()
her og beregner vores clusters:
<- kmeans(penguins_scaled,centers = 3)
kclust kclust
## K-means clustering with 3 clusters of sizes 129, 85, 119
##
## Cluster means:
## bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
## 1 -1.0452359 0.4858944 -0.8803701 -0.7616078
## 2 0.6710153 0.8040534 -0.2889118 -0.3835267
## 3 0.6537742 -1.1010497 1.1607163 1.0995561
##
## Clustering vector:
## [1] 1 1 1 1 1 1 1 1 1 1 1 1 2 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [38] 1 2 1 1 1 1 2 1 1 1 2 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 2 1 1 1 2 1 2 1 1 1 2
## [75] 1 2 1 1 1 1 1 1 1 1 1 2 1 1 1 2 1 1 1 2 1 2 1 1 1 1 1 1 1 2 1 2 1 2 1 2 1
## [112] 1 1 1 1 1 1 1 1 1 1 1 1 2 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 3 3
## [149] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [186] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [223] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [260] 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 2 1 2 2 2 2 2 2 2 1
## [297] 2 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 2 2 2 2 2 2 2 2 2 2 2 2 2
##
## Within cluster sum of squares by cluster:
## [1] 120.7030 109.4813 139.4684
## (between_SS / total_SS = 72.2 %)
##
## Available components:
##
## [1] "cluster" "centers" "totss" "withinss" "tot.withinss"
## [6] "betweenss" "size" "iter" "ifault"
Man få forskellige ting frem, for eksempel:
Cluster means
- det svarer til de centroids markerede med x i den ovenstående figur - bemærk at her er de 4-dimensionelle da vi brugt 4 variabler til at beregne resultatet.Clustering
vector - hvilke cluster er hver observation blevet tilknyttet.Within cluster sum of squares
- Jo mindre, jo bedre - hvor meget observationerne indenfor samme cluster ligner hinanden (den totale squared afstand af observationerne fra deres nærmeste centroid).
9.2.4 Tidy up k-means resultaterne med pakken broom
Fra pakken broom
har vi mest beskæftiget os med glance()
indstil videre. Med glance()
få man enkel-linje baserede summary statistikker fra én eller flere modeller sammen i én dataramme, for at facilitete et plot/labels osv. Der er også to andre funktioner vi tager i bruge her. Her er en beskrivelse af de tre.
Broom verb | Beskrivelse |
---|---|
glance() |
single line summary - make elbow plot |
augment() |
Append dataset to clusters - make plots coloured by cluster |
tidy() |
Multi-line summary - extract centroids |
For at lave et plot af de clusters kan det især være nyttigt at benytte augment
. Her kan man se, at vi har fået en kolon der hedder .cluster
med i den oprindelige dataramme (jeg flyttet kolonen til første plads i følgende kode så man kan se den i de output af kursusnotater).
<- augment(kclust, penguins) #clustering = første plads, data = anden plads
kc1 %>% select(.cluster,all_of(names(penguins))) kc1
## # A tibble: 333 × 9
## .cluster species island bill_length_mm bill_depth_mm flipper_length_mm
## <fct> <fct> <fct> <dbl> <dbl> <int>
## 1 1 Adelie Torgersen 39.1 18.7 181
## 2 1 Adelie Torgersen 39.5 17.4 186
## 3 1 Adelie Torgersen 40.3 18 195
## 4 1 Adelie Torgersen 36.7 19.3 193
## 5 1 Adelie Torgersen 39.3 20.6 190
## 6 1 Adelie Torgersen 38.9 17.8 181
## 7 1 Adelie Torgersen 39.2 19.6 195
## 8 1 Adelie Torgersen 41.1 17.6 182
## 9 1 Adelie Torgersen 38.6 21.2 191
## 10 1 Adelie Torgersen 34.6 21.1 198
## # … with 323 more rows, and 3 more variables: body_mass_g <int>, sex <fct>,
## # year <fct>
Nu benytter vi kc1
til at lave et plot. Her giver jeg en farve efter .cluster
og shape efter species
så at vi kan sammenligne vores beregnet clusters med de tre forskellige arter. Bemæk her, at jeg kun har to variabler i plottet, men der er faktisk fire variabler som blev brugt til at lave de clusters i med funktionen kmeans
. En anden måde er at plotte de først to principal components i stedet for to af de fire variabler - det beskæftige vi os med næste gang.
ggplot(kc1, aes(x = scale(bill_length_mm),
y = scale(bill_depth_mm))) +
geom_point(aes(color = .cluster, shape = species)) + theme_minimal()
Vi kan også fk. optælle hvor mange af de tre arter vi får i hver af vores tre clusters, hvor vi kan se, at Adelie
og Chinstrap
er blevet mere blandet blandt to af de tre clusters end Gentoo
.
%>% count(.cluster, species) kc1
## # A tibble: 5 × 3
## .cluster species n
## <fct> <fct> <int>
## 1 1 Adelie 124
## 2 1 Chinstrap 5
## 3 2 Adelie 22
## 4 2 Chinstrap 63
## 5 3 Gentoo 119
9.2.5 Plot cluster centroids
Næste kigger vi på resultatet af funktionen tidy()
fra broom
-pakken. Her har vi fået en pæn dataramme med middelværdierne (centroids) af de tre clusters over de fire variabler som var brugt i beregningerne.
<- kclust %>% tidy()
kclust_tidy kclust_tidy
## # A tibble: 3 × 7
## bill_length_mm bill_depth_mm flipper_length_mm body_mass_g size withinss
## <dbl> <dbl> <dbl> <dbl> <int> <dbl>
## 1 -1.05 0.486 -0.880 -0.762 129 121.
## 2 0.671 0.804 -0.289 -0.384 85 109.
## 3 0.654 -1.10 1.16 1.10 119 139.
## # … with 1 more variable: cluster <fct>
I følgende benytter jeg kclust_tidy
som et ekstra datasæt i ovenstående plot, men indenfor en anden geom_point()
for at tilføje en x
form på midten af de tre clusters - se følgende tre punkter, der forklarer nogle detaljer i koden:
- Jeg bruger funktionen
scale()
påbill_length_mm
ogbill_depth_mm
, fordi min centroids, som også skal med i plottet, var beregnet på skaleret data. - Jeg behøver ikke at anvende
scale()
på min centroids lagrede ikclust_tidy
så jeg angiver bare akser-variablerne iaes()
uden at anvendescale()
. - Jeg har brugt
color
ogshape
som lokale aethetics i den førstegeom_point()
her, der de ikke eksistere som kolonner ikclust_tidy
.
ggplot(kc1, aes(x = scale(bill_length_mm), #need to scale the original data
y = scale(bill_depth_mm))) +
geom_point(aes(color = .cluster, shape = species)) +
geom_point(data = kclust_tidy,
aes(x = bill_length_mm, #don't need to scale again
y = bill_depth_mm),
size = 10, shape = "x", show.legend = FALSE) +
theme_bw()
Vi kan se at vores clusters ikke fanger de samme tre gruppe som variablen species
præcist - der er forskelligheder. Det kan være at vi også har fanget nogle oplysninger om fk. øen pingviner bor på, eller deres køn.
9.3 Kmeans: hvor mange clusters?
Vi gættede på 3 clusters i ovenstående analyse (da vi havde oplysninger om arter i forvejen) men det godt kunne være, at et andet antal clusters passer bedre til datasættet. Vi kan beregene flere clusterings og angiver forskellige antal clusters, og dernæst bruge outputterne fra resultaterne til at tage en beslutning om, hvor mange clusters vi gerne vil angiv i vores færdig clustering.
Det er vigtigt at kunne finde frem til en hensigtsmæssigt antal clusters -
- For mange clusters kan resultatere i over-fitting, hvor vi har for mange til at fortolke eller giver mening,
- For få kan betyde at vi mangler indsigter ind i strukturen eller vigtige trends i datasættet.
9.3.1 Få Broom output for forskellige antal clusters
I følgende laver jeg en custom funktion, der laver en clustering på datasættet penguins_scaled
og hvor jeg angiver, at antal beregnede clusters skal være .x
, der er en integer (fk. 1,3,99 osv.). Bemærk derfor, at selve data er samme hver gang jeg anvender funktionen - det er bare antal clusters jeg beregner, der kan variere.
<- ~kmeans(penguins_scaled,centers = .x) my_func
Dernæst laver jeg en tibble
med variablen k
som indeholder heltal fra 1 op til 9. Når man anvender funktionen map
på kolonnen k
med ovenstående funktion my_func
, svarer det til, at jeg anvender kmeans
ni gange, med antal clusters fra 1 til 9. Jeg gemmer clustering resultaterne i en kolon der hedder kclust
, og så anvende tidy
, glance
og augment
til at få de forskellige outputter fra mine clusterings.
<-
kclusts tibble(k = 1:9) %>%
mutate( kclust = map(k, my_func),
tidied = map(kclust, tidy),
glanced = map(kclust, glance),
augmented = map(kclust, ~.x %>% augment(penguins))
)
Husk at for at få frem resultaterne i de forskellige former fra tidy
,glance
og augment
er vi nødt til at anvende funktionen unnest()
- her gemme jeg resultaterne i tre nye dataframes, som vi kan referere til efterfølgende.
<- kclusts %>% unnest(tidied)
kclusts_tidy <- kclusts %>% unnest(augmented)
kclusts_augment <- kclusts %>% unnest(glanced) kclusts_glance
9.3.2 Elbow plot (glance)
Vi bruger tot.withinness
fra outputtet fra glance()
(dataframen kclusts_glance
). Det giver målinger for den totale afstand af observationerne fra deres nærmeste centroid (within sum of squares).
kclusts_glance
## # A tibble: 9 × 8
## k kclust tidied totss tot.withinss betweenss iter augmented
## <int> <list> <list> <dbl> <dbl> <dbl> <int> <list>
## 1 1 <kmeans> <tibble [1 × 7]> 1328 1328. 9.09e-13 1 <tibble>
## 2 2 <kmeans> <tibble [2 × 7]> 1328 551. 7.77e+ 2 1 <tibble>
## 3 3 <kmeans> <tibble [3 × 7]> 1328 370. 9.58e+ 2 3 <tibble>
## 4 4 <kmeans> <tibble [4 × 7]> 1328 304. 1.02e+ 3 2 <tibble>
## 5 5 <kmeans> <tibble [5 × 7]> 1328 228. 1.10e+ 3 3 <tibble>
## 6 6 <kmeans> <tibble [6 × 7]> 1328 200. 1.13e+ 3 3 <tibble>
## 7 7 <kmeans> <tibble [7 × 7]> 1328 206. 1.12e+ 3 4 <tibble>
## 8 8 <kmeans> <tibble [8 × 7]> 1328 174. 1.15e+ 3 5 <tibble>
## 9 9 <kmeans> <tibble [9 × 7]> 1328 232. 1.10e+ 3 4 <tibble>
Jo flere clusters, jo mindre statistikken tot.withinness
er som regel, men vi kan se i følgende plot, at efter 2 eller 3 clusters, er der ikke meget gevinst ved at bruge flere clusters. Derfor vælger man enten 2 eller 3. Plottet er ofte kaldes for en ‘elbow’ plot - man vælger de tal på den ‘elbow’, hvor der ikke er meget gevinst med at have flere clusters i datasættet (men det er selvfølgelig meget subjektiv, det tal man vælger til sidste).
%>%
kclusts_glance ggplot(aes(x = k, y = tot.withinss)) +
geom_line() +
geom_point() +
theme_bw()
9.3.3 Automatistke beslutning med pakken NbClust
Man kan man også overvejer at prøve noget mere automatisk. For eksempel pakken NbClust lave 30 forskellige clustering algoritme på datasættet fra antal clusters = 2 op til til antal cluster = 9 og for hver af de 30 tager en beslutning om de bedste antal clusters. Man kan således se hvilket antal clusters blev valgt fleste gange af de forskellige algoritme.
library(NbClust)
set.seed(24) #fordi outputt af NbClust har indbygget tilfældighed
<- NbClust(data = penguins_scaled,
cluster_30_indexes distance = "euclidean",
min.nc = 2,
max.nc = 9,
method = "complete")
Man kan se i følgende, at enten 2 eller 3 er optimelt, som passer sammen med den elbow plot methode.
as_tibble(cluster_30_indexes$Best.nc[1,]) %>%
ggplot(aes(x=factor(value))) +
geom_bar(stat="count",fill="blue") +
xlab("Number of clusters") + ylab("Number of clustering algorithms choosing this number") +
coord_flip() +
theme_minimal()
9.3.4 Plot de forskellige antal clusters (augment)
Vi kan også visualisere hvordan de forskellige antal clusters ser ud. Her kan vi bruge vores resultater fra funktionen augment
(kclusts_augment
), som indeholder tilknytninger af observationerne til clusters for hver af de 9 clusterings. Læg mærk til at kclusts_agument
har 2997 observationer, der svarer til 9 (antal clusterings) x 333 (antal observationer i penguins
), fordi vi brugt unnest
til at lægge samtlige resultaterne sammen.
%>% glimpse() kclusts_augment
## Rows: 2,997
## Columns: 13
## $ k <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
## $ kclust <list> [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
## $ tidied <list> [<tbl_df[1 x 7]>], [<tbl_df[1 x 7]>], [<tbl_df[1 x …
## $ glanced <list> [<tbl_df[1 x 4]>], [<tbl_df[1 x 4]>], [<tbl_df[1 x …
## $ species <fct> Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Adel…
## $ island <fct> Torgersen, Torgersen, Torgersen, Torgersen, Torgerse…
## $ bill_length_mm <dbl> 39.1, 39.5, 40.3, 36.7, 39.3, 38.9, 39.2, 41.1, 38.6…
## $ bill_depth_mm <dbl> 18.7, 17.4, 18.0, 19.3, 20.6, 17.8, 19.6, 17.6, 21.2…
## $ flipper_length_mm <int> 181, 186, 195, 193, 190, 181, 195, 182, 191, 198, 18…
## $ body_mass_g <int> 3750, 3800, 3250, 3450, 3650, 3625, 4675, 3200, 3800…
## $ sex <fct> male, female, female, female, male, female, male, fe…
## $ year <fct> 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007…
## $ .cluster <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
I følgende kode plotter jeg flipper_length_mm
vs bill_length_mm
, og anvende facet_wrap
så at hver clustering får sit eget plot (så der er 333 observationer pr. plot).
%>%
kclusts_augment ggplot(aes(x = flipper_length_mm, y = bill_length_mm,colour=.cluster)) +
geom_point(aes(shape=factor(species)), alpha = 0.8) +
facet_wrap(~ k) +
theme_bw()
Vi kan nemt inddrage kclusts_tidy()
og lave “X”-former, bare ved at tilføje en ekstra geom_point
og angive kclusts_tidy
. Jeg anvender først funktionen rename
så at variablen cluster
fra klusts_tidy
matcher til .cluster
fra kclusts_augment
.
<- kclusts_tidy %>% rename(.cluster=cluster)
kclusts_tidy
%>%
kclusts_augment ggplot(aes(x = scale(flipper_length_mm), y = scale(bill_length_mm),colour=.cluster)) + #scale here
geom_point(aes(shape=factor(species)), alpha = 0.8) +
facet_wrap(~ k) +
geom_point(data = kclusts_tidy,
aes(x=flipper_length_mm,y=bill_length_mm), #already based on scaled data, so don't scale
size = 10, shape = "x",col="black", show.legend = FALSE) +
theme_bw()
Vi kan prøve at kigge endnu dyber ind i resultaterne - her introducerer jeg sex
som en ekstra variabel i plottet. Husk at variablen sex
var ikke blevet brugt i vores k-means clusterings, men det kan være, at der er nogle aspekter af de fire variabler, som kan fortælle os nogle om kønnet af pingvingerne. For at spare plads, har jeg kun plottet antal clusters fra 2 til 5.
%>% filter(k %in% 2:5) %>%
kclusts_augment ggplot(aes(x = scale(flipper_length_mm), y = scale(bill_length_mm),colour=.cluster)) +
geom_point(aes(shape=factor(species)), alpha = 0.8) +
facet_grid(sex ~ k) +
geom_point(data = kclusts_tidy %>% filter(k %in% 2:5),
aes(x = flipper_length_mm,
y = bill_length_mm),
size = 10, shape = "x", colour = "black",show.legend = FALSE) +
theme_bw()
9.3.5 Nest/map ramme fra sidste gange
Som sidste bemærk med k-means, kan man også lave en clustering til de tre arter hver for sig. I følgende opretter jeg en nested dataframe, som indeholder 3 datasæt (penguins
opdelt efter variablen species
), og jeg anvender den custom funktion scale_me
til at udvælge de numeriske variabler og anvende scale()
i hvert datasæt.
<- ~.x %>% select(where(is.numeric)) %>% scale
scale_me
<- penguins %>%
penguins_nest group_by(species) %>%
nest() %>%
mutate("data_scaled" = map(data,scale_me))
Næste laver jeg en custom funktion til at lave en clustering på datasættet .x
, og angiver at antal clusters skal være 3. Bemærk at i ovenstående sektion varierede vi på antal clusters (indstilling centers
), men her fastlægger vi antal clusters og så variere selve datasæt i stedet for.
<- ~.x %>% kmeans(centers=3) cluster_me
Jeg anvender cluster_me
på mine skaleret datasæts, og så anvender glance
, augment
og tidy
på clustering resultater ligesom i ovenpå (bemærk brugen af map
til at augment
de opdelte datasæt).
<- penguins_nest %>%
penguins_nest mutate(clusters = map(data_scaled,cluster_me),
clusters_glance = map(clusters,glance),
clusters_augment = map2(clusters,data_scaled,~.x %>% augment(.y)), #I augment the scaled data so the correct scaling (based on individual datasets) appears in the next plot
clusters_tidy = map(clusters,tidy))
<- penguins_nest %>% unnest(clusters_augment)
nested_clusters_augment <- penguins_nest %>% unnest(clusters_tidy) nested_clusters_tidy
Til sidste laver jeg en plot af resultaterne:
%>%
nested_clusters_augment ggplot(aes(x=bill_length_mm,y=flipper_length_mm,colour=.cluster)) + #data already scaled
geom_point() +
facet_grid(~species) +
geom_point(data=nested_clusters_tidy,,
shape="X",colour="black",
size = 10) +
theme_bw()
9.4 Method 2: Hierarchical clustering
K-means er en meget populær metode til at lave clustering, men der er mange andre metoder, fk. hierarchical clustering. Vi skifter over til mtcars
, og ligesom i kmeans
skal vi første bruge scale
på de numeriske kolonner i de data.
<- mtcars %>% select(where(is.numeric)) %>% scale() mtcars_scaled
I modsætning til k-means, for at lave hierarchical clustering skal man første beregne afstanden mellem alle de observationer i de data. Det gør man med funktionen dist()
(som bruger den Euclidean distance som default):
<- dist(mtcars_scaled) d
For at lave en hierarchical clustering anvender man funktionen hclust()
. Metoden complete
er default men man kan afprøve de andre methoder (der er ikke en fast regel over for, hvilken metode man skal bruge).
<- hclust(d, method = "complete" )
mtcars_hc # Metoder: "average", "single", "complete", "ward.D"
I følgende arbejder vi lidt med mtcars_hc
til at få nogle clusters frem, og til at lave et plot.
9.4.1 Vælge ønkset antal clusters
Funktionen cutree
anvendes til at få clusters fra resultaterne af funktionen hclust
. For eksempel hvis man gerne vil have 4 clusters, bruger man k = 4
. Jeg specificerer order_clusters_as_data = FALSE
for at få clusters i rækkefølgen, som passer til plottet (dendrogram) vi laver (bemærk at man skal have pakken dendextend
installeret for at få den til at fungere).
library(dendextend)
<- cutree(mtcars_hc, k = 4, order_clusters_as_data = FALSE) clusters
Her laver jeg et overblik over, hvor mange observationerne fra mtcars
er i hver cluster:
tibble("cluster"=clusters) %>% group_by(cluster) %>% summarise(n())
FALSE # A tibble: 4 × 2
FALSE cluster `n()`
FALSE <int> <int>
FALSE 1 1 7
FALSE 2 2 8
FALSE 3 3 12
FALSE 4 4 5
9.4.2 Lav et pænt plot af dendrogram med ggplot2
Første anvender jeg funktionen dendro_data()
til at udtrække den “dendrogram” fra de hclust()
resultater.
library(ggdendro)
<- dendro_data(mtcars_hc %>% as.dendrogram, type = "rectangle") dend_data
Vi tilføjer vores clusters som vi beregnede ovenpå (det er derfor vi sikret rækkefølgen af de clusters):
$labels <- dend_data$labels %>%
dend_datamutate(cluster = clusters)
Vi benytter dend_data$segments
og dend_data$labels
til at lave et informativ plot af de data i ggplot2
.
ggplot(dend_data$segments) +
geom_segment(aes(x = x, y = y, xend = xend, yend = yend)) +
coord_flip() +
geom_text(data = dend_data$labels,
aes(x, y, label = label,col=factor(cluster)),
hjust=1,size=3) +
ylim(-3, 10) +
theme_dendro()
Så kan man se, der er fire clusters i dengrammet, og biler der er tætest på hinanden ligner hinanden mest - fk. Merc 280C og Merc 280 må være meget éns, og er som forventet lige ved siden af hinanden i plottet.
Man kan godt tilpasse ovenstående kode til et andet datasæt - se problemstillinger, men man må også gerne udvide plottet med de forskellige viden vi har om ggplot2.
9.4.3 Ekstra (valgfri): afprøve andre metoder på hierachical clustering
Valfri ekstra hvis du vil afprøve de fire metoder i hclust
- “average”, “single”, “complete” og “ward.D”.
# samme ggplot kommando som ovenpå lavet til en funktion
<- ~ggplot(.x$segments) +
den_plot geom_segment(aes(x = x, y = y, xend = xend, yend = yend)) +
coord_flip() +
geom_text(data = .x$labels,
aes(x, y, label = label),
hjust=1,size=2) +
ylim(-4, 10) + theme_dendro()
Vi iterate over de fire metoder og lave samme process som ovenpå med map. Derefter kan man lave et plot fk. med grid.arrange:
# fire metoder:
<- c( "average", "single", "complete", "ward.D")
m
<-
hc_results tibble(method = m) %>%
mutate( kclust = map(method, ~hclust(d, method = .x)),
dendrogram = map(kclust,as.dendrogram),
den_dat = map(dendrogram,~dendro_data(.x,type="rectangle")),
plot = map(den_dat,den_plot))
library(gridExtra)
grid.arrange(grobs = hc_results %>% pull(plot),ncol=2)
9.5 Problemstillinger
Problem 1) Quiz - Clustering
Problem 2) Funktionen kmeans. I ovenstående anvendt vi mtcars
i hierarchical clustering, men lad os se, hvordan det ser ud med k-means
. Du er velkommen til at tilpasse min ovenstående kode fa det penguins
datasæt:
a) Benyt kmeans
til at finde 2 clusters i datasættet mtcars
:
- husk at vælge kun de numeriske kolonner og scale datasættet i forvejen
- gem din clustering som
my_clusters
. - hvor mange observationer er der i hver af de to clusters?
b) Anvend funktionen augment
til at forbinde det oprindelige datasæt til dine clusters fra my_clusters
(skriv mtcars
indenfor funktionen augment
).
c) Brug dit “augmented” datasæt til at lave et scatter plot mellem to af de numeriske variabler (vælg selv) i datasættet og giv dem farver efter din clusters, som du har beregnet. Da du har forbundet det oprindeligt datasæt (der ikke var scaled) i augment
, scale din variabler i plottet.
d) Tilføj tidy
til at få fat i de middelværdier/centroids af hver af de 2 clusters og tilpas min kode fra notaterne (sektion 9.2.5) til at tilføje dem til plottet som ‘x’ (husk at din “centers”/centroids er allerede baserede på scaled data så du behøver ikke at anvende scale på deres værdier).
Problem 3) Hierarchical clustering øvelse
Vi laver en analyse af det msleep
datasæt. Jeg har lavet oprydningen og scaling for jer:
data(msleep)
<- msleep %>% select(name,where(is.numeric)) %>% drop_na()
msleep_clean <- msleep_clean %>% select(-name) %>% scale
msleep_scaled row.names(msleep_scaled) <- msleep_clean$name
Tilpas min kode fra kursusnotaterne (sektion 9.4) til at lave følgende:
a) Benyt funktioner dist
og dernæst hclust
på datasættet msleep_scaled
.
b) Benyt cutree
for at finde 5 clusters fra dine hclust
-resultater, og kalde det for clusters
. Husk at anvende order_clusters_as_data = FALSE
så at vi har den korrekt rækkefølge for et plot (OBS man skal installere/indlæse pakken dendextend
)
c) Benyt dendro_data
til at udtrække de dendrogram fra resultaterne og tilføj clusters
til dend_data$labels
(kopier kode fra 9.4.2).
d) Lav et dengrogram plot: igen tilpas koden (9.4.2) for mtcars
eksempel for nuværende data
Problem 4)
Inlæs data
<- read.csv("https://www.dropbox.com/s/7nb5pkruqt4fqn4/Wholesale%20customers%20data.csv?dl=1", header = TRUE) wholesale
a) Ændre på datasættet efter følgende instruks:
- Channel - anvend
recode
for at ændre til navne- 1 = horeca
- 2 = retail
- Region - anvend
recode
for at ændre til navne- 1 = Lisnon
- 2 = Oporto
- 3 = Other
- Anvend
map_if
til at transformere samtlige numeriske variabler med log (kode er:wholesale <- wholesale %>% map_if(is.numeric,log) %>% as_tibble()
).
b) Udvælg de numeriske variabler fra dit datasæt og anvende scale()
- kalde dit nye datasæt for wholescale_scale
c) Tilpas min kode fra sektion 9.3.1 til at lave 10 clusterings (k=1:10) på wholesale_scale
og gem dem i en dataframe, sammen med din clusterings resultater i “tidy”, “glance” og “augment” form.
d) Lav et elbow plot fra dit output fra glance
(sektion 9.3.2)
e) Udvælg clusterings hvor k er fra 2 til 7 fra dit output fra augment
og lav scatter plots af variabler Frozen
VS Fresh
, hvor du:
- Giv farve efter .cluster
- Adskil plots efter
k
- Prøv bagefter at også adskil dit plots yderligere efter
Channel
.
f) Tilpas koden fra 9.3.5 til at lave en analyse for “hoerca” og “retail” (variablen Channel
) hver for sig. Angiv 4 clusters i din analyse.
g) Lav et plot af din clustering (adskilt efter variablen Channel
) og få “x” på plotterne til at vise din cluster middelværdier for Frozen
og Fresh
.