<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>xinxin on I Code It</title>
    <link>https://icodeit.org/categories/xinxin/</link>
    <description>Recent content in xinxin on I Code It</description>
    <generator>Hugo -- gohugo.io</generator>
    <lastBuildDate>Wed, 01 Mar 2017 00:00:00 +0000</lastBuildDate><atom:link href="https://icodeit.org/categories/xinxin/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>可视化之根</title>
      <link>https://icodeit.org/2017/03/visualise-the-data-around-you/</link>
      <pubDate>Wed, 01 Mar 2017 00:00:00 +0000</pubDate>
      
      <guid>https://icodeit.org/2017/03/visualise-the-data-around-you/</guid>
      <description>可视化之根 多年前读过一篇非常震撼的文章，叫《Lisp之根》（英文版：The roots of Lisp），大意是Lisp仅仅通过一种数据结构（列表）和有限的几个函数，就构建出了一门极为简洁，且极具扩展性的编程语言。当时就深深的被这种设计哲学所震撼：一方面它足够简单，每个单独的函数都足够简单，另一方面它有非常复杂，像宏，高阶函数，递归等机制可以构建出任意复杂的程序，而复杂的机制又是由简单的组件组成的。
数据的可视化也是一样，组成一幅内容清晰、表达力强、美观的可视化信息图的也仅仅是一些基本的元素，这些元素的不同组合却可以产生出令人着迷的力量。
要列出“可视化元素之根”很容易：位置、长度、角度、形状、纹理、面积（体积）、色相、饱和度等几种有限的元素，邱南森在他的《数据之美》中提供了一张视觉元素的图，其中包含了大部分常用的元素。
令人振奋的是，这些元素可以自由组合，而且组合旺旺会产生1+1&amp;gt;2的效果。
心理学与认知系统 数据可视化其实是基于人类的视觉认知系统的，因此对人类视觉系统的工作方式有一些了解可以帮助我们设计出更为高效（更快的传递我们想要表达的信息给读者）的可视化作品。
心理物理学 在生活中，我们会遇到这样的场景：一件原价10元的商品，如果降价为5元，则消费者很容易购买；而一件原价100元的商品，降价为95元，则难以刺激消费者产生购买的冲动。这两个打折的绝对数字都是5元，但是效果是不一样的。
韦伯-费希纳定理描述的正是这种非理性的场景。这个定理的一个比较装逼的描述是：
 感觉量与物理量的对数值成正比，也就是说，感觉量的增加落后于物理量的增加，物理量成几何级数增长，而心理量成算术级数增长，这个经验公式被称为费希纳定律或韦伯-费希纳定律。
&amp;ndash; 摘自百度百科
 这个现象由人类的大脑构造而固有，因此在设计可视化作品时也应该充分考虑，比如：
 避免使用面积图作为对比 在做对比类图形时，当差异不明显时需要考虑采用非线性的视觉元素 选用多种颜色作为视觉编码时，差异应该足够大  比如：
