library("readr")
<- read_delim("datasets/stat5-8/crime2.csv", ";") crime
Stat8: Lösung
- Download dieses Lösungsscript via “</>Code” (oben rechts)
- Lösungstext als Download
Musterlösung Aufgabe 8.1: Clusteranalysen
Übungsaufgabe
(hier so ausführlich formuliert, wie dies auch in der Klausur der Fall sein wird)
- Ladet den Datensatz crime2.csv. Dieser enthält Raten von 7 Kriminatlitätsformen pro 100000 Einwohner und Jahr für die Bundesstaaten der USA.
- Führt eine k-means- und eine agglomerative Clusteranalyse eurer Wahl durch. Bitte beachet, dass wegen der sehr ungleichen Varianzen in jedem Fall eine Standardisierung stattfinden muss, damit die Distanzen zwischen den verschiedenen Kriminalitätsraten sinnvoll berechnet werden können.
- Überlegt in beiden Fällen, wie viele Cluster sinnvoll sind (k-means: z. B. visuelle Betrachtung einer PCA, agglomertive Clusteranalyse: z. B. Silhoutte-Plot).
- Entscheidet euch dann für eine der beiden Clusterungen und vergleicht dann die erhaltenen Cluster bezüglich der Kriminalitätsformen und interpretiert die Cluster entsprechend.
- Bitte erklärt und begründet die einzelnen Schritte, die ihr unternehmt, um zu diesem Ergebnis zu kommen. Dazu erstellt bitte ein Word-Dokument, in das ihr Schritt für Schritt den verwendeten R-Code, die dazu gehörigen Ausgaben von R, eure Interpretation derselben und die sich ergebenden Schlussfolgerungen für das weitere Vorgehen dokumentieren.
- Formuliert abschliessend einen Methoden- und Ergebnisteil (ggf. incl. adäquaten Abbildungen) zu dieser Untersuchung in der Form einer wissenschaftlichen Arbeit (ausformulierte schriftliche Zusammenfassung, mit je einem Absatz von ca. 60-100 Worten, resp. 3-8 Sätzen für den Methoden- und Ergebnisteil). D. h. alle wichtigen Informationen sollten enthalten sein, unnötige Redundanz dagegen vermieden werden.
- Zu erstellen sind (a) Ein lauffähiges R-Skript; (b) begründeter Lösungsweg (Kombination aus R-Code, R Output und dessen Interpretation) und (c) ausformulierter Methoden- und Ergebnisteil (für eine wiss. Arbeit).
Lösung
crime
# A tibble: 50 × 8
...1 Murder Rape Robbery Assault Burglary Theft Vehicle
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 ME 2 14.8 28 102 803 2347 164
2 NH 2.2 21.5 24 92 755 2208 228
3 VT 2 21.8 22 103 949 2697 181
4 MA 3.6 29.7 193 331 1071 2189 906
5 RI 3.5 21.4 119 192 1294 2568 705
6 CT 4.6 23.8 192 205 1198 2758 447
7 NY 10.7 30.5 514 431 1221 2924 637
8 NJ 5.2 33.2 269 265 1071 2822 776
9 PA 5.5 25.1 152 176 735 1654 354
10 OH 5.5 38.6 142 235 988 2574 376
# ℹ 40 more rows
Im mitgelieferten R-Skript habe ich die folgenden Analysen zunächst mit untransformierten, dann mit standardisierten Kriminalitätsraten berechnet. Ihr könnt die Ergebnisse vergleichen und seht, dass sie sehr unterschiedlich ausfallen.
<- crime
crimez c(2:8)] <- lapply(crime[, c(2:8)], scale)
crimez[, crimez
# A tibble: 50 × 8
...1 Murder[,1] Rape[,1] Robbery[,1] Assault[,1] Burglary[,1] Theft[,1]
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 ME -1.38 -1.32 -1.05 -1.25 -0.939 -0.760
2 NH -1.32 -0.853 -1.08 -1.32 -1.05 -0.945
3 VT -1.38 -0.832 -1.10 -1.24 -0.591 -0.294
4 MA -0.919 -0.287 0.467 0.398 -0.300 -0.970
5 RI -0.948 -0.860 -0.212 -0.601 0.232 -0.466
6 CT -0.630 -0.694 0.458 -0.508 0.00320 -0.213
7 NY 1.14 -0.232 3.41 1.12 0.0580 0.00774
8 NJ -0.456 -0.0452 1.16 -0.0766 -0.300 -0.128
9 PA -0.369 -0.604 0.0908 -0.716 -1.10 -1.68
10 OH -0.369 0.328 -0.000917 -0.292 -0.498 -0.458
# ℹ 40 more rows
# ℹ 1 more variable: Vehicle <dbl[,1]>
„scale“ führt eine Standardisierung (z-Transformation) durch, so dass alle Variablen anschiessen einen Mittelwert von 0 und eine SD von 1 haben, ausgenommen natürlich die 1. Spalte mit den Kürzeln der Bundesstaaten. Anschliessend wird das SSI-Kriterium getestet und zwar für Partitionierungen von 2 bis 6 Gruppen (wie viele Gruppen man maximal haben will, muss man pragmatisch nach der jeweiligen Fragestelltung entscheiden).
library("vegan")
<- cascadeKM(crimez[, c(2:8)],
crimez.KM.cascade inf.gr = 2, sup.gr = 6, iter = 100, criterion = "ssi"
)summary(crimez.KM.cascade)
Length Class Mode
partition 250 -none- numeric
results 10 -none- numeric
criterion 1 -none- character
size 30 -none- numeric
$results crimez.KM.cascade
2 groups 3 groups 4 groups 5 groups 6 groups
SSE 174.959159 144.699605 124.437221 108.119280 95.316398
ssi 1.226057 1.304674 1.555594 1.539051 1.351146
$partition crimez.KM.cascade
2 groups 3 groups 4 groups 5 groups 6 groups
1 2 2 4 4 6
2 2 2 4 4 6
3 2 2 4 4 6
4 1 1 2 5 2
5 2 1 3 5 2
6 2 1 3 5 5
7 1 1 2 3 1
8 1 1 2 5 2
9 2 2 3 1 5
10 2 1 3 1 5
11 2 2 3 1 5
12 1 1 2 3 1
13 1 3 1 3 3
14 2 2 4 4 6
15 2 2 4 4 6
16 2 2 4 4 6
17 1 1 3 1 1
18 2 2 4 4 6
19 2 2 4 4 6
20 2 2 4 4 6
21 2 2 3 1 5
22 2 1 3 1 5
23 1 1 2 3 1
24 2 2 3 1 5
25 2 2 4 4 6
26 2 1 3 1 5
27 1 1 3 1 1
28 1 1 1 3 1
29 1 3 1 3 3
30 2 2 3 1 5
31 1 1 2 1 1
32 2 1 3 1 5
33 2 2 3 1 5
34 2 2 3 1 5
35 1 3 1 3 1
36 1 1 1 2 4
37 1 3 1 3 3
38 2 2 4 4 6
39 2 2 4 4 6
40 2 2 4 4 6
41 1 3 1 2 4
42 1 3 1 2 4
43 1 3 1 2 4
44 2 2 4 4 6
45 1 3 1 3 3
46 1 3 1 2 4
47 1 3 1 2 4
48 1 3 1 3 3
49 1 3 1 2 4
50 2 2 3 4 5
# k-means visualisation
library("cclust")
plot(crimez.KM.cascade, sortg = TRUE)
Nach SSI ist die 4-Gruppenlösung die beste, mit dieser wird also weitergerechnet.
# 4 Kategorien sind nach SSI offensichtlich besonders gut
<- kmeans(crimez[, c(2:8)], 4)
modelz modelz
K-means clustering with 4 clusters of sizes 6, 22, 9, 13
Cluster means:
Murder Rape Robbery Assault Burglary Theft
1 0.3543004 1.22297941 0.03118143 0.6957454 1.3332749 1.5763188
2 -0.6982854 -0.77138855 -0.76544570 -0.8495717 -0.7970855 -0.4900018
3 1.3127382 0.94679752 1.65699740 1.2772894 0.9185274 0.6804530
4 0.1093718 0.08549954 0.13382617 0.2323462 0.0976527 -0.3693808
Vehicle
1 0.2190487
2 -0.8162838
3 1.1463189
4 0.4866986
Clustering vector:
[1] 2 2 2 4 4 4 3 4 2 4 2 3 3 2 2 2 4 2 2 2 2 4 3 2 2 4 4 4 3 2 4 4 2 2 3 4 3 2
[39] 2 2 1 1 1 2 3 1 1 3 1 2
Within cluster sum of squares by cluster:
[1] 13.15184 44.59027 27.50024 40.03154
(between_SS / total_SS = 63.5 %)
Available components:
[1] "cluster" "centers" "totss" "withinss" "tot.withinss"
[6] "betweenss" "size" "iter" "ifault"
# File für ANOVA (Originaldaten der Vorfälle, nicht die ztransformierten)
<- data.frame(crime, modelz[1])
crime.KM4 $cluster <- as.factor(crime.KM4$cluster)
crime.KM4 crime.KM4
...1 Murder Rape Robbery Assault Burglary Theft Vehicle cluster
1 ME 2.0 14.8 28 102 803 2347 164 2
2 NH 2.2 21.5 24 92 755 2208 228 2
3 VT 2.0 21.8 22 103 949 2697 181 2
4 MA 3.6 29.7 193 331 1071 2189 906 4
5 RI 3.5 21.4 119 192 1294 2568 705 4
6 CT 4.6 23.8 192 205 1198 2758 447 4
7 NY 10.7 30.5 514 431 1221 2924 637 3
8 NJ 5.2 33.2 269 265 1071 2822 776 4
9 PA 5.5 25.1 152 176 735 1654 354 2
10 OH 5.5 38.6 142 235 988 2574 376 4
11 IN 6.0 25.9 90 186 887 2333 328 2
12 IL 8.9 32.4 325 434 1180 2938 628 3
13 MI 11.3 67.4 301 424 1509 3378 800 3
14 WI 3.1 20.1 73 162 783 2802 254 2
15 MN 2.5 31.8 102 148 1004 2785 288 2
16 IA 1.8 12.5 42 179 956 2801 158 2
17 MO 9.2 29.2 170 370 1136 2500 439 4
18 ND 1.0 11.6 7 32 385 2049 120 2
19 SD 4.0 17.7 16 87 554 1939 99 2
20 NE 3.1 24.6 51 184 748 2677 168 2
21 KS 4.4 32.9 80 252 1188 3008 258 2
22 DE 4.9 56.9 124 241 1042 3090 272 4
23 MD 9.0 43.6 304 476 1296 2978 545 3
24 VA 7.1 26.5 106 167 813 2522 219 2
25 WV 5.9 18.9 41 99 625 1358 169 2
26 NC 8.1 26.4 88 354 1225 2423 208 4
27 SC 8.6 41.3 99 525 1340 2846 277 4
28 GA 11.2 43.9 214 319 1453 2984 430 4
29 FL 11.7 52.7 367 605 2221 4373 598 3
30 KY 6.7 23.1 83 222 824 1740 193 2
31 TN 10.4 47.0 208 274 1325 2126 544 4
32 AL 10.1 28.4 112 408 1159 2304 267 4
33 MS 11.2 25.8 65 172 1076 1845 150 2
34 AR 8.1 28.9 80 278 1030 2305 195 2
35 LA 12.8 40.1 224 482 1461 3417 442 3
36 OK 8.1 36.4 107 285 1787 3142 649 4
37 TX 13.5 51.6 240 354 2049 3987 714 3
38 MT 2.9 17.3 20 118 783 3314 215 2
39 ID 3.2 20.0 21 178 1003 2800 181 2
40 WY 5.3 21.9 22 243 817 3078 169 2
41 CO 7.0 42.3 145 329 1792 4231 486 1
42 NM 11.5 46.9 130 538 1845 3712 343 1
43 AZ 9.3 43.0 169 437 1908 4337 419 1
44 UT 3.2 25.3 59 180 915 4074 223 2
45 NV 12.6 64.9 287 354 1604 3489 478 3
46 WA 5.0 53.4 135 244 1861 4267 315 1
47 OR 6.6 51.1 206 286 1967 4163 402 1
48 CA 11.3 44.9 343 521 1696 3384 762 3
49 AK 8.6 72.7 88 401 1162 3910 604 1
50 HI 4.8 31.0 106 103 1339 3759 328 2
str(crime.KM4)
'data.frame': 50 obs. of 9 variables:
$ ...1 : chr "ME" "NH" "VT" "MA" ...
$ Murder : num 2 2.2 2 3.6 3.5 4.6 10.7 5.2 5.5 5.5 ...
$ Rape : num 14.8 21.5 21.8 29.7 21.4 23.8 30.5 33.2 25.1 38.6 ...
$ Robbery : num 28 24 22 193 119 192 514 269 152 142 ...
$ Assault : num 102 92 103 331 192 205 431 265 176 235 ...
$ Burglary: num 803 755 949 1071 1294 ...
$ Theft : num 2347 2208 2697 2189 2568 ...
$ Vehicle : num 164 228 181 906 705 447 637 776 354 376 ...
$ cluster : Factor w/ 4 levels "1","2","3","4": 2 2 2 4 4 4 3 4 2 4 ...
Von den agglomerativen Clusterverfahren habe ich mich für Ward’s minimum variance clustering entschieden, da dieses allgemein als besonders geeignet gilt.
Vor der Berechnung von crime.norm und crime.ch muss man die Spalte mit den Bundesstaatenkürzeln entfern.
library("cluster")
# Add rownames to the dataframe
<- as.data.frame(crime)
crime rownames(crime) <- crime$...1
<- crime[, -1]
crime2
# Agglomerative Clusteranalyse
<- decostand(crime2, "normalize")
crime.norm <- vegdist(crime.norm, "euc")
crime.ch # Attach site names to object of class 'dist'
attr(crime.ch, "Labels") <- rownames(crime2)
# Ward's minimum variance clustering
<- hclust(crime.ch, method = "ward.D2")
crime.ch.ward par(mfrow = c(1, 1))
plot(crime.ch.ward, labels = rownames(crime2), main = "Chord - Ward")
# Choose and rename the dendrogram ("hclust" object)
<- crime.ch.ward
hc # hc <- spe.ch.beta2
# hc <- spe.ch.complete
dev.new(title = "Optimal number of clusters", width = 12, height = 8, noRStudioGD = TRUE)
dev.off()
png
2
par(mfrow = c(1, 2))
# Average silhouette widths (Rousseeuw quality index)
library("cluster")
<- numeric(nrow(crime))
Si for (k in 2:(nrow(crime) - 1))
{<- silhouette(cutree(hc, k = k), crime.ch)
sil <- summary(sil)$avg.width
Si[k]
}<- which.max(Si)
k.best plot(1:nrow(crime), Si,
type = "h",
main = "Silhouette-optimal number of clusters",
xlab = "k (number of clusters)", ylab = "Average silhouette width"
)
axis(1, k.best, paste("optimum", k.best, sep = "\n"),
col = "red",
font = 2, col.axis = "red"
)points(k.best, max(Si), pch = 16, col = "red", cex = 1.5)
Demnach wären beim Ward’s-Clustering nur zwei Gruppen die optimale Lösung.
Für die Vergleiche der Bundesstaatengruppen habe ich mich im Folgenden für die k-means Clusterung mit 4 Gruppen entschieden.
Damit die Boxplots und die ANOVA direkt interpretierbar sind, werden für diese, anders als für die Clusterung, die untransformierten Incidenz-Werte verwendet (also crime statt crimez). Die Spalte mit der Clusterzugehörigkeit im Fall von k-means mit 4 Clustern hängt man als Spalte an (Achtung: muss als Faktor definiert werden!).
Anschliessend kann man die 7 ANOVAs rechnen, die Posthoc-Vergleiche durchführen und die zugehörigen Boxplots mit Buchstaben für die homogenen Gruppen erzeugen. Sinnvollerweise gruppiert man die Abbildungen gleich, z. B. je 2 x 2. Das Skript ist hier simple für jede Verbrechensart wiederholt. Erfahrenere R-Nutzer können das Ganze hier natürlich durch eine Schleife abkürzen.
library("multcomp")
par(mfrow = c(3, 3))
<- aov(Murder ~ cluster, data = crime.KM4)
ANOVA.Murder summary(ANOVA.Murder)
Df Sum Sq Mean Sq F value Pr(>F)
cluster 3 324.0 107.99 19.05 3.58e-08 ***
Residuals 46 260.8 5.67
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
<- cld(glht(ANOVA.Murder, linfct = mcp(cluster = "Tukey")))
letters boxplot(Murder ~ cluster, xlab = "Cluster", ylab = "Murder", data = crime.KM4)
mtext(letters$mcletters$Letters, at = 1:6)
<- aov(Rape ~ cluster, data = crime.KM4)
ANOVA.Rape summary(ANOVA.Rape)
Df Sum Sq Mean Sq F value Pr(>F)
cluster 3 6341 2113.6 24.69 1.14e-09 ***
Residuals 46 3938 85.6
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
<- cld(glht(ANOVA.Rape, linfct = mcp(cluster = "Tukey")))
letters boxplot(Rape ~ cluster, xlab = "Cluster", ylab = "Rape", data = crime.KM4)
mtext(letters$mcletters$Letters, at = 1:6)
<- aov(Robbery ~ cluster, data = crime.KM4)
ANOVA.Robbery summary(ANOVA.Robbery)
Df Sum Sq Mean Sq F value Pr(>F)
cluster 3 449894 149965 51.99 8.11e-15 ***
Residuals 46 132695 2885
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
<- cld(glht(ANOVA.Robbery, linfct = mcp(cluster = "Tukey")))
letters boxplot(Robbery ~ cluster, xlab = "Cluster", ylab = "Robbery", data = crime.KM4)
mtext(letters$mcletters$Letters, at = 1:6)
<- aov(Assault ~ cluster, data = crime.KM4)
ANOVA.Assault summary(ANOVA.Assault)
Df Sum Sq Mean Sq F value Pr(>F)
cluster 3 661962 220654 35.32 5.35e-12 ***
Residuals 46 287341 6247
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
<- cld(glht(ANOVA.Assault, linfct = mcp(cluster = "Tukey")))
letters boxplot(Assault ~ cluster, xlab = "Cluster", ylab = "Assault", data = crime.KM4)
mtext(letters$mcletters$Letters, at = 1:6)
<- aov(Burglary ~ cluster, data = crime.KM4)
ANOVA.Burglary summary(ANOVA.Burglary)
Df Sum Sq Mean Sq F value Pr(>F)
cluster 3 5692055 1897352 29.82 7.35e-11 ***
Residuals 46 2926800 63626
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
<- cld(glht(ANOVA.Burglary, linfct = mcp(cluster = "Tukey")))
letters boxplot(Burglary ~ cluster, data = crime.KM4, xlab = "Cluster", ylab = "Burglary")
mtext(letters$mcletters$Letters, at = 1:6)
<- aov(Theft ~ cluster, data = crime.KM4)
ANOVA.Theft summary(ANOVA.Theft)
Df Sum Sq Mean Sq F value Pr(>F)
cluster 3 14771705 4923902 17.52 9.98e-08 ***
Residuals 46 12926846 281018
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
<- cld(glht(ANOVA.Theft, linfct = mcp(cluster = "Tukey")))
letters boxplot(Theft ~ cluster, xlab = "Cluster", ylab = "Theft", data = crime.KM4)
mtext(letters$mcletters$Letters, at = 1:6)
<- aov(Vehicle ~ cluster, data = crime.KM4)
ANOVA.Vehicle summary(ANOVA.Vehicle)
Df Sum Sq Mean Sq F value Pr(>F)
cluster 3 1313441 437814 23.91 1.79e-09 ***
Residuals 46 842430 18314
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
<- cld(glht(ANOVA.Vehicle, linfct = mcp(cluster = "Tukey")))
letters boxplot(Vehicle ~ cluster, data = crime.KM4, xlab = "Cluster", ylab = "Vehicle")
mtext(letters$mcletters$Letters, at = 1:6)
Die Boxplots erlauben jetzt auch eine Beurteilung der Modelldiagnostik: sind die Residuen hinreichen normalverteilt (symmetrisch) und sind die Varianzen zwischen den Kategorien einigermassen ähnlich. Mit der Symmetrie/Normalverteilung sieht es OK aus. Die Varianzhomogenität ist nicht optimal – meist deutlich grössere Varianz bei höheren Mittelwerten. Eine log-Transformation hätte das verbessert und könnte hier gut begründet werden. Da die p-Werte sehr niedrig waren und die Varianzheterogenität noch nicht extrem war, habe ich aber von einer Transformation abgesehen, da jede Transformation die Interpretation der Ergebnisse erschwert. Jetzt muss man nur noch herausfinden, welche Bundesstaaten überhaupt zu welchem der vier Cluster gehören, sonst ist das ganze schöne Ergebnis nutzlos. Z. B. kann man in R auf den Dataframe clicken und ihn nach cluster sortieren.