大資料與機器學習 基礎篇 關聯分析
關聯規則是人類在認識客觀事物中形成的一種認知模式。這種關聯規則在人的認知裡與反射類似。如在小時候不小心被針扎到,會有痛感,這樣針刺和痛感就在大腦裡有了這種關聯。這就是人在認識事物的過程中在認知中所建立的關聯規則,即通過與客觀事件互動發現事物之間存在的依賴或因果關係。
關聯分析
這裡的關聯分析就是嘗試在資料中發現依賴或者因果關係的方法。這其中有一個非常經典的案例,就是“啤酒和尿布”案例。
案例分析
據說事情發生在20世紀80年代的美國沃爾瑪,銷售經理經過研究零售記錄發現啤酒和尿布會同時出現在很多購物記錄裡,令人百思不得其解。經過觀察發現,在這個地區會有很多年輕的父親會時常光顧超市,在超市裡為孩子購買尿布的同時也會為自己買上一些啤酒。從此,超市經理借題發揮,又專門把啤酒和尿布的銷售貨架放在一起,進而讓啤酒和尿布的銷量進一步提高。
這種研究方式在零售行業得到了比較廣泛的應用,這種分析也稱作“購物籃分析”。下面簡單介紹一下這個分析過程是怎麼實現的。
一個超市每天有很多的購物記錄,為例示例方便,假設只有5個購物記錄,購物記錄如下:
- 記錄1:(啤酒,香菸,白菜,雞蛋,酸奶,衛生紙)
- 記錄2:(紅酒,香菸,巧克力,酸奶)
- 記錄3:(牙刷,奶糖,食鹽,雞肉,衛生紙)
- 記錄4:(啤酒,酒杯,香菸,瓜子,花生,薯片)
- 記錄5:(酸奶,巧克力,味精)
我們先建立一個超市中所有商品和超市購物記錄的關係型資料庫。
#關聯分析(association analysis)
drop database if exists association_analysis;
create database association_analysis;
use association_analysis;
create table item
(ID char(5),
name varchar(20),
primary key (ID, name)
);
create table buy_list
(serial char(4) not null,
item_ID char(5) not null,
constraint buy_list_fk foreign key (item_ID) references item (ID)
);
insert into item values ('00001', 'Beer');
insert into item values ('00002', 'Cigarette');
insert into item values ('00003', 'Cabbage');
insert into item values ('00004', 'Egg');
insert into item values ('00005', 'Yogurt');
insert into item values ('00006', 'Toilet paper');
insert into item values ('00007', 'Wine');
insert into item values ('00008', 'Chocolate');
insert into item values ('00009', 'Toothbrush');
insert into item values ('00010', 'Toffee');
insert into item values ('00011', 'Saft');
insert into item values ('00012', 'Chicken');
insert into item values ('00013', 'Glass');
insert into item values ('00014', 'Sunflower seeds');
insert into item values ('00015', 'Peanut');
insert into item values ('00016', 'Chips');
insert into item values ('00017', 'Aginomoto');
insert into buy_list values ('0001', '00001');
insert into buy_list values ('0001', '00002');
insert into buy_list values ('0001', '00003');
insert into buy_list values ('0001', '00004');
insert into buy_list values ('0001', '00005');
insert into buy_list values ('0001', '00006');
insert into buy_list values ('0002', '00007');
insert into buy_list values ('0002', '00002');
insert into buy_list values ('0002', '00008');
insert into buy_list values ('0002', '00005');
insert into buy_list values ('0003', '00009');
insert into buy_list values ('0003', '00010');
insert into buy_list values ('0003', '00011');
insert into buy_list values ('0003', '00012');
insert into buy_list values ('0003', '00006');
insert into buy_list values ('0004', '00001');
insert into buy_list values ('0004', '00013');
insert into buy_list values ('0004', '00002');
insert into buy_list values ('0004', '00014');
insert into buy_list values ('0004', '00015');
insert into buy_list values ('0004', '00016');
insert into buy_list values ('0005', '00005');
insert into buy_list values ('0005', '00008');
insert into buy_list values ('0005', '00017');
由以下SQL語句可以建立一個購物記錄表的檢視。
create view buy_list_view (serial, item_name) as select serial, item.name
from buy_list left join item on (buy_list.item_ID = item.ID);
select * from buy_list_view;
-表1:購物記錄表Buy-list
流水號(serial) | 單品類別(item_name) |
---|---|
0001 | Beer |
0001 | Cigarette |
0001 | Cabbage |
0001 | Egg |
0001 | Yogurt |
0001 | Toilet paper |
0002 | Wine |
0002 | Cigarette |
0002 | Chocolate |
0002 | Yogurt |
0003 | Toothbrush |
0003 | Toffee |
0003 | Saft |
0003 | Chicken |
0003 | Toilet paper |
0004 | Beer |
0004 | Glass |
0004 | Cigarette |
0004 | Sunflower seeds |
0004 | Peanut |
0004 | Chips |
0005 | Yogurt |
0005 | Chocolate |
0005 | Aginomoto |
在分析資料之前,先引出幾個概念:
- 頻繁模式:在每個購物記錄中出現的各種單品其實體現的是一種組合的性質。而這些單品的組合在記錄中是無序的,這種無序的組合叫做“模式”。這些模式裡,有的出現頻率很低,有的出現頻率很高,一般認為頻率較高的通常更有指導意義,這種高頻率的模式就被稱作“頻繁模式”。
- 支援度:衡量頻繁模式的有用性,為某一種模式在所有模式中出現的記錄。例如,在上例中,購買(啤酒,香菸)模式的支援度為40%,也就說明,在所有的購物記錄中,有40%的購物記錄都包含這兩種模式。
- 置信度:衡量頻繁模式的“確定性”,置信度是有“方向性”的,為某種模式中不同維度之間的一種關係。例如,在上例中,購買啤酒的記錄裡有100%的記錄都購買了香菸,那麼就說購買啤酒後購買香菸的置信度為100%;反向來看,購買香菸的記錄裡有67%的記錄都購買了啤酒,那麼就說購買香菸後購買啤酒的置信度為67%。
示例計算(啤酒,香菸)模式的支援度:
#(啤酒,香菸)模式的購物記錄數量
select @someSerial := count(*) from(
select *, count(*) from (
select serial from (
buy_list left join item on (buy_list.item_ID = item.ID)
)
where item.name = 'Beer'
union all
select serial from (
buy_list left join item on (buy_list.item_ID = item.ID)
)
where item.name = 'Cigarette'
) as tmp1
group by tmp1.serial having count(*) = 2
) as tmp2;
#所有的購物記錄的數量
select @totalSerial := count(*) from (
select serial from buy_list group by serial
) as tmp;
set @support = @someSerial / @totalSerial;
select @support;
示例計算“啤酒=>香菸”的置信度和“香菸=>啤酒”的置信度:
select @beerSerial := count(*) from buy_list_view where item_name = 'Beer';
select @cigaretteSerial := count(*) from buy_list_view where item_name =
'Cigarette';
set @confidenceBeerToCigarette = @someSerial / @beerSerial;
set @confidenceCigaretteToBeer = @someSerial / @cigaretteSerial;
select @confidenceBeerToCigarette;
select @confidenceCigaretteToBeer;
為了表達簡便,也常記錄如下:
- 啤酒=>香菸[support=40%,confidence=100%]
- 香菸=>啤酒[support=40%,confidence=67%]
我們認為,設定門限“最小支援度”和“最小置信度”之後,支援度和置信度同時高於這兩個門限的模式就可以認為是頻繁模式。
如果支援度高置信度低,說明這兩種情況確實同時出現,但是“轉化率”可能比較低。而如果支援度比較低,但是轉化率比較高,說明這種模式在所有的模式裡很平常,不能算作“頻繁模式”。所以基於這樣的原因,通常還是會選擇支援度和置信度都高於閾值的模式作為頻繁模式。
Apriori演算法
Apriori演算法用於求出所有的頻繁模式,求解過程有以下幾個步驟。
步驟1:先設定一個最小支援度作為閾值門限值進行掃描,因為對同時過濾最小支援度和最小置信度這兩個操作來說,最小支援度的查詢更為簡單一些。假設設定的最小支援度為40%。
在資料庫中建立一個單品支援度的表。
#Apriori演算法求頻繁模式
import pymysql
import numpy as np
# 開啟資料庫連線
db = pymysql.connect( "localhost", "user", "password",
"association_analysis" )
# 使用cursor()方法獲取操作遊標
cursor = db.cursor()
# 獲取商品條目
sql = "select name from item"
cursor.execute(sql)
items = cursor.fetchall()
# 求所有商品的支援度
supports = []
for i in range( len(items) ):
sql = "select count(*) from buy_list_view where item_name = '"+items[i][0]
+"'"
cursor.execute(sql)
supports.append( cursor.fetchone() )
supports = np.array(supports)
supports.shape = len(items)
sql = "select count(*) from ( select serial from buy_list group by serial )
as tmp"
cursor.execute(sql)
total = cursor.fetchone()
supports = supports / total
# 建立單品支援度表
sql = "drop table if exists supports"
cursor.execute(sql)
sql = "create table supports ( item_name varchar(20), support float(2) )"
cursor.execute(sql)
for i in range( len(items) ):
sql = "insert into supports values ('"+items[i][0]+"', "+str(supports[i])
+")"
try:
# 執行sql語句
cursor.execute(sql)
# 提交到資料庫執行
db.commit()
except:
# 如果發生錯誤則回滾
db.rollback()
db.close()
select * from supports;
- 表2:單品支援度表
單品類別(item_name) | 單品支援度(support) |
---|---|
Beer | 0.4 |
Cigarette | 0.6 |
Cabbage | 0.2 |
Egg | 0.2 |
Yogurt | 0.6 |
Toilet paper | 0.4 |
Wine | 0.2 |
Chocolate | 0.4 |
Toothbrush | 0.2 |
Toffee | 0.2 |
Saft | 0.2 |
Chicken | 0.2 |
Glass | 0.2 |
Sunflower seeds | 0.2 |
Peanut | 0.2 |
Chips | 0.2 |
Aginomoto | 0.2 |
步驟2:掃描所有滿足最小支援度的單品。如果一個模式滿足最小支援度40%,那麼這個模式中的所有單品都必須滿足最小支援度40%,這是一個必要條件。
select * from supports where support>0.4;
- 表3:支援度大於等於40%的單品
單品類別(item_name) | 單品支援度(support) |
---|---|
Beer | 0.4 |
Cigarette | 0.6 |
Yogurt | 0.6 |
Toilet paper | 0.4 |
Chocolate | 0.4 |
步驟3:查詢滿足條件的2項模式。即支援度滿足條件的集合作笛卡爾積。上面的示例給出了計算(啤酒,香菸)模式的支援度。
- 表3:滿足條件的2項模式
2項模式 | 支援度(support) | 2項模式 | 支援度(support) |
---|---|---|---|
Beer,Cigarette | 40% | Cigarette,Toilet paper | 20% |
Beer,Yogurt | 20% | Cigarette,Chocolate | 20% |
Beer,Toilet paper | 20% | Yogurt,Toilet paper | 20% |
Beer,Chocolate | 0% | Yogurt,Chocolate | 40% |
Cigarette,Yogurt | 40% | Toilet paper,Chocolate | 0% |
- 表4:滿足最小支援度的2項模式
2項模式 | 支援度(support) |
---|---|
Beer,Cigarette | 40% |
Yogurt,Chocolate | 40% |
Cigarette,Yogurt | 40% |
步驟4:查詢滿足條件的3項模式。注意,2項模式滿足最小支援度是3項模式滿足最小支援度的必要條件,所以只需從滿足最小支援度的2項模式中查詢即可。
- 表5:候選的3項模式
3項模式 | 支援度(support) |
---|---|
Beer,Cigarette,Yogurt | 20% |
Beer,Cigarette,Chocolate | 0 |
Cigarette,Yogurt,Chocolate | 0 |
觀察發現,到目前為止,所有的3項模式都不滿足最小支援度,那麼演算法到這裡就結束了,因為不可能再找到滿足條件的3項,4項以及以後任何多種符合條件的頻繁模式了。
示例程式碼:求出所有候選模式的支援度和置信度並建表。
#Apriori演算法求頻繁模式
import pymysql
import numpy as np
# 開啟資料庫連線
db = pymysql.connect( "localhost", "user", "password",
"association_analysis" )
# 使用cursor()方法獲取操作遊標
cursor = db.cursor()
# # # # # # # # # # # # # # # # # # #
# 求出所有的2項模式(有順序)
sql = 'select * from supports where support>0.4'
cursor.execute(sql)
frequentItems = cursor.fetchall()
frequentPatterns = []
for i in range(len(frequentItems)):
for j in range(len(frequentItems)):
if i==j:
continue
frequentPatterns.append( [frequentItems[i][0], frequentItems[j][0]] )
patternCounts=[]
# 求2項模式支援度
for i in range(len(frequentPatterns)):
sql="select count(*) from(
select *, count(*) from (
select serial from (
buy_list left join item on (buy_list.item_ID = item.ID) )
where item.name = '" + frequentPatterns[i][0] + "'
union all
select serial from (
buy_list left join item on (buy_list.item_ID = item.ID) )
where item.name = '"+frequentPatterns[i][1] + "'
) as tmp1
group by tmp1.serial having count(*) = 2
) as tmp2"
cursor.execute(sql)
patternCounts.append(cursor.fetchall())
patternCounts = np.array(patternCounts)
patternCounts.shape = len(frequentPatterns)
# 所有的購物記錄的數量
sql = "select count(*) from ( select serial from buy_list group by serial )
as tmp"
cursor.execute(sql)
total = cursor.fetchone()
patternSupports = patternCounts / total
# 求2項模式置信度
patternConfidences = []
for i in range(len(frequentPatterns)):
sql = "select count(*) from buy_list_view where item_name = '" +
frequentPatterns[i][0] + "'"
cursor.execute(sql)
itemCounts = cursor.fetchone()[0]
patternConfidences.append(patternCounts[i] / itemCounts)
# 建立2項模式支援度和置信度表
sql = "drop table if exists association"
cursor.execute(sql)
sql = "create table association ( pattern varchar(50), support float(2),
confidence float(2) )"
cursor.execute(sql)
for i in range(len(frequentPatterns)):
sql = "insert into association values ('" + frequentPatterns[i][0] + "=>" +
frequentPatterns[i][1] + "', "+str(patternSupports[i]) + ", " +
str(patternConfidences[i]) + ")"
try:
# 執行sql語句
cursor.execute(sql)
# 提交到資料庫執行
db.commit()
except:
# 如果發生錯誤則回滾
db.rollback()
db.close()
建立的關聯分析表如下。
- 表6:關聯分析表
2項模式(pattern) | 支援度(support) | 置信度(confidence) |
---|---|---|
Beer=>Cigarette | 0.4 | 1 |
Beer=>Yogurt | 0.2 | 0.5 |
Beer=>Toilet paper | 0.2 | 0.5 |
Beer=>Chocolate | 0 | 0 |
Cigarette=>Beer | 0.4 | 0.666667 |
Cigarette=>Yogurt | 0.4 | 0.666667 |
Cigarette=>Toilet paper | 0.2 | 0.333333 |
Cigarette=>Chocolate | 0.2 | 0.333333 |
Yogurt=>Beer | 0.2 | 0.333333 |
Yogurt=>Cigarette | 0.4 | 0.666667 |
Yogurt=>Toilet paper | 0.2 | 0.333333 |
Yogurt=>Chocolate | 0.4 | 0.666667 |
Toilet paper=>Beer | 0.2 | 0.5 |
Toilet paper=>Cigarette | 0.2 | 0.5 |
Toilet paper=>Yogurt | 0.2 | 0.5 |
Toilet paper=>Chocolate | 0 | 0 |
Chocolate=>Beer | 0 | 0 |
Chocolate=>Cigarette | 0.2 | 0.5 |
Chocolate=>Yogurt | 0.4 | 1 |
Chocolate=>Toilet paper | 0 | 0 |
求出所有的頻繁模式。
select pattern from association where support>=0.4 and confidence>=0.4;
- 表7:頻繁模式表
頻繁模式(pattern) |
---|
Beer=>Cigarette |
Cigarette=>Beer |
Cigarette=>Yogurt |
Yogurt=>Cigarette |
Yogurt=>Chocolate |
Chocolate=>Yogurt |
由上表可得,頻繁模式有(香菸,啤酒),(香菸,酸奶),(酸奶,巧克力)。
關聯分析和相關性分析
在使用Apriori演算法計算出有較高支援度和較高置信度的頻繁模式之後,要對這些頻繁模式進行一些甄別和分析。但是,是不是所有的頻繁模式都是潛在有用的。
下面引出有關相關規則的分析。之前提過關聯規則的一種記述方式,Item1=>Item2 [支援度,置信度],現在給這種記述方式增加一個維度為:Item1=>Item2 [支援度,置信度,關聯度]。
提升度(Lift)是一種簡單的關聯度度量:
上式與樸素貝葉斯公式類似,等號左邊是A和B的相關性定義,右邊分子是發生A的情況下發生B的概率,分母是發生B的概率。
- 當相關性是1時,P(B|A)與P(B)相等,也就是說在全樣本空間內,B發生的概率和在發生A的情況下發生B的概率是一樣的,那麼A和B就是毫無關係。
- 當相關性大於1時,P(B|A)大於P(B),也就是說在全樣本空間內,發生A的情況下發生B的概率要比單獨統計B發生的概率要大,那麼B和A就是正相關的。也就是A的發生促進了B的發生。
- 當相關性小於1時,P(B|A)小於P(B),也就是說在全樣本空間內,發生A的情況下發生B的概率要比單獨統計B發生的概率要小,那麼B和A就是負相關的。也就是A的發生抑制了B的發生。
示例程式碼:求出所有候選模式的關聯度並建表。
import pymysql
# 開啟資料庫連線
db = pymysql.connect( "localhost", "user", "password",
"association_analysis" )
# 使用cursor()方法獲取操作遊標
cursor = db.cursor()
# # # # # # # # # # # # # # # # # #
# 求關聯度
sql = "select * from supports"
cursor.execute(sql)
supports = cursor.fetchall()
sql = "select pattern, support from association"
cursor.execute(sql)
patterns = cursor.fetchall()
patternsTmp = []
for i in range(len(patterns)):
tmp = patterns[i][0].split("=>")
tmp.append(patterns[i][1])
patternsTmp.append(tmp)
def searchSupport(targetItem, supports):
for support in supports:
if support[0]==targetItem:
return support[1]
lift = []
for pattern in patterns:
tmp = pattern[2]
tmp = tmp / searchSupport(pattern[0], supports)
tmp = tmp / searchSupport(pattern[1], supports)
lift.append(tmp)
# 為association表新增列lift
sql = "alter table association add lift float(2)"
cursor.execute(sql)
for i in range(len(lift)):
if lift[i] == 0:
continue
sql = "update association set lift = " + str(lift[i]) + " where pattern =
'" + patterns[i][0] + "'"
try:
# 執行sql語句
cursor.execute(sql)
# 提交到資料庫執行
db.commit()
except:
# 如果發生錯誤則回滾
db.rollback()
db.close()
最終關聯分析表如下。
- 表8:關聯分析表
2項模式(pattern) | 支援度(support) | 置信度(confidence) | 關聯度(lift) |
---|---|---|---|
Beer=>Cigarette | 0.4 | 1 | 1.66667 |
Beer=>Yogurt | 0.2 | 0.5 | 0.833333 |
Beer=>Toilet paper | 0.2 | 0.5 | 1.25 |
Beer=>Chocolate | 0 | 0 | NULL |
Cigarette=>Beer | 0.4 | 0.666667 | 1.66667 |
Cigarette=>Yogurt | 0.4 | 0.666667 | 1.11111 |
Cigarette=>Toilet paper | 0.2 | 0.333333 | 0.833333 |
Cigarette=>Chocolate | 0.2 | 0.333333 | 0.833333 |
Yogurt=>Beer | 0.2 | 0.333333 | 0.833333 |
Yogurt=>Cigarette | 0.4 | 0.666667 | 1.11111 |
Yogurt=>Toilet paper | 0.2 | 0.333333 | 0.833333 |
Yogurt=>Chocolate | 0.4 | 0.666667 | 1.66667 |
Toilet paper=>Beer | 0.2 | 0.5 | 1.25 |
Toilet paper=>Cigarette | 0.2 | 0.5 | 0.833333 |
Toilet paper=>Yogurt | 0.2 | 0.5 | 0.833333 |
Toilet paper=>Chocolate | 0 | 0 | NULL |
Chocolate=>Beer | 0 | 0 | NULL |
Chocolate=>Cigarette | 0.2 | 0.5 | 0.833333 |
Chocolate=>Yogurt | 0.4 | 1 | 1.66667 |
Chocolate=>Toilet paper | 0 | 0 | NULL |
對於負相關模式,一般來說,如果X和Y都是頻繁的,但是很少或者不一起出現,那麼就說X和Y是負相關的,X和Y組成的模式就是負相關模式。如果X和Y組成的模式支援度遠遠小於X的支援度與Y的支援度的乘積,那麼就說X和Y是強負相關的。