ML13-規(guī)則關(guān)聯(lián)算法

規(guī)則關(guān)聯(lián)引入了兩個數(shù)學(xué)度量公式來度量頻繁度與關(guān)聯(lián)度耿币。本主題就是說明這么兩件事兒铐殃,當(dāng)然最后的結(jié)果是產(chǎn)生關(guān)聯(lián)規(guī)則螃概。
??1. 頻繁度:支持度
??2. 關(guān)聯(lián)度:置信度
??3. 實現(xiàn)頻繁集尋找與規(guī)則生成采盒。


算法適合用于商品推薦,屬于無監(jiān)督學(xué)習(xí)兼砖。


關(guān)聯(lián)分析算法說明

  • 關(guān)聯(lián)分析:

    • 在大規(guī)模數(shù)據(jù)集中尋找有意義的關(guān)系奸远,包含兩個過程:
      • 找出:頻繁項集(Frequent Item Sets):
        • 出現(xiàn)次數(shù)比較多的數(shù)據(jù)項(物品)的集合;
        • 項集:一個或者多個數(shù)據(jù)項的集合(物品的集合)讽挟;
        • 項集的大小機(jī)會項集中數(shù)據(jù)項的個數(shù)懒叛。
      • 再從頻繁項集中找出:關(guān)聯(lián)規(guī)則(Association Rules):
        • 兩個數(shù)據(jù)項(物品)之間的關(guān)系
  • 關(guān)聯(lián)分析的特點:

    • 適合大數(shù)據(jù)樣本;
    • 但是在大數(shù)據(jù)樣本下耽梅,速度比較慢薛窥;

關(guān)聯(lián)分析的實現(xiàn)思路

  • 關(guān)聯(lián)分析引入了兩個量來度量頻繁度與關(guān)聯(lián)度:

支持度

  • 頻繁度的度量-支持度(Support):
    • 就是已知數(shù)據(jù)項同時出現(xiàn)的頻率;
    • 數(shù)據(jù)集中包含數(shù)據(jù)項的比例 = \dfrac{\text{數(shù)據(jù)項數(shù)量}}{\text{總得樣本數(shù)量}}眼姐。
      • 一個數(shù)據(jù)項構(gòu)成的項集的情況:\dfrac{\text{數(shù)據(jù)項1的數(shù)量}}{\text{總得樣本數(shù)量}}诅迷。
      • 多個數(shù)據(jù)項的構(gòu)成的項集情況:\dfrac{\text{(數(shù)據(jù)項1,}\cdots \text{众旗,數(shù)據(jù)項n)的數(shù)量}}{\text{總得樣本數(shù)量}}罢杉。

置信度

  • 關(guān)聯(lián)度的度量-置信度(Confidence):
    - 就是條件概率:數(shù)據(jù)項1出現(xiàn)的情況下,數(shù)據(jù)項2出現(xiàn)的條件概率贡歧;
    - P(數(shù)據(jù)項1 | 數(shù)據(jù)項2) = \dfrac{ P(\text{數(shù)據(jù)項1}, \text{數(shù)據(jù)項2}) } {P(\text{數(shù)據(jù)項2})}

支持度與置信度的理解例子

  • 使用著名的豆奶尿布說明支持度與置信度的概念:
交易流水號 商品
01 豆奶滩租,萵苣
02 萵苣,尿布利朵,葡萄酒律想,甜菜
03 豆奶,尿布绍弟,葡萄酒技即,橙汁
04 萵苣,豆奶樟遣,尿布而叼,葡萄酒
05 萵苣郭脂,豆奶,尿布澈歉,橙汁
  1. 支持度:

    • 豆奶的支持度:P(豆奶) = \dfrac{4}{5}
    • (豆奶,尿布)的支持度 = P(豆奶屿衅,尿布) = \dfrac{3}{5} = P(豆奶|尿布) \times P(尿布) = \dfrac{3}{4} \times \dfrac{4}{5}
  2. 置信度:

    • (葡萄酒埃难,尿布)的置信度 = P(葡萄酒 | 尿布) = \dfrac{3}{4} = P(\text{葡萄酒,尿布}) / P(尿布) = \dfrac{\dfrac{3}{5}}{\dfrac{4}{5}} = \dfrac{3}{4}
  3. 支持度與置信度公式:

    • 聯(lián)合概率:P(X, Y)
    • 條件概率:P(X | Y) = \dfrac{P(X,Y)}{P(Y)}

尋找最大頻繁項集的算法

頻繁項集的算法

  • 頻繁項集的尋找算法:

    • 假設(shè)k=1, 有一個大小為N_k數(shù)據(jù)集合S_k,并:

      1. 構(gòu)建一個k個項組成的備選項集的列表C_k涤久;
      2. 計算每個備選項集的支持度涡尘;
      3. 根據(jù)支持度,保留頻繁的項集,得到保留項集列表L_k响迂;
      4. k = k + 1考抄,使用保留項集列表,構(gòu)建數(shù)據(jù)集合S_k蔗彤,并繼續(xù)第1步川梅。
    • 算法M次循環(huán)結(jié)束,得到M個保留項集列表\{L_1, L_2,\cdots, L_M\}

  • 尋找最大頻繁項集核心包含三個:

    • 構(gòu)建備選項集列表然遏;
    • 計算每個備選項集的支持度贫途;
    • 根據(jù)支持選取的項集的標(biāo)準(zhǔn),生成保留項集列表待侵;
  • 豆奶與尿布的頻繁集經(jīng)典說明
    • 頻繁項集計算

頻繁項集算法實現(xiàn)

構(gòu)建備選項集列表

  1. 原始數(shù)據(jù)集
import numpy as np
# 準(zhǔn)備數(shù)據(jù)集
data =  np.array([
    ['豆奶', '萵苣'],
    ['萵苣', '尿布', '葡萄酒', '甜菜'],
    ['豆奶', '尿布', '葡萄酒', '橙汁'],
    ['萵苣', '豆奶', '尿布', '葡萄酒'],
    ['萵苣', '豆奶', '尿布', '橙汁']
])
  1. 構(gòu)建數(shù)據(jù)集合
