基于相似性聚类

很多时候,我们想了解一群人中的一个成员与其他成员之间有多么相似。例如,假设我们是一家品牌营销公司,刚刚完成了一份有潜力新品牌的研究调查问卷。在这份调查问卷中,我们向一群人展示了新品牌的几个特征,并且要求他们对这个新品牌的每个特征按五分制打分。同时也收集了目标人群的社会经济特征,例如:年龄、性别、种族、住址的邮编以及大概的年收入。

通过这份调查问卷,我们想搞清楚品牌如何吸引不同社会经济特征的人群。最重要的是,我们想要知道这个品牌是否有很大的吸引力。换个角度想这个问题,我们想看看那些最喜欢这个品牌特征的人们,是否有多种多样的社会经济特征。实现这一点的一种方法就是对问卷受访者的聚类结果进行可视化。然后就可以使用各种各样的视觉线索,识别出不同社会经济特征的成员。也就是说,我们想要看到大量不同性别、不同种族以及不同收入的人聚类到一起。

同样,我们想要使用这些知识来看看,相近的人群如何基于品牌吸引力而聚类到一起的。我们也想看看一个聚类中有多少人,或者它与其他聚类的距离有多远。这也能告诉我们这个品牌的什么特征吸引了不同社会经济特征的人群。

在提出这些问题时,我们使用这样的名词,如“近”和“远”,它们本身都是说明距离概念的词。因此,为了使聚类结果之间的距离更形象化,我们需要引入一些个体聚集的空间概念。

对不同的观测记录,如何理解用距离的概念来阐明它们之间的相似性和相异性。这需要针对分析数据定义一些不同类型的距离矩阵。例如,在假设的品牌市场情形中,我们可以利用调查规模的顺序性以及一种非常直接的方式发现不同受访者之间的距离:简单计算绝对差异。

可是,仅仅计算这些距离还不够。下面我们介绍一种叫做多维定标(multidimensional scaling,MDS)的技术。该技术的目的是基于观察值之间的距离度量进行聚类。通过MDS,我们可以只使用所有点之间的一个距离度量,就能将数据进行可视化。

距离度量与多维定标简介

在正式开始介绍之前,假设已有一份非常简单的数据,这份数据中有四个用户,以及他们对六个产品的评分。每个用户按要求对每个产品给出一个“拇指向上”(1)或者“拇指向下”(-1)的评价,如果他们对某个产品没有任何意见,也可忽略这个产品(0)。有很多评价系统采用这种方式,包括YouTube等。我们想使用这个评分数据度量每个用户和其他用户之间有多大的相似性。

在这个简单的例子中,我们创建一个4*6的矩阵A,行表示用户,列表示产品。元素aij表示用户 i 给产品 j 的评价。

set.seed(851982)
ex.matrix<-matrix(sample(c(-1,0,1),24,replace=TRUE),nrow=4,ncol=6)
row.names(ex.matrix)<-c('A','B','C','D')
colnames(ex.matrix)<-c('P1','P2','P3','P4','P5','P6')

row.names

P1

P2

P3

P4

P5

P6

1

A

0

-1

0

-1

0

0

2

B

-1

0

1

1

1

0

3

C

0

0

0

1

-1

1

4

D

1

0

1

-1

0

0

矩阵A将用户和产品关联起来,我们需要把这矩阵转化成用户与用户关联的矩阵B:(B=AAT)

ex.matrix<-ex.matrix %*% t(ex.matrix)

row.names

A

B

C

D

1

A

2

-1

-1

1

2

B

-1

4

0

-1

3

C

-1

0

3

-1

4

D

1

-1

-1

3

非主对角线元素的正值越大,则两个用户的评价分就越一致;同样,负值越小,则两个用户的评价分就越不一致。因为我们最初的矩阵中的元素是随机的,所以不同用户之间的差异很小。对角线元素的值只是反映了每个用户对多少个产品进行了评分。

