1. 程式人生 > >資料探勘-MovieLens資料集_電影推薦_親和性分析_Aprioro演算法

資料探勘-MovieLens資料集_電影推薦_親和性分析_Aprioro演算法

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Created on Tue Feb  7 14:38:33 2017

電影推薦分析:
    使用 親和性分析方法 基於 Apriori演算法 推薦電影

@author: yingzhang
"""

#讀取資料集: http://grouplens.org/datasets/movielens/
import os
#使用pandas載入資料
import pandas as pd
'''
1m資料集讀取方法
'''
#data_folder=os.path.join( os.path.expanduser("~"),"ml-1m")
#ratings_filename=os.path.join( data_folder,"ratings.dat")
#all_ratings=pd.read_csv( ratings_filename, delimiter="::",header=None, names=["UserID","MovieID","Rating","Datetime"])
'''
100k資料集讀取方法
'''
data_folder=os.path.join( os.path.expanduser("~"),"ml-100k")
#獲取使用者評分資料
ratings_filename=os.path.join( data_folder,"u.data")
all_ratings=pd.read_csv( ratings_filename, delimiter="\t",header=None, names=["UserID","MovieID","Rating","Datetime"])

all_ratings[:1]
#輸出的資料格式如下
'''
   UserID  MovieID  Rating   Datetime
0       1     1193       5  978300760
'''
#時間格式要轉換一下
all_ratings["Datetime"]=pd.to_datetime(all_ratings["Datetime"],unit='s')
all_ratings[:1]

#新增一列,用來存使用者對某個電影是否喜歡 ( 如果評分大於3)
all_ratings["Favorable"]=all_ratings["Rating"]>3
all_ratings[:10]
#輸出的資料格式如下: Favorable這一列的資料表明使用者是否喜歡這部電影
'''
   UserID  MovieID  Rating            Datetime Favorable
0       1     1193       5 2000-12-31 22:12:40      True
1       1      661       3 2000-12-31 22:35:09     False
'''
#從資料集中取前200名使用者的打分資料作訓練集
ratings=all_ratings[  all_ratings['UserID'].isin(range(200))]
#過濾一次資料,只保留使用者喜歡的電影(即 Favorable為True值的)
favorable_ratings=ratings[ratings["Favorable"]]
favorable_ratings[:5]

#因為要生成頻繁項集,所以我們只對打分超過一次的使用者感興趣,所以按照UserID分組,並遍歷每個使用者看過的每一部電影,存到一個字典中
from collections import defaultdict
favorable_reviews_by_users=dict((k,frozenset(v.values)) 
                                for k,v in favorable_ratings.groupby("UserID")["MovieID"])
print("length: {0}".format( len(favorable_reviews_by_users) ) )

#再建立一個數據框,存入每部電影評價分為3分以上的人數( 即 Favorable為True)的數量
num_favorable_by_movie=ratings[["MovieID","Favorable"]].groupby("MovieID").sum()
#檢視結果
num_favorable_by_movie
#排序輸出前五名
num_favorable_by_movie.sort( "Favorable",ascending=False)[:5]


#建立一個函式,它接收新發現的頻繁項集,建立超集,檢測頻繁程度
'''
favorable_reviews_by_users: 使用者打分情況的集合
k_1_itemsets: 上一個頻繁項集
min_support:最小支援度
返回值格式:
    dict( 頻繁項集    支援度 )
'''
def find_frequent_itemsets( favorable_reviews_by_users, k_1_itemsets, min_support):
    counts=defaultdict( int )
    #迴圈使用者和他們的打分資料
    for user,reviews in favorable_reviews_by_users.items():
        #再迴圈前一次找出的頻繁項集,判斷它們是否為 reviews的子集,
        for itemset in k_1_itemsets:
            if itemset.issubset( reviews):  #如果是,表明使用者已經為子集中的電影打過分了
                #那麼接下來,就要遍歷使用者打過分卻沒有出現在項集reviews中的電影了,因為這樣可以用它來生成超集,更新該項集的計數
                for other_reviewed_movie in reviews-itemset: #other_reviewed_movie 使用者打過分,但還不在頻繁項集中
                    current_superset=itemset|frozenset( (other_reviewed_movie,))
                    counts[current_superset]+=1    #這個頻繁項集的數量支援度+1
    #函式最後檢測達到支援度要求的項集,只返回頻繁度夠的頻繁項集
    return dict(  [(itemset,frequency) for itemset,frequency in counts.items()  if frequency>=min_support   ]           )            
                
import sys
#建立一個字典,存不同長度的頻繁項集
#資料格式:
    #頻繁項集長度   對應的頻繁項集
frequent_itemsets={}
min_support=50   #要求的最小支援度
#從頻繁項集長度為1的開始,並且支援度要大於50
frequent_itemsets[1]= dict((frozenset((movie_id,)),row["Favorable"]) for movie_id,row in num_favorable_by_movie.iterrows() if row["Favorable"]>min_support)
#輸出頻繁集長度為1,支援度大於50的所有的電影資訊
frequent_itemsets[1]
print("there are {0} movie with more than {1} favorable reviews".format( len(frequent_itemsets[1]), min_support))
sys.stdout.flush()   #將緩衝區的內容輸出到終端

#定義要找的頻繁集的最大長度
max_length=20
#迴圈頻繁集長度從2到 max_length
for k in range(2, max_length):
    cur_frequent_itemsets=find_frequent_itemsets(  favorable_reviews_by_users,  frequent_itemsets[k-1], min_support   )
    if len(cur_frequent_itemsets)==0:
        print("can not find any frequent itemsets of length {0}".format( k ))
        sys.stdout.flush()
        break
    else:
        print(" find {0} frequent itemsets of length {1}".format(len(cur_frequent_itemsets), k))
        print("\t data as following:")
        #print( cur_frequent_itemsets )
        sys.stdout.flush()
        frequent_itemsets[k]=cur_frequent_itemsets
# del itemsets of length 1
#del frequent_itemsets[1]

#######################################################################
#以上Apriori演算法結束後,得到了一系列的頻繁項集,但它還不是關聯規則。頻繁項集是一組達到最小支援度的專案,而關聯規則是由前提和結論組成
#從頻繁項集中抽取關聯規則,把其中幾部電影作為前提,另一部電影作為結論組成規則: 如果使用者喜歡xx,yy,zz,那麼他們也會喜歡ttt
#遍歷頻繁項集,為每個項集生成規則
candidate_rules=[]
#itemset_length 頻繁項集長度
# itemset_counts :  (itemset,frequency)
for itemset_length,itemset_counts in frequent_itemsets.items():
    #取出itemset_counts中的每個鍵,{電影1,電影2,...}
    for itemset in itemset_counts.keys():
        #迴圈頻繁項集中的每部電影,生成條件和結論
        for conclusion in itemset:   
            premise=itemset-set((conclusion,))
            candidate_rules.append((premise,conclusion))
print("there are {0} candidate rules".format( len(candidate_rules)))
#print("the rules as following:")
#print( candidate_rules)
            
#######################################################################
#計算每條規則的置信度: 
#先用兩個字典存規則應驗, 規則不適用數量
correct_counts=defaultdict(int)   #規則應驗
incorrect_counts=defaultdict(int)    #規則不適用
#迴圈所有的使用者及他們喜歡的電影
for user, reviews in favorable_reviews_by_users.items():
    #迴圈所有的規則
    for candidate_rule in candidate_rules:
        premise,conclusion=candidate_rule
        #判斷前提是否是 reviews中的一個子集, 並且結論也在 reviews中,說明這條規則應驗,否則不適用
        if premise.issubset(reviews):
            if conclusion in reviews:
                correct_counts[candidate_rule]+=1
            else:
                incorrect_counts[candidate_rule]+=1
#計算置信度
rule_confidence={candidate_rule: correct_counts[candidate_rule]/ float(correct_counts[candidate_rule]+incorrect_counts[candidate_rule]) for candidate_rule in candidate_rules}
#設定最低置信度
min_confidence=0.9
#過濾掉小於最低置信度的規則
rule_confidence={candidate_rule:  confidence for    candidate_rule,confidence in rule_confidence.items() if confidence>min_confidence}
print( "the total of rules which bigger than min_confidence is {}".format( len(rule_confidence )) )
                 
#排序輸出前五條置信度最高的規則
from operator import itemgetter
sorted_confidence=sorted( rule_confidence.items(),key=itemgetter(1),reverse=True)
for index in range(5):
    print("Rule #{0}".format(index+1))
    (premise,conclusion)=sorted_confidence[index][0]
    print("Rule: if a person recommends {0} they will also recommend {1}".format( premise, conclusion))
    print( " - Confidence: {0:.3f}".format( rule_confidence[(premise,conclusion)]))
    print("")
    
#######################################################################
#載入電影的名字
#100k資料集取法
movie_name_filename=os.path.join( data_folder,"u.item")
movie_name_data=pd.read_csv(movie_name_filename,delimiter="|",header=None,encoding="mac-roman")
movie_name_data.columns=["MovieID", "Title", "Release Date", "Video Release", "IMDB", "<UNK>", "Action", "Adventure",
                           "Animation", "Children's", "Comedy", "Crime", "Documentary", "Drama", "Fantasy", "Film-Noir",
                           "Horror", "Musical", "Mystery", "Romance", "Sci-Fi", "Thriller", "War", "Western"]
#定義一個查詢電影名的函式
def get_movie_name(movie_id):
    title_object=movie_name_data[movie_name_data["MovieID"]==movie_id]["Title"]
    title=title_object.values[0]
    return title
#測試這個函式
get_movie_name(4)

#重新排序輸出前五條置信度最高的規則
for index in range(5):
    print("Rule #{0}".format(index+1))
    (premise,conclusion)=sorted_confidence[index][0]
    premise_names=",".join( get_movie_name(idx) for idx in premise  )
    conclusion_name=get_movie_name( conclusion)
    print("Rule: if a person recommends {0} they will also recommend {1}".format( premise_names, conclusion_name))
    print( " - Confidence: {0:.3f}".format( rule_confidence[(premise,conclusion)]))
    print("")


#######################################################################
#評估: 尋找最好的規則. 
#抽取所有沒有用於訓練的資料作為測試集, 訓練集資料用了前200名使用者的打分資料,測試集用其它的資料即可
test_dataset=  all_ratings[~all_ratings['UserID'].isin(range(200))]
test_favorable_ratings=test_dataset[test_dataset["Favorable"]]
test_favorable_reviews_by_users=dict((k,frozenset(v.values)) 
                                for k,v in test_favorable_ratings.groupby("UserID")["MovieID"])  
#計算規則應驗的數量
test_correct_counts=defaultdict(int)   #規則應驗
test_incorrect_counts=defaultdict(int)    #規則不適用
#迴圈所有的使用者及他們喜歡的電影
for user, reviews in test_favorable_reviews_by_users.items():
    #迴圈所有的規則
    for candidate_rule in candidate_rules:
        premise,conclusion=candidate_rule
        #判斷前提是否是 reviews中的一個子集, 並且結論也在 reviews中,說明這條規則應驗,否則不適用
        if premise.issubset(reviews):
            if conclusion in reviews:
                test_correct_counts[candidate_rule]+=1
            else:
                test_incorrect_counts[candidate_rule]+=1     

#計算置信度
test_rule_confidence={candidate_rule: test_correct_counts[candidate_rule]/ float(test_correct_counts[candidate_rule]+test_incorrect_counts[candidate_rule]) for candidate_rule in candidate_rules}
print( len(test_rule_confidence))
#最後排序輸出前五名
sorted_test_confidence=sorted(  test_rule_confidence.items(),key=itemgetter(1),reverse=True )
print( sorted_test_confidence[:5])

#輸出規則資訊
for index in range(10):
    print("Rule #{0}".format(index+1))
    (premise,conclusion)=sorted_confidence[index][0]
    premise_names=",".join( get_movie_name(idx) for idx in premise  )
    conclusion_name=get_movie_name( conclusion)
    print("Rule: if a person recommends {0} they will also recommend {1}".format( premise_names, conclusion_name))
    print( " - Train Confidence: {0:.3f}".format( rule_confidence[(premise,conclusion)]))
    print( " - Test Confidence: {0:.3f}".format( test_rule_confidence[(premise,conclusion)]))
    print("")