S = set()
for item in data:
    S = S | frozenset(item)

print(S)
{'萵苣', '豆奶', '甜菜', '尿布', '橙汁', '葡萄酒'}
  1. 構(gòu)建備選項集列表
  • 從數(shù)據(jù)集合構(gòu)建
C_1 = []    # 備選項集列表
for s in S:
    C_1.append(frozenset([s]))
C_1
[frozenset({'萵苣'}),
 frozenset({'豆奶'}),
 frozenset({'甜菜'}),
 frozenset({'尿布'}),
 frozenset({'橙汁'}),
 frozenset({'葡萄酒'})]
  • 從項集列表提升構(gòu)建

    • 為什么要從已有項集列表產(chǎn)生新的項集列表丢早?
      • 如果被拋棄的項集支持度最低,則任何包含這個項集的項集的支持度也會是最低的秧倾,也是被拋棄對象怨酝。
    • 提升構(gòu)建就是從大小為k的項集列表提升為k+1的項集列表。
  • 從1到2的情況

# 要構(gòu)建的長度為2備選項集列表
C_2 = []
# 不是從集合構(gòu)建那先,而是從已有頻繁項集列表中構(gòu)建农猬。
# 從C_1中兩兩組合
len_C = len(C_1)
for i in range(len_C):
    for j in range(i+1, len_C):
        C_2.append(C_1[i] | C_1[j])

C_2
[frozenset({'萵苣', '豆奶'}),
 frozenset({'甜菜', '萵苣'}),
 frozenset({'尿布', '萵苣'}),
 frozenset({'橙汁', '萵苣'}),
 frozenset({'萵苣', '葡萄酒'}),
 frozenset({'甜菜', '豆奶'}),
 frozenset({'尿布', '豆奶'}),
 frozenset({'橙汁', '豆奶'}),
 frozenset({'葡萄酒', '豆奶'}),
 frozenset({'尿布', '甜菜'}),
 frozenset({'橙汁', '甜菜'}),
 frozenset({'甜菜', '葡萄酒'}),
 frozenset({'尿布', '橙汁'}),
 frozenset({'尿布', '葡萄酒'}),
 frozenset({'橙汁', '葡萄酒'})]
  • 從2到3的情況
C_3 = []
len_C = len(C_2)
for i in range(len_C):
    for j in range(i+1, len_C):
        # 為了從2變成3,取最后一個不同的合并(前面1個相同胃榕,后面一個不同盛险,就山城長度為3的項集)
        # {'萵苣', '豆奶'}, {'萵苣', '葡萄酒'}, 合并就是{'萵苣', '豆奶', '葡萄酒'},
        item_outer = list(C_2[i])   # 外層循環(huán)的數(shù)據(jù),轉(zhuǎn)換為list是為了比較方便
        item_inner = list(C_2[j])   # 內(nèi)層循環(huán)的數(shù)據(jù)
        # 取全面3-2 =1 的數(shù)據(jù)比較
        #(構(gòu)建是長度為3的項集列表勋又,實際項集列表長度為2苦掘,只對最后一列差異化檢測,就需要對前面2-1楔壤,或者3-2的數(shù)據(jù)比較)
        # 2-1:2是上次項集列表大小鹤啡,3是構(gòu)建的項集列表大小。
        c_outer = item_outer[0:3-2]    # item_outer[0:2-1]   2表示舊的項集大小   
        c_inner = item_inner[0:3-2]
        c_outer.sort()   # 排序是為了比較
        c_inner.sort()
        if c_outer == c_inner:
            C_3.append(C_2[i] | C_2[j])
C_3
[frozenset({'甜菜', '萵苣', '豆奶'}),
 frozenset({'尿布', '萵苣', '豆奶'}),
 frozenset({'橙汁', '萵苣', '豆奶'}),
 frozenset({'萵苣', '葡萄酒', '豆奶'}),
 frozenset({'尿布', '甜菜', '萵苣'}),
 frozenset({'橙汁', '甜菜', '萵苣'}),
 frozenset({'甜菜', '萵苣', '葡萄酒'}),
 frozenset({'尿布', '橙汁', '萵苣'}),
 frozenset({'尿布', '萵苣', '葡萄酒'}),
 frozenset({'橙汁', '萵苣', '葡萄酒'}),
 frozenset({'尿布', '甜菜', '豆奶'}),
 frozenset({'橙汁', '甜菜', '豆奶'}),
 frozenset({'甜菜', '葡萄酒', '豆奶'}),
 frozenset({'尿布', '葡萄酒', '豆奶'}),
 frozenset({'尿布', '橙汁', '豆奶'}),
 frozenset({'橙汁', '葡萄酒', '豆奶'}),
 frozenset({'尿布', '橙汁', '甜菜'}),
 frozenset({'尿布', '甜菜', '葡萄酒'}),
 frozenset({'橙汁', '甜菜', '葡萄酒'}),
 frozenset({'尿布', '橙汁', '葡萄酒'})]
  • 通用的規(guī)律實現(xiàn):
def generate_item_set(C_k_1, k):   
    # C_k_1 表示項集大小為k-1的列表
    # k表示新的項集大小
    C_k = []
    len_C = len(C_k_1)
    for i in range(len_C):
        for j in range(i+1, len_C):
            # 為了從2變成3蹲嚣,取最后一個不同的合并(前面1個相同递瑰,后面一個不同祟牲,就山城長度為3的項集)
            # {'萵苣', '豆奶'}, {'萵苣', '葡萄酒'}, 合并就是{'萵苣', '豆奶', '葡萄酒'},
            item_outer = list(C_k_1[i])   # 外層循環(huán)的數(shù)據(jù),轉(zhuǎn)換為list是為了比較方便
            item_inner = list(C_k_1[j])   # 內(nèi)層循環(huán)的數(shù)據(jù)
            # 取全面3-2 =1 的數(shù)據(jù)比較
            #(構(gòu)建是長度為3的項集列表抖部,實際項集列表長度為2说贝,只對最后一列差異化檢測,就需要對前面2-1慎颗,或者3-2的數(shù)據(jù)比較)
            # 2-1:2是上次項集列表大小乡恕,3是構(gòu)建的項集列表大小。
            c_outer = item_outer[0:k-2]   
            c_inner = item_inner[0:k-2]
            c_outer.sort()   # 排序是為了比較
            c_inner.sort()
            if c_outer == c_inner:
                C_k.append(C_k_1[i] | C_k_1[j])
    return C_k