现在,我们有了关于用户之间差异的摘要,在一定程度上这是有用的。例如,用户A和用户D都给了产品4反对票;可是,用户D喜欢产品1和产品3,而用户A根本没有对他们进行评价。因此,从他们给出投票信息的那些产品角度来看,我们可以说这两个用户是相似的,因此我们有一个值为1的元素对应他们的关系。很遗憾,这个结果传达的信息是有限的,因为我们只能对用户共有评分记录给出一些解释。但是我们想要的方法是可以把用户评分记录的差异化到更丰富的表达程度。

为此,我们引入欧氏距离(Euclidean distance)的概念:

我们想要基于上面的矩阵的乘积所定义的整体相似性和差异性度量,来计算所有用户之间的欧氏距离。为了实现这一点,我们将把每个用户的所有评分作为一个向量。用户A和用户B的欧氏距离:

sqrt(sum((ex.matrix[1,]-ex.matrix[4,])^2))
[1] 2.236068

任意用户之间的距离:

ex.dist<-dist(ex.matrix)
ex.dist
         A        B        C
B 6.244998
C 5.477226 5.000000
D 2.236068 6.782330 6.082763#dist()函数返回一个关于距离的矩阵(可设置参数upper=TRUE覆盖只显示下三角的默认值)

可视化表示:

ex.mds<-cmdscale(ex.dist)
plot(ex.mds,type='n')
text(ex.mds,c('A','B','C','D'))

用户A和用户D确实在图的中间靠右的位置被聚类到了一起。可是,用户B和用户C根本就没有形成聚类。从我们的数据来看用户A和用户D具有某种程度的相似口味。想要深入理解请查阅MDS算法

如何对美国参议员做聚类

届国会是历史中意识形态两极化最严重的一届。在白宫和参议院中,最保守的民主党都要比最开明的共和党还要开明。如果把国会“中心”定义为两党重叠的部分,那么这个中心已经消失了。

 

                                                                                                                                                                                                                                                                               ——William A.Galston

使用MDS对参议员聚类进行可视化,以此观察两党成员是否有混合在一起的情况。可是,在做这些事情之前,我们需要一个参议员之间距离的衡量标准。幸运的是,我们可以使用立法者的公开记录创建一个合理的距离度量。我们可以使用记名投票记录来看一个立法者是支持还是反对一项提案。就像前面例子中用户给出的“拇指向上”或者“拇指向下”的评价一样,立法者使用“赞同”或者“反对”对法案进行投票。数据下载地址:http://www.voteview.com/,它是一个存放美国记名投票数据的仓库。

R code

library('foreign')
library('ggplot2')

data.dir <- file.path("data", "roll_call")
data.files <- list.files(data.dir)

data.files

#[1] "sen101kh.dta" "sen102kh.dta"
#[3] "sen103kh.dta" "sen104kh.dta"
#[5] "sen105kh.dta" "sen106kh.dta"
#[7] "sen107kh.dta" "sen108kh_7.dta"
#[9] "sen109kh.dta" "sen110kh_2008.dta"
#[11] "sen111kh.dta"

# Eighth code snippet
# Add all roll call vote data frames to a single list
rollcall.data <- lapply(data.files,
                        function(f)
                        {
                          read.dta(file.path(data.dir, f), convert.factors = FALSE)
                        })

# Ninth code snippet
dim(rollcall.data[[1]])
#[1] 103 647

head(rollcall.data[[1]])
#cong id state dist lstate party eh1 eh2 name V1 V2 V3 ... V638
#1 101 99908 99 0 USA 200 0 0 BUSH 1 1 1 ... 1
#2 101 14659 41 0 ALABAMA 100 0 1 SHELBY, RIC 1 1 1 ... 6
#3 101 14705 41 0 ALABAMA 100 0 1 HEFLIN, HOW 1 1 1 ... 6
#4 101 12109 81 0 ALASKA 200 0 1 STEVENS, TH 1 1 1 ... 1
#5 101 14907 81 0 ALASKA 200 0 1 MURKOWSKI, 1 1 1 ... 6
#6 101 14502 61 0 ARIZONA 100 0 1 DECONCINI, 1 1 1 ... 6

