手把手:一张图看清编程语言发展史,你也能用Python画出来!

大数据文摘作品
作者:peter gleeson
编译:周佳玉、丁慧、叶一、小鱼、钱天培
今天文摘菌要教大家制作一张编程语言的关系网络图。如果不知道什么是关系网络图,可以点击下方链接先来看一下最终成果:
http://programming-languages.herokuapp/#,
我们可以在这里看到从过去到现在的250多种编程语言之间的“设计影响”的关系,下面是该演示的截图:
接下来,就让我们一起来学做这个关系网络图吧!
在当今的超连接世界,网络在现代生活中无处不在。举个栗子,文摘菌的周末这样开启——通过北京的交通网络进城,然后去最喜欢的咖啡店的一家分店,并将笔记本连上他们的wi-fi。接下来,登录各种常用的社交网站。
众所周知,在过去几十年来最有影响力的公司中,有一部分是因为网络的力量而获得成功。
facebook、twitter、instagram、linkedin以及一些其他的社交媒体平台都依赖社交网络的小世界特性。这使他们能有效地将用户彼此(以及和广告商)之间连接起来。
谷歌目前的成功主要归因于他们早期在搜索引擎市场上的主导地位——部分原因是他们有能力通过他们的page rank网络算法来返回相关的结果。
亚马逊的高效配送网络使他们能够在一些主要城市提供当天发货。
网络算法在人工智能和机器学习等领域也是非常重要的。神经网络领域的研究非常热门。计算机视觉中许多必不可少的特征检测算法,在很大程度上也是依赖于使用网络来对图像的不同部分进行建模。
网络模型也可以解释大量的科学现象,包括有量子力学、生化途径以及生态和社会经济系统等。
那么,鉴于它们不可否认的重要性,我们应该如何更好地理解网络及其属性呢?
网络的数学研究被称为“图论”,是数学中较易理解的分支之一。 本文会介绍简单的网络知识,即便你没有相关背景知识也能轻松学会。
此外,我们将使用python 3.x和一款非常棒的开源软件gephi,通过关系网络将过去和现在的一系列编程语言的网络可视化联系起来。
首先,究竟什么是网络呢?
其实上面文摘菌举的栗子已经给了一些线索。交通网络由目的和路径的连接组成。社交网络通过个人和个人之间的关系进行连接。google的搜索引擎算法通过查看有哪些页面链接到其他页面,来评估不同网页的“顺序”。
更一般地说,网络是可以用节点和边描述的任何系统,或者通俗来讲,就是我们所说的“点和线”。
边连接节点(语言)的例子(该网络表示了编程语言相互影响的关系)
有些系统以这种方式建立网络比较容易。社交网络也许是最明显的例子。计算机文件系统则是另一种方式——文件夹和文件通过其“父”和“子”关系创建连接。
但是,网络的真正威力其实在于,许多系统都可以从网络的角度来建模,即使这起初并不明显。
代表网络
我们应该如何将点和线的图片转换成我们可以压缩的数字信号呢?
其中有一个解决方案是绘制一个邻接矩阵来表示我们的网络。
如果你不熟悉矩阵这个概念,这听起来可能有点吓人,但不要害怕。 把它们想象成可以一次执行许多计算的数字网格就好。下面是一个简单的例子:
在这个矩阵中,每个行和列的交集都是0或1,这取决于各个语言是否被链接。你也可以根据上面的插图观察到!
对于要解决的大多数问题而言,矩阵是以数学方式表示网络的好方法。然而从计算的角度来看,它有时可能会有点麻烦。
例如,即使节点数量相对较少(比如说有1000个),矩阵中的元素数目也会大得多(例如,1000^2 = 1,000,000)。
许多现实世界的系统会产生稀疏网络,在这些网络中,大多数节点只能连接其他所有节点中的一小部分。
如果我们将计算机内存中1000个节点的稀疏网络表示为邻接矩阵,那么我们将在ram中存储1,000,000个字节的数据。大多数将会是零。这里有一个更为有效的方法可以解决这个问题。
这种方法是使用边列表来代替邻接矩阵。这些正是他们所说的,它们只是一个节点对相互链接的列表。
表示网络的另一种手段是邻接表,它列出了每个节点后面与它进行链接的节点。例如:
收集数据,建立连接
任何网络模型以及可视化的表现都取决于构建网络本身所用的数据质量好坏。除了确保数据是准确和完整的同时,我们也需要一种推断节点之间边的合理方法。
这是相当关键的一步,随后对网络进行的任何分析和推断都取决于“关联标准”的合理性。
例如,在社交网络分析中,你可能会根据人们是否在社交媒体上相互关联来创建人与人之间的联系。在分子生物学中,你可能会基于基因的共同表达建立连接。
通常,我们还可以给边分配权重,从而体现关系的“强度”。
例如,对于网上零售的情况,可以根据产品被同时购买的频率来计算权重。用高权重的边连接经常被同时购买的产品,用低权重的边连接偶尔被同时购买的产品。和偶尔被同时购买的产品相比,那些不会被同时购买的产品根本就不会被网络连接。
正如你想的那样,将节点彼此连接的方法有可能很复杂。
但是对于本教程,我们将使用更简单的方式连接编程语言。我们要依靠维基百科。
维基百科所取得的的成功证明了它的可靠性。文章写作的开源合作方法也应该保证一定程度的客观性。
而且,它的页面结构相对一致,使其成为试用网页抓取技术的便利场所。
另一个便利工具是覆盖面广泛的、有据可查的维基百科api,这使得信息检索更容易。接下来让我们一起开始吧。
第一步:安装gephi
gephi可在linux、mac和windows的环境下进行安装。
对于这个项目,我使用了lubuntu。如果你使用的是ubuntu / debian,那么你可以按照下面的步骤来启动和运行gephi。如果不是,那么安装过程也不会差太多。
下载最新版本的gephi到你的系统(在撰写本文时是v.0.9.1)。准备就绪后,你需要提取文件。
你可能需要检查你的java jre版本。gephi需要最新版本。在我刚刚安装的lubuntu上,我只安装了default-jre,下面的一切将建立在此基础上。
在你准备好进行安装之前还有一步。为了将图表导出到web,你可以使用gephi的sigma.js插件。
从gephi的菜单栏中选择“工具”选项,然后选择“插件”。
点击“可用插件”标签并选择“sigmaexporter”(我也安装了json导出器,因为它是另一个有用的插件)。
点击“安装”按钮,你将完成整个安装过程。安装结束后,你需要重新启动gephi。
第二步:编写python脚本
本教程将使用python 3.x以及一些模块来进行简化。使用pip模块安装程序,需运行一下命令。
现在,在一个新的目录中,创建一个名为script.py的文件,并在你最喜欢的代码编辑器/ ide中打开它。以下是主要逻辑的大纲:
首先,你需要有一个编程语言的列表。
接下来,通过该列表并检索维基百科相关文章的html。
从中提取出每种语言所影响的编程语言列表。这是我们连接节点的粗略标准。
同时,我们可以抓取一些关于每种语言的元数据。
最后,将收集的所有数据写入一个.csv文件。
完整的脚本在这里:
(https://gist.github/anonymous/2a6c841fe04ebc6d55acc259b4ac4f72)。
导入模块
在script.py中,首先导入一些模块。
准备好后——从创建一个节点的列表开始。这是wikipedia模块派上用场的地方。它使得访问维基百科api非常容易。
添加下面的代码:
保存并运行上面的脚本,将看到打印出“list of programming languages”维基百科文章中的所有链接。
另外,还需要手动检查自动收集的数据。快速浏览后我们可以发现,除了许多实际的编程语言之外,该脚本还提供了一些额外的链接。
如:可能会看到“list of markup languages”,“comparison of programming languages”等。
虽然gephi允许你移除不想包含的节点,但为了节省时间,还是让我们先进行一轮数据清洗。
这些代码定义了要从数据中移除的子字符串列表。运行该脚本时遍历数据,移除所有包含不需要的子字符串的元素。
在python语言中,完成这些只需要一行代码!
其他辅助函数
现在我们可以开始从wikipedia抓取数据并建立一个边列表(并收集所有元数据)。为了更简便,让我们首先定义一些函数。
抓取html
第一个函数使用beautifulsoup模块来获取每种语言的wikipedia页面的html。
这个函数使用urllib.request模块来获取“https://en.wikipedia.org/wiki/”+“编程语言”页面的html。
然后传给beautifulsoup,它将读取html并解析为一个可以用来搜索信息的对象。
接下来,使用find_all()方法抓取感兴趣的html元素。
下面,是每种编程语言文章顶部的汇总表。该如何识别呢?
最简单的方法是访问其中一个编程语言页面。在这里,可以简单地使用浏览器的开发工具来检查感兴趣的元素。汇总表有html标记和css类“infobox”和“vevent”,因此可以使用这些来标识html中的表格。
用参数指定它:
find_all()返回符合标准的所有元素列表。为了指定感兴趣的元素,需要添加索引[0]。如果函数执行成功,则返回table对象,否则,返回none。
在使用了自动数据收集程序的情况下,全面的异常处理是非常重要的。如果没有,那么在最好的情况下如果脚本崩溃了,数据抓取程序需要重新开始执行。
在最坏的情况下,你获得数据集将包含不一致性和错误,这将为你后续的工作买下隐患。
检索元数据
下一个函数使用table对象来查找一些元数据。下面给出在表格中搜索语言第一次出现的年份的代码。
这个简短的函数以table对象作为参数,并调用beautifulsoup的get_text()函数生成一个字符串。
下一步是创建一个名为year的子字符串。该字符串存储了在“appear”这个词首次出现之后的30个字符。这个字符串应该包含语言第一次出现的年份。
为了仅提取年份,使用正则表达式(通过re模块)来匹配任何以1到3之间的数字开头、并紧邻三个数字的字符串。
如果执行成功,函数将返回一个整数的year。否则,我们会得到“could not determine”。你可能还想进一步挖掘元数据,例如范例,设计者或打字规律。
收集链接
我们还需要一个函数–该函数读入给定语言的table对象,输出一个包含其他编程语言的列表。
仔细观察上面代码中嵌套部分,到底是怎么回事呢?
这个函数利用了table对象具有结构一致性的事实。表中的信息存储在行中(相关的html标签是)。其中一行包含文字“\ ninfluenced \ n”。函数的第一部分查找这是哪一行。
一旦找到这一行,就可以确定下一行包含了被当前行影响的每种编程语言的链接。使用find_all(“a”)便可查找这些链接 - 其中参数“a”对应于html标签。
对于每个链接j,将其[“title”]属性添加到名为out的列表。对[“title”]属性感兴趣的原因是因为它将完全匹配存储在节点中的语言名称。
例如,java作为“java(编程语言)”存储在节点中,因此需要在整个数据集中使用这个确切的名称。
如果执行成功,getlinks()将返回一组编程语言。该函数的其余部分进行了异常处理,以防程序在某一阶段出现问题。
收集数据
最后,在一切准备就绪后执行脚本,收集数据并将其存储在两个列表对象中。
现在编写一个循环,将先前定义的函数应用于nodes中的每个词条,并将输出存储在edgelist和meta中。
该函数使用节点中的每种语言,并尝试从维基百科页面检索汇总表。
然后,该函数将检索表中列出的与目标语言所关联的全部语言。
对于同时出现在节点列表中的每种语言,将一个元素以[“source,target”]的形式添加到edgelist。通过这种方式,建立一个边的列表传给gephi。
出于调试的目的,打印添加到edgelist的每个元素——这样做仅仅为了确保一切都工作。如果想要更彻底地调试,也可以添加打印语句到except语句中。
接下来,获取语言的名称和年份,并将其添加到元列表中。
写进csv文件
一旦循环运行,最后一步是将edgelist和meta的内容写入到csv文件。通过使用前面导入的csv模块,完成上一步骤就容易多了。
完成了!保存脚本,并从终端运行:
$ python3 script.py
当构建边列表时,你可以�...