C_2 = generate_item_set(C_1,2)
C_2
[frozenset({'萵苣', '豆奶'}),
 frozenset({'甜菜', '萵苣'}),
 frozenset({'尿布', '萵苣'}),
 frozenset({'橙汁', '萵苣'}),
 frozenset({'萵苣', '葡萄酒'}),
 frozenset({'甜菜', '豆奶'}),
 frozenset({'尿布', '豆奶'}),
 frozenset({'橙汁', '豆奶'}),
 frozenset({'葡萄酒', '豆奶'}),
 frozenset({'尿布', '甜菜'}),
 frozenset({'橙汁', '甜菜'}),
 frozenset({'甜菜', '葡萄酒'}),
 frozenset({'尿布', '橙汁'}),
 frozenset({'尿布', '葡萄酒'}),
 frozenset({'橙汁', '葡萄酒'})]
C_3 = generate_item_set(C_2,3)
C_3
[frozenset({'甜菜', '萵苣', '豆奶'}),
 frozenset({'尿布', '萵苣', '豆奶'}),
 frozenset({'橙汁', '萵苣', '豆奶'}),
 frozenset({'萵苣', '葡萄酒', '豆奶'}),
 frozenset({'尿布', '甜菜', '萵苣'}),
 frozenset({'橙汁', '甜菜', '萵苣'}),
 frozenset({'甜菜', '萵苣', '葡萄酒'}),
 frozenset({'尿布', '橙汁', '萵苣'}),
 frozenset({'尿布', '萵苣', '葡萄酒'}),
 frozenset({'橙汁', '萵苣', '葡萄酒'}),
 frozenset({'尿布', '甜菜', '豆奶'}),
 frozenset({'橙汁', '甜菜', '豆奶'}),
 frozenset({'甜菜', '葡萄酒', '豆奶'}),
 frozenset({'尿布', '葡萄酒', '豆奶'}),
 frozenset({'尿布', '橙汁', '豆奶'}),
 frozenset({'橙汁', '葡萄酒', '豆奶'}),
 frozenset({'尿布', '橙汁', '甜菜'}),
 frozenset({'尿布', '甜菜', '葡萄酒'}),
 frozenset({'橙汁', '甜菜', '葡萄酒'}),
 frozenset({'尿布', '橙汁', '葡萄酒'})]

計算支持度

  • 計算支持度的關(guān)鍵是統(tǒng)計項集在元素數(shù)據(jù)集中同時出現(xiàn)的次數(shù)俯萎;
    • 這個涉及集合的包含運算傲宜。
  1. 數(shù)據(jù)準(zhǔn)備
    • 把原始數(shù)據(jù)集轉(zhuǎn)變成集合的列表
    • 需要一個候選項集列表
# 原始數(shù)據(jù)
data =  np.array([
    ['豆奶', '萵苣'],
    ['萵苣', '尿布', '葡萄酒', '甜菜'],
    ['豆奶', '尿布', '葡萄酒', '橙汁'],
    ['萵苣', '豆奶', '尿布', '葡萄酒'],
    ['萵苣', '豆奶', '尿布', '橙汁']
])

D = list(map(frozenset, data)) 
D
[frozenset({'萵苣', '豆奶'}),
 frozenset({'尿布', '甜菜', '萵苣', '葡萄酒'}),
 frozenset({'尿布', '橙汁', '葡萄酒', '豆奶'}),
 frozenset({'尿布', '萵苣', '葡萄酒', '豆奶'}),
 frozenset({'尿布', '橙汁', '萵苣', '豆奶'})]
# 把原始數(shù)據(jù)集編程一個數(shù)據(jù)項集合。
S = frozenset()
for item in D:
    S = S | item
# 使用數(shù)據(jù)項集合生成C1備選項集列表
C_1 = []    # 備選項集列表
for s in S:
    C_1.append(frozenset([s]))
C_1
[frozenset({'萵苣'}),
 frozenset({'豆奶'}),
 frozenset({'甜菜'}),
 frozenset({'尿布'}),
 frozenset({'橙汁'}),
 frozenset({'葡萄酒'})]
C_2 = generate_item_set(C_1,2)
C_3 = generate_item_set(C_2,3)
C_2, C_3
([frozenset({'萵苣', '豆奶'}),
  frozenset({'甜菜', '萵苣'}),
  frozenset({'尿布', '萵苣'}),
  frozenset({'橙汁', '萵苣'}),
  frozenset({'萵苣', '葡萄酒'}),
  frozenset({'甜菜', '豆奶'}),
  frozenset({'尿布', '豆奶'}),
  frozenset({'橙汁', '豆奶'}),
  frozenset({'葡萄酒', '豆奶'}),
  frozenset({'尿布', '甜菜'}),
  frozenset({'橙汁', '甜菜'}),
  frozenset({'甜菜', '葡萄酒'}),
  frozenset({'尿布', '橙汁'}),
  frozenset({'尿布', '葡萄酒'}),
  frozenset({'橙汁', '葡萄酒'})],
 [frozenset({'甜菜', '萵苣', '豆奶'}),
  frozenset({'尿布', '萵苣', '豆奶'}),
  frozenset({'橙汁', '萵苣', '豆奶'}),
  frozenset({'萵苣', '葡萄酒', '豆奶'}),
  frozenset({'尿布', '甜菜', '萵苣'}),
  frozenset({'橙汁', '甜菜', '萵苣'}),
  frozenset({'甜菜', '萵苣', '葡萄酒'}),
  frozenset({'尿布', '橙汁', '萵苣'}),
  frozenset({'尿布', '萵苣', '葡萄酒'}),
  frozenset({'橙汁', '萵苣', '葡萄酒'}),
  frozenset({'尿布', '甜菜', '豆奶'}),
  frozenset({'橙汁', '甜菜', '豆奶'}),
  frozenset({'甜菜', '葡萄酒', '豆奶'}),
  frozenset({'尿布', '葡萄酒', '豆奶'}),
  frozenset({'尿布', '橙汁', '豆奶'}),
  frozenset({'橙汁', '葡萄酒', '豆奶'}),
  frozenset({'尿布', '橙汁', '甜菜'}),
  frozenset({'尿布', '甜菜', '葡萄酒'}),
  frozenset({'橙汁', '甜菜', '葡萄酒'}),
  frozenset({'尿布', '橙汁', '葡萄酒'})])
  1. 計算支持度

    • 下面算法中set類型不能做字典key夫啊,只有frozenset才能使用字典key函卒,所以在所有操作中使用frozenset構(gòu)建集合。
