还是先放一张图比较好吧,哈哈
作为上一篇博文的应用篇,很有意思哦,同时又比较繁琐。
为了降低繁琐的工作量,所以需要牺牲一下准确性,在下面的统计分析中仅对文本做了粗略的处理工作。不过,也基本上过得去,不要太计较,我只是为了快乐地玩耍。
常见的问题(有的已经做了处理,有的尚未处理):
● 多人一名(重名)
● 一人多名(多种称呼,自己改过名,身份变更)
● 姓名包含(比如:脱脱,脱脱不花,这是两个人,)
● 语句中包含非人名的人名(比如有这样一句话“一张信用卡”,而有一哥们就叫“张信”)
● 名字起得太语言化(有一个叫“方法”的人)
● 人名中存在Unicode字符集中没有的汉字
● 难以区分的代词或称谓(你,我,他,父亲,太子……)
……
上面只是清洗数据时的麻烦,在调试程序时还会遇到更多的麻烦。比如:
- 582个点运行起来还是挺慢的,因此程序有待优化
- 大于等于3个点时,最终稳态就不唯一了,更别说500多个点了,因此初始状态很重要
- 连线较多时,由于想要以不同的颜色展示,因此黑背景时只能浅色线压深色线
获取数据
为了快速进入玩耍状态,需要去找些数据。主要数据来源的网站都在这儿了:
通用汉字数据:汉语国际教育技术研发中心,中国语言文字网:已经发布的语言文字规范
明朝人物数据:维基百科:明史人物列表,中国历史著名人物
《明朝那些事儿》:Ebook Search Engine(非常给力的电子书搜索引擎,献给有银子买Kindle没银子买电子书的朋友,墙外)
所有数据已经打包在附件中了,下载地址在本文最后。
代码与说明
运行下面代码之前,请先将附件解压至某合适的目录中,目录中尽量不要包含杂七杂八的字符(比如破折号“——”)。
1、统计数据
1.1、 导入数据
path = NotebookDirectory[]; hz = Flatten@ Import[path <> "Data\\汉字列表(增删处理后,共计8275字).xlsx", {"Data", "数据"}];(*所有简体汉字*) names = Import[ path <> "Data\\明史人物列表.xlsx", {"Data", "数据", All, {3, 4, 5, 6}}][[ 2 ;;]];(*明朝人名单*) names = Select[#, StringLength[#] > 0 &] & /@ names;(*明朝人名单*) name = names[[;; , 1]];(*明朝人名单,主名*) cc = Select[names, Length[#] > 1 &];(*有别名的人名单*) cc = Thread[# -> #[[1]]][[2 ;;]] & /@ cc;(*替换规则*)
1.2、导入书籍并清洗
book = Import[path <> "Data\\明朝那些事儿\\明朝那些事儿*.txt"];(*导入书籍*) book = StringJoin[book];(*将7本书合并*) book = StringReplace[book, "\n" .. -> "\n"];(*去掉重复的换行*) book = StringReplace[book, Except[{"\n", ",", "。", "!", "?"}~Join~hz] .. -> ""];(*除汉字和常用标点外,全部删除*) sentences = Select[StringSplit[book, "\n"], StringLength[#] > 1 &];(*按段落拆分文章,并过滤掉长度小于2的段落*) sentence = StringReplace[#, Flatten[cc]] & /@ sentences;(*替换别名为首列名称*) name = Pick[name, StringContainsQ[ls, #] & /@ name, True];(*书中出现过的名字*)
1.3、统计数据&保存备用
comNames = Select[Union[StringCases[#, name]] & /@ sentence, Length[#] > 1 &];(*出现在同一段落的名字并去重,忽略只有一个人名的段落*) comName = Union[Flatten[comNames]];(*有哪些名字出现过,忽略只有一个人名的段落*) ls = StringJoin[sentence]; comNameN = StringCount[ls, #] & /@ comName;(*名字出现的次数*) coxM = Association @@ Flatten[{#[[1]] -> #[[2]], RotateLeft[#[[1]]] -> #[[2]]} & /@ Tally[Sort /@ Flatten[Subsets[#, {2}] & /@ comNames, 1]]];(*相关度-出现在同一段落名字的次数统计*) n0 = Length[comName]; mR = Table[ls = coxM[{comName[[m]], comName[[n]]}]; If[Head[ls] == Integer, ls, 0, 0], {m, n0}, {n, n0}];(*相关矩阵*) Save[path <> "Data/Data", {mR, comName, comNameN}](*保存数据*)
1.4、人名频数概况
- >>统计数据下载(人名频数概况)
- 出现最多的名字(前20)
名字 | 出现次数 (忽略一个段落中只含一个名字) |
朱棣 | 1425 |
朱元璋 | 1419 |
徐阶 | 744 |
张居正 | 634 |
严嵩 | 597 |
王守仁 | 534 |
袁崇焕 | 531 |
朱祁镇 | 448 |
魏忠贤 | 448 |
胡宗宪 | 406 |
也先 | 384 |
高拱 | 377 |
朱厚照 | 339 |
陈友 | 330 |
陈友谅 | 328 |
戚继光 | 311 |
夏言 | 299 |
于谦 | 288 |
李如松 | 283 |
方法 | 274 |
- 出现最多的结合(前20)
名字1 | 名字2 | 同段出现次数 |
严嵩 | 徐阶 | 149 |
朱元璋 | 陈友谅 | 93 |
高拱 | 张居正 | 90 |
也先 | 朱祁镇 | 71 |
徐阶 | 高拱 | 66 |
朱棣 | 朱元璋 | 61 |
严嵩 | 夏言 | 60 |
汪直 | 胡宗宪 | 60 |
朱棣 | 盛庸 | 58 |
徐阶 | 严世蕃 | 53 |
徐达 | 常遇春 | 52 |
徐阶 | 张居正 | 52 |
朱棣 | 李景隆 | 50 |
朱元璋 | 胡惟庸 | 50 |
毛文龙 | 袁崇焕 | 50 |
朱棣 | 朱允炆 | 48 |
朱棣 | 朱高煦 | 45 |
朱棣 | 解缙 | 43 |
徐达 | 朱元璋 | 41 |
朱祁钰 | 朱祁镇 | 41 |
1.5、单词云图
出现次数大于10次的名字,共计255个,忽略只有一个人名的段落。
Remove["Global`*"]; path = NotebookDirectory[]; << (path <> "Data/Data");(*导入数据*) << (path <> "pic2D/PData51727");(*导入数据*) p = Flatten[Position[comNameN, _?(# > 10 &)]]; name = comName[[p]]; nameN = comNameN[[p]]; n = Length[name]; img = WordCloud[nameN -> name, Background -> GrayLevel[231/255], ImageSize -> {650, 650}, FontFamily -> "微软雅黑", MaxItems -> n, WordOrientation -> "Random"]
2、绘制2维力导向图
2.1、计算所需数据
在看长长的程序之前,先看一张运行时的动态图吧,也许能增加你的兴趣。一共迭代了1727次,压缩成87帧的动画,其中有两次手动调整(画面有跳跃的时候,实际上第一帧的时候就有手动调整)。
(*--------------------------导入数据------------------------*) Remove["Global`*"]; path = NotebookDirectory[]; << (path <> "Data/Data");(*导入数据,注意路径*) mR = Rescale[N[mR, 20]];(*将相关矩阵的取值调整到0~1*) comNameN = Rescale[N[comNameN, 20]];(*将人名出现次数列表的取值调整到0~1*) (*-----------------------初始化数据---------------------------*) cD = 2;(*维数,注意修改为3时,需要修改lP定义,以及绘图函数*) cE = 0.1;(*停止动能阈值*) cT = 0.5;(*使用渐短步长更为合适*) cR = 0.99;(*为简单起见,计算完所有速度之后,以此系数乘之*) cN = Length[comName];(*人名数量*) lM = 100 comNameN + 10;(*用人名出现次数构造质量列表*) lV = ConstantArray[0, {cN, cD}];(*初始速度*) fE[lV_] := lM.(Total[#^2] & /@ lV)/2;(*可以定义其它的动能,以提高计算速度*) fDis[x1_, x2_] := EuclideanDistance @@ N[{x1, x2}, 20];(*距离函数*) mfDis[lP_] := Outer[fDis, lP, lP, 1];(*距离矩阵函数*) fDir[x1_, x2_] := Normalize@N[x1 - x2, 20];(*方向函数*) mfDir[lP_] := Outer[fDir, lP, lP, 1];(*方向矩阵函数*) k0 = 1; fAF[d_] := If[d > 10^10, 100, d^2/k0](*引力函数*) fRF[d_] := If[d < 10^-10, 0, -k0^2/d];(*斥力函数*) SetAttributes[{fAF, fRF}, Listable];(*设置函数具有列表属性*) SeedRandom[2015];(*随机种子*) ww = RandomPrime[{cN + 1, 10^10}];(*随机相位*) lP = N[Table[ 3/(comNameN[[k]] + 0.25) k0 {Cos[2 Pi ww k/cN], Sin[2 Pi ww k/cN]}, {k, cN}], 20];(*各点的初始位置*) (*------------------------计算与保存数据--------------------------*) Dynamic[Column[{vE,k,Graphics[Flatten[{Table[{Thickness[0.005lineV[[\ k]]],GrayLevel[lineV[[k]]+0.01],Line[{lP[[line[[k,1]]]],lP[[line[[k,2]\ ]]]}]},{k,Length[line]}],Table[{GrayLevel[comNameN[[k]]^(1/5)],\ PointSize[0.01comNameN[[k]]^(1/5)],Point[lP[[k]]]},{k,Length[comNameN]\ }]}],Background\[Rule]Black,ImageSize\[Rule]Large,Frame\[Rule]True]}]]\ (*动态显示当前动能及各点位置,会降低运行速度,建议流离注释掉*) Dynamic[Column[{vE, k}]](*实时却能与迭代次数*) vE = cE + 100.;(*动能初值*) k = 0;(*迭代次数初值*) While[vE > cE, Parallelize[k += 1; Save[path <> "pic" <> ToString[cD] <> "D/PData" <> ToString[k], lP];(*保存数据*) Export[path <> "pic" <> ToString[cD] <> "D/img" <> ToString[k] <> ".png", Graphics[ Flatten[{Table[{Thickness[0.005 lineV[[k]]], GrayLevel[lineV[[k]] + 0.01], Line[{lP[[line[[k, 1]]]], lP[[line[[k, 2]]]]}]}, {k, Length[line]}], Table[{GrayLevel[comNameN[[k]]^(1/5)], PointSize[0.01 comNameN[[k]]^(1/5)], Point[lP[[k]]]}, {k, Length[comNameN]}]}], Background -> Black, ImageSize -> {500, 500}]];(*保存图片*) mDis = mfDis[lP];(*计算距离矩阵*) mDir = mfDir[lP];(*计算方向矩阵*) lF = Total[(fAF[mDis] mR + fRF[mDis]) mDir];(*计算合力列表*) lV = (lV + cT lF) cR/lM;(*计算速度列表*) vE = fE[lV cT];(*计算此当前动能*) lP = lP + lV cT;(*更新各点位置*) ]] (*------------------------手动调整--------------------------*) (*可以边运行边调整,也可以先停止计算再调整(但在恢复计算时需要修改迭代次数k)*) LocatorPane[Dynamic[lP],Dynamic[Column[{vE,Graphics[Flatten[{Table[{\ Thickness[0.005lineV[[k]]],GrayLevel[lineV[[k]]+0.01],Line[{lP[[line[[\ k,1]]]],lP[[line[[k,2]]]]}]},{k,Length[line]}],Table[{GrayLevel[\ comNameN[[k]]^(1/5)],PointSize[0.01comNameN[[k]]^(1/5)],Point[lP[[k]]]\ },{k,Length[comNameN]}]}],Background\[Rule]Black,ImageSize\[Rule]{\ 1500,1500}]}]],Appearance\[Rule]None](*手动调整点的位置,会降低运行速度,建议流离注释掉*)
2.2、绘图
Remove["Global`*"]; path = NotebookDirectory[]; << (path <> "Data/Data");(*导入数据*) << (path <> "pic2D/PData1727");(*导入数据*) mR = Rescale[N[mR, 20]];(*将相关矩阵的取值调整到0~1*) comNameN = Rescale[N[comNameN, 20]];(*将人名出现次数列表的取值调整到0~1*) line = Position[UpperTriangularize[mR], _?Positive];(*有关系的点对*) lineV = mR[[Sequence @@ #]] & /@ line;(*对应值*) line = line[[Ordering[lineV]]];(*有关系的点对,按对应值排序,保证关系强的线在弱线的上面*) lineV = lineV[[Ordering[lineV]]];(*对应值,按对应值排序*) img = Graphics[Flatten[{ Table[{Thickness[0.005 lineV[[k]]^(1/2)], GrayLevel[lineV[[k]] + 0.01], Line[{lP[[line[[k, 1]]]], lP[[line[[k, 2]]]]}]}, {k, Length[line]}](*线*), Table[{GrayLevel[comNameN[[k]]^(1/5)], PointSize[0.01 comNameN[[k]]^(1/5)], Point[lP[[k]]]}, {k, Length[comNameN]}](*点*), Table[ Inset[Style[comName[[k]], FontFamily -> "微软雅黑", 12 comNameN[[k]]^(1/5) + 6, RGBColor[0.5, 0.8, 0]], lP[[k]] + 0.4 comNameN[[k]]^(1/5)], {k, Length[comNameN]}](*标注人名*)}] , Background -> Black, ImageSize -> {4000, 4000}, PlotRange -> {{-30, 30}, {-30, 30}}];(*绘局部图*) If[FileNames[path <> "pic2D"] == 0, CreateDirectory[path <> "pic2D"]];(*如果没有目录则自动创建*) Export[path <> "pic2D/img.png", img];(*导出图像*)
3、绘制3维力导向图
如果不将3维图像做成动画,那么和2维也没什么分别。数据的计算2维图数据计算差不多,只需要把cD赋值为3,lP的初始位置需要修改,最后画图需要修改,其它都是一样的。
各点的初始位置
lP = N[Table[ 3/(comNameN[[k]] + 0.25) k0 {Cos[2 Pi ww k/(Floor[Sqrt[cN] + 1])] Cos[ Pi k/(Floor[Sqrt[cN] + 1])] + RandomReal[0.2], Sin[2 Pi ww k/(Floor[Sqrt[cN] + 1])] Cos[ Pi k/(Floor[Sqrt[cN] + 1])] + RandomReal[0.2], Sin[ Pi k/(Floor[Sqrt[cN] + 1])]}, {k, cN}], 20];(*各点的初始位置*)
绘图与导出动画
Remove["Global`*"]; path = NotebookDirectory[]; << (path <> "Data/Data");(*导入数据,注意路径*) << (path <> "pic3D/PData1243");(*导入数据,注意路径*) mR = Rescale[N[mR, 20]];(*将相关矩阵的取值调整到0~1*) comNameN = Rescale[N[comNameN, 20]];(*将人名出现次数列表的取值调整到0~1*) st = 2 Pi/60;(*60:动画帖数*) line = Position[UpperTriangularize[mR], _?(# > 0.1 &)];(*相关系数大于0.1的点对*) lineV = mR[[Sequence @@ #]] & /@ line;(*对应值*) line = line[[Ordering[lineV]]];(*有关系的点对,按对应值排序,保证关系强的线在弱线的上面*) lineV = lineV[[Ordering[lineV]]];(*对应值,按对应值排序*) centername = "朱元璋"; center = Position[comName, centername][[1, 1]];(*视点中心*) insetP = Intersection[Flatten[Position[comNameN, _?(# > 0.01 &)]], Flatten[Select[line, #[[1]] == center || #[[2]] == center &]] // Union];(*需要标注姓名的点*) pics = With[{obj = Graphics3D[Flatten[{ Table[{Opacity[lineV[[k]]^(1/2)], GrayLevel[lineV[[k]]^(1/2)], Cylinder[{lP[[line[[k, 1]]]], lP[[line[[k, 2]]]]}, 0.2 lineV[[k]]^(1/2)]}, {k, Length[line]}](*线*), Opacity[1], Table[{GrayLevel[0.9], Sphere[lP[[k]], 0.5 comNameN[[k]]^(1/5)]}, {k, Length[comNameN]}](*点和人名*), Table[ Inset[Style[comName[[insetP[[k]]]], FontFamily -> "宋体", 12 comNameN[[insetP[[k]]]]^(1/5) + 6, RGBColor[0.5, 0.8, 0]], lP[[insetP[[k]]]] + 1], {k, Length[insetP]}](*标注姓名*)}] , Background -> Black, Boxed -> False, ImageSize -> {500, 500}]}, Table[Show[obj, SphericalRegion -> True, ViewVector -> {{30 Cos[t], 30 Sin[t], 0} + lP[[center]], lP[[center]]}], {t, st, 2 Pi, st}]];(*生成动画的每一帖,通过参数ViewVector可以自定义观察轨迹*) If[FileNames[path <> "pic3D"] == 0, CreateDirectory[path <> "pic3D"]];(*如果没有目录则自动创建*) Export[path <> "pic3D/imgs.gif", pics];(*导出动画*)
全图
朱元璋
朱棣