数据可视化之美之疾病潜在关联

上面看到的就是这次做的结果,从 疾病表 的近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

原文地址:https://www.cnblogs.com/newbalanceteam/p/4924254.html