<audio id="audio" title="34 | 数据处理（二）：如何处理多元变量？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/19/28/196bdd87cf3a1af3b4dbe228cf608c28.mp3"></audio>

你好，我是月影。

数据处理是一门很深的学问，想要学好它，我们不仅需要掌握很复杂的理论，还需要不断地积累经验。不过，其中也有一些基础的数据处理技巧，掌握它们，我们就能更好地入门可视化了。

比如我们上节课重点讲解的数据分类，就是其中一种非常基础的数据处理技巧，也是数据处理的第一步。这一节课，我会以处理2014年北京市的天气历史数据为例，来和你进一步讨论数据处理的基础技巧，包括从数据到图表的展现以及处理多元变量的方法。

## 从数据到图表展现

一般来说，我们拿到的原始数据通常可以组织成表格的形式，表格中会有很多列，每一列都代表一个变量。比如，我们拿到的这份天气历史数据，它看起来可能是下面这样的：

<img src="https://static001.geekbang.org/resource/image/24/48/24b87b9076ab5df4c9c8ed486a6dc948.jpg" alt="">

这里有许多变量，比如时间、最高气温、平均气温、最低气温、最高湿度、平均湿度、最低湿度、露点等等。一般的情况下，我们会将其中我们最关心的一个变量平均气温，用一个图表展现出来。具体怎么做呢？我们可以来动手操作一下。

这份数据是csv格式的，是一张表，我们先用D3.js将数据读取出来，然后结构化成JSON对象。

```
const rawData = await (await fetch('beijing_2014.csv')).text();
const data = d3.csvParse(rawData);
const dataset = data.filter(d =&gt; new Date(d.Date).getMonth() &lt; 3)
  .map(d =&gt; {return {temperature: Number(d['Temperature(Celsius)(avg)']), date: d.Date, category: '平均气温'}});
console.log(dataset);

```

如上面代码所示，我们通过fetch读取csv的数据。CSV文件格式是用逗号和回车分隔的文本，所以我们用.text()读取内容。然后我们使用d3的csvParse方法，将数据解析成JSON数组。最后，我们再通过数组的filter和map，将我们感兴趣的数据取出来。这里，我们截取了1月到3月的平均气温数据。

取到了想要的数据，接下来我们就可以将它展示出来了，这一步我们可以使用数据驱动框架。在预习篇我们讲过，数据驱动框架是一种特殊的库，它们更专注于处理数据的组织形式，将数据呈现交给更底层的图形系统（DOM、SVG、Canvas）或通用图形库（SpriteJS、ThreeJS）去完成。

但是，为了方便你理解，这里我就不使用数据驱动框架了，而是直接采用一个图表库[QCharts](https://www.qcharts.cn/)，它是一个基于SpriteJS设计的图表库。与数据驱动框架相比，图表库虽然减少了灵活性，但是使用上更加方便，通过一些简单的配置，我们就可以完成图表的渲染。

用来展示平均气温最常见的图表就是折线图，展示折线图的过程可以简单分为4步：第一步是创建图表（Chart）并传入数据；第二步是创建图形（Visual)，这里我们创建的是折线图，所以使用Line对象；第三步是创建横、纵两个坐标轴（Axis）、提示（ToolTip）和一个图例（Legend）；最后一步是将图形、坐标轴、提示和图例都添加到图表上。具体的代码如下：

```
const { Chart, Line, Legend, Tooltip, Axis } = qcharts;
const chart = new Chart({
  container: '#app'
});
let clientRect={bottom:50};
chart.source(dataset, {
  row: 'category',
  value: 'temperature',
  text: 'date'
});

const line = new Line({clientRect});
const axisBottom = new Axis({clientRect}).style('grid', false);
axisBottom.attr('formatter', d =&gt; '');
const toolTip = new Tooltip({
  title: arr =&gt; {
    return arr.category
  }
});
const legend = new Legend();
const axisLeft = new Axis({ orient: 'left',clientRect }).style('axis', false).style('scale', false);

chart.append([line, axisBottom, axisLeft, toolTip, legend]);

```

这样，我们就将图表渲染到画布上了。

<img src="https://static001.geekbang.org/resource/image/3c/fd/3c35aed4df29c2617d6d877fed588ffd.jpg" alt="">

## 处理多元变量

刚才我们已经成功将平均气温这个变量用折线图展示出来了，但在很多数据可视化场景里，我们不只会关心一个变量，还会关注多个变量，比如，我们需要同时关注温度和湿度数据。那怎么才能把多个变量绘制在同一张图表上呢？换句话说，同一张图表怎么展示多元变量呢？

### 在一张图表上绘制多元变量

最简单的方式是直接在图表上同时绘制多个变量，每个变量对应一个图形，这样一张图表上就同时显示多个图形。

我们直接以刚才的代码为例，现在，我们修改例子中的代码，直接添加平均湿度数据，代码如下：

```
const rawData = await (await fetch('beijing_2014.csv')).text();
const data = d3.csvParse(rawData).filter(d =&gt; new Date(d.Date).getMonth() &lt; 3);
const dataset1 = data
  .map(d =&gt; {
    return {
      value: Number(d['Temperature(Celsius)(avg)']),
      date: d.Date,
      category: '平均气温'}
    });
const dataset2 = data
  .map(d =&gt; {
      return {
        value: Number(d['Humidity(%)(avg)']),
        date: d.Date,
        category: '平均湿度'}
      });

```

然后，我们修改图表的数据，将温度（dataset1）和湿度（dataset2）数据都传入图表，代码如下：

```
chart.source([...dataset1, ...dataset2], {
  row: 'category',
  value: 'value',
  text: 'date'
});

```

这样，我们就得到了同时显示温度和湿度数据的折线图。

<img src="https://static001.geekbang.org/resource/image/8a/f5/8a988aff89201fabea2d63629663ebf5.jpg" alt="">

### 用散点图分析变量的相关性

不过，你应该也发现了，把温度和深度同时绘制到一张折线图之后，我们很难直观地看出温度与湿度的相关性。所以，如果我们希望了解2014年全年，北京市温度和湿度之间的关联性，我们还得用另外的方式。那都有哪些方式呢？

一般来说，要分析两个变量的相关性，我们可以使用散点图，散点图有两个坐标轴，其中一个坐标轴表示变量A，另一个坐标轴表示变量B。这里，我们将平均温度、相对湿度数据获取出来，然后用QCharts的散点图（Scatter）来渲染。具体的代码和示意图如下：

```
const rawData = await (await fetch('beijing_2014.csv')).text();
const data = d3.csvParse(rawData);
console.log(data);
const dataset = data
  .map(d =&gt; {
    return {
      temperature: Number(d['Temperature(Celsius)(avg)']),
      humdity: Number(d['Humidity(%)(avg)']),
      category: '平均气温与湿度'}
    });

const { Chart, Scatter, Legend, Tooltip, Axis } = qcharts;
const chart = new Chart({
  container: '#app'
});
let clientRect={bottom:50};
chart.source(dataset, {
  row: 'category',
  value: 'temperature',
  text: 'humdity'
});

const scatter = new Scatter({
  clientRect,
  showGuideLine: true,
});
const toolTip = new Tooltip({
  title: (data) =&gt; '温度与湿度：',
  formatter: (data) =&gt; {
    return `温度：${data.temperature}C  湿度：${data.humdity}% `
  }
});
const legend = new Legend();
const axisLeft = new Axis({ orient: 'left',clientRect }).style('axis', false).style('scale', false);
const axisBottom = new Axis();

chart.append([scatter, axisBottom, axisLeft, toolTip, legend]);

```

<img src="https://static001.geekbang.org/resource/image/de/01/dec1994d0a99794182f581924d17ef01.jpg" alt="">

从这个图表我们可以看出，平均温度和相对湿度并没有相关性，所以点的空间分布比较随机。事实上也是如此，气温和绝对湿度有关，但相对湿度因为已经考虑过了温度因素，所以就和气温没有相关性了。

那你可能会有疑问，相关的图形长什么样呢？我们可以用另外两个变量，比如露点和平均温度，来试试看能不能画出相关的散点图。

我们先来说说什么是露点。在空气中水汽含量不变， 并且气压一定的情况下， 空气能够冷却达到饱和时的温度就叫做露点温度， 简称露点， 它的单位与气温相同。

从定义里我们知道，露点和温度与湿度都有相关性。接下来，我们来看一下露点和温度的相关性在散点图中是怎么体现的。很简单，我们只要修改一下上面代码里的数据，把平均湿度换成平均露点温度就行了。

```
const dataset = data
  .map(d =&gt; {
    return {
      temperature: Number(d['Temperature(Celsius)(avg)']),
      tdp: Number(d['Dew Point(Celsius)(avg)']),
      category: '平均气温与露点'}
    });

```

这样，我们最终呈现出来的散点图具有典型的数据正相关性，也就是说图形的点更集中在对角线附近的区域。

<img src="https://static001.geekbang.org/resource/image/48/1a/483712c909fcfd962072a2c19caaeb1a.jpg" alt="">

我们还可以把湿度数据也加上。

```
const dataset = data
  .map(d =&gt; {
    return {
      value: Number(d['Temperature(Celsius)(avg)']),
      tdp: Number(d['Dew Point(Celsius)(avg)']),
      category: '平均气温与露点'}
    });
const dataset2 = data
  .map(d =&gt; {
    return {
      value: Number(d['Humidity(%)(avg)']),
      tdp: Number(d['Dew Point(Celsius)(avg)']),
      category: '平均湿度与露点'}
    });
   
...
chart.source([...dataset, ...dataset2], {
  row: 'category',
  value: 'value',
  text: 'tdp'
});

```

我们发现，平均湿度和露点也是成正相关的，不过露点与温度的相关性更强，因为散点更集中一些。

<img src="https://static001.geekbang.org/resource/image/6d/86/6dcd043da6c8338865313105733f5286.jpg" alt="">

为了再强化理解，我们还可以看一组强相关的数据，比如平均温度和最低温度，你会发现，图上的散点基本上就在对角线上。

<img src="https://static001.geekbang.org/resource/image/2b/07/2b2c8f25ed11aeb7a00aac436a2f4e07.jpg" alt="">

总的来说，两个数据的散点越集中在对角线，说明这两个数据的相关性越强，当然这么说还不够严谨只是方便我们记忆而已。这里，我找到了一张散点图和相关性之间关系的总结图，你可以多了解一下。

<img src="https://static001.geekbang.org/resource/image/44/61/44ac95977e2143d67c079ec10a9c7661.jpg" alt="">

### 散点图的扩展

通过前面的例子，我们可以看到，用散点图可以分析出数据的二元变量之间的相关性，这对数据可视化场景的信息处理非常有用。不过，散点图也有明显的局限性，那就是它的维度只有二维，所以它一般只能处理二元变量，超过二维的多元变量的相关性，它处理起来就有些力不从心了。

不过，我们还不想放弃散点图在相关性上的优异表现。所以在处理高维度数据时，我们可以对散点图进行扩展，比如引入颜色、透明度、大小等信息来表示额外的数据维度，这样就可以处理多维数据了。

举个例子，我在下面给出了一张根据加州房产数据集制作的散点图。其中，点的大小代表街区人口数量、透明度代表人口密度，而颜色代表房价高低，并且加上经纬度代表点的位置。这个散点图一共表示了五维的变量（经度、纬度、人口总数、人口密度、房价高低），将它们都呈现在了一张图上，这在一定程度上表达了这些变量的相关信息。

