AVA 源碼解讀-規則邏輯

做可視化決策的過程,爲了節省人的工作量及提效,一個好的方案是推薦。
現有的智能可視化推薦系統分爲兩類: 基於規則的基於機器學習的
前者一般是根據專家經驗或實驗得到的可視化準則; 後者則是直接學習從數據到可視化的模型。

本文是在研究基於規則的可視化推薦的過程中,查看背後源碼規則部分實現的過程後的衍生物。

一、AVA 背景介紹

AVA 是阿里出品。定義如下:

AVA (AVA logo Visual Analytics) 是爲了更簡便的可視分析而生的技術框架。 其名稱中的第一個 A 具有多重涵義:它說明了這是一個出自阿里巴巴集團(Alibaba)技術框架,其目標是成爲一個自動化(Automated)、智能驅動(AI driven)、支持增強分析(Augmented)的可視分析解決方案。

二、推薦流程

數據有特徵,每一種類型也有特徵。

數據特徵在所有的圖表類型特徵中判斷後,當數據高度滿足某一種類型的所有特徵時,那麼就找到了指定的類型,渲染推薦給用戶。

  1. 給定數據
[
  { f1: 'a', f2: 70 },
  { f1: 'b', f2: 120 },
  { f1: 'c', f2: 900 },
  { f1: 'd', f2: 630 },
]
  1. 經過 @antv/dw-analyzer 分析處理後得到的具有更多特徵的數據集:
[
  {
    "count": 4,
    "distinct": 4,
    "type": "string",
    "recommendation": "string",
    "missing": 0,
    "samples": [
      "a",
      "b",
      "c",
      "d"
    ],
    "valueMap": {
      "a": 1,
      "b": 1,
      "c": 1,
      "d": 1
    },
    "maxLength": 1,
    "minLength": 1,
    "meanLength": 1,
    "containsChars": true,
    "containsDigits": false,
    "containsSpace": false,
    "containsNonWorlds": false,
    "name": "f1",
    "levelOfMeasurements": [
      "Nominal"
    ]
  },
  {
    "count": 4,
    "distinct": 4,
    "type": "integer",
    "recommendation": "integer",
    "missing": 0,
    "samples": [
      70,
      120,
      900,
      630
    ],
    "valueMap": {
      "70": 1,
      "120": 1,
      "630": 1,
      "900": 1
    },
    "minimum": 70,
    "maximum": 900,
    "mean": 430,
    "percentile5": 70,
    "percentile25": 70,
    "percentile50": 120,
    "percentile75": 630,
    "percentile95": 900,
    "sum": 1720,
    "variance": 121650,
    "stdev": 348.78360053190573,
    "zeros": 0,
    "name": "f2",
    "levelOfMeasurements": [
      "Interval",
      "Discrete"
    ]
  }
]
  1. 數據集是否是某一種圖表類型,那麼數據集需要符合該類圖表的條件(特徵)。因此知識庫中定義了43中圖表類型(g2plot中的圖表類型)的類型知識特徵 @antv/knowledge

舉例 basic_pie_chart 類型:

basic_pie_chart: {
  id: 'basic_pie_chart',
  name: 'Pie Chart',
  alias: ['Circle Chart', 'Pie'], // 別名
  family: ['PieCharts'], // 大類
  def: // 定義
    'A pie chart is a chart that the classification and proportion of data are represented by the color and arc length (angle, area) of the sector.',
  purpose: ['Comparison', 'Composition', 'Proportion'], // 分析目的:對比、成分、佔比
  coord: ['Polar'], // 座標系:極座標系 - 多用於圓形的圖形佈局
  category: ['Statistic'], // 圖形類別:統計圖表 - 折線圖、餅圖等用來表示數據的統計或聚合結果的經典圖表
  shape: ['Round'], // 形狀:圓形 - 如:餅圖、雷達圖,等
  dataPres: [ // 所需數據條件:下面解讀爲圖表中有且只有 1 個 數值 或 無序名詞 字段
    { minQty: 1, maxQty: 1, fieldConditions: ['Nominal'] },
    { minQty: 1, maxQty: 1, fieldConditions: ['Interval'] },
  ],
  channel: ['Angle', 'Area', 'Color'], // 視覺通道:角度、面積、顏色
},

詳解見文檔

  1. 數據特徵、圖表類型特徵有了,接着便是是特徵條件契合判斷。找出滿足條件的。@antv/chart-advisor

最終繪製圖如下:

三、規則探索

數據特徵多(>10),圖表類型多(43),每一種圖表類型特徵也多(>10),而且特徵權重不同。

數據特徵和圖表類型特徵,條件判斷該怎麼設計呢,才能使代碼易閱讀理解、易擴展呢?

條件判斷, 常見方式 if-else,當判斷條件較多,一般大於 3 時,採用 switch-case 實現。這兒都不適用。

龐大的條件判斷,這個庫給出了一種方案:規則 + 計分 實現。

1.規則過濾代碼片段過濾

const allTypes = Object.keys(Wiki) as ChartID[];

