Skip to content
On this page

分析-表格带你浅尝数据分析


从这一章开始我们将进入分析部分,这部分我们将用 Sparrow 去可视化数据,去解决下面提出的问题:

  1. 揭露中世纪是一个压抑的时代。(展现时代和哲学家数量的相关性和异常值)。
  2. 证明哲学家谈论的都是一些永恒的问题。(展现哲学家观点的特征)。
  3. 哲学家,哲学问题和流派的数量关系是怎样的?(展现哲学家,哲学问题和流派的数量的特征)。
  4. 哲学问题,哲学家和流派的数量随着时间是如何变化的?(探索哲学问题,哲学家,流派的数量的趋势,以及它们和时间的依赖关系)。
  5. 有多少哲学家的寿命超过了40岁,其中年龄最大和最小分别是多少?(探索哲学家寿命的分布和极值)。
  6. 每个哲学家回答了哪些问题?每个问题有哪些哲学家回答了?哲学家和问题之间的关系又如何?(探索哲学家和问题——网络数据之间的拓扑结构)。
  7. 每个流派有哪些哲学家?哪个流派的哲学家比较多?哪个比较少?(探索哲学家和流派——网络数据之间的拓扑结构)。
  8. 哲学的中心是怎么变化的的?(哲学家聚集地点——几何数据的形状)。

一般来说,在真正设计我们的可视化之前,我们大概看一下数据,然后再确定可视化设计。了解数据最基础的手段就是通过表格,那么这一章我们就用表格先去了解一下 philosophers.json 这份数据。

Sparrow

首先我们用我们的 Sparrow 去绘制表格,这里的思路就是用 Text 几何元素去绘制。具体的代码和效果如下:

javascript
(async () => {
  // 获得数据
  const response = await fetch(
    "https://gw.alipayobjects.com/os/bmw-prod/d345d2d7-a35d-4d27-af92-4982b3e6b213.json"
  );
  const data = await response.json();
  
  // 行头
  const keys = ["id", "name", "country", "lifespan", "points"];
  
  return sp.plot({
    data,
    type: "layer",
    transforms: [
      (data) => data.filter((_, i) => i < 10),
      // 预处理数据
      (data) =>
        data.map(({ lifespan, points, ...rest }) => ({
          ...rest,
          lifespan: `[${lifespan[0]}, ${lifespan[1]}]`,
          points: `[${points
            .slice(0, 2)
            .map((d) => d.slice(0, 2) + "...")
            .join(", ")}]`,
        })),
      // 转换成一系列文本
      (data) => {
        const ths = ["index", ...keys].map((key) => ({
          index: -1,
          key,
          value: key,
          header: true,
        }));
        const tds = data.flatMap((d, i) => {
          const cell = keys.map((key) => ({ index: i, key, value: d[key] }));
          return [...cell, { index: i, key: "index", value: i }];
        });
        return [...ths, ...tds].reverse();
      },
    ],
    scales: {
      y: { type: "band", padding: 0 },
      x: { domain: ["index", ...keys], padding: 0 },
      fontWeight: { type: "identity" },
    },
    width: 900,
    guides: {
      x: { display: false },
      y: { display: false },
    },
    encodings: {
      y: "index",
      x: "key",
    },
    children: [
      { // 背景的表格
        type: "cell",
        encodings: {
          fill: "none",
          stroke: "#eee",
        },
      },
      { // 前面的文字
        type: "text",
        paddingTop: 10,
        paddingLeft: 10,
        paddingBottom: 10,
        encodings: {
          text: (d) => (d.header ? d.value.toUpperCase() : d.value),
          fontWeight: (d) => (d.header ? "bold" : "normal"),
        },
        styles: {
          dx: "0.5em",
          dy: "-1em",
        },
      },
    ],
  });
})();

