zoukankan      html  css  js  c++  java
  • 数据可视化之美之疾病潜在关联

    上面看到的就是这次做的结果,从 疾病表 的近200万条数据分析得到的结果,可以从这里看到实时演示

    广告一下,个人博客icodeit.cn

    原始数据处理


    该表展现了用户所患疾病的关系,每一条数据包含两个有效数据字段“用户id”、“疾病名称”。但是原始数据中包含一些无效数据:

    1
    2
    select fld_UserId, count(*) c from tab_UserDisease
    group by fld_UserId order by c desc limit 10;

    通过该sql分析发现数据中有部分用户存在大量疾病关系,这部分数据不满足合理性,应该剔除。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //抓取原始数据,同时加以筛选,患有2-9种疾病的认为有效
    $sql = "SELECT fld_UserId userid, group_concat(fld_DiseaseName) diseases
    FROM tab_UserDisease
    where fld_UserId in (
    select fld_UserId from (SELECT fld_UserId, count(*) c
    FROM tab_UserDisease group by fld_UserId
    ) ta where ta.c>1 and ta.c<10) group by fld_UserId";
    $data = $dbexecuter->query($sql);

    //疾病数据
    $sql = "SELECT fld_DiseaseName FROM tab_Disease";
    $this->diseases = $dbexecuter->queryColumn($sql, 'fld_DiseaseName');

    这里获取到的原始数据是以id、疾病的形式展现的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    array(150720) {
    [0] =>
    array(2) {
    'userid' =>
    string(1) "0"
    'diseases' =>
    string(31) "肺动脉瓣狭窄,性病,宫颈炎,妇科病"
    }
    [1] =>
    array(2) {
    'userid' =>
    string(1) "1"
    'diseases' =>
    string(41) "乙肝,消化道出血,直肠肛管疾病,功能性胃肠病"
    }
    [2] =>
    array(2) {
    'userid' =>
    string(1) "3"
    'diseases' =>
    string(60) "高血压,心肌梗死,冠心病,小三阳,糖尿病,心脏病,肺癌,癫痫,银屑病"
    }

    (more elements)...
    }

    为方便后续处理,需要对原始数据进行转置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //原始数据转置,转换为疾病为key,患者为value的数组
    $this->record = array();
    foreach ($data as $id => $row)
    {
    $diseases = preg_split("/,/", $row['diseases']);
    foreach ($diseases as $disease)
    {
    if (!isset($this->record[$disease]))
    {
    $this->record[$disease] = array();
    }
    $this->record[$disease][] = $id;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    array() {
    '便秘' =>
    array(1268) {
    [0] =>
    int(55)
    [1] =>
    int(290)
    }
    '发烧' =>
    array(1525) {
    [0] =>
    int(54)
    [1] =>
    int(643)
    }

    (more elements)...
    }

    Apriori算法


    Apriori算法是一种最有影响的挖掘布尔关联规则频繁项集的算法Apriori使用一种称作逐层搜索的迭代方法,“K-1项集”用于搜索“K项集”。

    首先,找出频繁“1项集”的集合,该集合记作L1。L1用于找频繁“2项集”的集合L2,而L2用于找L3。如此下去,直到不能找到“K项集”。找每个Lk都需要一次数据库扫描。

    核心思想是:连接步剪枝步。连接步是自连接,原则是保证前k-2项相同,并按照字典顺序连接。剪枝步,是使任一频繁项集的所有非空子集也必须是频繁的。反之,如果某

    个候选的非空子集不是频繁的,那么该候选肯定不是频繁的,从而可以将其从CK中删除。
    简单的讲,1、发现频繁项集,过程为(1)扫描(2)计数(3)比较(4)产生频繁项集(5)连接、剪枝,产生候选项集 重复步骤(1)~(5)直到不能发现更大的频集

    伪代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    C[k]: 长度为 k的候选项集  
    L[k] : 长度为k的频繁项集

    L[1] = {频繁项};
    for (k = 1; L[k] !=∅; k++) do begin
    C[k+1] = 由 L[k]产生的候选;
    for each 数据库中的事务t do
    增加包含在t 中的所有候选C[k+1]的计数
    L[k+1] = C[k+1]中满足 min_support的候选
    end
    return L[1..k];

    实现


    核心部分,这里节省时间,只计算到二元频繁项:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //获取首个候选项集
    $itemlist = $this->getFirstCandidate();
    $idx = 0;
    while($idx >= 2)
    {
    //计算支持度,获取满足支持度的候选项集
    $itemlist = $this->getSupportedItemset($itemlist);
    //获取候选项计数+1的候选项集
    $itemlist = $this->getNextCandidate($itemlist);

    $idx++;
    }
    file_put_contents('/tmp/diseaselink.out', $itemlist);

    获取首个候选项集,这里直接取了所有的疾病:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private function getFirstCandidate()
    {
    $itemset = array();
    foreach ($this->diseases as $disease)
    {
    $itemset[] = array($disease);
    }
    return $itemset;
    }

    连接步

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    private function getNextCandidate($itemset)
    {
    $nowCount = count($itemset[0]);
    $nextItemset = array();
    for ($i = 0; $i < count($itemset); $i++)
    {
    for ($j = $i; $j < count($itemset); $j++)
    {
    $temp = array_unique(array_merge($itemset[$i], $itemset[$j]));
    {
    if (count($temp) == $nowCount + 1)
    {
    $nextItemset[] = $temp;
    }
    }
    }
    }
    return $nextItemset;
    }

    减枝步 计算支持度,因为在原始数据处理阶段已经进行了数据转置,当我们需要计算同时患有某些疾病的人数时,只需要计算这些疾病患者数据的交集并计数即可:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    private function getSupportedItemset($itemset)
    {
    $supportedItemset = array();
    foreach ($itemset as $item)
    {
    $patients = isset($this->record[$item[0]]) ?
    $this->record[$item[0]] : null;
    foreach ($item as $disease)
    {
    if (!is_array($patients))
    {
    break;
    }
    $patients = array_intersect($patients, $this->record[$disease]);
    }
    if (is_array($patients) && count($patients) >= self::$MIN_SUPPORT)
    {
    $supportedItemset[] = $item;
    }
    }
    return $supportedItemset;
    }

    如此循环便可以得出各级支持的频繁项集,这里只获取了两级频繁项,最终序列化写入到了文件中。后续还需要对输出的数据进行json序列化处理,以便于页面使用,页面展现使用了数据可视化常用的前端库d3js

    by zc

  • 相关阅读:
    将数字转化为字符串
    给定一列数字将其平移n位
    判断回文数的问题
    c语言链表逆序的问题
    python中类属性和实例属性的区别
    python中__repr__()方法
    python中模块和包
    flask如何写一个model
    遍历文件夹下excel文件并且写入一个新excel
    python统计任务耗时
  • 原文地址:https://www.cnblogs.com/newbalanceteam/p/4924254.html
Copyright © 2011-2022 走看看