# 使用字典存放統(tǒng)計結(jié)果
item_count = {}
# 循環(huán)計算每個項集的支持度
for  c in C_3:
    # 循環(huán)統(tǒng)計在原始數(shù)據(jù)中出現(xiàn)的次數(shù)
    for d in D:
        if c.issubset(d):  # 項集包含在原始數(shù)據(jù)項中
            item_count[c] = item_count.get(c, 0) + 1
item_count
{frozenset({'尿布', '萵苣', '豆奶'}): 2,
 frozenset({'橙汁', '萵苣', '豆奶'}): 1,
 frozenset({'萵苣', '葡萄酒', '豆奶'}): 1,
 frozenset({'尿布', '甜菜', '萵苣'}): 1,
 frozenset({'甜菜', '萵苣', '葡萄酒'}): 1,
 frozenset({'尿布', '橙汁', '萵苣'}): 1,
 frozenset({'尿布', '萵苣', '葡萄酒'}): 2,
 frozenset({'尿布', '葡萄酒', '豆奶'}): 2,
 frozenset({'尿布', '橙汁', '豆奶'}): 2,
 frozenset({'橙汁', '葡萄酒', '豆奶'}): 1,
 frozenset({'尿布', '甜菜', '葡萄酒'}): 1,
 frozenset({'尿布', '橙汁', '葡萄酒'}): 1}
# 支持度計算如下:
item_support = {}
total_num = len(D)
for k, v in item_count.items():
    item_support[k] = v / total_num

item_support
{frozenset({'尿布', '萵苣', '豆奶'}): 0.4,
 frozenset({'橙汁', '萵苣', '豆奶'}): 0.2,
 frozenset({'萵苣', '葡萄酒', '豆奶'}): 0.2,
 frozenset({'尿布', '甜菜', '萵苣'}): 0.2,
 frozenset({'甜菜', '萵苣', '葡萄酒'}): 0.2,
 frozenset({'尿布', '橙汁', '萵苣'}): 0.2,
 frozenset({'尿布', '萵苣', '葡萄酒'}): 0.4,
 frozenset({'尿布', '葡萄酒', '豆奶'}): 0.4,
 frozenset({'尿布', '橙汁', '豆奶'}): 0.4,
 frozenset({'橙汁', '葡萄酒', '豆奶'}): 0.2,
 frozenset({'尿布', '甜菜', '葡萄酒'}): 0.2,
 frozenset({'尿布', '橙汁', '葡萄酒'}): 0.2}

根據(jù)支持度返回頻繁項集列表

  • 把支持度封裝成函數(shù)
    • 同時設(shè)置一個支持度閾值撇眯,用于保留頻繁項集列表报嵌。
def calcute_support(D, C, threshold):
    # 使用字典存放統(tǒng)計結(jié)果
    item_count = {}
    total_num = len(D)
    # 循環(huán)計算每個項集的支持度
    for  c in C:
        # 循環(huán)統(tǒng)計在原始數(shù)據(jù)中出現(xiàn)的次數(shù)
        for d in D:
            if c.issubset(d):  # 項集包含在原始數(shù)據(jù)項中
                item_count[c] = item_count.get(c, 0) + 1
    # 支持度計算如下:
    item_support = {}
    total_num = len(D)
    for k, v in item_count.items():
        support = v / total_num
        if support >= threshold:
            item_support[k] = support
    return item_support

O = calcute_support(D,C_3,0.4)
O, list(O.keys())
({frozenset({'尿布', '萵苣', '豆奶'}): 0.4,
  frozenset({'尿布', '萵苣', '葡萄酒'}): 0.4,
  frozenset({'尿布', '葡萄酒', '豆奶'}): 0.4,
  frozenset({'尿布', '橙汁', '豆奶'}): 0.4},
 [frozenset({'尿布', '萵苣', '豆奶'}),
  frozenset({'尿布', '萵苣', '葡萄酒'}),
  frozenset({'尿布', '葡萄酒', '豆奶'}),
  frozenset({'尿布', '橙汁', '豆奶'})])
  • 返回所有的頻繁項集列表的列表
def generate_item_set(C_k_1, k):   
    # C_k_1 表示項集大小為k-1的列表
    # k表示新的項集大小
    C_k = []
    len_C = len(C_k_1)
    for i in range(len_C):
        for j in range(i+1, len_C):
            # 為了從2變成3,取最后一個不同的合并(前面1個相同叛本,后面一個不同沪蓬,就山城長度為3的項集)
            # {'萵苣', '豆奶'}, {'萵苣', '葡萄酒'}, 合并就是{'萵苣', '豆奶', '葡萄酒'},
            item_outer = list(C_k_1[i])   # 外層循環(huán)的數(shù)據(jù),轉(zhuǎn)換為list是為了比較方便
            item_inner = list(C_k_1[j])   # 內(nèi)層循環(huán)的數(shù)據(jù)
            # 取全面3-2 =1 的數(shù)據(jù)比較
            #(構(gòu)建是長度為3的項集列表来候,實際項集列表長度為2跷叉,只對最后一列差異化檢測,就需要對前面2-1营搅,或者3-2的數(shù)據(jù)比較)
            # 2-1:2是上次項集列表大小云挟,3是構(gòu)建的項集列表大小。
            c_outer = item_outer[0:k-2]   
            c_inner = item_inner[0:k-2]
            c_outer.sort()   # 排序是為了比較
            c_inner.sort()
            if c_outer == c_inner:
                C_k.append(C_k_1[i] | C_k_1[j])
    return C_k