image.png 可以发现 Sparrow 确实也可以绘制简单的表格,但是也存在不少问题:

  • 容器高度不能根据行数自适应,所以只能绘制少部分数据。
  • 不具有交互功能:不能改变每个行或者列的占比。
  • 绘制交叉表会比较麻烦,只适合绘制非嵌套的数据。

那么接下来我们就试试 AntV 的 S2,去绘制相同的数据,并且找出哲学家观点关键词的信息洞察

任务分析

我们先来分析一下“找出哲学家观点关键词的信息洞察” 这个任务。

这个任务需要分析哲学和问题之间的关系,分成几步:

  • 罗列出哲学家信息和观点,然后将观点进行分词,获得所有关键词以及出现频次,获得要分析的数据。
  • 基于原生数据进行基本数据分析,确定数据间的基本关联关系。
  • 使用表可视化分析引擎 [S2] 绘制多维分析表。
  • 为了更加直观看出哲学家思考方向的差异性,我们使用不同背景颜色表现哲学家关键词的权重占比。
  • 获取数据洞察。

可视分析

数据处理

首先我们处理一下 philosophers.json 这里的数据,获取所有哲学家的观点列表,以及观点中的关键字所占词频,然后使用社区开源的 nodejieba 进行关键词抽取。

数据分析

在进行数据分析之前,我们先来了解几个简单的基本概念:

  • 度量(指标):数值本身,比如价格、数量等。
  • 维度:可以理解为分析数据的角度,比如省份、类型等。

通过观察一条样例数据,我们可以得到「国家」、「姓名」、「出生年份」、 「逝世年份」、 「观点」、「关键词」六个维度,以及关键词 「权重」这个指标。我们可以根据「国家」将哲学家进行分组,而每个哲学家提出的观点又可以按照 「关键词」进行拆分,每个关键词用 「权重」数据去描述。这样一来我们就可以按照以下看数思路进行分析:

  • 查看一个国家有哪些哲学家
  • 每个哲学家的基本信息有哪些
  • 哲学家分别提出了怎样的观点
  • 每个观点对应的关键词有哪些

用 S2 绘制多维分析表

在绘制多维分析表之前,我们需要了解 S2 是怎么绘制多维分析表的。S2 是数据驱动的表可视分析引擎。通过 schema 指定行列维度和指标的位置,结合配置项设置相关分析属性,将明细数据和行列维度关联起来。从代码层面来反应整体分为三步:

  1. 数据准备
  2. 配置项准备
  3. 渲染

1、数据准备

  • 首先将原始数据按照「关键词」维度进行拆分,将数据打平,得到如下 S2 可以直接消费的明细数据:

(全量数据)

