package ca.pfv.spmf.algorithms.frequentpatterns.fpgrowth;
/* This file is copyright (c) 2008-2013 Philippe Fournier-Viger
*
* This file is part of the SPMF DATA MINING SOFTWARE
* (http://www.philippe-fournier-viger.com/spmf).
*
* SPMF is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* SPMF is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with
* SPMF. If not, see .
*/
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import ca.pfv.spmf.patterns.itemset_array_integers_with_count.Itemset;
import ca.pfv.spmf.patterns.itemset_array_integers_with_count.Itemsets;
import ca.pfv.spmf.tools.MemoryLogger;
/**
* This is an implementation of the FPGROWTH algorithm (Han et al., 2004).
* FPGrowth is described here:
*
*
* Han, J., Pei, J., & Yin, Y. (2000, May). Mining frequent patterns without candidate generation. In ACM SIGMOD Record (Vol. 29, No. 2, pp. 1-12). ACM
*
*
* This is an optimized version that saves the result to a file
* or keep it into memory if no output path is provided
* by the user to the runAlgorithm method().
*
* @see FPTree
* @see Itemset
* @see Itemsets
* @author Philippe Fournier-Viger
*/
public class AlgoFPGrowth {
// for statistics
private long startTimestamp; // start time of the latest execution
private long endTime; // end time of the latest execution
private int transactionCount = 0; // transaction count in the database
private int itemsetCount; // number of freq. itemsets found
// parameter
public int relativeMinsupp;// the relative minimum support
BufferedWriter writer = null; // object to write the output file
// The patterns that are found
// (if the user want to keep them into memory)
protected Itemsets patterns = null;
private MemoryLogger memoryLogger = null;
/**
* Constructor
*/
public AlgoFPGrowth() {
}
/**
* Method to run the FPGRowth algorithm.
* @param input the path to an input file containing a transaction database.
* @param output the output file path for saving the result (if null, the result
* will be returned by the method instead of being saved).
* @param minsupp the minimum support threshold.
* @return the result if no output file path is provided.
* @throws IOException exception if error reading or writing files
*/
public Itemsets runAlgorithm(String input, String output, double minsupp) throws FileNotFoundException, IOException {
// record start time
startTimestamp = System.currentTimeMillis();
// number of itemsets found
itemsetCount =0;
//initialize tool to record memory usage
memoryLogger = new MemoryLogger();
memoryLogger.checkMemory();
// if the user want to keep the result into memory
if(output == null){
writer = null;
patterns = new Itemsets("FREQUENT ITEMSETS");
}else{ // if the user want to save the result to a file
patterns = null;
writer = new BufferedWriter(new FileWriter(output));
}
// (1) PREPROCESSING: Initial database scan to determine the frequency of each item
// The frequency is stored in a map:
// key: item value: support
final Map mapSupport = new HashMap();
scanDatabaseToDetermineFrequencyOfSingleItems(input, mapSupport);
// convert the minimum support as percentage to a
// relative minimum support
this.relativeMinsupp = (int) Math.ceil(minsupp * transactionCount);
// (2) Scan the database again to build the initial FP-Tree
// Before inserting a transaction in the FPTree, we sort the items
// by descending order of support. We ignore items that
// do not have the minimum support.
FPTree tree = new FPTree();
// read the file
BufferedReader reader = new BufferedReader(new FileReader(input));
String line;
// for each line (transaction) until the end of the file
while( ((line = reader.readLine())!= null)){
// if the line is a comment, is empty or is a
// kind of metadata
if (line.isEmpty() == true ||
line.charAt(0) == '#' || line.charAt(0) == '%'
|| line.charAt(0) == '@') {
continue;
}
String[] lineSplited = line.split(" ");
// Set alreadySeen = new HashSet();
List transaction = new ArrayList();
// for each item in the transaction
for(String itemString : lineSplited){
Integer item = Integer.parseInt(itemString);
// only add items that have the minimum support
if( //alreadySeen.contains(item) == false &&
mapSupport.get(item) >= relativeMinsupp){
transaction.add(item);
//alreadySeen.add(item);
}
}
// sort item in the transaction by descending order of support
Collections.sort(transaction, new Comparator(){
public int compare(Integer item1, Integer item2){
// compare the frequency
int compare = mapSupport.get(item2) - mapSupport.get(item1);
// if the same frequency, we check the lexical ordering!
if(compare == 0){
return (item1 - item2);
}
// otherwise, just use the frequency
return compare;
}
});
// add the sorted transaction to the fptree.
tree.addTransaction(transaction);
}
// close the input file
reader.close();
// We create the header table for the tree
tree.createHeaderList(mapSupport);
// (5) We start to mine the FP-Tree by calling the recursive method.
// Initially, the prefix alpha is empty.
int[] prefixAlpha = new int[0];
// if at least an item is frequent
if(tree.headerList.size() > 0) {
fpgrowth(tree, prefixAlpha, transactionCount, mapSupport);
}
// close the output file if the result was saved to a file
if(writer != null){
writer.close();
}
// record the execution end time
endTime= System.currentTimeMillis();
// check the memory usage
memoryLogger.checkMemory();
// return the result (if saved to memory)
return patterns;
}
/**
* This method scans the input database to calculate the support of single items
* @param input the path of the input file
* @param mapSupport a map for storing the support of each item (key: item, value: support)
* @throws IOException exception if error while writing the file
*/
private void scanDatabaseToDetermineFrequencyOfSingleItems(String input,
final Map mapSupport)
throws FileNotFoundException, IOException {
//Create object for reading the input file
BufferedReader reader = new BufferedReader(new FileReader(input));
String line;
// for each line (transaction) until the end of file
while( ((line = reader.readLine())!= null)){
// if the line is a comment, is empty or is a
// kind of metadata
if (line.isEmpty() == true ||
line.charAt(0) == '#' || line.charAt(0) == '%'
|| line.charAt(0) == '@') {
continue;
}
// split the line into items
String[] lineSplited = line.split(" ");
// for each item
for(String itemString : lineSplited){
// increase the support count of the item
Integer item = Integer.parseInt(itemString);
// increase the support count of the item
Integer count = mapSupport.get(item);
if(count == null){
mapSupport.put(item, 1);
}else{
mapSupport.put(item, ++count);
}
}
// increase the transaction count
transactionCount++;
}
// close the input file
reader.close();
}
/**
* This method mines pattern from a Prefix-Tree recursively
* @param tree The Prefix Tree
* @param prefix The current prefix "alpha"
* @param mapSupport The frequency of each item in the prefix tree.
* @throws IOException exception if error writing the output file
*/
private void fpgrowth(FPTree tree, int[] prefixAlpha, int prefixSupport, Map mapSupport) throws IOException {
// We need to check if there is a single path in the prefix tree or not.
if(tree.hasMoreThanOnePath == false){
// That means that there is a single path, so we
// add all combinations of this path, concatenated with the prefix "alpha", to the set of patterns found.
if(tree.root.childs.size() ==0) {
System.out.println("Test");
}
addAllCombinationsForPathAndPrefix(tree.root.childs.get(0), prefixAlpha); // CORRECT?
}else{ // There is more than one path
fpgrowthMoreThanOnePath(tree, prefixAlpha, prefixSupport, mapSupport);
}
}
/**
* Mine an FP-Tree having more than one path.
* @param tree the FP-tree
* @param prefix the current prefix, named "alpha"
* @param mapSupport the frequency of items in the FP-Tree
* @throws IOException exception if error writing the output file
*/
private void fpgrowthMoreThanOnePath(FPTree tree, int [] prefixAlpha, int prefixSupport, Map mapSupport) throws IOException {
// For each frequent item in the header table list of the tree in reverse order.
for(int i= tree.headerList.size()-1; i>=0; i--){
// get the item
Integer item = tree.headerList.get(i);
// get the support of the item
int support = mapSupport.get(item);
// if the item is not frequent, we skip it
if(support < relativeMinsupp){
continue;
}
// Create Beta by concatening Alpha with the current item
// and add it to the list of frequent patterns
int [] beta = new int[prefixAlpha.length+1];
System.arraycopy(prefixAlpha, 0, beta, 0, prefixAlpha.length);
beta[prefixAlpha.length] = item;
// calculate the support of beta
int betaSupport = (prefixSupport < support) ? prefixSupport: support;
// save beta to the output file
saveItemset(beta, betaSupport);
// === Construct beta's conditional pattern base ===
// It is a subdatabase which consists of the set of prefix paths
// in the FP-tree co-occuring with the suffix pattern.
List> prefixPaths = new ArrayList>();
FPNode path = tree.mapItemNodes.get(item);
while(path != null){
// if the path is not just the root node
if(path.parent.itemID != -1){
// create the prefixpath
List prefixPath = new ArrayList();
// add this node.
prefixPath.add(path); // NOTE: we add it just to keep its support,
// actually it should not be part of the prefixPath
//Recursively add all the parents of this node.
FPNode parent = path.parent;
while(parent.itemID != -1){
prefixPath.add(parent);
parent = parent.parent;
}
// add the path to the list of prefixpaths
prefixPaths.add(prefixPath);
}
// We will look for the next prefixpath
path = path.nodeLink;
}
// (A) Calculate the frequency of each item in the prefixpath
// The frequency is stored in a map such that:
// key: item value: support
Map mapSupportBeta = new HashMap();
// for each prefixpath
for(List prefixPath : prefixPaths){
// the support of the prefixpath is the support of its first node.
int pathCount = prefixPath.get(0).counter;
// for each node in the prefixpath,
// except the first one, we count the frequency
for(int j=1; j prefixPath : prefixPaths){
treeBeta.addPrefixPath(prefixPath, mapSupportBeta, relativeMinsupp);
}
// Create the header list.
treeBeta.createHeaderList(mapSupportBeta);
// Mine recursively the Beta tree if the root as child(s)
if(treeBeta.root.childs.size() > 0){
// recursive call
fpgrowth(treeBeta, beta, betaSupport, mapSupportBeta);
}
}
}
/**
* This method is for adding recursively all combinations of nodes in a path, concatenated with a given prefix,
* to the set of patterns found.
* @param prefix the prefix
* @param minsupportForNode the support of this path.
* @throws IOException exception if error while writing the output file
*/
private void addAllCombinationsForPathAndPrefix(FPNode node, int[] prefix) throws IOException {
// Concatenate the node item to the current prefix
int [] itemset = new int[prefix.length+1];
System.arraycopy(prefix, 0, itemset, 0, prefix.length);
itemset[prefix.length] = node.itemID;
// save the resulting itemset to the file with its support
saveItemset(itemset, node.counter);
// recursive call if there is a node link
// if(node.nodeLink != null){
// addAllCombinationsForPathAndPrefix(node.nodeLink, prefix);
if(node.childs.size() != 0) {
addAllCombinationsForPathAndPrefix(node.childs.get(0), itemset);
addAllCombinationsForPathAndPrefix(node.childs.get(0), prefix);
}
}
/**
* Write a frequent itemset that is found to the output file or
* keep into memory if the user prefer that the result be saved into memory.
*/
private void saveItemset(int [] itemset, int support) throws IOException {
// increase the number of itemsets found for statistics purpose
itemsetCount++;
// We sort the itemset before showing it to the user so that it is
// in lexical order.
Arrays.sort(itemset);
// if the result should be saved to a file
if(writer != null){
// Create a string buffer
StringBuffer buffer = new StringBuffer();
// write the items of the itemset
for(int i=0; i< itemset.length; i++){
buffer.append(itemset[i]);
if(i != itemset.length-1){
buffer.append(' ');
}
}
// Then, write the support
buffer.append(" #SUP: ");
buffer.append(support);
// write to file and create a new line
writer.write(buffer.toString());
writer.newLine();
}// otherwise the result is kept into memory
else{
// create an object Itemset and add it to the set of patterns
// found.
Itemset itemsetObj = new Itemset(itemset);
itemsetObj.setAbsoluteSupport(support);
patterns.addItemset(itemsetObj, itemsetObj.size());
}
}
/**
* Print statistics about the algorithm execution to System.out.
*/
public void printStats() {
System.out
.println("============= FP-GROWTH - STATS =============");
long temps = endTime - startTimestamp;
System.out.println(" Transactions count from database : " + transactionCount);
System.out.print(" Max memory usage: " + memoryLogger.getMaxMemory() + " mb \n");
System.out.println(" Frequent itemsets count : " + itemsetCount);
System.out.println(" Total time ~ " + temps + " ms");
System.out
.println("===================================================");
}
/**
* Get the number of transactions in the last transaction database read.
* @return the number of transactions.
*/
public int getDatabaseSize() {
return transactionCount;
}
}
Ads help maintain this website.