# Tenth code snippet
# This function takes a single data frame of roll call votes and returns a
# Senator-by-vote matrix.
rollcall.simplified <- function(df)
{
  no.pres <- subset(df, state < 99)

  for(i in 10:ncol(no.pres))
  {
    no.pres[,i] <- ifelse(no.pres[,i] > 6, 0, no.pres[,i])
    no.pres[,i] <- ifelse(no.pres[,i] > 0 & no.pres[,i] < 4, 1, no.pres[,i])
    no.pres[,i] <- ifelse(no.pres[,i] > 1, -1, no.pres[,i])
  }

  return(as.matrix(no.pres[,10:ncol(no.pres)]))
}

rollcall.simple <- lapply(rollcall.data, rollcall.simplified)

# Eleventh code snippet
# Multiply the matrix by its transpose to get Senator-to-Senator tranformation,
# and calculate the Euclidan distance between each Senator.
rollcall.dist <- lapply(rollcall.simple, function(m) dist(m %*% t(m)))

# Do the multidimensional scaling
rollcall.mds <- lapply(rollcall.dist,
                       function(d) as.data.frame((cmdscale(d, k = 2)) * -1))

# Twelfth code snippet
# Add identification information about Senators back into MDS data frames
congresses <- 101:111

for(i in 1:length(rollcall.mds))
{
  names(rollcall.mds[[i]]) <- c("x", "y")

  congress <- subset(rollcall.data[[i]], state < 99)

  congress.names <- sapply(as.character(congress$name),
                           function(n) strsplit(n, "[, ]")[[1]][1])

  rollcall.mds[[i]] <- transform(rollcall.mds[[i]],
                                 name = congress.names,
                                 party = as.factor(congress$party),
                                 congress = congresses[i])
}

head(rollcall.mds[[1]])

#x y name party congress
#2 -11.44068 293.0001 SHELBY 100 101
#3 283.82580 132.4369 HEFLIN 100 101
#4 885.85564 430.3451 STEVENS 200 101
#5 1714.21327 185.5262 MURKOWSKI 200 101
#6 -843.58421 220.1038 DECONCINI 100 101
#7 1594.50998 225.8166 MCCAIN 200 101

# Thirteenth code snippet
# Create a plot of just the 110th Congress
cong.110 <- rollcall.mds[[9]]

base.110 <- ggplot(cong.110, aes(x = x, y = y)) +
  scale_size(range = c(2,2), guide = 'none') +
  scale_alpha(guide = 'none') +
  theme_bw() +
  theme(axis.ticks = element_blank(),
        axis.text.x = element_blank(),
        axis.text.y = element_blank(),
        panel.grid.major = element_blank()) +
  ggtitle("Roll Call Vote MDS Clustering for 110th U.S. Senate") +
  xlab("") +
  ylab("") +
  scale_shape(name = "Party", breaks = c("100", "200", "328"),
              labels = c("Dem.", "Rep.", "Ind."), solid = FALSE) +
  scale_color_manual(name = "Party", values = c("100" = "black",
                                                "200" = "dimgray",
                                                "328"="grey"),
                     breaks = c("100", "200", "328"),
                     labels = c("Dem.", "Rep.", "Ind."))

print(base.110 + geom_point(aes(shape = party,
                                alpha = 0.75,
                                size = 2)))
print(base.110 + geom_text(aes(color = party,
                               alpha = 0.75,
                               label = cong.110$name,
                               size = 2)))

# Fourteenth code snippet
# Create a single visualization of MDS for all Congresses on a grid
all.mds <- do.call(rbind, rollcall.mds)
all.plot <- ggplot(all.mds, aes(x = x, y = y)) +
  geom_point(aes(shape = party, alpha = 0.75, size = 2)) +
  scale_size(range = c(2, 2), guide = 'none') +
  scale_alpha(guide = 'none') +
  theme_bw() +
  theme(axis.ticks = element_blank(),
        axis.text.x = element_blank(),
        axis.text.y = element_blank(),
        panel.grid.major = element_blank()) +
  ggtitle("Roll Call Vote MDS Clustering for U.S. Senate (101st - 111th Congress)") +
       xlab("") +
       ylab("") +
       scale_shape(name = "Party",
                   breaks = c("100", "200", "328"),
                   labels = c("Dem.", "Rep.", "Ind."),
                   solid = FALSE) +
      facet_wrap(~ congress)

