English (unofficial) translations of posts at kexue.fm
Source

Efficient Implementation of the Apriori Algorithm with Numpy

Translated by DeepSeek V4 Pro. Translations can be inaccurate, please refer to the original post for important stuff.

A classic example of association rules: Beer and Diapers

Three years ago, I wrote "Efficient Implementation of the Apriori Algorithm with Pandas", which provided a Python implementation of the Apriori algorithm and received recognition from some readers. However, my Python skills were not very good at that time, so looking back, that implementation was not elegant (though the speed was acceptable), and it did not support variable-length input data. I previously promised to rewrite this algorithm to solve these issues, and now it is finally completed.

I won’t repeat the introduction to the Apriori algorithm; here is the code:

#! -*- coding: utf-8 -*-

import numpy as np


class Apriori:

    def __init__(self, min_support, min_confidence):
        self.min_support = min_support # Minimum support
        self.min_confidence = min_confidence # Minimum confidence

    def count(self, filename='apriori.txt'):
        self.total = 0 # Total number of data rows
        items = {} # Item list

        # Statistics to get the item list
        with open(filename) as f:
            for l in f:
                self.total += 1
                for i in l.strip().split(','): # Separated by commas
                    if i in items:
                        items[i] += 1.
                    else:
                        items[i] = 1.

        # Deduplicate item list and map to IDs
        self.items = {i:j/self.total for i,j in items.items() if j/self.total > self.min_support}
        self.item2id = {j:i for i,j in enumerate(self.items)}

        # 0-1 matrix of the item list
        self.D = np.zeros((self.total, len(items)), dtype=bool)

        # Re-traverse the file to get the 0-1 matrix
        with open(filename) as f:
            for n,l in enumerate(f):
                for i in l.strip().split(','):
                    if i in self.items:
                        self.D[n, self.item2id[i]] = True

    def find_rules(self, filename='apriori.txt'):
        self.count(filename)
        rules = [{(i,):j for i,j in self.items.items()}] # Record frequent itemsets at each step
        l = 0 # Number of items in the frequent itemsets of the current step

        while rules[-1]: # Includes the construction process from k-frequent to k+1 frequent itemsets
            rules.append({})
            keys = sorted(rules[-2].keys()) # Sort each k-frequent itemset lexicographically (Core)
            num = len(rules[-2])
            l += 1
            for i in range(num): # Traverse each pair of k-frequent itemsets
                for j in range(i+1,num):
                    # If the first k-1 items overlap, these two k-frequent itemsets can be combined into a k+1 frequent itemset
                    if keys[i][:l-1] == keys[j][:l-1]:
                        _ = keys[i] + (keys[j][l-1],)
                        _id = [self.item2id[k] for k in _]
                        # Obtain co-occurrence counts via multiplication and calculate support
                        support = 1. * sum(np.prod(self.D[:, _id], 1)) / self.total 
                        if support > self.min_support: # Check if it is frequent enough
                            rules[-1][_] = support

        # Traverse each frequent itemset and calculate confidence
        result = {}
        for n,relu in enumerate(rules[1:]): # For all k, traverse k-frequent itemsets
            for r,v in relu.items(): # Traverse all k-frequent itemsets
                for i,_ in enumerate(r): # Traverse all permutations, i.e., is (A,B,C) actually A,B -> C or A,C -> B?
                    x = r[:i] + r[i+1:]
                    confidence = v / rules[n][x] # Confidence of different permutations
                    if confidence > self.min_confidence: # If the confidence is high enough, add to result
                        result[x+(r[i],)] = (confidence, v)

        return sorted(result.items(), key=lambda x: -x[1][0]) # Sort by confidence in descending order
Usage:
from pprint import pprint

# Test file from https://kexue.fm/archives/3380
model = Apriori(0.06, 0.75)
pprint(model.find_rules('apriori.txt'))
Output:

[((’A3’, ’F4’, ’H4’), (0.8795180722891566, 0.07849462365591398)),
((’C3’, ’F4’, ’H4’), (0.875, 0.07526881720430108)),
((’B2’, ’F4’, ’H4’), (0.7945205479452054, 0.06236559139784946)),
((’C2’, ’E3’, ’D2’), (0.7543859649122807, 0.09247311827956989)),
((’D2’, ’F3’, ’H4’, ’A2’), (0.7532467532467533, 0.06236559139784946))]

The meaning of the result is that the first n-1 items imply the n-th item, i.e., A3 + F4 \rightarrow H4, D2 + F3 + H4 \rightarrow A2, etc.

This implementation is relatively more concise, removing the dependency on the Pandas library and using only Numpy. Variable naming is also clearer. The programming logic remains unchanged, so theoretically, efficiency will not decrease and might even improve. It should be compatible with Python 2.x and 3.x. If there are any issues, please feel free to point them out.

When reposting, please include the original address of this article: https://kexue.fm/archives/5525

For more detailed reposting matters, please refer to: "Scientific Space FAQ"