json
[
    {
        "points": "水是万物之源。",
        "name": "泰利斯",
        "country": "希腊",
        "word": "水是",
        "weight": 11.739204307083542,
        "start": -624,
        "end": -546
   },
   {
        "points": "水是万物之源。",
        "name": "泰利斯",
        "country": "希腊",
        "word": "万物",
        "weight": 7.75434839431,
        "start": -624,
        "end": -546
  },
  {
        "points": "水是万物之源。",
        "name": "泰利斯",
        "country": "希腊",
        "word": "之源",
        "weight": 9.23723855786,
        "start": -624,
        "end": -546
  },
  ...

  • 将维度按照数据分析确定的维度顺序依次放置在行维度位置,并指定「频率」为指标。
const s2DataConfig = { 
  fields: { // 维度指标配置项	
    rows: ['country', 'name', 'start', 'end', 'points', 'word'], // 行头
    columns: [], // 列头
    values: ['weight'], // 数值
  },
  meta: [ // 对各个维度指标进行别名、格式化配置
    {
      field: 'word',  // 维度
      name: '关键词',  // 别名
    },
    {
      field: 'points',
      name: '观点',
    },
    {
      field: 'name',
      name: '姓名',
    },
    {
      field: 'country',
      name: '国家',
    },
    {
      field: 'start',
      name: '出生',
      formatter: getFormatter, // 格式化方法
    },
    {
      field: 'end',
      name: '逝世',
      formatter: getFormatter,
    },
    {
      field: 'weight',
      name: '权重',
      formatter: (val) => val.toFixed(2),
    },
  ],
  data, // 可从 GitHub 中下载
};

const getFormatter = (val) => {
  if (val < 0) {
    return `公元前${replace(val, '-', '')}年`;
  } else {
    return `${val}年`;
  }
};

2. 配置项准备

这一步除了基本表体展示交互相关的基础配置项,主要做的就是文本和单元格背景颜色通道映射的配置。 首先准备一个色板如下:

json
const PALETTE_COLORS = [
  '#B8E1FF',
  '#9AC5FF',
  '#7DAAFF',
  '#5B8FF9',
  '#3D76DD',
  '#085EC0',
  '#0047A5',
  '#00318A',
  '#001D70',
];

计算得到该组数据的最大最小值:

json
import { max, min} from 'lodash';

const weights = data.map((item) => item.weight);
const maxWeight = max(weights);
const minWeight = min(weights);
const weightSpan = maxWeight - minWeight;

将数值区间和颜色区间进行关联,并将权重数值映射到对应颜色区间:

const s2Options = {
  width: '',
  height: 400,
  conditions: { // 字段标记
    text: [ // 权重(weight)大于 20 的数值展示为白色
      {
        field: 'weight',
        mapping(value) {
          if (value >= 20) { 
            return {
              fill: '#fff',
            };
          }
        },
      },
    ],
    background: [ // 权重(weight)在不同的范围,展示不同的背景色
      {
        field: 'weight',
        mapping(value) {
          let backgroundColor;
          const colorIndex =
                Math.floor(
                  (((value - minWeight) / weightSpan) * 100) /
                  PALETTE_COLORS.length,
                ) - 1;
          if (colorIndex <= 0) {
            backgroundColor = PALETTE_COLORS[0];
          } else if (colorIndex >= PALETTE_COLORS.length) {
            backgroundColor = PALETTE_COLORS[PALETTE_COLORS.length - 1];
          } else {
            backgroundColor = PALETTE_COLORS[colorIndex];
          }

          return {
            fill: backgroundColor,
          };
        },
      },
    ],
  },
  interaction: { // 关闭部分交互操作
    selectedCellsSpotlight: false,
    hoverHighlight: false,
  },
};

3. 渲染

有了前面的准备工作,绘制多维分析表就很容易了。

ReactDOM.render(
  <SheetComponent
    dataCfg={s2DataConfig}
    options={s2Options}
    adaptive={true}
    header={{
      title: '哲学家的观点',
        extra: [<PaletteLegend />],
    }}
    onDataCellMouseUp={onDataCellMouseUp}
    />,

  document.getElementById('container'),
);

最后绘制出来的效果如下图所示,也可以打开我们部署好的 线上示例

image.png

数据洞察

从中我们可以获取一些有效信息和洞察:

  • 每个国家有那些哲学家
  • 每个哲学家的详细信息
  • 哲学家每个关键词的权重占比

拓展

透视表

在统计学中,透视表是矩阵格式的一种表格,显示多变量频率分布。它们提供了两个变量(或者多个)之间的相互关系的基本画面,可以帮助发现它们之间的相互作用,帮助业务进行交叉探索分析,是目前商业 BI 分析领域中使用频率最高的图表之一。

S2

S2AntV 团队推出的数据表可视化引擎,是多维交叉分析领域的表格解决方案,数据驱动视图,提供底层核心库、基础组件库、业务场景库,具备自由扩展的能力,让开发者既能开箱即用,也能基于自身场景自由发挥。 官网demo示例

小结

这一章带大家用表格去展示和认识数据,表格其实也算一种古老的数据可视化方法。再一次认识数据之后,接下来我们就正式进入分析环节!