1010
1111from collections import defaultdict
1212from itertools import combinations
13- from typing import List , Dict , Tuple , Set
1413
1514
16- def load_data () -> List [ List [str ]]:
15+ def load_data () -> list [ list [str ]]:
1716 """
1817 Returns a sample transaction dataset.
1918
20- >>> data = load_data()
21- >>> len(data)
22- 4
23- >>> 'milk' in data[0]
24- True
19+ >>> load_data()
20+ [['milk'], ['milk', 'butter'], ['milk', 'bread'], ['milk', 'bread', 'chips']]
2521 """
2622 return [["milk" ], ["milk" , "butter" ], ["milk" , "bread" ], ["milk" , "bread" , "chips" ]]
2723
@@ -31,17 +27,17 @@ class Apriori:
3127
3228 def __init__ (
3329 self ,
34- transactions : List [ List [str ]],
30+ transactions : list [ list [str ]],
3531 min_support : float = 0.25 ,
3632 min_confidence : float = 0.5 ,
3733 min_lift : float = 1.0 ,
3834 ) -> None :
39- self .transactions : List [ Set [str ]] = [set (t ) for t in transactions ]
35+ self .transactions : list [ set [str ]] = [set (t ) for t in transactions ]
4036 self .min_support : float = min_support
4137 self .min_confidence : float = min_confidence
4238 self .min_lift : float = min_lift
43- self .itemsets : List [ Dict [frozenset , float ]] = []
44- self .rules : List [ Tuple [frozenset , frozenset , float , float ]] = []
39+ self .itemsets : list [ dict [frozenset , float ]] = []
40+ self .rules : list [ tuple [frozenset , frozenset , float , float ]] = []
4541
4642 self .find_frequent_itemsets ()
4743 self .generate_association_rules ()
@@ -54,34 +50,36 @@ def _get_support(self, itemset: frozenset) -> float:
5450
5551 def confidence (self , antecedent : frozenset , consequent : frozenset ) -> float :
5652 """Calculate confidence of a rule A -> B."""
57- support_antecedent : float = self ._get_support (antecedent )
58- support_both : float = self ._get_support (antecedent | consequent )
53+ support_antecedent = self ._get_support (antecedent )
54+ support_both = self ._get_support (antecedent | consequent )
5955 return support_both / support_antecedent if support_antecedent > 0 else 0.0
6056
6157 def lift (self , antecedent : frozenset , consequent : frozenset ) -> float :
6258 """Calculate lift of a rule A -> B."""
63- support_consequent : float = self ._get_support (consequent )
64- conf : float = self .confidence (antecedent , consequent )
59+ support_consequent = self ._get_support (consequent )
60+ conf = self .confidence (antecedent , consequent )
6561 return conf / support_consequent if support_consequent > 0 else 0.0
6662
67- def find_frequent_itemsets (self ) -> List [ Dict [frozenset , float ]]:
63+ def find_frequent_itemsets (self ) -> list [ dict [frozenset , float ]]:
6864 """Generate all frequent itemsets."""
69- item_counts : Dict [frozenset , int ] = defaultdict (int )
65+ item_counts : dict [frozenset , int ] = defaultdict (int )
7066 for t in self .transactions :
7167 for item in t :
7268 item_counts [frozenset ([item ])] += 1
7369
7470 total : int = len (self .transactions )
75- current_itemsets : Dict [frozenset , float ] = {
76- k : v / total for k , v in item_counts .items () if v / total >= self .min_support
71+ current_itemsets : dict [frozenset , float ] = {
72+ k : v / total
73+ for k , v in item_counts .items ()
74+ if v / total >= self .min_support
7775 }
7876 if current_itemsets :
7977 self .itemsets .append (current_itemsets )
8078
8179 k : int = 2
8280 while current_itemsets :
83- candidates : Set [frozenset ] = set ()
84- keys : List [frozenset ] = list (current_itemsets .keys ())
81+ candidates : set [frozenset ] = set ()
82+ keys : list [frozenset ] = list (current_itemsets .keys ())
8583 for i in range (len (keys )):
8684 for j in range (i + 1 , len (keys )):
8785 union = keys [i ] | keys [j ]
@@ -91,8 +89,10 @@ def find_frequent_itemsets(self) -> List[Dict[frozenset, float]]:
9189 ):
9290 candidates .add (union )
9391
94- freq_candidates : Dict [frozenset , float ] = {
95- c : self ._get_support (c ) for c in candidates if self ._get_support (c ) >= self .min_support
92+ freq_candidates : dict [frozenset , float ] = {
93+ c : self ._get_support (c )
94+ for c in candidates
95+ if self ._get_support (c ) >= self .min_support
9696 }
9797 if not freq_candidates :
9898 break
@@ -103,26 +103,24 @@ def find_frequent_itemsets(self) -> List[Dict[frozenset, float]]:
103103
104104 return self .itemsets
105105
106- def generate_association_rules (self ) -> List [Tuple [frozenset , frozenset , float , float ]]:
106+ def generate_association_rules (
107+ self ,
108+ ) -> list [tuple [frozenset , frozenset , float , float ]]:
107109 """Generate association rules with min confidence and lift."""
108110 for level in self .itemsets :
109111 for itemset in level :
110112 if len (itemset ) < 2 :
111113 continue
112114 for i in range (1 , len (itemset )):
113115 for antecedent in combinations (itemset , i ):
114- antecedent_set : frozenset = frozenset (antecedent )
115- consequent_set : frozenset = itemset - antecedent_set
116- conf : float = self .confidence (antecedent_set , consequent_set )
117- lft : float = self .lift (antecedent_set , consequent_set )
118- rule : Tuple [frozenset , frozenset , float , float ] = (
119- antecedent_set ,
120- consequent_set ,
121- conf ,
122- lft ,
123- )
124- if rule not in self .rules and conf >= self .min_confidence and lft >= self .min_lift :
125- self .rules .append (rule )
116+ antecedent_set = frozenset (antecedent )
117+ consequent_set = itemset - antecedent_set
118+ conf = self .confidence (antecedent_set , consequent_set )
119+ lft = self .lift (antecedent_set , consequent_set )
120+ if conf >= self .min_confidence and lft >= self .min_lift :
121+ self .rules .append (
122+ (antecedent_set , consequent_set , conf , lft )
123+ )
126124 return self .rules
127125
128126
@@ -131,10 +129,8 @@ def generate_association_rules(self) -> List[Tuple[frozenset, frozenset, float,
131129
132130 doctest .testmod ()
133131
134- transactions : List [List [str ]] = load_data ()
135- model : Apriori = Apriori (
136- transactions , min_support = 0.25 , min_confidence = 0.1 , min_lift = 0.0
137- )
132+ transactions = load_data ()
133+ model = Apriori (transactions , min_support = 0.25 , min_confidence = 0.1 , min_lift = 0.0 )
138134
139135 print ("Frequent itemsets:" )
140136 for level in model .itemsets :
@@ -143,8 +139,7 @@ def generate_association_rules(self) -> List[Tuple[frozenset, frozenset, float,
143139
144140 print ("\n Association Rules:" )
145141 for rule in model .rules :
146- antecedent , consequent , conf , lift_value = rule
142+ antecedent , consequent , conf , lift = rule
147143 print (
148- f"{ set (antecedent )} -> { set (consequent )} , "
149- f"conf={ conf :.2f} , lift={ lift_value :.2f} "
144+ f"{ set (antecedent )} -> { set (consequent )} , conf={ conf :.2f} , lift={ lift :.2f} "
150145 )
0 commit comments