/*********************************************************************** This file is part of KEEL-software, the Data Mining tool for regression, classification, clustering, pattern mining and so on. Copyright (C) 2004-2010 F. Herrera (herrera@decsai.ugr.es) L. S�nchez (luciano@uniovi.es) J. Alcal�-Fdez (jalcala@decsai.ugr.es) S. Garc�a (sglopez@ujaen.es) A. Fern�ndez (alberto.fernandez@ujaen.es) J. Luengo (julianlm@decsai.ugr.es) This program 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. This program 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 this program. If not, see http://www.gnu.org/licenses/ **********************************************************************/ /** * * File: Rule.java * * Auxiliary class to repressent rules for the INNER algorithm * * @author Written by Joaquin Derrac (University of Granada) 17/10/2009 * @version 1.1 * @since JDK1.5 * */ package keel.Algorithms.Hyperrectangles.INNER; import java.util.Arrays; import java.util.Comparator; import keel.Dataset.Attribute; public class Rule implements Comparator{ private static int problemSize; //attributes of the problem private static int nValues[]; //cardinality of each attribute (real==-1) private static int nClasses; //number of classes private static boolean isNominal[]; //for nominal attributes private static double defaultImpurityLevelL[]; //left boundary of the impurity level private static double defaultImpurityLevelR[]; //right boundary of the impurity level private static double trainingData[][]; //copy of the training data private static int trainingOutput[]; //copy of the training output private static int nInstances[]; //number of instances in each class private double valueMin[]; //for numeric attributes private double valueMax[]; //for numeric attributes private double differenceTable[][]; //for nominal attributes private boolean dontCare[]; // conditions pruned private int size; //size of the rule private int output; //output attribute private double impurity; // level of impurity /** * Sets the size of the problem * * @param value Number of attributes of the rule * */ public static void setSize(int value){ problemSize=value; isNominal=new boolean[problemSize]; nValues=new int[problemSize]; }//end-method /** * Test which attributes are nominal * * @param inputs Attributes' descriptions * */ public static void setAttributes(Attribute[] inputs){ for(int i=0;i<problemSize;i++){ if(inputs[i].getType()==Attribute.NOMINAL){ isNominal[i]=true; } else{ isNominal[i]=false; } } }//end-method /** * Sets the number of different values for an attribute * * @param value Number of values * @param pos Index of the attribute * */ public static void setNumValue(int value,int pos){ nValues[pos]=value; }//end-method /** * Sets the number of classes of the problem * * @param value Number of classes * */ public static void setNClasses(int value){ nClasses=value; }//end-method /** * Returns the output class of the rule * * @return Output class of the rule */ public int getOutput(){ return output; }//end-method /** * Copies the training data iniside the class * * @param trainData training data Training data * @param trainOutput training output Output values * */ public static void copyData(double trainData [][], int trainOutput []){ trainingData=trainData; trainingOutput=trainOutput; }//end-method /** * Sets the array of instances of each class * * @param vector Array of instances of each class * */ public static void setNInstances(int vector []){ nInstances=vector; }//end-method /** * Computes the initial confidence intervals of the impurity levels of each class */ public static void computeDefaultImpurityLevels(){ double mean, rest, div; double p; double n; double z; double up, low; defaultImpurityLevelL= new double[nClasses]; defaultImpurityLevelR= new double[nClasses]; z=1.96; //alpha=0.95 in a normal table n=(double)trainingData.length; div=1.0+(z*z/n); for(int i=0;i<nClasses;i++){ p=((double)nInstances[i]/n); mean=p+(z*z/(2*n)); rest=z*Math.sqrt(((p*(1.0-p))/n)+((z*z)/(4*n*n))); low=mean-rest; up=mean+rest; defaultImpurityLevelL[i]=low/div; defaultImpurityLevelR[i]=up/div; } }//end-method /** * Default builder. Generates a void rule */ public Rule(){ valueMin=new double[problemSize]; valueMax=new double[problemSize]; dontCare=new boolean[problemSize]; differenceTable=new double [problemSize][]; for(int i=0;i<problemSize;i++){ differenceTable[i]=new double[nValues[i]]; } size=problemSize; output=-1; impurity=-1.0; }//end-method /** * Builder. Generates a rule covering only a point * * @param instance Basic instance * @param out Ouput of the instance */ public Rule(double instance[],int out){ valueMin=new double[problemSize]; valueMax=new double[problemSize]; dontCare=new boolean[problemSize]; Arrays.fill(dontCare, false); differenceTable=new double [problemSize][]; for(int i=0;i<problemSize;i++){ differenceTable[i]=new double[nValues[i]]; } for(int i=0;i<instance.length;i++){ if(isNominal[i]){ Arrays.fill(differenceTable[i], 1); differenceTable[i][(int)instance[i]]=0; }else{ valueMin[i]=instance[i]; valueMax[i]=instance[i]; } } size=problemSize; output=out; computeInitialImpurity(); }//end-method /** * Computes the initial impurity level of a rule */ private void computeInitialImpurity(){ double mean, rest, div; double p; double n; double z; double up, low; z=1.96; //alpha=0.95 in a normal table n=1.0; div=1.0+(z*z/n); p=1.0; mean=p+(z*z/(2*n)); rest=z*Math.sqrt(((p*(1.0-p))/n)+((z*z)/(4*n*n))); low=mean-rest; up=mean+rest; low=low/div; up=up/div; impurity= 100.0 * ((defaultImpurityLevelR[output]-low)/(up-low)); }//end-method /** * Computes the distance between two rules * * @param another Second rule * * @return Distance between the two rules */ public double ruleDistance (Rule another){ double dist=0.0; double value; for(int i=0;i<problemSize;i++){ if(!dontCare[i]){ if(isNominal[i]){ value=ruleNominalDistance(differenceTable[i],another.differenceTable[i]); } else{ value=ruleRealDistance(valueMin[i],valueMax[i],another.valueMin[i],another.valueMax[i]); } value=value*value; dist+=value; } } dist=Math.sqrt(dist); return dist; }//end-method /** * Computes the distance between a real attribute of two rules * * @param aMin Lower bound of the first rule * @param aMax Upper bound of the first rule * @param bMin Lower bound of the second rule * @param bMax Upper bound of the second rule * * @return Distance between the two attributes */ private double ruleRealDistance(double aMin,double aMax,double bMin,double bMax){ double dist=0.0; if(aMin<bMin){ if(bMin>aMax){ dist=bMin-aMax; } } if(aMin>bMin){ if(bMax<aMin){ dist=aMin-bMax; } } return dist; }//end-method /** * Computes the distance between a nominal attribute of two rules * * @param tableA Difference table of the first rule * @param tableB Difference table of the secoind rule * * @return Distance between the two attributes */ private double ruleNominalDistance(double tableA[],double tableB[]){ double dist=0.0; double max=-1.0; for(int i=0;i<tableA.length;i++){ if(tableB[i]!=1){ if(tableA[i]>max){ max=tableA[i]; } } } if(max==-1.0){ dist=Double.MAX_VALUE; } else{ dist=max; } return dist; }//end-method /** * Computes the distance between a rule and an instance * * @param example The instance * * @return Distance between the rule and the instance */ public double distance (double example[]){ double dist=0.0; double value; for(int i=0;i<example.length;i++){ if(!dontCare[i]){ if(isNominal[i]){ value=nominalDistance(i,example[i]); } else{ value=realDistance(i,example[i]); } value=value*value; dist+=value; } } dist=Math.sqrt(dist); return dist; }//end-method /** * Computes the distance between a rule and an instance in a real valued attribute * * @param att Attribute index * @param value Value of the instance's attribute * * @return Distance between the rule and the instance */ private double realDistance(int att, double value){ if(value<valueMin[att]){ return valueMin[att]-value; } if(value>valueMax[att]){ return value-valueMax[att]; } return 0.0; }//end-method /** * Computes the distance between a rule and an instance in a nominal valued attribute * * @param att Attribute index * @param value Value of the instance's attribute * * @return Distance between the rule and the instance */ private double nominalDistance(int att, double value){ if(differenceTable[att][(int)value]==0.0){ return 0.0; } double val=Double.MAX_VALUE; int lower=-1; for(int i=0;i<differenceTable[att].length;i++){ if(differenceTable[att][i]<val){ lower=i; } } if(lower==(int)value){ return 0.0; } return 1.0; }//end-method /** * Generalizes a continuous attribute * * @param att Attribute index * @param value Value to obtain * @param percentage Percentage of generalization allowed * @param out Output class of the example to be covered * */ public void generalizeContinuous(int att,double value, double percentage, int out){ if(out==output){ if(value<valueMin[att]){ valueMin[att]-=(valueMin[att]-value)*percentage; valueMin[att]=Math.max(0.0, valueMin[att]); } if(value>valueMax[att]){ valueMax[att]+=(value-valueMax[att])*percentage; valueMax[att]=Math.min(1.0, valueMax[att]); } } else{ if(value<valueMin[att]){ valueMin[att]+=(valueMin[att]-value)*percentage; valueMin[att]=Math.min(valueMax[att], valueMin[att]); } if(value>valueMax[att]){ valueMax[att]-=(value-valueMax[att])*percentage; valueMin[att]=Math.max(valueMax[att], valueMin[att]); } } }//end-method /** * Generalizes a nominal attribute * * @param att Attribute index * @param value Value to obtain * @param percentage Percentage of generalization allowed * @param out Output class of the example to be covered * */ public void generalizeNominal(int att,double value, double percentage, int out){ double tValue = differenceTable[att][(int)value]; double movement = percentage*(tValue+1.0); if(out==output){ tValue=tValue-movement; tValue=Math.max(0.0,tValue); } else{ tValue=tValue+movement; tValue=Math.min(1.0,tValue); } }//end-method /** * Computes the impurity level of a rule */ public void computeImpurityLevel(){ double mean, rest, div; double p; double hits; double n; double z; double up, low; z=1.96; //alpha=0.95 in a normal table n=0.0; hits=0.0; for(int i=0;i<trainingOutput.length;i++){ if(inside(trainingData[i])){ if(trainingOutput[i]==output){ hits+=1.0; } n+=1.0; } } div=1.0+(z*z/n); if(n==0){ impurity=Double.MAX_VALUE; }else{ p=(hits/n); mean=p+(z*z/(2*n)); rest=z*Math.sqrt(((p*(1.0-p))/n)+((z*z)/(4*n*n))); low=mean-rest; up=mean+rest; low=low/div; up=up/div; impurity= 100.0 * ((defaultImpurityLevelR[output]-low)/(up-low)); } }//end-method /** * Computes the impurity level of a rule, by considering only those instances which are not already covered by other rules * * @return Impurity level */ public double getSpecialImpurityLevel(Rule ruleset[]){ double mean, rest, div; double p; double hits; double n; double z; double up, low; double newImpurity; boolean consider; z=1.96; //alpha=0.95 in a normal table n=0.0; hits=0.0; for(int i=0;i<trainingOutput.length;i++){ if(inside(trainingData[i])){ //special consideration consider=true; for(int j=0;j<ruleset.length&&consider;j++){ if(ruleset[j].impurity<impurity){ if(ruleset[j].inside(trainingData[i])){ consider=false; } } } if(consider){ if(trainingOutput[i]==output){ hits+=1.0; } n+=1.0; } } } div=1.0+(z*z/n); if(n==0){ newImpurity=Double.MAX_VALUE; }else{ p=(hits/n); mean=p+(z*z/(2*n)); rest=z*Math.sqrt(((p*(1.0-p))/n)+((z*z)/(4*n*n))); low=mean-rest; up=mean+rest; low=low/div; up=up/div; newImpurity= 100.0 * ((defaultImpurityLevelR[output]-low)/(up-low)); } return newImpurity; }//end-method /** * Tests if an instance is covered by the rule * * @param example Instance to be tested * @return True if it is covered. False, if not. */ public boolean inside(double example []){ boolean isInside=true; for(int i=0;i<size && isInside;i++){ if(!dontCare[i]){ if(isNominal[i]){ if(differenceTable[i][(int)example[i]]== 0.0){ isInside=false; } } else{ if(example[i]<valueMin[i]){ isInside=false; } if(example[i]>valueMax[i]){ isInside=false; } } } } return isInside; }//end-method /** * Returns the impurity level of a rule */ public double getImpurityLevel(){ return impurity; }//end-method /** * Clone method * * @return A intialized copy of the rule */ @Override public Rule clone(){ Rule clon=new Rule(); clon.size=size; for(int i=0;i<size;i++){ if(isNominal[i]){ System.arraycopy(differenceTable[i], 0, clon.differenceTable[i], 0, differenceTable[i].length); } else{ clon.valueMax[i]=valueMax[i]; clon.valueMin[i]=valueMin[i]; } } clon.output=output; clon.impurity=impurity; System.arraycopy(dontCare, 0, clon.dontCare, 0, dontCare.length); return clon; }//end-method /** * Prune redundant conditions of a rule, if it decreases its impurity level */ public void pruneConditions(){ boolean delete=true; double before,after; while(delete){ delete=false; for(int i=0;i<problemSize&&!delete;i++){ if(!dontCare[i]){ before=impurity; dontCare[i]=true; computeImpurityLevel(); after=impurity; if(after<before){ delete=true; } else{ dontCare[i]=false; } } } } computeImpurityLevel(); }//end-method /** * Test if two rules intersects * * @param another Second rule. * * @return True if the rules intersect. False, if not. */ public boolean intersect(Rule another){ boolean intersect=true; for(int i=0;i<problemSize&&intersect;i++){ if((!dontCare[i])&&(!another.dontCare[i])){ if(isNominal[i]){ for(int j=0;j<differenceTable[i].length&&intersect;j++){ if(differenceTable[i][j]!=another.differenceTable[i][j]){ intersect=false; } } }else{ if((valueMin[i]<another.valueMin[i])||(valueMin[i]>another.valueMax[i])){ if((valueMax[i]<another.valueMin[i])||(valueMax[i]>another.valueMax[i])){ intersect=false; } } } } } return intersect; }//end-method /** * Test the rule contains a second rule * * @param another Second rule. * * @return True if the second rule is contained. False, if not. */ public boolean contains(Rule another){ boolean inside=true; if(another.output!=output){ inside=false; } for(int i=0;i<problemSize&&inside;i++){ if(!dontCare[i]){ if(another.dontCare[i]){ inside=false; } if(isNominal[i]){ for(int j=0;j<differenceTable[i].length&&inside;j++){ if(differenceTable[i][j]<another.differenceTable[i][j]){ inside=false; } } }else{ if(another.valueMin[i]<valueMin[i]){ inside=false; } if(another.valueMax[i]>valueMax[i]){ inside=false; } } } } return inside; }//end-method /** * Computes the inclussion degree of two rules in a given attribute * * @param another Second rule. * @param att Attribute selected * * @return Degree of inclussion */ public double inclusionDegree(Rule another, int att){ double degree=0.0; double num, den; if(dontCare[att]||another.dontCare[att]){ return 0.0; } if(isNominal[att]){ num=0.0; den=0.0; for(int i=0;i<differenceTable[att].length;i++){ num+=((1.0-differenceTable[att][i])*(1.0-another.differenceTable[att][i])); den+=(1.0-differenceTable[att][i]); } degree=num/den; } else{ if(valueMin[att]<another.valueMin[att]){ if(another.valueMin[att]>valueMax[att]){ degree=(Math.min(valueMax[att], another.valueMax[att])-Math.max(valueMin[att], another.valueMin[att]))/(valueMax[att]-another.valueMin[att]); } } if(valueMin[att]>another.valueMin[att]){ if(another.valueMax[att]<valueMin[att]){ degree=(Math.min(valueMax[att], another.valueMax[att])-Math.max(valueMin[att], another.valueMin[att]))/(valueMax[att]-another.valueMin[att]); } } } degree=Math.abs(degree); return degree; }//end-method /** * Performs an extension of a rule to another * * @param another Second rule. * * @return Final Rule */ public Rule testExtension(Rule another){ Rule extended; int selected=-1; computeImpurityLevel(); extended=clone(); for(int i=0;i<problemSize&&selected<0;i++){ if(inclusionDegree(another,i)>0.0){ selected=i; } } if(selected>=0){ if(!isNominal[selected]){ extended.valueMin[selected]=Math.min(extended.valueMin[selected], another.valueMin[selected]); extended.valueMax[selected]=Math.max(extended.valueMax[selected], another.valueMax[selected]); } else{ for(int j=0;j<differenceTable[selected].length;j++){ extended.differenceTable[selected][j]=Math.max(extended.differenceTable[selected][j], another.differenceTable[selected][j]); } } } else{ for(int i=0;i<problemSize;i++){ selected=i; if(!isNominal[selected]){ extended.valueMin[selected]=Math.min(extended.valueMin[selected], another.valueMin[selected]); extended.valueMax[selected]=Math.max(extended.valueMax[selected], another.valueMax[selected]); } else{ for(int j=0;j<differenceTable[selected].length;j++){ extended.differenceTable[selected][j]=Math.max(extended.differenceTable[selected][j], another.differenceTable[selected][j]); } } } } extended.computeImpurityLevel(); if(extended.impurity<=impurity){ return extended; } return null; }//end-method /** * Equals method * * @param rul Another rule * @return True of both rules are equal. False, if not */ @Override public boolean equals(Object rul) { Rule another=(Rule)rul; boolean isEqual=true; if(output!=another.output){ isEqual=false; } for(int i=0;i<size && isEqual;i++){ if(isNominal[i]){ if(dontCare[i]!=another.dontCare[i]){ isEqual=false; } else{ for(int j=0;j<differenceTable[i].length&& isEqual;j++){ if(differenceTable[i][j]!=another.differenceTable[i][j]){ isEqual=false; } } } } else{ if(valueMin[i]!=another.valueMin[i]){ isEqual=false; } if(valueMax[i]!=another.valueMax[i]){ isEqual=false; } } } return isEqual; }//end-method*/ /** * To String method * * @return A text string representing the contents of the rule */ @Override public String toString() { String text=""; for(int i=0;i<size;i++){ text+="Att"+i+": "; if(isNominal[i]){ if(dontCare[i]){ text+="TRUE "; } else{ for(int j=0;j<differenceTable[i].length;j++){ text+=differenceTable[i][j]+"-"; } } } else{ if(dontCare[i]){ text+="TRUE "; } else{ text+=valueMin[i]+"-"; text+=valueMax[i]+" "; } } } text+="Class = "+output; text+=" Impurity= "+impurity; return text; }//end-method*/ /** * Compare two rules, regarding its impurity level * * @param o First rule. * @param o2 Second rule. * * @return Order of the rules */ public int compare(Object o,Object o2) { Rule dir = (Rule)o; Rule dir2 = (Rule)o2; if(dir.impurity > dir2.impurity) return -1; else if(dir.impurity == dir2.impurity) return 0; else return 1; }//end-method }//end-class