const list: Advice[] = allTypes.map((t) => {
    // anaylze score
    let score = 0;

    let hardScore = 1;
    Rules.filter((r: Rule) => r.hardOrSoft === 'HARD' && r.specChartTypes.includes(t as ChartID)).forEach(
      (hr: Rule) => {
        const score = hr.check({ dataProps, chartType: t, purpose, preferences });
        hardScore *= score;
        record[hr.id] = score;
      }
    );

    let softScore = 0;
    Rules.filter((r: Rule) => r.hardOrSoft === 'SOFT' && r.specChartTypes.includes(t as ChartID)).forEach(
      (sr: Rule) => {
        const score = sr.check({ dataProps, chartType: t, purpose, preferences });
        softScore += score;
        record[sr.id] = score;
      }
    );

    score = hardScore * (1 + softScore);
    
    // ....
}

allTypes 來源於圖表類型;

import { CKBJson, LevelOfMeasurement as LOM, ChartID } from '@antv/knowledge';
const Wiki = CKBJson('en-US', true);

其中dataProps就是data提取特徵後的數據集:數據特徵提取

export function dataToDataProps(data: any[]): FieldInfo[] {
  const dataTypeInfos = DWAnalyzer.typeAll(data);
  const dataProps: FieldInfo[] = [];

  dataTypeInfos.forEach((info) => {
    const lom = [];
    if (DWAnalyzer.isNominal(info)) lom.push('Nominal');
    if (DWAnalyzer.isOrdinal(info)) lom.push('Ordinal');
    if (DWAnalyzer.isInterval(info)) lom.push('Interval');
    if (DWAnalyzer.isDiscrete(info)) lom.push('Discrete');
    if (DWAnalyzer.isContinuous(info)) lom.push('Continuous');
    if (DWAnalyzer.isTime(info)) lom.push('Time');

    const newInfo: FieldInfo = { ...info, levelOfMeasurements: lom as LOM[] };

    dataProps.push(newInfo);
  });

  return dataProps;
}

每個圖表類,所有規則按 hard 和 soft 分別過濾,把規則集合中符合當前圖表類的規則,再遍歷一遍,傳遞數據,調用check計算得分,最後計算綜合得分。

softScore = 0;
softScore += score;
hardScore = 1;
hardScore *= score;

score = hardScore * (1 + softScore)

2.規則類定義屬性和方法規則類:定義了以下方法和屬性

this._id = id;
this._hardOrSoft = hardOrSoft;
this._specChartTypes = specChartTypes;
this._weight = weight;
this.validator = validator;

在check的時候,將規則定義的權重加上

check(args: Info) {
    return this.validator(args) * this._weight;
}

3.規則集合定義規則規則集合

const ChartRules: Rule[] = [
  // Data must satisfy the data prerequisites
  new Rule('data-check', 'HARD', allChartTypes, 1.0, (args): number => {
    let result = 0;
    const { dataProps, chartType } = args;

    if (dataProps && chartType && Wiki[chartType]) {
      result = 1;
      const dataPres = Wiki[chartType].dataPres || [];

      for (const dataPre of dataPres) {
        if (!verifyDataProps(dataPre, dataProps)) {
          result = 0;
          return result;
        }
      }
    }
    return result;
  }),
  // ...
  
  // Some charts should has at most N series.
  new Rule(
    'series-qty-limit',
    'SOFT',
    ['pie_chart', 'donut_chart', 'radar_chart', 'rose_chart'],
    0.8,
    (args): number => {
      let result = 0;
      const { dataProps, chartType } = args;

      let limit = 6;
      if (chartType === 'pie_chart' || chartType === 'donut_chart' || chartType === 'rose_chart') limit = 6;
      if (chartType === 'radar_chart') limit = 8;

      if (dataProps) {
        const field4Series = dataProps.find((field) => hasSubset(field.levelOfMeasurements, ['Nominal']));
        const seriesQty = field4Series && field4Series.count ? field4Series.count : 0;

        if (seriesQty >= 2 && seriesQty <= limit) {
          result = 2 / seriesQty;
        }
      }
      return result;
    }
  ),
  // ...
]

hard 可以理解爲硬性條件,weight 都爲 1。

soft 理解爲非硬性條件,因此 weight 視條件而定,(0,1) 直接取值。

result 可以理解爲每個規則的得分。

結合規則 rule 類理解,每個規則針對特定的圖表類型集合,有名稱、hardorsoft、weight、得分計算規則。

一共定義了11個規則:

["data-check", "data-field-qty", "no-redundant-field", "purpose-check", "series-qty-limit", "bar-series-qty", "line-field-time-ordinal", "landscape-or-portrait", "diff-pie-sector", "nominal-enum-combinatorial", "limit-series"]

每個規則針對多個圖表類型。

每個規則內部將數特徵和圖表類型特徵所需條件進行比較判斷,計算出分值。

4.代碼日誌

如上面的舉例的data:

在遍歷所有圖表類型,通過所有規則後,其中 pie_chart 和 donut_chart 得分相同 1.8303440007183807,最高,因此最後渲染的是 餅圖:

score

經過了這些規則得分:

data-check: 1  hard
data-field-qty: 1  hard
diff-pie-sector: 0.4303440007183805  soft
limit-series: 0  soft
no-redundant-field: 1  hard
nominal-enum-combinatorial: 0  soft
purpose-check: 1  hard
series-qty-limit: 0.4  soft

43中類型得分圖如下:

推薦一個的話,就推薦得分最高的那個圖表類型。

推薦多個的話,就推薦得分大於0或得分大於1的圖表類型(看實際需求)。

總結

  1. 規則的代碼設計邏輯可應用於分類。優點是易閱讀理解、易擴展。

舉例:鮮花識別

當你在花園裏看到一種花,很漂亮,你想知道它叫什麼名字,有什麼故事,寓意着什麼?

會不會也向玫瑰一樣,有美麗的名字,象徵着愛。

  1. 對於可視化的認知更深了,特別推薦仔細閱讀知識庫文檔
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章