实时数据可视化

实时数据特征 通常来说,可视化的报表会以更高效率的方式将数据背后隐藏的信息传递给我们。通过一个简单的BarChart,我们就很容易对比某商品在第二季度中的销量差异;而通过一条简单的LineChart,则很容易看出员工平均工作时间在某个月份的分布。这些报表都或多或少与时间相关:随着时间的流逝,某项指标会因为各种各样的因素而产生变化。 另一方面,在某些领域,我们需要更高时效性的报表。比如产品的线上指标分析:有多少用户当前在线,主站的负载情况如何,有多少在线交易正在形成等等。此外,很多运维数据也希望有更高的实时性,比如目前服务器的负载如何,过去的5分钟的负载情况又是什么样子的等等。 这类报表的特点是: 高时效性 对于细粒度的指标,数据量可能会很大 过了某段特定的时间段,数据的价值会骤降 比如上图是Mac上的CPU使用情况的实时报表,它展现了一段时间内的各个核上的计算负载。这些信息不断产生,有不断被丢弃,没有人关注一个小时之前的CPU占用,只要能展示出最近几分钟的就好。 基于这些特性,如何存取数据、如何分析度量结果、如何滚动历史数据等等都会遇到和其他图表不尽相同的问题。另外,由于实时数据的可视化与时间是强相关的 – 它本质上必须是一个动态的图表,这与其他的图表类型又有不同。我们在这篇文章中将会讨论这些问题,以及解决这些问题的常见方案。 数据指标 对于实时数据,我们关注不同事件发生的次数,以及事件发生时持续的时长等。我们首先需要定义一些对象: 计数器(counter) 计时器(timer) 标量(gauge) 计数器 计数器涉及需要被记录次数的事件(通常是每发生一次,计数器加一/减一),这类数据的增长/减少规律比较固定,比如: 响应为200的请求 - response.code === 200 从某个session产生的请求 - session.id === 'b1b2b3bab22123bb1a' 计时器 计时器涉及所有应该记录时间长度的事件,通常这类时间我们可以通过引入一个时间段(interval)来计算一些统计信息,比如平均值,方差,标准差,最大值,最小值等。比如: 请求响应时间 - response.time 停留时间 - stay.time 标量 还有一种经常会用到的量,我们不关注过程中它的变化倾向,只关注某个时刻上的数字/状态,比如: 节点是否可用 某一时刻的进程数 某一时刻的CPU负载/内存占用率 数据处理典型流程 对于生产环境,实时数据既可以以日志的形式提供,也可以是来源于事件数据库。日志是最常见的形式,几乎所有的系统都会以各种各样的方式记录日志,大部分的日志会提供滚动机制:日志会被记录到一个固定尺寸的文件中,旧的日志会被滚动的写入到另一个文件(通常还会有配套的定时任务来清理更早的日志等)。另一方面,对于很多基于事件的软件系统中,事件会被写入到数据库中,这些数据也可以用作实时数据可视化的来源。 原始数据往往不能直接用来做可视化展现,通常我们需要做一些预处理,这些过程包括: 原始数据获取 结构化 初步统计 高阶统计 数据结构化 有很多的工具可以帮助我们实现这些步骤,比如我们通过一个简单的配置,就可以让logstash自动将源源不断产生的日志数据写入到statsd(最终周期性的写入到graphite数据库中): input { stdin {} } filter { grok { match => { "message" => "%{DATA:time} %{DATA:status} %{NUMBER:request_time} %{DATA:campaign} %{DATA:mac} %{DATA:ap_mac} %{GREEDYDATA:session}" } } } output { stdout { codec => rubydebug } statsd { host => 'localhost' increment => "airport....

June 17, 2018 · 3 min · 邱俊涛 | Juntao Qiu

ThoughtWorks洞见在讲什么

ThoughtWorks洞见 ThoughtWorks洞见是ThoughtWorks的一个媒体渠道,汇集了来自ThoughtWorks最优秀的经验和思考,并分享给真正对软件有意愿思考和不断改进的人(修改自官方版本)。 截至目前为止,ThoughtWorks洞见已经汇集了50余位作者的300+篇文章(就在刚才,又有一篇更新)。那么这些文章中都在讨论什么样的话题呢?这篇文章将通过一些技术手段,提取出洞见中的关键字,然后采用可视化的方式呈现出来。 数据获取 本来我打算从RSS上读feed,解析出文章的link,再将所有文章爬一遍,最后保存到本地。不过写了几行代码后发现Wordpress(ThoughtWorks洞见目前托管在一个Wordpress上)默认地只输出最新的feed,这对于关键字提取来说数量远远不够。众所周知,语料库越大,效果越好。 既然是洞见本质上来说就是一个静态站点,那么最简单、最暴力的方式就是直接把站点克隆到本地。这一步通过使用wget可以很容易做到: wget --mirror -p --html-extension --convert-links -e robots=off -P . \ http://insights.thoughtworkers.org/ 默认地,wget会以站点的完整域名为目录名,然后保存整个站点到本地。我大概看了一下,其实不需要所有的目录,只需要一个层次即可,所以这里用find来做一个过滤,然后将文件名写到一个本地文件filepaths中。 find insights.thoughtworkers.org/ -name index.html -depth 2 > filepaths 这个文件的内容是这样的: insights.thoughtworkers.org/10-common-questions-of-ba/index.html insights.thoughtworkers.org/10-tips-for-good-offshore-ba/index.html insights.thoughtworkers.org/10-ways-improve-your-pairing-experience/index.html insights.thoughtworkers.org/100-years-computer-science/index.html insights.thoughtworkers.org/1000-cars-improve-beijing-transportation/index.html insights.thoughtworkers.org/3d-printing/index.html insights.thoughtworkers.org/4-advices-for-aid/index.html insights.thoughtworkers.org/5-appointments-with-agile-team/index.html insights.thoughtworkers.org/5-ways-exercise-visual-design/index.html insights.thoughtworkers.org/7-step-agenda-effective-retrospective/index.html insights.thoughtworkers.org/a-decade/index.html insights.thoughtworkers.org/about-team-culture/index.html insights.thoughtworkers.org/about-tw-insights/index.html insights.thoughtworkers.org/agile-coach/index.html insights.thoughtworkers.org/agile-communication/index.html insights.thoughtworkers.org/agile-craftman/index.html ... 数据处理 这样我就可以很容易在python脚本中读取各个文件并做处理了。有了文件之后,需要做这样一些事情: 抽取HTML中的文本信息 将文本分词成列表 计算列表中所有词的TFIDF值 计算每个词出现的频率 将结果持久化到本地 这里需要用到这样一些pyhton库: BeautifulSoap 解析HTML文档并抽取文本 jieba 分词 sk-learn 计算单词出现频率 pandas 其他数据处理 def extract_post_content(file): soup = BeautifulSoup(open(file).read(), "html.parser") return soup....

March 12, 2017 · 2 min · 邱俊涛 | Juntao Qiu

可视化之根

可视化之根 多年前读过一篇非常震撼的文章,叫《Lisp之根》(英文版:The roots of Lisp),大意是Lisp仅仅通过一种数据结构(列表)和有限的几个函数,就构建出了一门极为简洁,且极具扩展性的编程语言。当时就深深的被这种设计哲学所震撼:一方面它足够简单,每个单独的函数都足够简单,另一方面它有非常复杂,像宏,高阶函数,递归等机制可以构建出任意复杂的程序,而复杂的机制又是由简单的组件组成的。 数据的可视化也是一样,组成一幅内容清晰、表达力强、美观的可视化信息图的也仅仅是一些基本的元素,这些元素的不同组合却可以产生出令人着迷的力量。 要列出“可视化元素之根”很容易:位置、长度、角度、形状、纹理、面积(体积)、色相、饱和度等几种有限的元素,邱南森在他的《数据之美》中提供了一张视觉元素的图,其中包含了大部分常用的元素。 令人振奋的是,这些元素可以自由组合,而且组合旺旺会产生1+1>2的效果。 心理学与认知系统 数据可视化其实是基于人类的视觉认知系统的,因此对人类视觉系统的工作方式有一些了解可以帮助我们设计出更为高效(更快的传递我们想要表达的信息给读者)的可视化作品。 心理物理学 在生活中,我们会遇到这样的场景:一件原价10元的商品,如果降价为5元,则消费者很容易购买;而一件原价100元的商品,降价为95元,则难以刺激消费者产生购买的冲动。这两个打折的绝对数字都是5元,但是效果是不一样的。 韦伯-费希纳定理描述的正是这种非理性的场景。这个定理的一个比较装逼的描述是: 感觉量与物理量的对数值成正比,也就是说,感觉量的增加落后于物理量的增加,物理量成几何级数增长,而心理量成算术级数增长,这个经验公式被称为费希纳定律或韦伯-费希纳定律。 – 摘自百度百科 这个现象由人类的大脑构造而固有,因此在设计可视化作品时也应该充分考虑,比如: 避免使用面积图作为对比 在做对比类图形时,当差异不明显时需要考虑采用非线性的视觉元素 选用多种颜色作为视觉编码时,差异应该足够大 比如: 如上图中,当面积增大之后,肉眼越来越难从形状的大小中解码出实际的数据差异,上边的三组矩形(每行的两个为一组),背后对应的数据如下,可以看到每组中的两个矩形的绝对差都是5: var data = [ {width: 5, height: 5}, {width: 10, height: 10}, {width: 50, height: 50}, {width: 55, height: 55}, {width: 100, height: 100}, {width: 105, height: 105} ]; 格式塔学派 格式塔学派是心理学中的一个重要流派,她强调整体认识,而不是结构主义的组成说。格式塔认为,人类在看到画面时,会优先将其简化为一个整体,然后再细化到每个部分;而不是先识别出各个部分,再拼接为整体。 比如那条著名的斑点狗: 我们的眼睛-大脑可以很容易的看出阴影中的斑点狗,而不是先识别出狗的四条腿或者尾巴(事实上在这张图中,人眼无法识别出各个独立的部分)。 格式塔理论有几个很重要的原理: 接近性原理 相似性原理 封闭性原理 连续性原理 主体/背景原理 当然,格式塔学派后续还有一些发展,总结出了更多的原理。工程上,这些原理还在大量使用,指导设计师设计各式各样的用户界面。鉴于网上已经有众多的格式塔理论及其应用的文章,这里就不在赘述。有兴趣的同学可以参考这几篇文章: 优设上的一篇格式塔文章 优设上的一篇关于格式塔与Web设计的文章 腾讯CDC的一篇格式塔介绍 视觉设计的基本原则 《写给大家看的设计书》一书中,作者用通俗易懂的方式给出了几条设计的基本原则,这些原则完全可以直接用在数据可视化中的设计中:...

March 1, 2017 · 3 min · 邱俊涛 | Juntao Qiu

新生儿日常记录的可视化 - 星空图

数据来源 从女儿心心出生开始,我们就通过各种方式记录她的各种信息:睡眠记录,吃药记录,体温记录,换尿布记录,哺乳记录等等。毕竟,处于忙乱状态的人们是很难精确地回忆各种数字的,特别是在体检时面对医生的询问时。大部分父母无法准确回答小孩上周平均的睡眠时间,或者平均的小便次数,这在很多时候会影响医生的判断。 我和我老婆的手机上都安装了宝宝生活记录(Baby Tracker)(这里强烈推荐一下,免费版就很好用,不过界面下方有个讨厌的广告,我自己买了无广告的Pro版本),这样心心的每次活动我们都会记录下来,很有意思的是这个APP的数据可以以CSV格式导出(这个太棒了!),而且它自身就可以生成各种的报告,报告还可以以PDF格式导出并发送给其他应用。 有了现实世界中的一组数据 – 我们记录的差不多100天的数据,而且正好我最近在复习D3相关的知识,正好可以用来做一些有趣的练习。 数据准备 从Baby Tracker导出的数据是一些CSV文件组成是压缩包,解压之后大致结果是这样的: 哺乳记录 睡眠记录 换尿布记录 喂药/体温记录 里程碑记录 我就从最简单换尿布数据记录开始吧。我们首先需要将数据做一些清洗和归一化,这样方便前端页面的计算和渲染。数据处理我一般会选择Python+Pandas的组合,只需要写很少的代码就可以完成任务。 python + pandas 原始数据看起来是这样的: name,date,status,note 心心,2016/11/13 17:00,嘘嘘 心心,2016/11/13 19:48,嘘嘘+便便 心心,2016/11/13 22:23,便便 心心,2016/11/14 00:19,便便,一点点,感觉很稀,穿厚点 心心,2016/11/14 04:33,嘘嘘 心心,2016/11/14 09:20,便便 心心,2016/11/14 11:33,便便 心心,2016/11/14 16:14,便便 心心,2016/11/14 21:12,嘘嘘+便便 心心,2016/11/14 23:12,嘘嘘+便便 心心,2016/11/15 00:32,嘘嘘+便便,有点稀 心心,2016/11/15 03:45,干爽 心心,2016/11/15 07:06,嘘嘘 心心,2016/11/15 10:30,嘘嘘+便便 为了方便展示,我需要将数据统计成这样: date,urinate,stool 2016-11-13,2,2 2016-11-14,3,6 2016-11-15,6,8 我不关心每一天不同时刻换尿布的事件本身,只关心每天中,大小便的次数分布,也就是说,我需要这三项数据:日期,当天的小便次数,当天的大便次数。这个用pandas很容易就可以整理出来了,status字段的做一个微小的函数转换(当然可以写的更漂亮,不过在这里不是重点,暂时跳过): import numpy as np import pandas as pd diaper = pd.read_csv('data/diaper_data.csv', usecols=['date', 'status']) diaper['date'] = pd....

February 22, 2017 · 2 min · 邱俊涛 | Juntao Qiu

可视化你的足迹

可视化你的足迹 上一篇文章讲述了如何在服务器端通过MapServer来生成地图。虽然MapServer发布出来的地图是标准的WMS服务,但是我们还需要一个客户端程序来展现。我们在上一篇中,通过一些小脚本将照片中的地理信息抽取到了一个GeoJSON文件中。GeoJSON是一种向量图层格式,向量数据可以在服务器端绘制成栅格图,也可以直接在客户端canvas上直接绘制出来。当数据量比较大的时候,我们更倾向于在服务器端绘制,这样只需要在网络上传输一张图片(而且可以做缓存)。大数据量的客户端绘制在性能上会比较差(当然现在已经有了一些新的解决方案,我们后续再细谈),特别是有用户交互时,会出现明显的卡顿。 在本文中,我将分别使用客户端和服务端绘制的两种方式来展现两种不同的地图:使用OpenLayers直接在客户端绘制矢量图,以及使用Leaflet来展示在服务器端绘制好的栅格图层。 使用OpenLayers3展示GeoJSON 展示GeoJSON非常容易,也是一种比较直接的方式,只需要将GeoJSON文件发送到前端,然后直接通过客户端渲染即可。使用OpenLayers3的API,代码会是这样: $.getJSON('data/places-ive-been-3857.json').done(function(geojson) { var vectorSource = new ol.source.Vector({ features: (new ol.format.GeoJSON()).readFeatures(geojson) }); }); 客户端发送一个ajax请求,得到GeoJSON数据之后,将其转换成一个向量类型。OpenLayers定义了很多中格式读取器,比如KML的,GML的,GeoJSON的等等。然后我们可以定义一个样式函数: var image = new ol.style.Circle({ radius: 5, fill: null, stroke: new ol.style.Stroke({color: '#f04e98', width: 1}) }); var styles = { 'Point': [new ol.style.Style({ image: image })] }; var styleFunction = function(feature, resolution) { return styles[feature.getGeometry().getType()]; }; 这个函数会应用到向量集的Point类型,将其绘制为一个红色,半径为5像素的圆圈。有了数据和样式,我们再来创建一个新的向量,然后生成一个新的图层: var vectorLayer = new ol.layer.Vector({ source: vectorSource, style: styleFunction }); 创建地图,为了方便对照,我们加入了另外一个ol.source.Stamen图层作为参照。这样当缩放到较小的区域时,我们可以清楚的知道当前的点和地物的对照,比如道路名称,建筑名称等,从而确定目前的位置。这是一种非常常见的GIS应用的场景,但是需要注意的是,不同的图层需要有相同的空间映射方式,OpenLayers默认才用EPSG:3857,所以需要两者都采用该投影: var map = new ol....

September 20, 2015 · 1 min · 邱俊涛 | Juntao Qiu