Document clustering, or text clustering, is a very popular application of clustering algorithms. A web search engine, like Google, often returns thousands of results for a simple query. For example, if you type the search term “jaguar” into Google, around 200 million results are returned. This makes it very difficult to browse or find relevant information, especially if the search term has multiple meanings. If we search for “jaguar”, we might be looking for information about the animal, the car, or the Jacksonville Jaguars football team.
Clustering methods can be used to automatically group search results into categories, making it easier to find relavent results. This method is used in the search engines PolyMeta and Helioid, as well as on FirstGov.gov, the official Web portal for the U.S. government. The two most common algorithms used for document clustering are Hierarchical and k-means.
In this problem, we’ll be clustering articles published on Daily Kos, an American political blog that publishes news and opinion articles written from a progressive point of view. Daily Kos was founded by Markos Moulitsas in 2002, and as of September 2014, the site had an average weekday traffic of hundreds of thousands of visits.
The file dailykos.csv contains data on 3,430 news articles or blogs that have been posted on Daily Kos. These articles were posted in 2004, leading up to the United States Presidential Election. The leading candidates were incumbent President George W. Bush (republican) and John Kerry (democratic). Foreign policy was a dominant topic of the election, specifically, the 2003 invasion of Iraq.
Each of the variables in the dataset is a word that has appeared in at least 50 different articles (1,545 words in total). The set of words has been trimmed according to some of the techniques covered in the previous week on text analytics (punctuation has been removed, and stop words have been removed). For each document, the variable values are the number of times that word appeared in the document.
setwd("C:/Users/jzchen/Documents/Courses/Analytics Edge/Unit_6_Clustering")
kos <- read.csv("dailykos.csv")
str(kos)
## 'data.frame': 3430 obs. of 1545 variables:
## $ abandon : int 0 0 0 0 0 0 0 0 0 0 ...
## $ abc : int 0 0 0 0 0 0 0 0 0 0 ...
## $ ability : int 0 0 0 0 0 0 0 0 0 0 ...
## $ abortion : int 0 0 0 0 0 0 0 0 0 0 ...
## $ absolute : int 0 0 0 0 0 0 0 0 0 0 ...
## $ abstain : int 0 0 1 0 0 0 0 0 0 0 ...
## $ abu : int 0 0 0 0 0 0 0 0 0 0 ...
## $ abuse : int 0 0 0 0 0 0 0 0 0 0 ...
## $ accept : int 0 0 0 0 0 0 0 0 0 0 ...
## $ access : int 0 0 0 0 0 0 0 0 0 0 ...
## $ accomplish : int 0 0 0 0 0 0 0 0 0 0 ...
## $ account : int 0 0 2 0 0 0 0 0 0 0 ...
## $ accurate : int 0 0 0 0 0 0 0 0 0 0 ...
## $ accusations : int 0 0 0 2 0 0 0 0 0 0 ...
## $ achieve : int 0 0 0 0 0 0 0 0 0 0 ...
## $ acknowledge : int 0 0 0 0 0 0 0 0 0 0 ...
## $ act : int 0 0 0 0 0 0 0 0 0 0 ...
## $ action : int 2 0 0 0 0 0 0 0 0 0 ...
## $ active : int 0 0 0 0 0 0 0 0 0 0 ...
## $ activist : int 0 0 0 0 0 0 0 0 0 0 ...
## $ actual : int 0 0 0 0 0 0 0 0 0 0 ...
## $ add : int 0 0 0 0 0 0 0 0 0 0 ...
## $ added : int 1 0 0 0 1 0 0 0 1 0 ...
## $ addition : int 0 0 0 0 0 0 0 0 0 0 ...
## $ address : int 0 0 0 0 0 0 0 0 0 0 ...
## $ admin : int 0 0 1 0 0 0 0 0 0 0 ...
## $ administration : int 1 0 0 0 0 0 0 0 0 0 ...
## $ admit : int 0 0 0 0 1 0 0 0 0 0 ...
## $ advance : int 0 0 0 0 0 0 0 0 0 0 ...
## $ advantage : int 0 0 0 1 0 0 0 0 0 0 ...
## $ advertise : int 0 0 1 0 0 0 0 0 0 0 ...
## $ advised : int 0 0 0 0 0 0 0 0 0 0 ...
## $ affair : int 0 0 0 0 0 0 0 0 0 0 ...
## $ affect : int 0 0 0 0 0 0 0 0 0 0 ...
## $ affiliate : int 0 0 0 0 0 0 0 0 0 0 ...
## $ afghanistan : int 0 0 0 0 0 0 0 0 0 0 ...
## $ afraid : int 0 0 0 0 0 0 0 0 0 0 ...
## $ afternoon : int 0 0 0 0 0 0 0 0 0 0 ...
## $ age : int 0 0 0 0 0 0 0 0 0 0 ...
## $ agencies : int 0 0 0 0 0 0 0 0 0 0 ...
## $ agenda : int 0 0 0 0 0 0 0 0 0 0 ...
## $ agree : int 0 0 0 0 0 0 0 0 0 0 ...
## $ ahead : int 0 0 0 0 0 0 0 0 0 0 ...
## $ aid : int 0 0 0 1 1 0 0 0 0 0 ...
## $ aim : int 0 0 0 0 0 0 0 0 0 0 ...
## $ air : int 0 0 0 0 0 0 0 0 0 0 ...
## $ alaska : int 0 0 0 0 0 0 0 0 0 0 ...
## $ allegation : int 0 0 0 0 0 0 0 0 0 0 ...
## $ allegory : int 0 0 0 0 0 0 0 0 0 0 ...
## $ allied : int 0 0 0 0 0 0 0 0 0 0 ...
## $ allowed : int 0 0 0 0 0 0 0 0 0 0 ...
## $ alternative : int 0 0 0 0 0 0 0 0 0 0 ...
## $ altsite : int 0 0 1 0 0 0 0 0 0 0 ...
## $ amazing : int 0 0 0 0 0 0 0 0 0 0 ...
## $ amendment : int 0 0 0 0 0 0 0 0 0 0 ...
## $ america : int 0 0 0 0 0 0 0 0 0 0 ...
## $ american : int 0 0 0 0 1 0 0 0 0 0 ...
## $ amount : int 0 0 0 0 0 0 0 0 0 0 ...
## $ amp : int 0 0 0 0 0 0 0 0 0 0 ...
## $ analysis : int 0 0 0 0 1 0 0 0 0 0 ...
## $ analyst : int 0 0 0 0 0 0 0 0 0 0 ...
## $ anecdotal : int 0 0 1 0 0 0 0 0 0 0 ...
## $ anger : int 0 0 0 0 0 0 0 0 0 0 ...
## $ angry : int 0 0 0 0 0 0 0 0 0 0 ...
## $ announce : int 0 0 0 0 0 0 0 0 0 0 ...
## $ annual : int 0 0 0 0 0 0 0 0 0 0 ...
## $ answer : int 0 0 0 1 0 0 1 0 0 0 ...
## $ apologies : int 0 0 0 0 0 0 0 0 0 0 ...
## $ apparent : int 0 0 0 0 0 0 0 0 0 0 ...
## $ appeal : int 0 0 0 0 0 0 0 0 0 0 ...
## $ appearance : int 0 0 0 0 0 0 0 0 0 0 ...
## $ applied : int 0 0 0 0 0 0 0 0 0 0 ...
## $ appointed : int 0 0 0 0 0 0 0 0 0 0 ...
## $ approach : int 0 0 0 0 0 0 1 0 0 0 ...
## $ approval : int 1 0 0 0 1 0 0 0 1 0 ...
## $ apr : int 0 0 0 0 0 0 0 0 0 0 ...
## $ april : int 0 0 0 0 0 0 0 0 0 0 ...
## $ arab : int 0 0 0 0 0 0 0 0 0 0 ...
## $ area : int 0 0 0 0 0 0 0 0 0 0 ...
## $ arent : int 0 0 0 0 0 0 0 0 0 0 ...
## $ arg : int 0 0 0 0 0 0 0 0 0 0 ...
## $ argue : int 0 0 0 0 0 0 0 0 0 0 ...
## $ argument : int 0 0 0 0 0 0 0 0 0 0 ...
## $ arizona : int 0 0 0 0 0 0 0 0 0 0 ...
## $ arm : int 0 0 0 0 0 0 0 0 0 0 ...
## $ armstrong : int 0 0 0 0 0 0 0 0 0 0 ...
## $ army : int 0 0 0 0 0 0 0 0 0 0 ...
## $ arrest : int 0 0 0 0 0 0 0 0 0 0 ...
## $ arrive : int 0 0 0 0 0 0 0 0 0 0 ...
## $ article : int 0 0 0 0 0 0 0 0 0 0 ...
## $ asap : int 0 0 1 0 0 0 0 0 0 0 ...
## $ asked : int 0 0 0 0 0 0 0 0 0 0 ...
## $ ass : int 0 0 0 0 0 0 0 0 0 0 ...
## $ assess : int 0 0 0 0 0 0 0 0 0 0 ...
## $ assist : int 0 0 0 0 0 0 0 0 0 0 ...
## $ associate : int 0 0 0 0 0 0 0 0 0 0 ...
## $ assume : int 0 0 0 0 0 0 0 0 0 0 ...
## $ atlanta : int 0 0 1 0 0 0 0 0 0 0 ...
## $ atrios : int 0 0 0 0 0 0 1 0 0 0 ...
## [list output truncated]
kosDist <- dist(kos, method = "euclidean")
kosHierClust <- hclust(kosDist, metho = "ward.D")
plot(kosHierClust)
Based on the dendrogram, it seems two or three clusters would be a good choice. But in this problem, we are trying to cluster news articles or blog posts into groups. This can be used to show readers categories to choose from when trying to decide what to read. So we need to have something more than two or three. Let’s pick 7 clusters.
kosClusters <- cutree(kosHierClust, k = 7)
Now, we don’t really want to run tapply on every single variable when we have over 1,000 different variables. Let’s instead use the subset function to subset our data by cluster. Create 7 new datasets, each containing the observations from one of the clusters.
cluster1 <- subset(kos, kosClusters == 1)
cluster2 <- subset(kos, kosClusters == 2)
cluster3 <- subset(kos, kosClusters == 3)
cluster4 <- subset(kos, kosClusters == 4)
cluster5 <- subset(kos, kosClusters == 5)
cluster6 <- subset(kos, kosClusters == 6)
cluster7 <- subset(kos, kosClusters == 7)
To look at the frequency of each word in each cluster, we use the colMeans command. We will look at the top 6 words in each cluster
tail(sort(colMeans(cluster1)))
## state republican poll democrat kerry bush
## 0.7575039 0.7590837 0.9036335 0.9194313 1.0624013 1.7053712
tail(sort(colMeans(cluster2)))
## bush democrat challenge vote poll november
## 2.847352 2.850467 4.096573 4.398754 4.847352 10.339564
tail(sort(colMeans(cluster3)))
## elect parties state republican democrat bush
## 1.647059 1.665775 2.320856 2.524064 3.823529 4.406417
tail(sort(colMeans(cluster4)))
## campaign voter presided poll bush kerry
## 1.431655 1.539568 1.625899 3.589928 7.834532 8.438849
tail(sort(colMeans(cluster5)))
## american presided administration war iraq
## 1.090909 1.120393 1.230958 1.776413 2.427518
## bush
## 3.941032
tail(sort(colMeans(cluster6)))
## race bush kerry elect democrat poll
## 0.4579832 0.4887955 0.5168067 0.5350140 0.5644258 0.5812325
tail(sort(colMeans(cluster7)))
## democrat clark edward poll kerry dean
## 2.148325 2.497608 2.607656 2.765550 3.952153 5.803828
set.seed(1000)
KMC <- kmeans(kos, centers = 7)
str(KMC)
## List of 9
## $ cluster : int [1:3430] 4 4 6 4 1 4 7 4 4 4 ...
## $ centers : num [1:7, 1:1545] 0.0342 0.0556 0.0253 0.0136 0.0491 ...
## ..- attr(*, "dimnames")=List of 2
## .. ..$ : chr [1:7] "1" "2" "3" "4" ...
## .. ..$ : chr [1:1545] "abandon" "abc" "ability" "abortion" ...
## $ totss : num 896461
## $ withinss : num [1:7] 76583 52693 99504 258927 88632 ...
## $ tot.withinss: num 730632
## $ betweenss : num 165829
## $ size : int [1:7] 146 144 277 2063 163 329 308
## $ iter : int 7
## $ ifault : int 0
## - attr(*, "class")= chr "kmeans"
Subset data into 7 clusters
Kcluster1 <- subset(kos, KMC$cluster == 1)
Kcluster2 <- subset(kos, KMC$cluster == 2)
Kcluster3 <- subset(kos, KMC$cluster == 3)
Kcluster4 <- subset(kos, KMC$cluster == 4)
Kcluster5 <- subset(kos, KMC$cluster == 5)
Kcluster6 <- subset(kos, KMC$cluster == 6)
Kcluster7 <- subset(kos, KMC$cluster == 7)
Output the six most frequent words in each cluster, for each of the k-means clusters.
tail(sort(colMeans(Kcluster1)))
## state iraq kerry administration presided
## 1.609589 1.616438 1.636986 2.664384 2.767123
## bush
## 11.431507
tail(sort(colMeans(Kcluster2)))
## primaries democrat edward clark kerry dean
## 2.319444 2.694444 2.798611 3.090278 4.979167 8.277778
tail(sort(colMeans(Kcluster3)))
## administration iraqi american bush war
## 1.389892 1.610108 1.685921 2.610108 3.025271
## iraq
## 4.093863
tail(sort(colMeans(Kcluster4)))
## elect republican kerry poll democrat bush
## 0.6010664 0.6175473 0.6495395 0.7474552 0.7891420 1.1473582
tail(sort(colMeans(Kcluster5)))
## race senate state parties republican democrat
## 2.484663 2.650307 3.521472 3.619632 4.638037 6.993865
tail(sort(colMeans(Kcluster6)))
## democrat bush challenge vote poll november
## 2.899696 2.960486 4.121581 4.446809 4.872340 10.370821
tail(sort(colMeans(Kcluster7)))
## presided voter campaign poll bush kerry
## 1.324675 1.334416 1.383117 2.788961 5.970779 6.480519
Use the table function to compare the cluster assignment of hierarchical clustering to the cluster assignment of k-means clustering.
Which Hierarchical Cluster best corresponds to K-Means Cluster 2?
table(KMC$cluster)
##
## 1 2 3 4 5 6 7
## 146 144 277 2063 163 329 308
table(kosClusters)
## kosClusters
## 1 2 3 4 5 6 7
## 1266 321 374 139 407 714 209
table(kosClusters, KMC$cluster)
##
## kosClusters 1 2 3 4 5 6 7
## 1 3 11 64 1045 32 0 111
## 2 0 0 0 0 0 320 1
## 3 85 10 42 79 126 8 24
## 4 10 5 0 0 1 0 123
## 5 48 0 171 145 3 1 39
## 6 0 2 0 712 0 0 0
## 7 0 116 0 82 1 0 10
we read that 116 (80.6%) of the observations in K-Means Cluster 2 also fall in Hierarchical Cluster 7.