print(all.plot)

# This is the code omitted from the chapter.  This is used to create shnazy plots of everything!
for(i in 1:length(rollcall.mds))
{
  mds <- rollcall.mds[[i]]
  congress <- congresses[i]
  plot.title <- paste("Roll Call Vote MDS Clustering for ",
                      congress,
                      " U.S. Senate",
                      sep = "")

  # Build base plot
  mds.plot <- ggplot(mds, aes(x = x, y = y)) +
    scale_size(range = c(2, 2), guide = 'none') +
    scale_alpha(guide = 'none') +
    theme_bw() +
    theme(axis.ticks = element_blank(),
          axis.text.x = element_blank(),
          axis.text.y = element_blank(),
          panel.grid.major = element_blank()) +
    ggtitle(plot.title) +
    xlab("") +
    ylab("")

  # Build up point and text plots separately
  mds.point <- mds.plot + geom_point(aes(shape = party,
                                         alpha = 0.75,
                                         size = 2))
  mds.text <- mds.plot + geom_text(aes(color = party,
                                       alpha = 0.75,
                                       label = mds$name,
                                       size = 2))

  # Fix labels, shapes and colors
  if(length(levels(mds$party)) > 2)
  {
    mds.point <- mds.point + scale_shape(name = "Party",
                                         breaks = c("100", "200", "328"),
                                         labels = c("Dem.", "Rep.", "Ind."),
                                         solid = FALSE)
    mds.text <- mds.text + scale_color_manual(name = "Party",
                                              values = c("100" = "black",
                                                         "200" = "dimgray",
                                                         "328" = "gray"),
                                              breaks = c("100", "200", "328"),
                                              labels = c("Dem.", "Rep.", "Ind."))
  }
  else
  {
    mds.point <- mds.point + scale_shape(name = "Party",
                                         breaks = c("100", "200"),
                                         labels = c("Dem.", "Rep."),
                                         solid = FALSE)
    mds.text <- mds.text + scale_color_manual(name = "Party",
                                              values = c("100" = "black",
                                                         "200" = "dimgray"),
                                              breaks = c("100", "200"),
                                              labels = c("Dem.", "Rep."))
  }

  ggsave(plot = mds.point,
         filename = file.path('images',
                              'senate_plots',
                              paste(congress, "_point.pdf", sep = "")),
         width = 8,
         height = 5)
  ggsave(plot = mds.text,
         filename = file.path('images',
                              'senate_plots',
                              paste(congress, "_names.pdf", sep = "")),
         width = 8,
         height = 5)
}

                                                                                                       

从这些结果可以看出,美国参议院实际上和过去一样具有党派性。大体上来说,在每一届国会中我们只能看到大量的三角形和圆形分别聚集在一起,而只有少数异常数据点。你也许会说,第101届和第102届国会两级分化不那么严重。这是坐标轴刻度造成的结果。这不意味这这两届国会的两极分化更轻。因为在一张图中对他们进行可视化,必须在不同的面板中使用相同的刻度,这导致有些图看上去更拥挤,而有的图铺得更开。

