具体内容来源于《数据可视化之美》的11章的维基百科可视化
数据
编辑历史(见图1),包含了指向所有前期版本的完整文本的链接的列表,同时提供了关于作者的信息、编辑时间以及评论。评论是可选的,它是给作者一个机会来解释本次编辑的目的。但是编辑时间和作者这两个信息是自动写到日志中的。如果某个编辑没有登录到系统,则记录该用户的IP地址来取代其用户名。
维基百科的编辑历史在2003年已经很大了,而到今天则更是达到了巨大的地步。当然,不同文章所做的编辑次数很不相同。当我们最初开始梳理肘,关于“Microsoft”的那篇文章共有198个版本(总共是6.3MB的文本),而关于“Cat”的那篇文章却只有54个版本。最开始,我们写了一个程序直接从该网站上下载编辑历史。但是,我们很快意识到这是一种很不友好的方式,因为它会给维基百科的服务器带来压力.因而,我们使用了维基百科网上免费提供的一份大文件.如果你想对其中的任何数据进行可视化,最好的方式就是自己下载一份该文件快照的最新版[参考:http://en.wikipedia.org/wiki/Wikipedia:Snapshots]。
图1
历史流:对编辑历史进行可视化
维基百科可以显示几组不同版本的差别,突出所增加和删除的文字,但是我们希望能够看到一篇文章随着时间推移的所有编辑的总体概览。为了达到这个目的,我们引入了一种新的称为“历史流” (history flow)的可视化技术。
即使我们手中有数据,我们也无法直接开始编写图形代码。我们需要自己计算出相邻的文章之间的差别。找出两篇文章的差异出现的位置以及内容间的具体区别,这看起来像个日常的运作程序,类似于普通用户使用的应用程序Microsoft Word以及开发者们使用的类似版本控制软件这样的开发者工具。但是这种做也实际上比看起来更灵活一些,虽然(可能也正因为)这个问题已经被人们研究了很长时间,最终发现不存在最佳的方式来实现这个功能。
目前的挑战在于不存在唯一的某种方式能够描述文本之间的区别。举个例子,考虑以下两个句子:
- “行动迅速的棕色狐狸跳过了大柱子(The quick brown fox jumped over the big post)。”
- “大的棕色的狐狸跳过了毡壶( The big brown fox jumped over the clay pots )。”
大多数算也能够告诉你和第一个句子相比,在第二个句子中,单词quick (迅速的)被删除了,而单词clay (瓷)是新增的.但是单词“大”呢?它是否是在一个地方被插入了而在另一个地方删除了,或者只是简单地从结尾移到开头?类似地,单词post (柱子)是否是被删掉并由单词pots (壶)代替,或者“post”这个单词的字母序被重新组合成了“ pots”?
不同的解释在逻辑上都是一致的,因此目标是选择一个在特定上下文中有意义的算法。对我们而言,我们认为编辑可能改变一块文本位置——把一个单词或一个句子从文本的一个地方移动到另一个地方——但是不太可能通过改变字母位置来改变个别单词。因此,我们选择PaulHeckel提出的算法,虽然该算法能把单词作为原子单元进行处理,它使我们能够追踪大段落的位置变换。该算法的输出是两个序列之间的一组对应,其形式是“文件A中的第5个单词和文件B中的第127个单词对应。”
Heckel的算法实现很简单,我们很快就一切准备就绪并开始分析。对于每一篇文章,我们有每个版本的文本,还有不同版本之间的“对应”关系.但是应该如何对不同版本进行展示?首先,因为这是基于时间的数据,使用x轴表示次序是有意义的,把第一个版本放在左边,第二个版本放在右边等。这种方式适用于查看一篇文章的编辑历史,因为文档中每个位置都像一条“河流”上的不同“湍流”。刚开始,x轴只是表示序列化信息,每个版本是一个坐标点,不同坐标点之间的像素数相同,然后我们加入一个根据编辑时间的设置版本间距离的选项,因此间隔很短的版本之间在空间距离上也很紧密。这两种查看数据的方式后来都被证明是很有用的。
接下来,我们需要对文档位置和段落之间的对应关系进行编码。我们决定使用竖线描绘版本,其长度与每个版本的长度相对应。实际上,y轴对每个版本内部的文档位置进行编码。一且我们做出这个决定,就很容易知道应该如何在一个版本到另一个版本间画线来描述匹配关系了,如图2所示(它是我们在开始编码前在白板上手工描绘的一个素描)。
实现代码:(参考了lscj网站的某位学霸的)
opar <- par(no.readonly=TRUE)
# 读数据文件
a <- read.table(file("data3.txt"), header = TRUE, sep=":")
men <- levels(a$name) # 版本发布人
total <- max(a$wc) # 最大版本内容
# 画布要素全局设置
par(lwd=5,bg="#3D3D3DFF")
col1 <- c("#5FD213FF","#FD5800FF","#129DF0FF")
cst <- 20 # 设定版本颜色渐变的步长
palette(col1)
debug0 <- F # 控制是否输出关联区域绘图
# 根据版本内容设定坐标系的大小
h <- max(total + 1)
w <- max(a$no.)
plot(0,0, type="n", col=1, xlim=c(-0.5, w+0.3), ylim=c(0,h+1))
# 对第一次版本处理
a2 <- strsplit(as.character(a$cont[1]),";")
x <- c(1,1)
y <- c(h, h - as.numeric(a2[[1]][2]))
lines(x,y, col= which(men == a2[[1]][1]))
text(1, h+1 ,paste("version",1),col="white")
# 开始绘图
for(i in 2:w){
# 分解数据:其中a21表示版本人,a22表示版本内容大小,a23表示版本的历史修改步长
# 技巧:a23中(0,1)2个数字在程序中做了特殊处理 0 表示删除,1 表示新增
a2 <- strsplit(as.character(a$cont[i]),";")
a21 <- strsplit(a2[[1]][1],",")[[1]]
a22 <- as.numeric(strsplit(a2[[1]][2],",")[[1]])
a23 <- as.integer(strsplit(a2[[1]][3],",")[[1]])
hy1 <- 0
hy2 <- h
py1 <- 0
py2 <- h
text(i, h+1 ,paste("version",i),col="white")
j <- 1
while(j <= length(a21)){
if(a23[j] == 0){
py2 <- py2 - a22[j]
}else{
x <- c(i,i)
y <- c(hy2, hy1 <- hy2 - a22[j])
lines(x,y, col= which(men == a21[j]))
py1 <- py2 - a22[j]
if(a23[j] > 1 && debug0){
xx <- c(i,i,i-1,i-1,i)
yy <- c(hy2 ,hy1 ,py1 ,py2 ,hy2)
col2 <- col1[which(men == a21[j])]
col3 <- as.integer(paste('0x',substr(col2, 8,9),sep='')) - cst*i
col2 <- paste(substr(col2, 1,7),toupper(as.hexmode(col3)),sep="")
polygon(xx, yy, col=col2, border=NA)
py2 <- py1
}
hy2 <- hy1
}
j <- j + 1
}
}
# 输出版本发布人姓名
x <- -0.5
y <- 5
h <- 1.5
for(m in men){
text(x,y,m,col=grep(m, men),adj = c(0, NA))
y <- y - h
}
par(opar)
要想从上图变到下图,将代码中debug0 <- F
改为debug0 <- T
即可。