def calcute_support(D, C, threshold):
    # 使用字典存放統(tǒng)計結(jié)果
    item_count = {}
    total_num = len(D)
    # 循環(huán)計算每個項集的支持度
    for  c in C:
        # 循環(huán)統(tǒng)計在原始數(shù)據(jù)中出現(xiàn)的次數(shù)
        for d in D:
            if c.issubset(d):  # 項集包含在原始數(shù)據(jù)項中
                item_count[c] = item_count.get(c, 0) + 1
    # 支持度計算如下:
    item_support = {}
    total_num = len(D)
    for k, v in item_count.items():
        support = v / total_num
        if support >= threshold:
            item_support[k] = support
    return item_support


def get_all_often(data, support):
    all_often = {}
    # 把數(shù)據(jù)集轉(zhuǎn)換為集合的列表
    D = list(map(frozenset, data))
    # 生成備選項集列表
    S = frozenset()
    for item in D:
        S = S | item
    # 使用數(shù)據(jù)項集合生成C1備選項集列表
    C_1 = []    # 備選項集列表
    for s in S:
        C_1.append(frozenset([s]))
    # 計算C1的頻繁項集列表
    O_1 = calcute_support(D, C_1, support)
    all_often.update(O_1)
    # 訓(xùn)練計算2转质,3园欣,4,休蟹。沸枯。。直到返回的列表為0
    #     print(O_1)
    k = 2
    while True:
        # 候選項集列表
        C = generate_item_set(list(O_1.keys()), k)
        O = calcute_support(D, C, support)
        # 如果沒有備選項集列表赂弓,則結(jié)束
        if len(O)==0:
            break
        k +=1
        all_often.update(O)
        O_1 = O
    return all_often

data =  np.array([
    ['豆奶', '萵苣'],
    ['萵苣', '尿布', '葡萄酒', '甜菜'],
    ['豆奶', '尿布', '葡萄酒', '橙汁'],
    ['萵苣', '豆奶', '尿布', '葡萄酒'],
    ['萵苣', '豆奶', '尿布', '橙汁']
])

list_often = get_all_often(data, 0.4)
list_often
{frozenset({'萵苣'}): 0.8,
 frozenset({'豆奶'}): 0.8,
 frozenset({'尿布'}): 0.8,
 frozenset({'橙汁'}): 0.4,
 frozenset({'葡萄酒'}): 0.6,
 frozenset({'萵苣', '豆奶'}): 0.6,
 frozenset({'尿布', '萵苣'}): 0.6,
 frozenset({'萵苣', '葡萄酒'}): 0.4,
 frozenset({'尿布', '豆奶'}): 0.6,
 frozenset({'橙汁', '豆奶'}): 0.4,
 frozenset({'葡萄酒', '豆奶'}): 0.4,
 frozenset({'尿布', '橙汁'}): 0.4,
 frozenset({'尿布', '葡萄酒'}): 0.6,
 frozenset({'尿布', '萵苣', '豆奶'}): 0.4,
 frozenset({'尿布', '萵苣', '葡萄酒'}): 0.4,
 frozenset({'尿布', '葡萄酒', '豆奶'}): 0.4,
 frozenset({'尿布', '橙汁', '豆奶'}): 0.4}

得到關(guān)聯(lián)規(guī)則

  • 規(guī)則度量是置信度(就是條件概率)