R语言学习笔记 之 可视化地研究参议员相似性的更多相关文章

  1. R语言学习笔记︱Echarts与R的可视化包——地区地图

    笔者寄语:感谢CDA DSC训练营周末上完课,常老师.曾柯老师加了小课,讲了echart与R结合的函数包recharts的一些基本用法.通过对比谢益辉老师GitHub的说明文档,曾柯老师极大地简化了一 ...

  2. R语言学习笔记之: 论如何正确把EXCEL文件喂给R处理

    博客总目录:http://www.cnblogs.com/weibaar/p/4507801.html ---- 前言: 应用背景兼吐槽 继续延续之前每个月至少一次更新博客,归纳总结学习心得好习惯. ...

  3. R语言学习笔记:基础知识

    1.数据分析金字塔 2.[文件]-[改变工作目录] 3.[程序包]-[设定CRAN镜像] [程序包]-[安装程序包] 4.向量 c() 例:x=c(2,5,8,3,5,9) 例:x=c(1:100) ...

  4. R语言学习笔记(二)

    今天主要学习了两个统计学的基本概念:峰度和偏度,并且用R语言来描述. > vars<-c("mpg","hp","wt") &g ...

  5. R语言学习笔记:小试R环境

    买了三本R语言的书,同时使用来学习R语言,粗略翻下来感觉第一本最好: <R语言编程艺术>The Art of R Programming <R语言初学者使用>A Beginne ...

  6. R语言学习笔记——C#中如何使用R语言setwd()函数

    在R语言编译器中,设置当前工作文件夹可以用setwd()函数. > setwd("e://桌面//")> setwd("e:\桌面\")> s ...

  7. R语言学习笔记-机器学习1-3章

    在折腾完爬虫还有一些感兴趣的内容后,我最近在看用R语言进行简单机器学习的知识,主要参考了<机器学习-实用案例解析>这本书. 这本书是目前市面少有的,纯粹以R语言为基础讲解的机器学习知识,书 ...

  8. R语言学习笔记(一)

    1.不同的行业对数据集(即表格)的行和列称谓不同,统计学家称其为观测(observation)和变量(variable): 2.R语言存储数据的结构: ①向量:类似于C语言里的一位数组,执行组合功能的 ...

  9. R语言学习笔记

    向量化的函数 向量化的函数 ifelse/which/where/any/all/cumsum/cumprod/对于矩阵而言,可以使用rowSums/colSums.对于“穷举所有组合问题" ...

随机推荐

  1. PHP和CS的引用传值

    PHP的引用传值 function change_value($num){ $num+=2; } $age = 3; change_value(&$age); echo $age; CS的引用 ...

  2. The Art of Computer Programming

    <计算机程序设计艺术>即<The Art of Computer Programming>是计算机领域里颠峰级的里程碑,加上国外人士对它的推崇,所以提起它的大名简直就象法律书籍 ...

  3. 每天一道LeetCode--409 .Longest Palindrome

    Given a string which consists of lowercase or uppercase letters, find the length of the longest pali ...

  4. Android ListView动态改变Item高度

    在adapter的getView方法中进行设置,代码如下 @Override public View getView(int position, View convertView, ViewGroup ...

  5. Part 14 ng hide and ng show in AngularJS

    ng-hide and ng-show directives are used to control the visibility of the HTML elements. Let us under ...

  6. Part 59 to 60 Difference between Convert ToString and ToString,String and StringBuilder

    Part 59 Difference between Convert ToString and ToString Part 60 Difference between String and Strin ...

  7. Elementary os的安装

      1.         使用安装文件进行数据读取 2.         进入安装界面 3.         选择语言并进行安装(可以先试用) 4.         选择继续(可以勾选两个选项,意思是 ...

  8. asp.net判断访问者是否来自移动端

    主要就是通过客户端传递的User-agent来判断访问网站的客户端是PC还是手机. .NET中就是Request.ServerVariables["HTTP_USER_AGENT" ...

  9. C++ 单链表基本操作

    链表一直是面试的高频题,今天先总结一下单链表的使用,下节再总结双向链表的.本文主要有单链表的创建.插入.删除节点等. 1.概念 单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数 ...

  10. 《gpg文件加密的使用》RHEL6

    甲端: 首先是要生成一对密钥: 提示是否要生成2048个字节的密钥对:   下面都是生成密钥对时的步骤: 按“o”键开始生成密钥对: 提示要我给密钥对加个密码: 输入2次 之后密钥对的字符需要我按键盘 ...