[<img src="https://static001.geekbang.org/resource/image/bc/61/bcbf60ff83cf90a4d5e05d4f78b2b161.jpeg" alt="" title="图片来源：知乎">](https://zhuanlan.zhihu.com/p/141118125)

这里，我带你做个简单的分析。从这张图上，我们很容易就可以得出两个结论，第一个是，房价比较高的区域集中于两个中心，并且都靠近海湾。第二个是房价高的地方对应的人口密集度也高。

这就是用散点图处理多维数据的方法了。

### 其他图表形式

事实上，处理多维信息，我们还可以用其他的图表展现形式，比如用晴雨表来表示数据变化的趋势就比较合适。北大可视化实验室在疫情期间就制作了一张疫情数据晴雨表，你能明显看出每个省份每天的疫情变化。如下所示：

[<img src="https://static001.geekbang.org/resource/image/48/93/4837642324296791cd00230938bc2e93.jpg" alt="" title="图片来源：mp.weixin.qq.com">](https://mp.weixin.qq.com/s/Nq0-p6z1GO869XaS0zliiw)

再比如，还有[平行坐标图](https://ww2.mathworks.cn/help/stats/examples/visualizing-multivariate-data.html)。平行坐标图也有横纵两个坐标轴，并且把要进行对比的五个不同参数都放在了水平方向的坐标上。在下面的示意图中，绘制了所有4缸、6缸或8缸汽车在五个不同参数（变量）上的对比。

<img src="https://static001.geekbang.org/resource/image/23/26/23948ddb9761907d3fd1316c506d3726.jpeg" alt="" title="左为平行坐标图，右为热力图">

此外，我们还可以用[热力图](https://www.jianshu.com/p/a575e53bcaa9)、[三维直方图](https://www.jianshu.com/p/a575e53bcaa9)、[三维气泡图](http://https://zhuanlan.zhihu.com/p/147243101)等等其他的可视化形式来展现多维度的信息。

<img src="https://static001.geekbang.org/resource/image/d8/1e/d805e7f932c3403c9732fec8bae1141e.jpeg" alt="" title="左为三维直方图，右为三维气泡图">

总之，这些数据展现形式的基本实现原理，我们都在前面的视觉篇中讲过了。在掌握了视觉基础知识之后，我们就可以用自己想要的呈现形式，自由发挥，设计出各种直观的、形式有趣的图表了。

## 要点总结

这一节课，我们主要讨论数据到图表的展现以及如何处理多元变量。

在数据到图表的展现中，我们首先用d3.js把原始数据从csv中读取出来，然后选择我们需要的数据，用简单的图表库，比如，使用QCharts图表库进行渲染。渲染过程可以分为4步，分别是：创建图表对象 Chart并传入数据，创建图形Visual，创建坐标轴、提示和图例，把图形、坐标轴、提示和图例添加到图表对象中完成渲染。

在处理多元变量的时候，我们可以用散点图表示变量的相关性。对于超过二维的数据，我们可以扩展散点图，调整颜色、大小、透明度等等手段表达额外的信息。除了散点图之外，我们还可以用晴雨表、平行坐标图、热力图、三维直方图、气泡图等等图表，来表示多维数据的相关性。

到这里，我们用两节课的时间讲完了可视化的数据处理的基础部分。这部分内容如果再深入下去，就触达了数据分析的领域了，这也是一个和可视化密切相关的学科。那我也说过，可视化的重点，一是数据、二是视觉，视觉往深入研究，就进入渲染引擎、游戏等等领域，数据往深入研究，就进入数据分析的领域。所以，在可视化的入门或者说是基础阶段，掌握我现在讲的这些基础知识就够了。当然，如果你想深入研究也是好事，你可以参考我在课后给出的文章好好阅读一下。

## 小试牛刀

<li>
我在GitHub代码仓库里放了两份数据，一份是我们今天讲课用到的，另一份是[2013到2018年的全国各地空气质量数据](https://github.com/akira-cn/graphics/blob/master/data/weather/2013-2018.csv)。你能把2014年北京每日PM2.5的数据用折线图表示出来吗？你还能结合这两份数据，用散点图分析平均气温和PM2.5指数的相关性吗？
</li>
<li>
你可以模仿我北大可视化实验室的疫情晴雨表，实现一个2018年全国各城市空气质量晴雨表吗？
</li>

欢迎在留言区和我讨论，分享你的答案和思考，也欢迎你把这节课分享给你的朋友，我们下节课见！

## 源码

[课程中完整示例代码见GitHub仓库](https://github.com/akira-cn/graphics/tree/master/data/weather)

## 推荐阅读

[1] [从1维到6维-多维数据可视化策略](https://zhuanlan.zhihu.com/p/147243101)

[2] [QCharts](https://www.qcharts.cn/)
