FP-Growth: Complete Guide — Principles, Examples and Python Implementation
Summary
FP-Growth (Frequent Pattern Growth) is an algorithm for extracting frequent patterns and association rules that revolutionizes the traditional approach defined by Apriori. Instead of iteratively generating and testing candidate combinations, FP-Growth builds a compact tree structure called the FP-Tree (Frequent Pattern Tree) that encodes the entire set of transactions in a single pass. Recursive mining of this tree extracts all frequent itemsets without ever generating explicit candidates. This approach is generally 10 to 100 times faster than Apriori, particularly on dense datasets. In this guide, we will explore the mathematical principle of the algorithm, the intuition behind the FP-Tree structure, a practical implementation with the mlxtend library, and four concrete use cases.
Mathematical Principle of FP-Growth
Context and definitions
Let I = {i₁, i₂, …, iₘ} be a set of items and D = {T₁, T₂, …, Tₙ} be a transaction database, where each transaction Tₖ is a subset of I. An itemset is a non-empty subset of items. The support of an itemset X is defined as the number (or proportion) of transactions containing X:
support(X) = |{T ∈ D | X ⊆ T}| / |D|
An itemset is said to be frequent if its support is greater than or equal to a minimum threshold min_support.
FP-Tree construction
The FP-Tree is a compressed tree structure built in two passes over the database:
First pass — frequency computation and header table creation:
- Scan all transactions to count the frequency of each item.
- Remove items whose frequency is less than min_support × |D|.
- Sort the remaining items in decending frequency order. This ordered list constitutes the header table.
- Each entry in the header table points to the corresponding first node in the tree via a linked chain (node chain).
Second pass — inserting transactions into the tree:
- For each transaction:
– Remove infrequent items.
– Sort the remaining items according to the header table order (descending frequency).
– Insert this sorted sequence as a path in the tree:- If a corresponding node already exists on the path, increment its counter.
- Otherwise, create a new node with a counter initialized to 1.
- Each transaction thus becomes a root-to-leaf path, and common prefixes are shared, which compresses the representation.
The root of the tree is an empty (null) node. Each node contains three pieces of information: the item name, the counter (number of transactions passing through this node), and a link to the next node of the same item in the tree (via the header table chain).
Recursive mining of frequent itemsets
Once the FP-Tree is built, extraction of frequent items is done recursively, without candidate generation:
-
For each item α in the header table (processed from bottom to top, i.e., from the least frequent to the most frequent items):
– Collect all paths leading to occurrences of α by going up from each α node to the root. This set of paths forms the conditional pattern base* of α.
– Build the conditional FP-Tree from this base: recount the items in the conditional paths and keep only those that meet the min_support threshold.
– If the conditional FP-Tree is not empty, mine it recursively to find all frequent itemsets β in this subtree.
– Each itemset β found is combined with the suffix α to form the frequent itemset β ∪ {α}. - If the conditional FP-Tree contains only a single linear path, all subsets of that path are automatically frequent, allowing direct enumeration without further recursion.
Key property: the downward closure property (or anti-monotonicity) guarantees that every subset of a frequent itemset is also frequent. This property is exploited by the conditional FP-Tree to ensure mining completeness without having to explicitly test all combinations.
Association rule extraction
From the extracted frequent itemsets, association rules of the form A ⇒ B are generated:
- Confidence: conf(A ⇒ B) = support(A ∪ B) / support(A)
- Lift: lift(A ⇒ B) = conf(A ⇒ B) / support(B) — a lift greater than 1 indicates a positive correlation.
- Leverage: lev(A ⇒ B) = support(A ∪ B) − support(A) × support(B)
- Conviction: conv(A ⇒ B) = (1 − support(B)) / (1 − conf(A ⇒ B))
Intuition: why FP-Growth is so efficient
To truly understand the power of FP-Growth, let’s briefly compare it with Apriori:
Apriori works by iterative elimination: it first tests all individual items, then all pairs of frequent items, then all triplets, and so on. At each level k, it generates C(n,k) potential candidates and scans the entire database to count their occurrences. On a dataset with 100 items, this can represent millions of candidates to test, and as many passes over the data.
FP-Growth, on the other hand, completely changes the strategy. Instead of testing all possible combinations, it builds a compressed tree in only two passes that contains all the necessary information. Imagine that each transaction is a path in a forest: transactions sharing the same items follow the same trails. Common parts are shared (a single path with a counter), and deviations are branches. It’s like having a compressed index of all existing combinations in your data.
Recursive mining then navigates this tree instead of recomputing counts. To find combinations containing a given item, you simply follow the header table chains and go up the corresponding paths. This is extremely more economical in terms of memory access and number of passes over the data.
The performance gain is dramatic when data is dense (many recurring items with numerous shared prefixes). On very sparse data, the tree is less compressed, but FP-Growth generally remains competitive.
Python Implementation with mlxtend
In practice, a manual implementation of FP-Growth is rarely used. The mlxtend (Machine Learning Extensions) library provides an optimized implementation accessible via two key functions: fpgrowth for extracting frequent itemsets and association_rules for generating rules.
import pandas as pd
from mlxtend.preprocessing import TransactionEncoder
from mlxtend.frequent_patterns import fpgrowth, apriori, association_rules
import time
# --- 1. Data preparation ---
# Example dataset: supermarket transactions
transactions = [
["milk", "bread", "butter"],
["milk", "eggs"],
["bread", "butter", "eggs"],
["milk", "bread", "butter", "eggs"],
["milk", "bread"],
["milk", "bread", "butter"],
["bread", "butter"],
["milk", "bread", "butter", "yogurt"],
["eggs", "yogurt"],
["milk", "eggs", "yogurt"],
["bread", "yogurt"],
["milk", "bread", "yogurt"],
["butter", "eggs", "yogurt"],
["milk", "bread", "butter", "eggs", "yogurt"],
["bread", "butter", "yogurt"],
]
# One-hot encoding with TransactionEncoder
te = TransactionEncoder()
te_ary = te.fit(transactions).transform(transactions)
df = pd.DataFrame(te_ary, columns=te.columns_)
print("Encoded data:")
print(df.head())
# --- 2. Performance comparison: FP-Growth vs Apriori ---
min_sup = 0.3
# FP-Growth
start_fg = time.time()
fp_items = fpgrowth(df, min_support=min_sup, use_colnames=True)
fp_time = time.time() - start_fg
print(f"\nFP-Growth ({fp_time:.4f}s):")
print(fp_items.sort_values("support", ascending=False).to_string(index=False))
# Apriori (for comparison)
start_ap = time.time()
ap_items = apriori(df, min_support=min_sup, use_colnames=True)
ap_time = time.time() - start_ap
print(f"\nApriori ({ap_time:.4f}s):")
print(ap_items.sort_values("support", ascending=False).to_string(index=False))
# Speed ratio
print(f"\nFP-Growth is {ap_time/fp_time:.1f}x faster than Apriori")
# --- 3. Association rule extraction ---
rules = association_rules(fp_items, metric="confidence", min_threshold=0.5)
rules = rules.sort_values("lift", ascending=False)
print("\nAssociation rules (sorted by lift):")
cols = ["antecedents", "consequents", "support", "confidence", "lift", "leverage", "conviction"]
print(rules[cols].to_string(index=False))
# --- 4. Filter by lift > 1 ---
strong_rules = rules[rules["lift"] > 1.0]
print(f"\nRules with positive correlation (lift > 1): {len(strong_rules)}")
print(strong_rules[["antecedents", "consequents", "lift"]].to_string(index=False))
Analyzing the results
This example illustrates several key points:
-
Frequent itemsets are returned with their support. We can see that the set
{milk, bread, butter}appears frequently, which is consistent with our data. - Speed comparison generally shows that FP-Growth is significantly faster than Apriori, even on a small dataset. On industrial-scale datasets (millions of transactions, hundreds of items), the gap is measured in orders of magnitude.
-
Association rules reveal correlations between products. For example, a rule like
{butter}⇒{bread}with a lift of 1.3 indicates that customers who buy butter are 30% more likely to buy bread than the general average.
Essential Hyperparameters
min_support
The most critical parameter. It defines the minimum frequency threshold for an itemset to be considered frequent.
- Too high (e.g., 0.5): few itemsets are found, risk of missing interesting but moderately frequent patterns.
- Too low (e.g., 0.01): combinatorial explosion in the number of itemsets found, excessive memory consumption, long computation time.
use_colnames
Boolean parameter of the fpgrowth function. When use_colnames=True, itemsets are returned with the actual column names (e.g., {'milk', 'bread'}) instead of numeric indices. This is almost always preferable for readability and compatibility with association_rules.
max_len
Limits the maximum size of itemsets returned. Useful for controlling complexity when the number of itemsets explodes.
# Keep only itemsets of size 1 to 3
items_limited = fpgrowth(df, min_support=0.1, use_colnames=True, max_len=3)
association_rules parameters
metric: filtering metric ("confidence","lift","leverage","conviction").min_threshold: minimum threshold for the chosen metric.num_itemsets: number of input frequent itemsets (calculated automatically).
# Rules with lift > 1.5 only
rules_lift = association_rules(fp_items, metric="lift", min_threshold=1.5)
Advantages and Limitations of FP-Growth
Advantages
- No candidate generation: Unlike Apriori, FP-Growth never explicitly generates the set of candidate combinations.
- Only two passes over the data: Building the FP-Tree requires exactly two passes over the transaction database.
- Natural compression: Common prefixes are shared, which drastically reduces memory usage on dense data.
- Superior performance: Generally 10 to 100 times faster than Apriori, depending on data density.
- Exhaustive and identical results: FP-Growth finds exactly the same frequent itemsets as Apriori, without any approximation.
- Elegant recursive mining: The decomposition by conditional subtrees is conceptually elegant and easy to parallelize.
Limitations
- Memory consumption on sparse data: When transactions share few prefixes, the FP-Tree is poorly compressed and can even exceed the size of the original database.
- Costly FP-Tree construction: The recursive allocation of nodes and header table chains can be heavy for millions of distinct items.
- Sensitivity to min_support: Like Apriori, a poorly calibrated threshold renders the algorithm unusable (too many results or no results).
- Not suitable for incremental data: The FP-Tree must be entirely rebuilt when new transactions are added. Variants like FP-Stream exist for streaming.
- No default weighting: The standard algorithm does not distinguish items by weight or profit. Extensions like Weighted FP-Growth address this point.
4 Concrete Use Cases
1. Market Basket Analysis
The historical application of FP-Growth. A supermarket or e-commerce site analyzes shopping carts to identify products frequently bought together. The extracted rules directly feed recommendation systems (“Customers who bought X also bought Y”), shelf merchandising (placing correlated products near each other), and targeted promotional campaigns (cross-promotions).
# Example of rule-based recommendation
top_rules = rules.sort_values("lift", ascending=False).head(5)
for _, row in top_rules.iterrows():
ante = ", ".join(row["antecedents"])
cons = ", ".join(row["consequents"])
print(f"If a customer buys {{{ante}}}, they are likely to buy {{{cons}}}"
f" (lift={row['lift']:.2f})")
2. Medical diagnosis and pharmacovigilance
In the healthcare field, FP-Growth is used to analyze co-occurrences of symptoms, diagnoses, and medications in patient records. It can identify combinations of symptoms that signal a specific pathology, or detect rare but recurrent drug interactions. The major advantage here is the ability to process thousands of diagnostic codes (ICD-10) without combinatorial explosion.
3. Intrusion detection in cybersecurity
Network system logs generate event sequences that can be treated as transactions. FP-Growth identifies recurrent event combinations associated with known attacks. For example, a sequence of port scanning + SSH connection attempt + privilege escalation can constitute a frequent pattern in intrusion logs. The extracted rules are then used to enrich detection systems (IDS/IPS) with new behavioral signatures.
4. Web navigation analysis and personalization
Browsing sessions on a website can be modeled as transactions, where each visited page is an item. FP-Growth reveals typical user paths: which pages are frequently visited together, which link sequences are most common. This information can be used to optimize information architecture, create contextual shortcuts, and offer personalized content based on the user’s current path.
See also
- Calculating Sums of Squares of Unitary Divisors in Python: Complete Guide and Optimized Code
- Interview Question: Converting Roman Numerals to Integer with Python