如上图中，当面积增大之后，肉眼越来越难从形状的大小中解码出实际的数据差异，上边的三组矩形（每行的两个为一组），背后对应的数据如下，可以看到每组中的两个矩形的绝对差都是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的一篇格式塔介绍  视觉设计的基本原则 《写给大家看的设计书》一书中，作者用通俗易懂的方式给出了几条设计的基本原则，这些原则完全可以直接用在数据可视化中的设计中：</description>
      <content:encoded><![CDATA[<h2 id="可视化之根">可视化之根</h2>
<p>多年前读过一篇非常震撼的文章，叫<a href="http://daiyuwen.freeshell.org/gb/rol/roots_of_lisp.html">《Lisp之根》</a>（英文版：<a href="http://www.paulgraham.com/rootsoflisp.html">The roots of Lisp</a>），大意是Lisp仅仅通过一种数据结构（列表）和有限的几个函数，就构建出了一门极为简洁，且极具扩展性的编程语言。当时就深深的被这种设计哲学所震撼：一方面它足够简单，每个单独的函数都足够简单，另一方面它有非常复杂，像宏，高阶函数，递归等机制可以构建出任意复杂的程序，而复杂的机制又是由简单的组件组成的。</p>
<p>数据的可视化也是一样，组成一幅内容清晰、表达力强、美观的可视化信息图的也仅仅是一些基本的元素，这些元素的不同组合却可以产生出令人着迷的力量。</p>
<p>要列出“可视化元素之根”很容易：位置、长度、角度、形状、纹理、面积（体积）、色相、饱和度等几种有限的元素，邱南森在他的<a href="https://book.douban.com/subject/25833225/">《数据之美》</a>中提供了一张视觉元素的图，其中包含了大部分常用的元素。</p>
<p><img loading="lazy" src="/images/2017/03/cues-resized.png" type="" alt=""  /></p>
<p>令人振奋的是，这些元素可以自由组合，而且组合旺旺会产生<code>1+1&gt;2</code>的效果。</p>
<h3 id="心理学与认知系统">心理学与认知系统</h3>
<p>数据可视化其实是基于人类的视觉认知系统的，因此对人类视觉系统的工作方式有一些了解可以帮助我们设计出更为高效（更快的传递我们想要表达的信息给读者）的可视化作品。</p>
<h4 id="心理物理学">心理物理学</h4>
<p>在生活中，我们会遇到这样的场景：一件原价10元的商品，如果降价为5元，则消费者很容易购买；而一件原价100元的商品，降价为95元，则难以刺激消费者产生购买的冲动。这两个打折的绝对数字都是5元，但是效果是不一样的。</p>
<p><a href="https://zh.wikipedia.org/wiki/%E9%9F%8B%E4%BC%AF-%E8%B2%BB%E5%B8%8C%E7%B4%8D%E5%AE%9A%E7%90%86">韦伯-费希纳定理</a>描述的正是这种<em>非理性</em>的场景。这个定理的一个比较装逼的描述是：</p>
<blockquote>
<p>感觉量与物理量的对数值成正比，也就是说，感觉量的增加落后于物理量的增加，物理量成几何级数增长，而心理量成算术级数增长，这个经验公式被称为费希纳定律或韦伯-费希纳定律。</p>
<p>&ndash; 摘自百度百科</p>
</blockquote>
<p>这个现象由人类的大脑构造而固有，因此在设计可视化作品时也应该充分考虑，比如：</p>
<ul>
<li>避免使用面积图作为对比</li>
<li>在做对比类图形时，当差异不明显时需要考虑采用非线性的视觉元素</li>
<li>选用多种颜色作为视觉编码时，差异应该足够大</li>
</ul>
<p>比如：</p>
<p><img loading="lazy" src="/images/2017/03/squares-resized.png" type="" alt=""  /></p>
<p>如上图中，当面积增大之后，肉眼越来越难从形状的大小中<strong>解码</strong>出实际的数据差异，上边的三组矩形（每行的两个为一组），背后对应的数据如下，可以看到每组中的两个矩形的绝对差都是<strong>5</strong>:</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">var</span> <span class="nx">data</span> <span class="o">=</span> <span class="p">[</span>
  <span class="p">{</span><span class="nx">width</span><span class="o">:</span> <span class="mi">5</span><span class="p">,</span> <span class="nx">height</span><span class="o">:</span> <span class="mi">5</span><span class="p">},</span>
  <span class="p">{</span><span class="nx">width</span><span class="o">:</span> <span class="mi">10</span><span class="p">,</span> <span class="nx">height</span><span class="o">:</span> <span class="mi">10</span><span class="p">},</span>

  <span class="p">{</span><span class="nx">width</span><span class="o">:</span> <span class="mi">50</span><span class="p">,</span> <span class="nx">height</span><span class="o">:</span> <span class="mi">50</span><span class="p">},</span>
  <span class="p">{</span><span class="nx">width</span><span class="o">:</span> <span class="mi">55</span><span class="p">,</span> <span class="nx">height</span><span class="o">:</span> <span class="mi">55</span><span class="p">},</span>

  <span class="p">{</span><span class="nx">width</span><span class="o">:</span> <span class="mi">100</span><span class="p">,</span> <span class="nx">height</span><span class="o">:</span> <span class="mi">100</span><span class="p">},</span>
  <span class="p">{</span><span class="nx">width</span><span class="o">:</span> <span class="mi">105</span><span class="p">,</span> <span class="nx">height</span><span class="o">:</span> <span class="mi">105</span><span class="p">}</span>
<span class="p">];</span>
</code></pre></div><h4 id="格式塔学派">格式塔学派</h4>
<p><a href="https://zh.wikipedia.org/wiki/%E6%A0%BC%E5%BC%8F%E5%A1%94%E5%AD%A6%E6%B4%BE">格式塔学派</a>是心理学中的一个重要流派，她强调整体认识，而不是<code>结构主义</code>的组成说。格式塔认为，人类在看到画面时，会优先将其简化为一个整体，然后再细化到每个部分；而不是先识别出各个部分，再拼接为整体。</p>
<p>比如那条著名的斑点狗：</p>
<p><img loading="lazy" src="/images/2017/03/dog-dog-resized.png" type="" alt=""  /></p>
<p>我们的眼睛-大脑可以很容易的看出阴影中的斑点狗，而不是先识别出狗的四条腿或者尾巴（事实上在这张图中，人眼无法识别出各个独立的部分）。</p>
<p>格式塔理论有几个很重要的原理：</p>
<ul>
<li>接近性原理</li>
<li>相似性原理</li>
<li>封闭性原理</li>
<li>连续性原理</li>
<li>主体/背景原理</li>
</ul>
<p>当然，格式塔学派后续还有一些发展，总结出了更多的原理。工程上，这些原理还在大量使用，指导设计师设计各式各样的用户界面。鉴于网上已经有众多的格式塔理论及其应用的文章，这里就不在赘述。有兴趣的同学可以参考这几篇文章：</p>
<ul>
<li><a href="http://www.uisdc.com/gestalt-psychology-knowledge">优设上的一篇格式塔文章</a></li>
<li><a href="http://www.uisdc.com/gestalt-website">优设上的一篇关于格式塔与Web设计的文章</a></li>
<li><a href="http://cdc.tencent.com/2010/07/23/%E6%A0%BC%E5%BC%8F%E5%A1%94%E5%BF%83%E7%90%86%E5%AD%A65%E9%A1%B9%E6%B3%95%E5%88%99%E7%9A%84%E5%AD%A6%E4%B9%A0%E4%B8%8E%E6%80%9D%E8%80%83/">腾讯CDC的一篇格式塔介绍</a></li>
</ul>
<h3 id="视觉设计的基本原则">视觉设计的基本原则</h3>
<p><a href="https://book.douban.com/subject/3323633/">《写给大家看的设计书》</a>一书中，作者用通俗易懂的方式给出了几条设计的基本原则，这些原则完全可以直接用在数据可视化中的设计中：</p>
<ul>
<li>亲密性（将有关联的信息物理上放在一起，而关联不大的则通过留白等手段分开）</li>
<li>对齐（将元素通过水平，垂直方向对齐，方便视觉识别）</li>
<li>重复（重复使用某一模式，比如标题1的字体颜色，标题2的字体颜色等，保持重复且一致）</li>
<li>对比（通过强烈的对比将不同的信息区分开）</li>
</ul>
<p><img loading="lazy" src="/images/2017/03/alignment-desc-resized.png" type="" alt=""  /></p>
<p>如果稍加留意，就会发现现实世界中在大量的使用这几个原则。1，2，3三个标题的形式就是重复性的体现；每个标题的内容自成一体是因为组成它的元素（数字，两行文字）的距离比较近，根据亲密性原则，人眼会自动将其归为一类；超大的数字字体和较小的文字形成了对比；大标题的颜色和其他内容形成了对比等等。</p>
<p>这些原则其实跟上面提到的格式塔学派，以及韦伯-费希纳定理事实上是相关的，在理解了这些人类视觉识别的机制之后，使用这些原则就非常自然和得心应手了。</p>
<h4 id="一些例子">一些例子</h4>
<ul>
<li>淡化图表的网格（和数据图形产生对比）</li>
<li>通过深色来强调标尺（强烈的线条和其余部分产生对比）</li>
<li>离群点的高亮（通过不同颜色产生对比）</li>
<li>使用颜色（通过不同的颜色，利用亲密性原则方便读者对数据分组）</li>
<li>元素颜色和legend（使用重复性原则）</li>
<li>同一个页面上有多个图表，采取同样的图例，色彩选择（强调重复性原则）</li>
</ul>
<p><img loading="lazy" src="/images/2017/03/twers-gender-compare-resized.png" type="" alt=""  /></p>
<h3 id="实例">实例</h3>
<p><a href="http://icodeit.org/2017/02/data-visualization-from-baby/">上篇文章</a>提到我通过一个手机App收集到了女儿成长的一些记录，包括哺乳信息，换尿布记录，以及睡眠信息。这个例子中，我会一步步的介绍如何将这些信息可视化出来，并解释其中使用的视觉原理。</p>
<p>可视化的第一步是要明确<code>你想要从数据中获取什么信息</code>，我想要获取的信息是孩子的睡眠总量以及睡眠时间分布情况。</p>
<h4 id="进阶版的条形图">进阶版的条形图</h4>
<p>确定了可视化的目的之后，第二步是选取合适的视觉编码。上面提到过，对于人眼来说，最精确的视觉编码方式是长度。我们可以将<code>睡眠时间</code>转化为<code>长度</code>来展现，最简单的方式是按天聚合，然后化成柱状图。比如：</p>
<pre><code>2016/11/21,768
2016/11/22,760
2016/11/23,700
</code></pre><p>不过这种图无法看出时间的分布。我们可以考虑通过条形图的变体来满足前面提到的两个核心诉求。先来在纸上画一个简单的草图。纵轴是24小时，横轴是日期。和普通的条形图不一样的是，每个条形的总长度是固定的，而且条形代表的不是简单非数据类型，而是24小时。在草稿中，每个画斜线的方块表示孩子在睡眠状态，而虚线部分表示她醒着。</p>
<p><img loading="lazy" src="/images/2017/03/sleeping-bar-draft-resized.png" type="" alt=""  /></p>
<h4 id="原始数据">原始数据</h4>
<pre><code>name,date,length,note
心心,2016/11/21 19:23,119,
心心,2016/11/21 22:04,211,
心心,2016/11/22 02:03,19,
心心,2016/11/22 02:23,118,
心心,2016/11/22 05:58,242,
心心,2016/11/22 10:57,128,
心心,2016/11/22 14:35,127,
心心,2016/11/22 17:15,127,
心心,2016/11/22 20:02,177,
心心,2016/11/23 01:27,197,
</code></pre><p>这里有个问题，我们的纵轴是24小时，如果她晚上23点开始睡觉，睡了3个小时，那么这个条形就回超出24格的轴。我写了一个函数来做数据转换:</p>
<div class="highlight"><pre class="chroma"><code class="language-rb" data-lang="rb"><span class="nb">require</span> <span class="s1">&#39;csv&#39;</span>
<span class="nb">require</span> <span class="s1">&#39;active_support/all&#39;</span>
<span class="nb">require</span> <span class="s1">&#39;json&#39;</span>

<span class="n">csv</span> <span class="o">=</span> <span class="no">CSV</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="s1">&#39;./visualization/data/sleeping_data_refined.csv&#39;</span><span class="p">,</span> <span class="ss">:headers</span> <span class="o">=&gt;</span> <span class="ss">:first_row</span><span class="p">)</span>

<span class="n">data</span> <span class="o">=</span> <span class="o">[]</span>
<span class="n">csv</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">row</span><span class="o">|</span>
    <span class="n">date</span> <span class="o">=</span> <span class="no">DateTime</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">row</span><span class="o">[</span><span class="s1">&#39;date&#39;</span><span class="o">]</span><span class="p">,</span> <span class="s2">&#34;%Y/%m/%d %H:%M&#34;</span><span class="p">)</span>
    
    <span class="n">mins_until_end_of_day</span> <span class="o">=</span> <span class="n">date</span><span class="o">.</span><span class="n">seconds_until_end_of_day</span> <span class="o">/</span> <span class="mi">60</span>
    <span class="n">diff</span> <span class="o">=</span> <span class="n">mins_until_end_of_day</span> <span class="o">-</span> <span class="n">row</span><span class="o">[</span><span class="s1">&#39;length&#39;</span><span class="o">].</span><span class="n">to_i</span>
    
    <span class="k">if</span> <span class="p">(</span><span class="n">diff</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="k">then</span>
        <span class="n">data</span> <span class="o">&lt;&lt;</span> <span class="p">{</span>
            <span class="ss">:name</span> <span class="o">=&gt;</span> <span class="n">row</span><span class="o">[</span><span class="s1">&#39;name&#39;</span><span class="o">]</span><span class="p">,</span>
            <span class="ss">:date</span> <span class="o">=&gt;</span> <span class="n">row</span><span class="o">[</span><span class="s1">&#39;date&#39;</span><span class="o">]</span><span class="p">,</span>
            <span class="ss">:length</span> <span class="o">=&gt;</span> <span class="n">row</span><span class="o">[</span><span class="s1">&#39;length&#39;</span><span class="o">]</span><span class="p">,</span>
            <span class="ss">:note</span> <span class="o">=&gt;</span> <span class="n">row</span><span class="o">[</span><span class="s1">&#39;note&#39;</span><span class="o">]</span>
        <span class="p">}</span>
    <span class="k">else</span> 
        <span class="n">data</span> <span class="o">&lt;&lt;</span> <span class="p">{</span>
            <span class="ss">:name</span> <span class="o">=&gt;</span> <span class="n">row</span><span class="o">[</span><span class="s1">&#39;name&#39;</span><span class="o">]</span><span class="p">,</span>
            <span class="ss">:date</span> <span class="o">=&gt;</span> <span class="n">date</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s2">&#34;%Y/%m/%d %H:%M&#34;</span><span class="p">),</span>
            <span class="ss">:length</span> <span class="o">=&gt;</span> <span class="n">mins_until_end_of_day</span><span class="p">,</span>
            <span class="ss">:note</span> <span class="o">=&gt;</span> <span class="n">row</span><span class="o">[</span><span class="s1">&#39;note&#39;</span><span class="o">]</span>
        <span class="p">}</span>

        <span class="n">data</span> <span class="o">&lt;&lt;</span> <span class="p">{</span>
            <span class="ss">:name</span> <span class="o">=&gt;</span> <span class="n">row</span><span class="o">[</span><span class="s1">&#39;name&#39;</span><span class="o">]</span><span class="p">,</span>
            <span class="ss">:date</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="n">date</span><span class="o">.</span><span class="n">beginning_of_day</span> <span class="o">+</span> <span class="mi">1</span><span class="o">.</span><span class="n">day</span><span class="p">)</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s2">&#34;%Y/%m/%d %H:%M&#34;</span><span class="p">),</span>
            <span class="ss">:length</span> <span class="o">=&gt;</span> <span class="n">diff</span><span class="o">.</span><span class="n">abs</span><span class="p">,</span>
            <span class="ss">:note</span> <span class="o">=&gt;</span> <span class="n">row</span><span class="o">[</span><span class="s1">&#39;note&#39;</span><span class="o">]</span>
        <span class="p">}</span>
    <span class="k">end</span>
<span class="k">end</span>
</code></pre></div><p>有了<em>干净</em>的数据之后，我们可以编写一些前端的代码来绘制条形图了。画图的时候有几个要注意的点：</p>
<ul>
<li>每天内的时间段对应的矩形需要有相同的X坐标</li>
<li>不同的睡眠长度要有颜色区分（睡眠时间越长，颜色越深）</li>
</ul>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">var</span> <span class="nx">dateRange</span> <span class="o">=</span> <span class="mi">_</span><span class="p">.</span><span class="nx">uniq</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span>
	<span class="kd">var</span> <span class="nx">date</span> <span class="o">=</span> <span class="nx">d</span><span class="p">.</span><span class="nx">date</span><span class="p">;</span>
	<span class="k">return</span> <span class="p">[</span><span class="nx">date</span><span class="p">.</span><span class="nx">getYear</span><span class="p">(),</span> <span class="nx">date</span><span class="p">.</span><span class="nx">getMonth</span><span class="p">(),</span> <span class="nx">date</span><span class="p">.</span><span class="nx">getDate</span><span class="p">()].</span><span class="nx">join</span><span class="p">(</span><span class="s2">&#34;/&#34;</span><span class="p">);</span>
<span class="p">});</span>

<span class="nx">xScale</span><span class="p">.</span><span class="nx">domain</span><span class="p">(</span><span class="nx">dateRange</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">d</span><span class="p">.</span><span class="nx">date</span><span class="p">;</span> <span class="p">}));</span>

<span class="kd">function</span> <span class="nx">getFirstInDomain</span><span class="p">(</span><span class="nx">date</span><span class="p">)</span> <span class="p">{</span>
	<span class="kd">var</span> <span class="nx">domain</span> <span class="o">=</span> <span class="nx">xScale</span><span class="p">.</span><span class="nx">domain</span><span class="p">();</span>

	<span class="kd">var</span> <span class="nx">index</span> <span class="o">=</span> <span class="mi">_</span><span class="p">.</span><span class="nx">findIndex</span><span class="p">(</span><span class="nx">domain</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">date</span><span class="p">.</span><span class="nx">getYear</span><span class="p">()</span> <span class="o">===</span> <span class="nx">d</span><span class="p">.</span><span class="nx">getYear</span><span class="p">()</span> 
			<span class="o">&amp;&amp;</span> <span class="nx">date</span><span class="p">.</span><span class="nx">getMonth</span><span class="p">()</span> <span class="o">===</span> <span class="nx">d</span><span class="p">.</span><span class="nx">getMonth</span><span class="p">()</span> 
			<span class="o">&amp;&amp;</span> <span class="nx">date</span><span class="p">.</span><span class="nx">getDate</span><span class="p">()</span> <span class="o">===</span> <span class="nx">d</span><span class="p">.</span><span class="nx">getDate</span><span class="p">();</span>
	<span class="p">});</span>

	<span class="k">return</span> <span class="nx">domain</span><span class="p">[</span><span class="nx">index</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div><p>函数<code>getFirstInDomain</code>可以根据一个日期值返回一个<code>X</code>坐标，这样<code>2016/11/21 19:23</code>和<code>2016/11/21 22:04</code>都会返回一个整数值（借助d3提供的标尺函数）。</p>
<p>另外，我们根据每次睡觉的分钟数将睡眠质量划分为5个等级：</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">var</span> <span class="nx">level</span> <span class="o">=</span> <span class="nx">d3</span><span class="p">.</span><span class="nx">scale</span><span class="p">.</span><span class="nx">threshold</span><span class="p">()</span>
  <span class="p">.</span><span class="nx">domain</span><span class="p">([</span><span class="mi">60</span><span class="p">,</span> <span class="mi">120</span><span class="p">,</span> <span class="mi">180</span><span class="p">,</span> <span class="mi">240</span><span class="p">,</span> <span class="mi">300</span><span class="p">])</span>
  <span class="p">.</span><span class="nx">range</span><span class="p">([</span><span class="s2">&#34;low&#34;</span><span class="p">,</span> <span class="s2">&#34;fine&#34;</span><span class="p">,</span> <span class="s2">&#34;medium&#34;</span><span class="p">,</span> <span class="s2">&#34;good&#34;</span><span class="p">,</span> <span class="s2">&#34;great&#34;</span><span class="p">,</span> <span class="s2">&#34;prefect&#34;</span><span class="p">]);</span>
</code></pre></div><p>然后在绘制过程中，根据实际数据值来确定不同的<em>CSS Class</em>：</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">svg</span><span class="p">.</span><span class="nx">selectAll</span><span class="p">(</span><span class="s2">&#34;.bar&#34;</span><span class="p">)</span>
	<span class="p">.</span><span class="nx">data</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span>
	<span class="p">.</span><span class="nx">enter</span><span class="p">()</span>
	<span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="s2">&#34;rect&#34;</span><span class="p">)</span>
	<span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;class&#34;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">level</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span><span class="o">+</span><span class="s2">&#34; bar&#34;</span><span class="p">;</span>
	<span class="p">})</span>
<span class="c1">//...
</span></code></pre></div><p>实现之后，看起来是这个样子的。事实上这个图标可以比较清楚的看出大部分睡眠集中在0-6点，而中午的10-13点以及黄昏18-20点基本上只有一些零星的睡眠。</p>
<p><img loading="lazy" src="/images/2017/03/star-bar-color-resized.png" type="" alt=""  /></p>
<h4 id="星空图">星空图</h4>
<p>上面的图有一个缺点，是当日期很多的时候（上图差不多有100天的数据），X轴会比较难画，如果缩减成按周，或者按月，又会增加很多额外的复杂度。</p>
<p>另外一个尝试是变形：既然这个统计是和时间相关的，那么圆形的钟表形象是一个很好的隐喻，每天24小时自然的可以映射为一个圆。而睡眠时间可以通过弧长来表示，睡眠时间越长，弧长越大：</p>
<p><img loading="lazy" src="/images/2017/03/sleeping-circle-draft-resized.png" type="" alt=""  /></p>
<h4 id="角度转弧度">角度转弧度</h4>
<p>我们首先将整个圆（360度）按照分钟划分，则每分钟对应的角度数为：<code>360/(24*60)</code>，再将角度转化为弧度：<code>degree * π/180</code>：</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">var</span> <span class="nx">perAngle</span> <span class="o">=</span> <span class="p">(</span><span class="mi">360</span> <span class="o">/</span> <span class="p">(</span><span class="mi">24</span> <span class="o">*</span> <span class="mi">60</span><span class="p">))</span> <span class="o">*</span> <span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">PI</span><span class="o">/</span><span class="mi">180</span><span class="p">);</span>
</code></pre></div><p>那么对于指定的时间，比如<code>10:20</code>，先计算出其分钟数：<code>10*60+20</code>，再乘以<code>preAngle</code>，就可以得出起始弧度；起始时间的分钟数加上睡眠时长，再乘以<code>preAngle</code>，就是结束弧度。</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">function</span> <span class="nx">startAngle</span><span class="p">(</span><span class="nx">date</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">start</span> <span class="o">=</span> <span class="p">(</span><span class="nx">date</span><span class="p">.</span><span class="nx">getHours</span><span class="p">()</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">+</span> <span class="nx">date</span><span class="p">.</span><span class="nx">getMinutes</span><span class="p">())</span> <span class="o">*</span> <span class="nx">perAngle</span><span class="p">;</span>
    <span class="k">return</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nx">start</span><span class="o">*</span><span class="mi">1000</span><span class="p">)</span><span class="o">/</span><span class="mi">1000</span><span class="p">;</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">endAngle</span><span class="p">(</span><span class="nx">date</span><span class="p">,</span> <span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">end</span> <span class="o">=</span> <span class="p">(</span><span class="nx">date</span><span class="p">.</span><span class="nx">getHours</span><span class="p">()</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">+</span> <span class="nx">date</span><span class="p">.</span><span class="nx">getMinutes</span><span class="p">()</span> <span class="o">+</span> <span class="nx">length</span><span class="p">)</span> <span class="o">*</span> <span class="nx">perAngle</span><span class="p">;</span>
    <span class="k">return</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nx">end</span><span class="o">*</span><span class="mi">1000</span><span class="p">)</span><span class="o">/</span><span class="mi">1000</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><p>实现的结果是这样的：</p>
<p><img loading="lazy" src="/images/2017/03/star-middle-resized.png" type="" alt=""  /></p>
<p>初看起来，它像是星空图，但是图中的不同颜色含义没有那么直观，我们需要在图上补充一个图例。通过使用d3的线性标尺和定义svg的渐变来实现：</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">var</span> <span class="nx">colorScale</span> <span class="o">=</span> <span class="nx">d3</span><span class="p">.</span><span class="nx">scale</span><span class="p">.</span><span class="nx">linear</span><span class="p">()</span>
  <span class="p">.</span><span class="nx">range</span><span class="p">([</span><span class="s2">&#34;#2c7bb6&#34;</span><span class="p">,</span> <span class="s2">&#34;#00a6ca&#34;</span><span class="p">,</span><span class="s2">&#34;#00ccbc&#34;</span><span class="p">,</span><span class="s2">&#34;#90eb9d&#34;</span><span class="p">,</span><span class="s2">&#34;#ffff8c&#34;</span><span class="p">,</span><span class="s2">&#34;#f9d057&#34;</span><span class="p">].</span><span class="nx">reverse</span><span class="p">());</span>

<span class="kd">var</span> <span class="nx">defs</span> <span class="o">=</span> <span class="nx">vis</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="s2">&#34;defs&#34;</span><span class="p">);</span>

<span class="kd">var</span> <span class="nx">linearGradient</span> <span class="o">=</span> <span class="nx">defs</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="s2">&#34;linearGradient&#34;</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;id&#34;</span><span class="p">,</span> <span class="s2">&#34;linear-gradient&#34;</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;x1&#34;</span><span class="p">,</span> <span class="s2">&#34;0%&#34;</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;y1&#34;</span><span class="p">,</span> <span class="s2">&#34;0%&#34;</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;x2&#34;</span><span class="p">,</span> <span class="s2">&#34;100%&#34;</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;y2&#34;</span><span class="p">,</span> <span class="s2">&#34;0%&#34;</span><span class="p">);</span>

<span class="nx">linearGradient</span><span class="p">.</span><span class="nx">selectAll</span><span class="p">(</span><span class="s2">&#34;stop&#34;</span><span class="p">)</span> 
  <span class="p">.</span><span class="nx">data</span><span class="p">(</span> <span class="nx">colorScale</span><span class="p">.</span><span class="nx">range</span><span class="p">()</span> <span class="p">)</span>                  
  <span class="p">.</span><span class="nx">enter</span><span class="p">().</span><span class="nx">append</span><span class="p">(</span><span class="s2">&#34;stop&#34;</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;offset&#34;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">,</span><span class="nx">i</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">i</span><span class="o">/</span><span class="p">(</span><span class="nx">colorScale</span><span class="p">.</span><span class="nx">range</span><span class="p">().</span><span class="nx">length</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span> <span class="p">})</span>
  <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;stop-color&#34;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">d</span><span class="p">;</span> <span class="p">});</span>
</code></pre></div><p>定义好渐变和渐变的颜色取值范围之后，就可以来绘制图例了。</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">var</span> <span class="nx">legendWidth</span> <span class="o">=</span> <span class="mi">300</span><span class="p">;</span>

<span class="kd">var</span> <span class="nx">legendsvg</span> <span class="o">=</span> <span class="nx">vis</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="s2">&#34;g&#34;</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;class&#34;</span><span class="p">,</span> <span class="s2">&#34;legendWrapper&#34;</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;transform&#34;</span><span class="p">,</span> <span class="s2">&#34;translate(&#34;</span> <span class="o">+</span> <span class="p">(</span><span class="nx">width</span><span class="o">/</span><span class="mi">2</span><span class="o">+</span><span class="nx">legendWidth</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&#34;,&#34;</span> <span class="o">+</span> <span class="p">(</span><span class="nx">height</span> <span class="o">-</span> <span class="mi">40</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&#34;)&#34;</span><span class="p">);</span>

<span class="c1">//Draw the Rectangle
</span><span class="c1"></span><span class="nx">legendsvg</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="s2">&#34;rect&#34;</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;class&#34;</span><span class="p">,</span> <span class="s2">&#34;legendRect&#34;</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="o">-</span><span class="nx">legendWidth</span><span class="o">/</span><span class="mi">2</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;y&#34;</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;width&#34;</span><span class="p">,</span> <span class="nx">legendWidth</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;height&#34;</span><span class="p">,</span> <span class="mf">3.5</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">style</span><span class="p">(</span><span class="s2">&#34;fill&#34;</span><span class="p">,</span> <span class="s2">&#34;url(#linear-gradient)&#34;</span><span class="p">);</span>
  
<span class="c1">//Append title
</span><span class="c1"></span><span class="nx">legendsvg</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="s2">&#34;text&#34;</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;class&#34;</span><span class="p">,</span> <span class="s2">&#34;legendTitle&#34;</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;y&#34;</span><span class="p">,</span> <span class="o">-</span><span class="mi">10</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">style</span><span class="p">(</span><span class="s2">&#34;text-anchor&#34;</span><span class="p">,</span> <span class="s2">&#34;middle&#34;</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">text</span><span class="p">(</span><span class="s2">&#34;Sleeping Minutes&#34;</span><span class="p">);</span>
</code></pre></div><p><img loading="lazy" src="/images/2017/03/star-with-legend-resized.png" type="" alt=""  /></p>
<p>图上的每段弧都会有鼠标移动上去的tooltip，这样可以很好的和读者大脑中的钟表隐喻对照起来，使得图表更容易理解。</p>
<p><img loading="lazy" src="/images/2017/03/star-with-tooltip-resized.png" type="" alt=""  /></p>
<p>由于我将整个圆分成了24份，这点和普通的钟表事实上有差异，那么如果加上钟表的刻度，会不会更好一些呢？从结果来看，这样的标线反而有点画蛇添足，所以我在最后的版本中去掉了钟表的标线。</p>
<p>可以看到，我们通过圆形的钟表隐喻来体现每一天的睡眠分布，然后用颜色的深浅来表示每次睡眠的时长。由于钟表的形象已经深入人心，因此读者很容易发现<code>0点</code>在圆环群的正上方。中心的黄色实心圆帮助读者视线先聚焦在最内侧的圆上，然后逐渐向外，这和日期的分布方向正好一致。</p>
<p>最终的结果在这里：<a href="http://bl.ocks.org/abruzzi/d01a221df9cf79b918a00033695092c9">心心的睡眠记录</a>，完整的<a href="https://github.com/abruzzi/health-recording">代码在这里</a>。</p>
<h4 id="更进一步">更进一步</h4>
<p>一个完整的可视化作品，不但要运用各种视觉编码来将数据转换为视觉元素，背景信息也同样重要。既然这个星空图是关于<code>睡眠主题</code>的，那么一个包含她在睡觉的图片集合则会加强这种视觉暗示，帮助读者快速理解。</p>
<h5 id="制作背景图">制作背景图</h5>
<p>我从相册中选取了很多女儿睡觉时拍的照片，现在需要有个工具将这些照片缩小成合适大小，然后拼接成一个大的图片。这其中有很多有趣的地方，比如图片有横屏、竖屏之分，有的还是正方形的，我需要让缩放的结果是正方形的，这样容易拼接一些。</p>
<p>好在有<code>imagemagick</code>这种神器，只需要一条命令就可以做到：</p>
<div class="highlight"><pre class="chroma"><code class="language-sh" data-lang="sh">$ montage *.jpg -geometry +0+0 -resize 128x128^ <span class="se">\
</span><span class="se"></span>-gravity center -crop 128x128+0+0 xinxin-sleeping.jpg
</code></pre></div><p>这条命令将当前目录下的所有的<code>jpg</code>文件缩放成128x128像素，并从中间开始裁剪<code>-gravity center</code>，<code>+0+0</code>表示图片之间的缝隙，最后将结果写入到<code>xinxin-sleeping.jpg</code>中。</p>
<p>拼接好图片之后，就可以通过CSS或者图片编辑器为其添加模糊效果，并设置深灰色半透明遮罩。</p>
<div class="highlight"><pre class="chroma"><code class="language-css" data-lang="css"><span class="nt">body</span> <span class="p">{</span>
	<span class="k">background-image</span><span class="p">:</span><span class="nb">url</span><span class="p">(</span><span class="s1">&#39;/xinxin-sleeping.png&#39;</span><span class="p">);</span>
	<span class="k">background-size</span><span class="p">:</span><span class="kc">cover</span><span class="p">;</span>
	<span class="k">background-position</span><span class="p">:</span><span class="kc">center</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div><p>当然，背景信息只是补充作用，需要避免喧宾夺主。因此图片做了模糊处理，且加上了深灰色的半透明Mask（此处应用了格式塔理论中的主体/背景原理）。</p>
<p><img loading="lazy" src="/images/2017/03/star-dark-resized.png" type="" alt=""  /></p>
<h3 id="小结">小结</h3>
<p>这篇文章讨论了可视化作品背后的一些视觉元素理论，以及人类的视觉识别机制。在这些机制的基础上，介绍了如何运用常用的设计原则来进行视觉编码。最后，通过一个实例来介绍如何运用这些元素 &ndash; 以及更重要的，这些元素的组合 &ndash; 来制作一个漂亮的、有意义的可视化图表。</p>
<h4 id="参考资料">参考资料</h4>
<p>这里有一些关于认知系统和设计原则的书籍，如果你感兴趣的话，可以用来参考</p>
<ul>
<li><a href="https://book.douban.com/subject/6792322/">《认知与设计》</a></li>
<li><a href="https://book.douban.com/subject/3323633/">《写给大家看的设计书》</a></li>
<li><a href="https://book.douban.com/subject/25833225/">《数据之美》</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>新生儿日常记录的可视化 - 星空图</title>
      <link>https://icodeit.org/2017/02/data-visualization-from-baby/</link>
      <pubDate>Wed, 22 Feb 2017 00:00:00 +0000</pubDate>
      
      <guid>https://icodeit.org/2017/02/data-visualization-from-baby/</guid>
      <description>数据来源 从女儿心心出生开始，我们就通过各种方式记录她的各种信息：睡眠记录，吃药记录，体温记录，换尿布记录，哺乳记录等等。毕竟，处于忙乱状态的人们是很难精确地回忆各种数字的，特别是在体检时面对医生的询问时。大部分父母无法准确回答小孩上周平均的睡眠时间，或者平均的小便次数，这在很多时候会影响医生的判断。
我和我老婆的手机上都安装了宝宝生活记录(Baby Tracker)（这里强烈推荐一下，免费版就很好用，不过界面下方有个讨厌的广告，我自己买了无广告的Pro版本），这样心心的每次活动我们都会记录下来，很有意思的是这个APP的数据可以以CSV格式导出（这个太棒了！），而且它自身就可以生成各种的报告，报告还可以以PDF格式导出并发送给其他应用。
有了现实世界中的一组数据 &amp;ndash; 我们记录的差不多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(&amp;#39;data/diaper_data.csv&amp;#39;, usecols=[&amp;#39;date&amp;#39;, &amp;#39;status&amp;#39;]) diaper[&amp;#39;date&amp;#39;] = pd.</description>
      <content:encoded><![CDATA[<h2 id="数据来源">数据来源</h2>
<p>从女儿<code>心心</code>出生开始，我们就通过各种方式记录她的各种信息：睡眠记录，吃药记录，体温记录，换尿布记录，哺乳记录等等。毕竟，处于忙乱状态的人们是很难精确地回忆各种数字的，特别是在体检时面对医生的询问时。大部分父母无法准确回答小孩上周平均的睡眠时间，或者平均的小便次数，这在很多时候会影响医生的判断。</p>
<p>我和我老婆的手机上都安装了<a href="http://www.nighp.com/babytracker/">宝宝生活记录(Baby Tracker)</a>（这里强烈推荐一下，免费版就很好用，不过界面下方有个讨厌的广告，我自己买了无广告的Pro版本），这样<code>心心</code>的每次活动我们都会记录下来，很有意思的是这个APP的数据可以以<code>CSV</code>格式导出（这个太棒了！），而且它自身就可以生成各种的报告，报告还可以以PDF格式导出并发送给其他应用。</p>
<p><img loading="lazy" src="/images/2017/02/xinxin-chart-resized.png" type="" alt=""  /></p>
<p>有了现实世界中的一组数据 &ndash; 我们记录的差不多100天的数据，而且正好我最近在复习D3相关的知识，正好可以用来做一些有趣的练习。</p>
<h3 id="数据准备">数据准备</h3>
<p>从<code>Baby Tracker</code>导出的数据是一些CSV文件组成是压缩包，解压之后大致结果是这样的：</p>
<ul>
<li>哺乳记录</li>
<li>睡眠记录</li>
<li>换尿布记录</li>
<li>喂药/体温记录</li>
<li>里程碑记录</li>
</ul>
<p>我就从最简单换尿布数据记录开始吧。我们首先需要将数据做一些清洗和归一化，这样方便前端页面的计算和渲染。数据处理我一般会选择<code>Python+Pandas</code>的组合，只需要写很少的代码就可以完成任务。</p>
<h4 id="python--pandas">python + pandas</h4>
<p>原始数据看起来是这样的：</p>
<pre><code>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,嘘嘘+便便
</code></pre><p>为了方便展示，我需要将数据统计成这样：</p>
<pre><code>date,urinate,stool
2016-11-13,2,2
2016-11-14,3,6
2016-11-15,6,8
</code></pre><p>我不关心每一天不同时刻换尿布的事件本身，只关心每天中，大小便的次数分布，也就是说，我需要这三项数据：<code>日期</code>，<code>当天的小便次数</code>，<code>当天的大便次数</code>。这个用<code>pandas</code>很容易就可以整理出来了，<code>status</code>字段的做一个微小的函数转换（当然可以写的更漂亮，不过在这里不是重点，暂时跳过）:</p>
<div class="highlight"><pre class="chroma"><code class="language-py" data-lang="py"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
<span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="nn">pd</span>

<span class="n">diaper</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_csv</span><span class="p">(</span><span class="s1">&#39;data/diaper_data.csv&#39;</span><span class="p">,</span> <span class="n">usecols</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;date&#39;</span><span class="p">,</span> <span class="s1">&#39;status&#39;</span><span class="p">])</span>
<span class="n">diaper</span><span class="p">[</span><span class="s1">&#39;date&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">to_datetime</span><span class="p">(</span><span class="n">diaper</span><span class="p">[</span><span class="s1">&#39;date&#39;</span><span class="p">],</span> <span class="nb">format</span><span class="o">=</span><span class="s1">&#39;%Y/%m/</span><span class="si">%d</span><span class="s1"> %H:%M&#39;</span><span class="p">)</span>
<span class="n">diaper</span><span class="o">.</span><span class="n">index</span><span class="o">=</span><span class="n">diaper</span><span class="p">[</span><span class="s1">&#39;date&#39;</span><span class="p">]</span>

<span class="k">def</span> <span class="nf">mapper</span><span class="p">(</span><span class="n">key</span><span class="p">):</span>
    <span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="s1">&#39;嘘嘘&#39;</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">pd</span><span class="o">.</span><span class="n">Series</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="n">index</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;urinate&#39;</span><span class="p">,</span> <span class="s1">&#39;stool&#39;</span><span class="p">])</span>
    <span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="s1">&#39;便便&#39;</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">pd</span><span class="o">.</span><span class="n">Series</span><span class="p">([</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="n">index</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;urinate&#39;</span><span class="p">,</span> <span class="s1">&#39;stool&#39;</span><span class="p">])</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">pd</span><span class="o">.</span><span class="n">Series</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="n">index</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;urinate&#39;</span><span class="p">,</span> <span class="s1">&#39;stool&#39;</span><span class="p">])</span>
    
<span class="n">converted</span> <span class="o">=</span> <span class="n">diaper</span><span class="p">[</span><span class="s1">&#39;status&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">apply</span><span class="p">(</span><span class="n">mapper</span><span class="p">)</span>
<span class="n">diaper</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">concat</span><span class="p">([</span><span class="n">diaper</span><span class="p">,</span> <span class="n">converted</span><span class="p">],</span> <span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>


<span class="n">grouped</span> <span class="o">=</span> <span class="n">diaper</span><span class="o">.</span><span class="n">groupby</span><span class="p">(</span><span class="n">pd</span><span class="o">.</span><span class="n">TimeGrouper</span><span class="p">(</span><span class="s1">&#39;D&#39;</span><span class="p">))</span>

<span class="n">result</span> <span class="o">=</span> <span class="n">grouped</span><span class="o">.</span><span class="n">aggregate</span><span class="p">(</span><span class="n">np</span><span class="o">.</span><span class="n">sum</span><span class="p">)</span>
<span class="n">result</span><span class="o">.</span><span class="n">to_csv</span><span class="p">(</span><span class="s1">&#39;data/diaper_normolized.csv&#39;</span><span class="p">)</span>
</code></pre></div><p>这里的<code>pd.TimeGrouper('D')</code>表示按天分组。好了，存起来的<code>diaper_normolized.csv</code>文件就是我们想要的了，接下来就看如何可视化了。</p>
<h3 id="可视化">可视化</h3>
<p>仔细看一下数据，自然的想法是横坐标为日期，纵坐标为嘘嘘/便便的次数，然后分别将嘘嘘和便便的绘制成曲线即可。这个例子我使用<code>D3</code>来做可视化的工具，<code>D3</code>本身的API层次比较偏底层，这点和<code>jQuery</code>有点类似。</p>
<h4 id="尝试1---曲线图">尝试1 - 曲线图</h4>
<p>最简单的情况，只需要定义两条线条函数。</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">var</span> <span class="nx">valueline</span> <span class="o">=</span> <span class="nx">d3</span><span class="p">.</span><span class="nx">svg</span><span class="p">.</span><span class="nx">line</span><span class="p">()</span>
	<span class="p">.</span><span class="nx">x</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">x</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">date</span><span class="p">);</span>
	<span class="p">})</span>
	<span class="p">.</span><span class="nx">y</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">y</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">urinate</span><span class="p">);</span>
	<span class="p">});</span>

<span class="kd">var</span> <span class="nx">stoolline</span> <span class="o">=</span> <span class="nx">d3</span><span class="p">.</span><span class="nx">svg</span><span class="p">.</span><span class="nx">line</span><span class="p">()</span>
	<span class="p">.</span><span class="nx">x</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">x</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">date</span><span class="p">);</span>
	<span class="p">})</span>
	<span class="p">.</span><span class="nx">y</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">y</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">stool</span><span class="p">);</span>
	<span class="p">});</span>
</code></pre></div><p><img loading="lazy" src="/images/2017/02/xinxin-curve-hard-resized.png" type="" alt=""  /></p>
<p>可以看到，直接将点连接起来，线条的拐点看起来会非常的尖锐。这个可以通过使用D3提供的插值函数来解决，比如采用<code>basis</code>方式插值：</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">var</span> <span class="nx">valueline</span> <span class="o">=</span> <span class="nx">d3</span><span class="p">.</span><span class="nx">svg</span><span class="p">.</span><span class="nx">line</span><span class="p">()</span>
	<span class="p">.</span><span class="nx">interpolate</span><span class="p">(</span><span class="s1">&#39;basis&#39;</span><span class="p">)</span>
	<span class="p">.</span><span class="nx">x</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">x</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">date</span><span class="p">);</span>
	<span class="p">})</span>
	<span class="p">.</span><span class="nx">y</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">y</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">urinate</span><span class="p">);</span>
	<span class="p">});</span>
</code></pre></div><p><img loading="lazy" src="/images/2017/02/xinxin-curve-resized.png" type="" alt=""  /></p>
<p>曲线图倒是看起来比较简单，可以看出基本的走势。比如<code>新生儿</code>阶段，大小便的次数都比较多，随着月龄的增长，呈现出了下降的趋势，而且便便次数降低了很多。</p>
<h4 id="尝试2---散点图气泡图">尝试2 - 散点图（气泡图）</h4>
<p>曲线图看起来并不是太直观，我们接下来尝试一下其他的图表类型。比如散点图是一个比较好的选择：</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">svg</span><span class="p">.</span><span class="nx">selectAll</span><span class="p">(</span><span class="s2">&#34;dot&#34;</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">data</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">enter</span><span class="p">().</span><span class="nx">append</span><span class="p">(</span><span class="s2">&#34;circle&#34;</span><span class="p">)</span>
  	<span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s1">&#39;stroke&#39;</span><span class="p">,</span> <span class="s1">&#39;#FD8D3C&#39;</span><span class="p">)</span>
  	<span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s1">&#39;fill&#39;</span><span class="p">,</span> <span class="s1">&#39;#FD8D3C&#39;</span><span class="p">)</span>
  	<span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s1">&#39;opacity&#39;</span><span class="p">,</span> <span class="s2">&#34;.7&#34;</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span><span class="k">return</span> <span class="mi">3</span><span class="p">;})</span>
    <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;cx&#34;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">x</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">date</span><span class="p">);</span> <span class="p">})</span>
    <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;cy&#34;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">y</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">urinate</span><span class="p">);</span> <span class="p">});</span>
</code></pre></div><p><img loading="lazy" src="/images/2017/02/xinxin-diaper-scatter-resized.png" type="" alt=""  /></p>
<p>这里还使用了不同的颜色来区分嘘嘘和便便，但是信息强调的也不够充分。这时候可以通过<code>尺寸</code>的不同，<code>色彩饱和度</code>的差异再次强调各个点之间的对比：</p>
<div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">svg</span><span class="p">.</span><span class="nx">selectAll</span><span class="p">(</span><span class="s2">&#34;dot&#34;</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">data</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">enter</span><span class="p">().</span><span class="nx">append</span><span class="p">(</span><span class="s2">&#34;circle&#34;</span><span class="p">)</span>
  	<span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s1">&#39;stroke&#39;</span><span class="p">,</span> <span class="s1">&#39;#FD8D3C&#39;</span><span class="p">)</span>
  	<span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s1">&#39;fill&#39;</span><span class="p">,</span> <span class="s1">&#39;#FD8D3C&#39;</span><span class="p">)</span>
  	<span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s1">&#39;opacity&#39;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span><span class="k">return</span> <span class="nx">d</span><span class="p">.</span><span class="nx">urinate</span> <span class="o">*</span> <span class="mf">0.099</span><span class="p">})</span>
    <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span><span class="k">return</span> <span class="nx">d</span><span class="p">.</span><span class="nx">urinate</span><span class="p">;})</span>
    <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;cx&#34;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">x</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">date</span><span class="p">);</span> <span class="p">})</span>
    <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="s2">&#34;cy&#34;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">y</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">urinate</span><span class="p">);</span> <span class="p">})</span>
    <span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s2">&#34;mouseover&#34;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span>
		<span class="nx">div</span><span class="p">.</span><span class="nx">html</span><span class="p">(</span><span class="nx">formatTime</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">date</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&#34;, 嘘嘘: &#34;</span>  <span class="o">+</span> <span class="nx">d</span><span class="p">.</span><span class="nx">urinate</span> <span class="o">+</span> <span class="s2">&#34;次&#34;</span><span class="p">)</span>
		<span class="p">.</span><span class="nx">style</span><span class="p">(</span><span class="s2">&#34;left&#34;</span><span class="p">,</span> <span class="p">(</span><span class="nx">d3</span><span class="p">.</span><span class="nx">event</span><span class="p">.</span><span class="nx">pageX</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&#34;px&#34;</span><span class="p">)</span>
		<span class="p">.</span><span class="nx">style</span><span class="p">(</span><span class="s2">&#34;top&#34;</span><span class="p">,</span> <span class="p">(</span><span class="nx">d3</span><span class="p">.</span><span class="nx">event</span><span class="p">.</span><span class="nx">pageY</span> <span class="o">-</span> <span class="mi">28</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&#34;px&#34;</span><span class="p">)</span>
		<span class="p">.</span><span class="nx">style</span><span class="p">(</span><span class="s2">&#34;opacity&#34;</span><span class="p">,</span> <span class="p">.</span><span class="mi">9</span><span class="p">)</span>
		<span class="p">.</span><span class="nx">style</span><span class="p">(</span><span class="s2">&#34;background&#34;</span><span class="p">,</span> <span class="s2">&#34;#FD8D3C&#34;</span><span class="p">);</span>
	<span class="p">})</span>
	<span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s2">&#34;mouseout&#34;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span> 
		<span class="nx">div</span><span class="p">.</span><span class="nx">style</span><span class="p">(</span><span class="s2">&#34;opacity&#34;</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
	<span class="p">});</span>
</code></pre></div><p><img loading="lazy" src="/images/2017/02/xinxin-diaper-bubble-resized.png" type="" alt=""  /></p>
<p>此处的圆的半径与嘘嘘次数相关，圆的透明度也和嘘嘘的次数相关，这样从不同的视觉编码上重复强调数据的差异，效果比单纯的曲线图和散点图会更好一些。</p>
<h3 id="一点理论">一点理论</h3>
<p>数据可视化过程可以分为这样几个步骤：</p>
<ol>
<li>明确可视化的目的</li>
<li>数据获取</li>
<li>数据预处理</li>
<li>选择合适的图表</li>
<li>结果呈现</li>
</ol>
<p>当然，可视化本身就是一个需要不断迭代的过程，步骤的2-5可能会经过多次迭代和修正：比如在呈现之后发现有信息没有充分展现，则需要回退到图表选择上，而不同的图表需要的数据又可能会有不同，我们可能需要又回到数据预处理、甚至数据获取阶段。</p>
<h4 id="选择合适的图表">选择合适的图表</h4>
<p>对于新手而言，图表的选择是非常困难的。同样的数据集，用不同的图表展现，效果会有很大差异。另一方面，对于手头的数据集，做何种预处理并以不同的角度来展现也同样充满挑战。</p>
<p>不过好在已经有人做过相关的研究，并形成了一个非常简便可行的Matrix：</p>
<ul>
<li><a href="http://extremepresentation.typepad.com/blog/2015/01/announcing-the-slide-chooser.html">选取合适的图表</a></li>
<li><a href="http://extremepresentation.typepad.com/blog/2015/01/announcing-the-slide-chooser.html">选取合适的图表之新版</a></li>
</ul>
<p>通过这个Martix，你可以根据变量的数量，变量的类型很方便的选取合适的图表格式。这个表可以保证你不出大的差错，最起码可以很清晰的将结果展现出来。</p>
<h4 id="视觉编码visual-encoding">视觉编码（Visual Encoding）</h4>
<p>视觉编码，简而言之就是将数据映射为可视化的元素，这些元素可以帮助人们很快的区分出数据的差异。比如通过颜色的不同来区分<code>分类型元素</code>(亚太区收入，亚洲区收入)，通过长度的不同来表示数量的不同等等。视觉编码有很多不同的元素：</p>
<ol>
<li>位置</li>
<li>形状（体积）</li>
<li>纹理</li>
<li>角度</li>
<li>长度</li>
<li>色彩</li>
<li>色彩饱和度</li>
</ol>
<p><img loading="lazy" src="/images/2017/02/visual-encoding.png" type="" alt="Semiology of Graphics"  /></p>
<blockquote>
<p>Within the plane a mark can be at the top or the bottom, to the right or the left. The eye perceives two independent dimensions along X and Y, which are distinguished orthogonally. A variation in light energy produces a third dimension in Z, which is independent of X and Y…</p>
<p>The eye is sensitive, along the Z dimension, to 6 independent visual variables, which can be superimposed on the planar figures: the size of the marks, their value, texture, color, orientation, and shape. They can represent differences (≠), similarities (≡), a quantified order (Q), or a nonquantified order (O), and can express groups, hierarchies, or vertical movements.</p>
</blockquote>
<p><a href="http://esripress.esri.com/display/index.cfm?fuseaction=display&amp;websiteID=190">来源：Semiology of Graphics</a></p>
<p>而且，人类对这些不同元素的认知是不同的，比如人们更容易辨识位置的不同，而比较难以区分饱和度的差异。这也是为什么大部分图表都会有坐标轴和标尺的原因（当然，还有网格），而像饼图这样的图形则很难让读者从形状上看出不同部分的差异。</p>
<p>Jock Mackinlay发表过关于不同视觉编码优先级的研究：</p>
<p><img loading="lazy" src="/images/2017/02/mackinlay-hierarchy-resized.png" type="" alt="Mackinlay"  /></p>
<p>越靠近上面的元素，在人眼所能识别的精度就越高。在图表类型的选取上，也需要充分考虑这些研究的成果，选取合理的视觉编码。</p>
<p>正如前面所说，可视化是一个不断迭代的过程，要将同样的信息展现出来，可能需要尝试不同的视觉编码的组合，比如：</p>
<p><img loading="lazy" src="/images/2017/02/xinxin-other-resized.png" type="" alt=""  /></p>
<p>上面几个图表对应的<a href="https://github.com/abruzzi/health-recording">代码在这里</a>，下一篇我们来看看数据可视化的其他知识。</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