理解關(guān)聯(lián)規(guī)則

  • 所謂關(guān)聯(lián)規(guī)則就是在已經(jīng)得到頻繁項集的基礎(chǔ)上绑榴,可以使用頻繁項集生成關(guān)聯(lián)規(guī)則:
    • 假設(shè)項集為{'尿布', '甜菜', '萵苣', '葡萄酒'},則可以生成很多規(guī)則:
      • {尿布', '甜菜', '萵苣'} -> {'葡萄酒'}
      • {'尿布', '甜菜', '葡萄酒'} -> {'萵苣'}
      • {'尿布', '萵苣', '葡萄酒'} -> {'甜菜'}
      • {'甜菜', '萵苣', '葡萄酒'} -> {'尿布''}
      • \cdots
      • { '甜菜', '萵苣'} -> {'尿布', '葡萄酒'}
      • \cdots
    • 關(guān)聯(lián)規(guī)則表示為:項集 -> 項集
      • 后面項集稱為:后繼(consequent或者right-hand-side:RHS):
        • 根據(jù)后繼大小稱為n-后繼盈魁;比如上面的就是1-后繼翔怎。
      • 前面項集稱為:先導(dǎo)(antecedent或left-hand-side:LHS)
    • 關(guān)聯(lián)規(guī)則可以分成強(qiáng)關(guān)聯(lián)規(guī)則與弱關(guān)聯(lián)規(guī)則:
      • 強(qiáng)關(guān)聯(lián)規(guī)則:支持度與置信度都滿足閾值的。
      • 弱關(guān)聯(lián)規(guī)則:支持度與置信度只有一個滿足閾值的。
  • 關(guān)聯(lián)規(guī)則分析就是:
    • 計算關(guān)聯(lián)規(guī)則的置信度赤套。
    • 找出滿足閾值的關(guān)聯(lián)規(guī)則飘痛。

分析1-后繼關(guān)鍵規(guī)則

# 這里我們只計算1-后繼的情況
# 規(guī)則的存放形式{(先導(dǎo),后繼), 置信度}
# 生成關(guān)聯(lián)規(guī)則容握,計算關(guān)聯(lián)規(guī)則的置信度
def generate_rule_confidence(all_often, often_item, threshold):
    # all_often所有的頻繁項集l列表:字典類型宣脉,提供用來計算置信度的支持度
    # often_item需要生成規(guī)則項集:集合類型,同來生成規(guī)則
    # threshold置信度閾值:小數(shù)類型剔氏,用來保留強(qiáng)關(guān)聯(lián)規(guī)則
    # 生成1-后繼的集合(后繼本身也是集合脖旱,大小為1)
    rhs_1s = []
    for item in often_item:
        rhs_1s .append(frozenset([item]))
    # 產(chǎn)生規(guī)則,并計算置信度
    rules_confidence = {}
    for rhs in  rhs_1s:
        rule = (often_item - rhs, rhs)
        # print(all_often[often_item], all_often[rhs], all_often[often_item - rhs])
        confidence = all_often[often_item] / all_often[rhs]
        if confidence >= threshold:
            rules_confidence[rule] = confidence
    
    return rules_confidence

generate_rule_confidence(list_often, list(list_often.keys())[16], 0.5)
{(frozenset({'尿布', '豆奶'}), frozenset({'橙汁'})): 1.0,
 (frozenset({'橙汁', '豆奶'}), frozenset({'尿布'})): 0.5,
 (frozenset({'尿布', '橙汁'}), frozenset({'豆奶'})): 0.5}

分析所有頻繁項集的規(guī)則

def get_all_rule(list_often, threshold):
    all_rules = {}
    for item in  list(list_often.keys()):
        # 只計算項集大于2的情況
        if len(item) >=2:
            all_rules.update(generate_rule_confidence(list_often, item, threshold))
    return all_rules
get_all_rule(list_often, 0.7)
{(frozenset({'豆奶'}), frozenset({'萵苣'})): 0.7499999999999999,
 (frozenset({'萵苣'}), frozenset({'豆奶'})): 0.7499999999999999,
 (frozenset({'尿布'}), frozenset({'萵苣'})): 0.7499999999999999,
 (frozenset({'萵苣'}), frozenset({'尿布'})): 0.7499999999999999,
 (frozenset({'豆奶'}), frozenset({'尿布'})): 0.7499999999999999,
 (frozenset({'尿布'}), frozenset({'豆奶'})): 0.7499999999999999,
 (frozenset({'豆奶'}), frozenset({'橙汁'})): 1.0,
 (frozenset({'尿布'}), frozenset({'橙汁'})): 1.0,
 (frozenset({'葡萄酒'}), frozenset({'尿布'})): 0.7499999999999999,
 (frozenset({'尿布'}), frozenset({'葡萄酒'})): 1.0,
 (frozenset({'尿布', '豆奶'}), frozenset({'橙汁'})): 1.0}

進(jìn)一步的思考

  1. n-后繼的規(guī)則分析介蛉。
  2. 有好的算法可以減少計算次數(shù)。
  3. 規(guī)則去重的問題:
    • 下面的規(guī)則是否算重復(fù)的:
      • (frozenset({'尿布', '豆奶'}), frozenset({'橙汁'})): 1.0}
      • (frozenset({'豆奶'}), frozenset({'橙汁'})): 1.0,
      • (frozenset({'尿布'}), frozenset({'橙汁'})): 1.0,
  4. 上面代碼更好的通用性溶褪。

實戰(zhàn)

  • 分析用戶購買的習(xí)慣與商品的關(guān)系
    • 根據(jù)訂單號與商品的關(guān)系币旧。
    • 根據(jù)訂單號與商品類別的關(guān)系。
  • 分析商品價格與商品的關(guān)系
    • 分析價格段與商品的關(guān)系
  1. 讀取數(shù)據(jù)
import pandas as pd
import numpy as np

detail_data = pd.read_csv('assiocation.csv')
# detail_data[0:10]
  • 按照訂單號分組猿妈,形成商品列表
data_product =  detail_data[['Order Number', 'Product']]

data_group_by_order = data_product.groupby(['Order Number'])
# for one_group in data_group_by_order:
#     print(one_group)
# data_group_by_order.groups
# data_group_by_order.indices

def products_concate(grp):
    list_concate = []
    for d in grp['Product']:
        list_concate .append(d)
        
    return  pd.Series(data=[list_concate], index=['Product'])

products = data_group_by_order.apply(products_concate)
list_products = []
for pr in products['Product']:
    list_products.append(pr)
# list_products
def generate_item_set(C_k_1, k):   
    # C_k_1 表示項集大小為k-1的列表
    # k表示新的項集大小
    C_k = []
    len_C = len(C_k_1)
    for i in range(len_C):
        for j in range(i+1, len_C):
            # 為了從2變成3吹菱,取最后一個不同的合并(前面1個相同,后面一個不同彭则,就山城長度為3的項集)
            # {'萵苣', '豆奶'}, {'萵苣', '葡萄酒'}, 合并就是{'萵苣', '豆奶', '葡萄酒'},
            item_outer = list(C_k_1[i])   # 外層循環(huán)的數(shù)據(jù)鳍刷,轉(zhuǎn)換為list是為了比較方便
            item_inner = list(C_k_1[j])   # 內(nèi)層循環(huán)的數(shù)據(jù)
            # 取全面3-2 =1 的數(shù)據(jù)比較
            #(構(gòu)建是長度為3的項集列表,實際項集列表長度為2俯抖,只對最后一列差異化檢測输瓜,就需要對前面2-1,或者3-2的數(shù)據(jù)比較)
            # 2-1:2是上次項集列表大小芬萍,3是構(gòu)建的項集列表大小尤揣。
            c_outer = item_outer[0:k-2]   
            c_inner = item_inner[0:k-2]
            c_outer.sort()   # 排序是為了比較
            c_inner.sort()
            if c_outer == c_inner:
                C_k.append(C_k_1[i] | C_k_1[j])
    return C_k

def calcute_support(D, C, threshold):
    # 使用字典存放統(tǒng)計結(jié)果
    item_count = {}
    total_num = len(D)
    # 循環(huán)計算每個項集的支持度
    for  c in C:
        # 循環(huán)統(tǒng)計在原始數(shù)據(jù)中出現(xiàn)的次數(shù)
        for d in D:
            if c.issubset(d):  # 項集包含在原始數(shù)據(jù)項中
                item_count[c] = item_count.get(c, 0) + 1
    # 支持度計算如下:
    item_support = {}
    total_num = len(D)
    for k, v in item_count.items():
        support = v / total_num
        if support >= threshold:
            item_support[k] = support
    return item_support


def get_all_often(data, support):
    all_often = {}
    # 把數(shù)據(jù)集轉(zhuǎn)換為集合的列表
    D = list(map(frozenset, data))
    # 生成備選項集列表
    S = frozenset()
    for item in D:
        S = S | item
    # 使用數(shù)據(jù)項集合生成C1備選項集列表
    C_1 = []    # 備選項集列表
    for s in S:
        C_1.append(frozenset([s]))
    # 計算C1的頻繁項集列表
    O_1 = calcute_support(D, C_1, support)
    all_often.update(O_1)
    # 訓(xùn)練計算2,3柬祠,4北戏,。漫蛔。嗜愈。直到返回的列表為0
    #     print(O_1)
    k = 2
    while True:
        # 候選項集列表
        C = generate_item_set(list(O_1.keys()), k)
        O = calcute_support(D, C, support)
        # 如果沒有備選項集列表,則結(jié)束
        if len(O)==0:
            break
        k +=1
        all_often.update(O)
        O_1 = O
    return all_often

# 這里我們只計算1-后繼的情況
# 規(guī)則的存放形式{(先導(dǎo)莽龟,后繼), 置信度}
# 生成關(guān)聯(lián)規(guī)則蠕嫁,計算關(guān)聯(lián)規(guī)則的置信度
def generate_rule_confidence(all_often, often_item, threshold):
    # all_often所有的頻繁項集l列表:字典類型,提供用來計算置信度的支持度
    # often_item需要生成規(guī)則項集:集合類型轧房,同來生成規(guī)則
    # threshold置信度閾值:小數(shù)類型拌阴,用來保留強(qiáng)關(guān)聯(lián)規(guī)則
    # 生成1-后繼的集合(后繼本身也是集合,大小為1)
    rhs_1s = []
    for item in often_item:
        rhs_1s .append(frozenset([item]))
    # 產(chǎn)生規(guī)則奶镶,并計算置信度
    rules_confidence = {}
    for rhs in  rhs_1s:
        rule = (often_item - rhs, rhs)
        # print(all_often[often_item], all_often[rhs], all_often[often_item - rhs])
        confidence = all_often[often_item] / all_often[rhs]
        if confidence >= threshold:
            rules_confidence[rule] = confidence
    
    return rules_confidence

def get_all_rule(list_often, threshold):
    all_rules = {}
    for item in  list(list_often.keys()):
        # 只計算項集大于2的情況
        if len(item) >=2:
            all_rules.update(generate_rule_confidence(list_often, item, threshold))
    return all_rules

list_often = get_all_often(list_products, 0.02)
list_often
{frozenset({'Half-Finger Gloves'}): 0.06505747126436781,
 frozenset({'HL Mountain Tire'}): 0.0625287356321839,
 frozenset({'Touring-3000'}): 0.024521072796934867,
 frozenset({'ML Mountain Tire'}): 0.05065134099616858,
 frozenset({'Short-Sleeve Classic Jersey'}): 0.07088122605363985,
 frozenset({'Fender Set - Mountain'}): 0.09486590038314176,
 frozenset({'LL Mountain Tire'}): 0.038237547892720304,
 frozenset({'Racing Socks'}): 0.024521072796934867,
 frozenset({"Women's Mountain Shorts"}): 0.04475095785440613,
 frozenset({'Road-750'}): 0.0642911877394636,
 frozenset({'Sport-100'}): 0.290727969348659,
 frozenset({'Classic Vest'}): 0.02735632183908046,
 frozenset({'Road Tire Tube'}): 0.10551724137931034,
 frozenset({'Touring Tire'}): 0.04459770114942529,
 frozenset({'Road-350-W'}): 0.04681992337164751,
 frozenset({'Bike Wash'}): 0.040229885057471264,
 frozenset({'ML Road Tire'}): 0.04084291187739464,
 frozenset({'Mountain Bottle Cage'}): 0.09203065134099617,
 frozenset({'Mountain-400-W'}): 0.02574712643678161,
 frozenset({'Road Bottle Cage'}): 0.07701149425287357,
 frozenset({'Hydration Pack'}): 0.032796934865900386,
 frozenset({'Mountain-200'}): 0.1128735632183908,
 frozenset({'Water Bottle'}): 0.1917241379310345,
 frozenset({'HL Road Tire'}): 0.035478927203065135,
 frozenset({'Touring Tire Tube'}): 0.06873563218390805,
 frozenset({'Long-Sleeve Logo Jersey'}): 0.08068965517241379,
 frozenset({'LL Road Tire'}): 0.04659003831417625,
 frozenset({'Road-250'}): 0.023065134099616857,
 frozenset({'Cycling Cap'}): 0.1,
 frozenset({'Road-550-W'}): 0.04735632183908046,
 frozenset({'Mountain-500'}): 0.021839080459770115,
 frozenset({'Mountain Tire Tube'}): 0.13655172413793104,
 frozenset({'Touring-1000'}): 0.0621455938697318,
 frozenset({'Patch kit'}): 0.14061302681992338,
 frozenset({'Half-Finger Gloves', 'Sport-100'}): 0.026973180076628352,
 frozenset({'HL Mountain Tire', 'Sport-100'}): 0.020229885057471263,
 frozenset({'HL Mountain Tire', 'Mountain-200'}): 0.024061302681992337,
 frozenset({'HL Mountain Tire', 'Mountain Tire Tube'}): 0.042298850574712644,
 frozenset({'ML Mountain Tire', 'Mountain Tire Tube'}): 0.03333333333333333,
 frozenset({'Fender Set - Mountain', 'Sport-100'}): 0.02436781609195402,
 frozenset({'Fender Set - Mountain', 'Mountain-200'}): 0.033563218390804596,
 frozenset({'LL Mountain Tire', 'Mountain Tire Tube'}): 0.021226053639846743,
 frozenset({'Road Bottle Cage', 'Road-750'}): 0.02475095785440613,
 frozenset({'Road-750', 'Water Bottle'}): 0.021302681992337164,
 frozenset({'Road Tire Tube', 'Sport-100'}): 0.03977011494252874,
 frozenset({'Mountain Bottle Cage', 'Sport-100'}): 0.026436781609195402,
 frozenset({'Mountain-200', 'Sport-100'}): 0.03118773946360153,
 frozenset({'Sport-100', 'Water Bottle'}): 0.049885057471264364,
 frozenset({'Sport-100', 'Touring Tire Tube'}): 0.023065134099616857,
 frozenset({'Long-Sleeve Logo Jersey', 'Sport-100'}): 0.022911877394636015,
 frozenset({'Cycling Cap', 'Sport-100'}): 0.022375478927203065,
 frozenset({'Road-550-W', 'Sport-100'}): 0.020229885057471263,
 frozenset({'Mountain Tire Tube', 'Sport-100'}): 0.05739463601532567,
 frozenset({'Sport-100', 'Touring-1000'}): 0.02636015325670498,
 frozenset({'ML Road Tire', 'Road Tire Tube'}): 0.02781609195402299,
 frozenset({'HL Road Tire', 'Road Tire Tube'}): 0.024980842911877396,
 frozenset({'LL Road Tire', 'Road Tire Tube'}): 0.025593869731800768,
 frozenset({'Patch kit', 'Road Tire Tube'}): 0.0242911877394636,
 frozenset({'Touring Tire', 'Touring Tire Tube'}): 0.03885057471264368,
 frozenset({'Mountain Bottle Cage', 'Mountain-200'}): 0.03295019157088123,
 frozenset({'Mountain Bottle Cage', 'Water Bottle'}): 0.07647509578544061,
 frozenset({'Road Bottle Cage', 'Water Bottle'}): 0.06873563218390805,
 frozenset({'Mountain-200', 'Water Bottle'}): 0.02636015325670498,
 frozenset({'Cycling Cap', 'Water Bottle'}): 0.02950191570881226,
 frozenset({'Mountain Tire Tube', 'Patch kit'}): 0.03425287356321839,
 frozenset({'Road Bottle Cage',
            'Road-750',
            'Water Bottle'}): 0.021302681992337164,
 frozenset({'Mountain Bottle Cage',
            'Sport-100',
            'Water Bottle'}): 0.02153256704980843,
 frozenset({'Mountain Bottle Cage',
            'Mountain-200',
            'Water Bottle'}): 0.02636015325670498}
get_all_rule(list_often, 0.6)
{(frozenset({'Mountain Tire Tube'}),
  frozenset({'HL Mountain Tire'})): 0.6764705882352942,
 (frozenset({'Mountain Tire Tube'}),
  frozenset({'ML Mountain Tire'})): 0.6580937972768532,
 (frozenset({'Road Tire Tube'}),
  frozenset({'ML Road Tire'})): 0.6810506566604128,
 (frozenset({'Road Tire Tube'}),
  frozenset({'HL Road Tire'})): 0.7041036717062635,
 (frozenset({'Touring Tire Tube'}),
  frozenset({'Touring Tire'})): 0.8711340206185567,
 (frozenset({'Water Bottle'}),
  frozenset({'Mountain Bottle Cage'})): 0.8309741881765196,
 (frozenset({'Water Bottle'}),
  frozenset({'Road Bottle Cage'})): 0.8925373134328357}
  • 實戰(zhàn)例子的優(yōu)化:
    • 可以考慮商品大類的關(guān)聯(lián)規(guī)則分析迟赃,用來提升關(guān)聯(lián)規(guī)則分析結(jié)果陪拘。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市纤壁,隨后出現(xiàn)的幾起案子左刽,更是在濱河造成了極大的恐慌,老刑警劉巖酌媒,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件欠痴,死亡現(xiàn)場離奇詭異,居然都是意外死亡秒咨,警方通過查閱死者的電腦和手機(jī)喇辽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來雨席,“玉大人菩咨,你說我怎么就攤上這事《咐澹” “怎么了抽米?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長糙置。 經(jīng)常有香客問我云茸,道長,這世上最難降的妖魔是什么谤饭? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任标捺,我火速辦了婚禮,結(jié)果婚禮上揉抵,老公的妹妹穿的比我還像新娘宜岛。我一直安慰自己,他們只是感情好功舀,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布萍倡。 她就那樣靜靜地躺著,像睡著了一般辟汰。 火紅的嫁衣襯著肌膚如雪列敲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天帖汞,我揣著相機(jī)與錄音戴而,去河邊找鬼。 笑死翩蘸,一個胖子當(dāng)著我的面吹牛所意,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼扶踊,長吁一口氣:“原來是場噩夢啊……” “哼泄鹏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起秧耗,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤备籽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后分井,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體车猬,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年尺锚,在試婚紗的時候發(fā)現(xiàn)自己被綠了珠闰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡瘫辩,死狀恐怖铸磅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情杭朱,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布吹散,位于F島的核電站弧械,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏空民。R本人自食惡果不足惜刃唐,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望界轩。 院中可真熱鬧画饥,春花似錦、人聲如沸浊猾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽葫慎。三九已至衔彻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間偷办,已是汗流浹背艰额。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留椒涯,地道東北人柄沮。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親祖搓。 傳聞我的和親對象是個殘疾皇子狱意,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容