weka.associations.tertius.Rule Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of weka-stable Show documentation
Show all versions of weka-stable Show documentation
The Waikato Environment for Knowledge Analysis (WEKA), a machine
learning workbench. This is the stable version. Apart from bugfixes, this version
does not receive any other updates.
/*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* Rule.java
* Copyright (C) 2003 Peter A. Flach, Nicolas Lachiche
*
* Thanks to Amelie Deltour for porting the original C code to Java
* and integrating it into Weka.
*/
package weka.associations.tertius;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.RevisionHandler;
import weka.core.RevisionUtils;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Enumeration;
/**
* Class representing a rule with a body and a head.
*
* @author Amelie Deltour
* @version $Revision: 1.7 $
*/
public class Rule
implements Serializable, Cloneable, RevisionHandler {
/** for serialization */
private static final long serialVersionUID = -7763378359090435505L;
/** The body of the rule. */
private Body m_body;
/** The head of the rule. */
private Head m_head;
/** Can repeat predicates in the rule ? */
private boolean m_repeatPredicate;
/** Maximal number of literals in the rule. */
private int m_maxLiterals;
/** Can there be negations in the body ? */
private boolean m_negBody;
/** Can there be negations in the head ? */
private boolean m_negHead;
/** Is this rule a classification rule ? */
private boolean m_classRule;
/** Can there be only one literal in the head ? */
private boolean m_singleHead;
/** Number of instances in the data this rule deals with. */
private int m_numInstances;
/** Set of counter-instances of this rule. */
private ArrayList m_counterInstances;
/** Counter for the counter-instances of this rule. */
private int m_counter;
/** Confirmation of this rule. */
private double m_confirmation;
/** Optimistic estimate of this rule. */
private double m_optimistic;
/**
* Constructor for a rule when the counter-instances are not stored,
* giving all the constraints applied to this rule.
*
* @param repeatPredicate True if predicates can be repeated.
* @param maxLiterals Maximum number of literals.
* @param negBody True if negation is allowed in the body.
* @param negHead True if negation is allowed in the head.
* @param classRule True if the rule is a classification rule.
* @param horn True if the rule is a horn clause.
*/
public Rule(boolean repeatPredicate, int maxLiterals, boolean negBody,
boolean negHead, boolean classRule, boolean horn) {
m_body = new Body();
m_head = new Head();
m_repeatPredicate = repeatPredicate;
m_maxLiterals = maxLiterals;
m_negBody = negBody && !horn;
m_negHead = negHead && !horn;
m_classRule = classRule;
m_singleHead = classRule || horn;
}
/**
* Constructor for a rule when the counter-instances are stored,
* giving all the constraints applied to this rule.
* The counter-instances are initialized to all the instances in the dataset.
*
* @param instances The dataset.
* @param repeatPredicate True if predicates can be repeated.
* @param maxLiterals Maximum number of literals.
* @param negBody True if negation is allowed in the body.
* @param negHead True if negation is allowed in the head.
* @param classRule True if the rule is a classification rule.
* @param horn True if the rule is a horn clause.
*/
public Rule(Instances instances,
boolean repeatPredicate, int maxLiterals, boolean negBody,
boolean negHead, boolean classRule, boolean horn) {
m_body = new Body(instances);
m_head = new Head(instances);
m_repeatPredicate = repeatPredicate;
m_maxLiterals = maxLiterals;
m_negBody = negBody && !horn;
m_negHead = negHead && !horn;
m_classRule = classRule;
m_singleHead = classRule || horn;
m_numInstances = instances.numInstances();
m_counterInstances = new ArrayList(m_numInstances);
Enumeration enu = instances.enumerateInstances();
while (enu.hasMoreElements()) {
m_counterInstances.add(enu.nextElement());
}
}
/**
* Returns a shallow copy of this rule.
* The structured is copied but the literals themselves are not copied.
*
* @return A copy of this Rule.
*/
public Object clone() {
Object result = null;
try {
result = super.clone();
/* Clone the body and the head. */
((Rule) result).m_body = (Body) m_body.clone();
((Rule) result).m_head = (Head) m_head.clone();
/* Clone the set of counter-instances. */
if (m_counterInstances != null) {
((Rule) result).m_counterInstances
= (ArrayList) m_counterInstances.clone();
}
} catch (Exception e) {
/* An exception is not supposed to happen here. */
e.printStackTrace();
System.exit(0);
}
return result;
}
/**
* Test if an instance is a counter-instance of this rule.
*
* @param instance The instance to test.
* @return True if the instance is a counter-instance.
*/
public boolean counterInstance(Instance instance) {
return ((m_body.counterInstance(instance)
&& m_head.counterInstance(instance)));
}
/**
* Update the number of counter-instances of this rule in the dataset.
* This method should be used is the rule does not store its counter-instances.
*
* @param instances The dataset.
*/
public void upDate(Instances instances) {
Enumeration enu = instances.enumerateInstances();
m_numInstances = instances.numInstances();
m_counter = 0;
while (enu.hasMoreElements()) {
if (this.counterInstance((Instance) enu.nextElement())) {
m_counter++;
}
}
m_head.upDate(instances);
m_body.upDate(instances);
}
/**
* Get the confirmation value of this rule.
*
* @return The confirmation.
*/
public double getConfirmation() {
return m_confirmation;
}
/**
* Get the optimistic estimate of the confirmation obtained by refining
* this rule.
*
* @return The optimistic estimate.
*/
public double getOptimistic() {
return m_optimistic;
}
/*
* Get the expected number of counter-instances of this rule,
* calculated from the number of instances satisfying the body and
* the number of instances satisfying the negation of the head.
*
* @return The expected number of counter-instances.
*/
public double getExpectedNumber() {
return (double) m_body.getCounterInstancesNumber()
* (double) m_head.getCounterInstancesNumber()
/ (double) m_numInstances;
}
/**
* Get the expected frequency of counter-instances of this rule.
*
* @return The expected frequency of counter-instances.
*/
public double getExpectedFrequency() {
return getExpectedNumber() / (double) m_numInstances;
}
/**
* Get the observed number of counter-instances of this rule in the dataset.
*
* @return The observed number of counter-instances.
*/
public int getObservedNumber() {
if (m_counterInstances != null) {
return m_counterInstances.size();
} else {
return m_counter;
}
}
/**
* Get the observed frequency of counter-instances of this rule in the dataset.
*
* @return The expected frequency of counter-instances.
*/
public double getObservedFrequency() {
return (double) getObservedNumber() / (double) m_numInstances;
}
/**
* Get the rate of True Positive instances of this rule.
*
* @return The TP-rate.
*/
public double getTPRate() {
int tp = m_body.getCounterInstancesNumber() - getObservedNumber();
int fn = m_numInstances - m_head.getCounterInstancesNumber() - tp;
return ((double) tp / (double) (tp + fn));
}
/**
* Get the rate of False Positive instances of this rule.
*
* @return The FP-rate.
*/
public double getFPRate() {
int fp = getObservedNumber();
int tn = m_head.getCounterInstancesNumber() - fp;
return ((double) fp / (double) (fp + tn));
}
/**
* Calculate the confirmation of this rule.
*/
public void calculateConfirmation() {
double expected = getExpectedFrequency();
double observed = getObservedFrequency();
if ((expected == 0) || (expected == 1)) {
m_confirmation = 0;
} else {
m_confirmation = (expected - observed) / (Math.sqrt(expected) - expected);
}
}
/**
* Calculate the optimistic estimate of this rule.
*/
public void calculateOptimistic() {
int counterInstances = this.getObservedNumber();
int body = m_body.getCounterInstancesNumber();
int notHead = m_head.getCounterInstancesNumber();
int n = m_numInstances;
double expectedOptimistic;
/* optimistic expected number of counter-instances */
if (counterInstances <= body - notHead) {
expectedOptimistic = (double) (notHead * (body - counterInstances))
/ (double) (n * n);
} else if (counterInstances <= notHead - body) {
expectedOptimistic = (double) (body * (notHead - counterInstances))
/ (double) (n * n);
} else {
expectedOptimistic = (double) ((notHead + body - counterInstances)
* (notHead + body - counterInstances))
/ (double) (4 * n * n);
}
if ((expectedOptimistic == 0) || (expectedOptimistic == 1)) {
m_optimistic = 0;
} else {
m_optimistic = expectedOptimistic
/ (Math.sqrt(expectedOptimistic) - expectedOptimistic);
}
}
/**
* Test if this rule is empty.
*
* @return True if it is the empty rule.
*/
public boolean isEmpty() {
return (m_head.isEmpty() && m_body.isEmpty());
}
/**
* Give the number of literals in this rule.
*
* @return The number of literals.
*/
public int numLiterals() {
return m_body.numLiterals() + m_head.numLiterals();
}
/**
* Add a literal to the body of the rule.
*
* @param newLit The literal to add.
* @return The rule obtained by adding the literal, null if the literal can
* not be added because of the constraints on the rule.
*/
private Rule addTermToBody(Literal newLit) {
if (!m_negBody && newLit.negative()
|| (m_classRule && newLit.getPredicate().isClass())
|| (newLit instanceof IndividualLiteral
&& (((IndividualLiteral) newLit).getType()
- m_body.getType()) > 1
&& (((IndividualLiteral) newLit).getType()
- m_head.getType()) > 1)) {
return null;
} else {
Rule result = (Rule) this.clone();
result.m_body.addElement(newLit);
/* Update the counter-instances. */
if (m_counterInstances != null) {
for (int i = result.m_counterInstances.size() - 1; i >= 0; i--) {
Instance current = (Instance) result.m_counterInstances.get(i);
if (!result.m_body.canKeep(current, newLit)) {
result.m_counterInstances.remove(i);
}
}
}
return result;
}
}
/**
* Add a literal to the head of the rule.
*
* @param newLit The literal to add.
* @return The rule obtained by adding the literal, null if the literal can
* not be added because of the constraints on the rule.
*/
private Rule addTermToHead(Literal newLit) {
if ((!m_negHead && newLit.negative())
|| (m_classRule && !newLit.getPredicate().isClass())
|| (m_singleHead && !m_head.isEmpty())
|| (newLit instanceof IndividualLiteral
&& ((IndividualLiteral) newLit).getType()
!= IndividualLiteral.INDIVIDUAL_PROPERTY)) {
return null;
} else {
Rule result = (Rule) this.clone();
result.m_head.addElement(newLit);
/* Update counter-instances. */
if (m_counterInstances != null) {
for (int i = result.m_counterInstances.size() - 1; i >= 0; i--) {
Instance current = (Instance) result.m_counterInstances.get(i);
if (!result.m_head.canKeep(current, newLit)) {
result.m_counterInstances.remove(i);
}
}
}
return result;
}
}
/**
* Refine a rule by adding a range of literals of a predicate, either to
* the head or to the body of the rule.
*
* @param pred The predicate to consider.
* @param firstIndex The index of the first literal of the predicate to add.
* @param lastIndex The index of the last literal of the predicate to add.
* @param addTobody True if the literals should be added to the body.
* @param addToHead True if the literals should be added to the head.
* @return A list of rules obtained by refinement.
*/
private SimpleLinkedList refine(Predicate pred, int firstIndex, int lastIndex,
boolean addToBody, boolean addToHead) {
SimpleLinkedList result = new SimpleLinkedList();
Literal currentLit;
Literal negation;
Rule refinement;
for (int i = firstIndex; i < lastIndex; i++) {
currentLit = pred.getLiteral(i);
if (addToBody) {
refinement = addTermToBody(currentLit);
if (refinement != null) {
result.add(refinement);
}
}
if (addToHead) {
refinement = addTermToHead(currentLit);
if (refinement != null) {
result.add(refinement);
}
}
negation = currentLit.getNegation();
if (negation != null) {
if (addToBody) {
refinement = addTermToBody(negation);
if (refinement != null) {
result.add(refinement);
}
}
if (addToHead) {
refinement = addTermToHead(negation);
if (refinement != null) {
result.add(refinement);
}
}
}
}
return result;
}
/**
* Refine a rule by adding literal from a set of predictes.
*
* @param predicates The predicates available.
* @return The list of the children obtained by refining the rule.
*/
public SimpleLinkedList refine(ArrayList predicates) {
SimpleLinkedList result = new SimpleLinkedList();
Predicate currentPred;
boolean addToBody;
boolean addToHead;
if (this.numLiterals() == m_maxLiterals) {
return result;
}
if (this.isEmpty()) {
/* Literals can be added on both sides of the rule. */
for (int i = 0; i < predicates.size(); i++) {
currentPred = (Predicate) predicates.get(i);
result.addAll(refine(currentPred,
0, currentPred.numLiterals(),
true, true));
}
} else if (m_body.isEmpty() || m_head.isEmpty()) {
/* Literals can be added to the empty side only. */
LiteralSet side;
Literal last;
if (m_body.isEmpty()) {
side = m_head;
addToBody = true;
addToHead = false;
} else { // m_head.isEmpty()
side = m_body;
addToBody = false;
addToHead = true;
}
last = side.getLastLiteral();
currentPred = last.getPredicate();
if (m_repeatPredicate) {
result.addAll(refine(currentPred,
currentPred.indexOf(last) + 1,
currentPred.numLiterals(),
addToBody, addToHead));
}
for (int i = predicates.indexOf(currentPred) + 1; i < predicates.size();
i++) {
currentPred = (Predicate) predicates.get(i);
result.addAll(refine(currentPred,
0, currentPred.numLiterals(),
addToBody, addToHead));
}
} else {
Literal lastLitBody = m_body.getLastLiteral();
Literal lastLitHead = m_head.getLastLiteral();
Predicate lastPredBody = lastLitBody.getPredicate();
Predicate lastPredHead = lastLitHead.getPredicate();
int lastLitBodyIndex = lastPredBody.indexOf(lastLitBody);
int lastLitHeadIndex = lastPredHead.indexOf(lastLitHead);
int lastPredBodyIndex = predicates.indexOf(lastPredBody);
int lastPredHeadIndex = predicates.indexOf(lastPredHead);
Predicate inferiorPred;
Predicate superiorPred;
int inferiorLit;
int superiorLit;
addToBody = (m_head.numLiterals() == 1
&& (lastPredBodyIndex < lastPredHeadIndex
|| (lastPredBodyIndex == lastPredHeadIndex
&& lastLitBodyIndex < lastLitHeadIndex)));
addToHead = (m_body.numLiterals() == 1
&& (lastPredHeadIndex < lastPredBodyIndex
|| (lastPredHeadIndex == lastPredBodyIndex
&& lastLitHeadIndex < lastLitBodyIndex)));
if (addToBody || addToHead) {
/* Add literals in the gap between the body and the head. */
if (addToBody) {
inferiorPred = lastPredBody;
inferiorLit = lastLitBodyIndex;
superiorPred = lastPredHead;
superiorLit = lastLitHeadIndex;
} else { // addToHead
inferiorPred = lastPredHead;
inferiorLit = lastLitHeadIndex;
superiorPred = lastPredBody;
superiorLit = lastLitBodyIndex;
}
if (predicates.indexOf(inferiorPred)
< predicates.indexOf(superiorPred)) {
if (m_repeatPredicate) {
result.addAll(refine(inferiorPred,
inferiorLit + 1, inferiorPred.numLiterals(),
addToBody, addToHead));
}
for (int j = predicates.indexOf(inferiorPred) + 1;
j < predicates.indexOf(superiorPred); j++) {
currentPred = (Predicate) predicates.get(j);
result.addAll(refine(currentPred,
0, currentPred.numLiterals(),
addToBody, addToHead));
}
if (m_repeatPredicate) {
result.addAll(refine(superiorPred,
0, superiorLit,
addToBody, addToHead));
}
} else {
//((inferiorPred.getIndex() == superiorPred.getIndex())
//&& (inferiorLit < superiorLit))
if (m_repeatPredicate) {
result.addAll(refine(inferiorPred,
inferiorLit + 1, superiorLit,
addToBody, addToHead));
}
}
}
/* Add other literals. */
if (predicates.indexOf(lastPredBody) > predicates.indexOf(lastPredHead)) {
superiorPred = lastPredBody;
superiorLit = lastPredBody.indexOf(lastLitBody);
} else if (predicates.indexOf(lastPredBody)
< predicates.indexOf(lastPredHead)) {
superiorPred = lastPredHead;
superiorLit = lastPredHead.indexOf(lastLitHead);
} else {
superiorPred = lastPredBody;
if (lastLitBodyIndex > lastLitHeadIndex) {
superiorLit = lastPredBody.indexOf(lastLitBody);
} else {
superiorLit = lastPredHead.indexOf(lastLitHead);
}
}
if (m_repeatPredicate) {
result.addAll(refine(superiorPred,
superiorLit + 1, superiorPred.numLiterals(),
true, true));
}
for (int j = predicates.indexOf(superiorPred) + 1; j < predicates.size();
j++) {
currentPred = (Predicate) predicates.get(j);
result.addAll(refine(currentPred,
0, currentPred.numLiterals(),
true, true));
}
}
return result;
}
/**
* Test if this rule subsumes another rule.
*
* @param otherRule The other rule.
* @return True if the other rule is subsumed.
*/
public boolean subsumes(Rule otherRule) {
if (this.numLiterals() > otherRule.numLiterals()) {
return false;
}
return (m_body.isIncludedIn(otherRule) && m_head.isIncludedIn(otherRule));
}
/**
* Test if this rule and another rule correspond to the same clause.
*
* @param otherRule The other rule.
* @return True if both rules correspond to the same clause.
*/
public boolean sameClauseAs(Rule otherRule) {
return (this.numLiterals() == otherRule.numLiterals()
&& this.subsumes(otherRule));
}
/**
* Test if this rule is equivalent to another rule.
*
* @param otherRule The other rule.
* @return True if both rules are equivalent.
*/
public boolean equivalentTo(Rule otherRule) {
return (this.numLiterals() == otherRule.numLiterals()
&& m_head.negationIncludedIn(otherRule.m_body)
&& m_body.negationIncludedIn(otherRule.m_head));
}
/**
* Test if the body of the rule contains a literal.
*
* @param lit The literal to look for.
* @return True if the literal is contained in the body of the rule.
*/
public boolean bodyContains(Literal lit) {
return m_body.contains(lit);
}
/**
* Test if the head of the rule contains a literal.
*
* @param lit The literal to look for.
* @return True if the literal is contained in the head of the rule.
*/
public boolean headContains(Literal lit) {
return m_head.contains(lit);
}
/**
* Test if this rule is over the frequency threshold.
*
* @param minFrequency The frequency threshold.
* @return True if the rule is over the threshold.
*/
public boolean overFrequencyThreshold(double minFrequency) {
return (m_body.overFrequencyThreshold(minFrequency)
&& m_head.overFrequencyThreshold(minFrequency));
}
/**
* Test if the body of the rule is true.
*
* @return True if the body is always satisfied.
*/
public boolean hasTrueBody() {
return (!m_body.isEmpty()
&& m_body.hasMaxCounterInstances());
}
/**
* Test if the head of the rule is false.
*
* @return True if the body is never satisfied.
*/
public boolean hasFalseHead() {
return (!m_head.isEmpty()
&& m_head.hasMaxCounterInstances());
}
/**
* Return a String giving the confirmation and optimistic estimate of
* this rule.
*
* @return A String with the values of the rule.
*/
public String valuesToString() {
StringBuffer text = new StringBuffer();
DecimalFormat decimalFormat = new DecimalFormat("0.000000");
text.append(decimalFormat.format(getConfirmation()));
text.append(" ");
text.append(decimalFormat.format(getObservedFrequency()));
return text.toString();
}
/**
* Return a String giving the TP-rate and FP-rate of
* this rule.
*
* @return A String with the values of the rule.
*/
public String rocToString() {
StringBuffer text = new StringBuffer();
DecimalFormat decimalFormat = new DecimalFormat("0.000000");
text.append(decimalFormat.format(getConfirmation()));
text.append(" ");
text.append(decimalFormat.format(getTPRate()));
text.append(" ");
text.append(decimalFormat.format(getFPRate()));
return text.toString();
}
/**
* Retrun a String for this rule.
*
* @return The String describing this rule.
*/
public String toString() {
StringBuffer text = new StringBuffer();
text.append(m_body.toString());
text.append(" ==> ");
text.append(m_head.toString());
return text.toString();
}
/**
* Comparator used to compare two rules according to their confirmation value.
*/
public static Comparator confirmationComparator = new Comparator() {
public int compare(Object o1, Object o2) {
Rule r1 = (Rule) o1;
Rule r2 = (Rule) o2;
double conf1 = r1.getConfirmation();
double conf2 = r2.getConfirmation();
if (conf1 > conf2) {
return -1;
} else if (conf1 < conf2) {
return 1;
} else {
return 0;
}
}
};
/**
* Comparator used to compare two rules according to their observed number
* of counter-instances.
*/
public static Comparator observedComparator = new Comparator() {
public int compare(Object o1, Object o2) {
Rule r1 = (Rule) o1;
Rule r2 = (Rule) o2;
double obs1 = r1.getObservedFrequency();
double obs2 = r2.getObservedFrequency();
if (obs1 < obs2) {
return -1;
} else if (obs1 > obs2) {
return 1;
} else {
return 0;
}
}
};
/**
* Comparator used to compare two rules according to their optimistic estimate.
*/
public static Comparator optimisticComparator = new Comparator() {
public int compare(Object o1, Object o2) {
Rule r1 = (Rule) o1;
Rule r2 = (Rule) o2;
double opt1 = r1.getOptimistic();
double opt2 = r2.getOptimistic();
if (opt1 > opt2) {
return -1;
} else if (opt1 < opt2) {
return 1;
} else {
return 0;
}
}
};
/**
* Comparator used to compare two rules according to their confirmation and
* then their observed number of counter-instances.
*/
public static Comparator confirmationThenObservedComparator = new Comparator() {
public int compare(Object o1, Object o2) {
int confirmationComparison = confirmationComparator.compare(o1, o2);
if (confirmationComparison != 0) {
return confirmationComparison;
} else {
return observedComparator.compare(o1, o2);
}
}
};
/**
* Comparator used to compare two rules according to their optimistic estimate
* and then their observed number of counter-instances.
*/
public static Comparator optimisticThenObservedComparator = new Comparator() {
public int compare(Object o1, Object o2) {
int optimisticComparison = optimisticComparator.compare(o1, o2);
if (optimisticComparison != 0) {
return optimisticComparison;
} else {
return observedComparator.compare(o1, o2);
}
}
};
/**
* Returns the revision string.
*
* @return the revision
*/
public String getRevision() {
return RevisionUtils.extract("$Revision: 1.7 $");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy