/*
* RapidMiner
*
* Copyright (C) 2001-2014 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.operator.ports.metadata;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import com.rapidminer.example.Attributes;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorCapability;
import com.rapidminer.operator.OperatorCreationException;
import com.rapidminer.operator.ProcessSetupError.Severity;
import com.rapidminer.operator.learner.CapabilityProvider;
import com.rapidminer.operator.ports.InputPort;
import com.rapidminer.operator.ports.quickfix.ChangeAttributeRoleQuickFix;
import com.rapidminer.operator.ports.quickfix.OperatorInsertionQuickFix;
import com.rapidminer.operator.ports.quickfix.QuickFix;
import com.rapidminer.operator.preprocessing.discretization.AbstractDiscretizationOperator;
import com.rapidminer.operator.preprocessing.filter.MissingValueReplenishment;
import com.rapidminer.operator.preprocessing.filter.NominalToBinominal;
import com.rapidminer.operator.preprocessing.filter.NominalToNumeric;
import com.rapidminer.operator.preprocessing.filter.attributes.RegexpAttributeFilter;
import com.rapidminer.operator.preprocessing.filter.attributes.SingleAttributeFilter;
import com.rapidminer.operator.tools.AttributeSubsetSelector;
import com.rapidminer.tools.Ontology;
import com.rapidminer.tools.OperatorService;
/**
* This is a precondition for {@link InputPort}s that
* ensures that the capabilities given by an operator are
* matched by the delivered ExampleSet.
*
* @author Sebastian Land
*/
public class CapabilityPrecondition extends ExampleSetPrecondition {
protected final CapabilityProvider capabilityProvider;
public CapabilityPrecondition(CapabilityProvider capabilityProvider, InputPort inputPort) {
super(inputPort);
this.capabilityProvider = capabilityProvider;
}
@Override
public void makeAdditionalChecks(ExampleSetMetaData metaData) {
// regular attributes
if (metaData.containsAttributesWithValueType(Ontology.NOMINAL, false) == MetaDataInfo.YES) {
if (metaData.containsAttributesWithValueType(Ontology.BINOMINAL, false) == MetaDataInfo.YES) {
if (!capabilityProvider.supportsCapability(OperatorCapability.BINOMINAL_ATTRIBUTES)) {
List<QuickFix> fixes = new LinkedList<QuickFix>();
if (capabilityProvider.supportsCapability(OperatorCapability.NUMERICAL_ATTRIBUTES)) {
fixes.add(createToNumericalFix(null));
}
createLearnerError(OperatorCapability.BINOMINAL_ATTRIBUTES.getDescription(), fixes);
}
} else {
if (!capabilityProvider.supportsCapability(OperatorCapability.POLYNOMINAL_ATTRIBUTES)) {
List<QuickFix> fixes = new LinkedList<QuickFix>();
if (capabilityProvider.supportsCapability(OperatorCapability.BINOMINAL_ATTRIBUTES)) {
fixes.add(createToBinominalFix(null));
}
if (capabilityProvider.supportsCapability(OperatorCapability.NUMERICAL_ATTRIBUTES)) {
fixes.add(createToNumericalFix(null));
}
createLearnerError(OperatorCapability.POLYNOMINAL_ATTRIBUTES.getDescription(), fixes);
}
}
}
if ((metaData.containsAttributesWithValueType(Ontology.NUMERICAL, false) == MetaDataInfo.YES) && !capabilityProvider.supportsCapability(OperatorCapability.NUMERICAL_ATTRIBUTES)) {
createLearnerError(OperatorCapability.NUMERICAL_ATTRIBUTES.getDescription(), AbstractDiscretizationOperator.createDiscretizationFixes(getInputPort(), null));
}
checkLabelPreconditions(metaData);
// weighted examples
if (!capabilityProvider.supportsCapability(OperatorCapability.WEIGHTED_EXAMPLES)) {
switch (metaData.hasSpecial(Attributes.WEIGHT_NAME)) {
case YES:
createError(Severity.WARNING, "learner_does_not_support_weights");
break;
case NO:
case UNKNOWN:
default:
break;
}
}
// missing values
if (!capabilityProvider.supportsCapability(OperatorCapability.MISSING_VALUES)) {
if (metaData.getAllAttributes() != null)
for (AttributeMetaData amd: metaData.getAllAttributes()) {
if (!amd.isSpecial() || Attributes.LABEL_NAME.equals(amd.getRole())) {
if (amd.containsMissingValues() == MetaDataInfo.YES) {
createLearnerError(OperatorCapability.MISSING_VALUES.getDescription(),
Collections.singletonList(new OperatorInsertionQuickFix("insert_missing_value_replenishment", new String[0], 1, getInputPort()) {
@Override
public Operator createOperator() throws OperatorCreationException {
return OperatorService.createOperator(MissingValueReplenishment.class);
}
}));
break;
}
}
}
}
}
protected void checkLabelPreconditions(ExampleSetMetaData metaData) {
// label
//check if needs label
// TODO: This checks if it is supported, but not if it is required. This test will break if we add a new label type
// because it will then be incomplete.
if (!capabilityProvider.supportsCapability(OperatorCapability.NO_LABEL)) {
// if it supports no label it's simply irrelevant if label is present.
if (capabilityProvider.supportsCapability(OperatorCapability.ONE_CLASS_LABEL) || capabilityProvider.supportsCapability(OperatorCapability.BINOMINAL_LABEL) || capabilityProvider.supportsCapability(OperatorCapability.POLYNOMINAL_LABEL) || capabilityProvider.supportsCapability(OperatorCapability.NUMERICAL_LABEL)) {
switch (metaData.hasSpecial(Attributes.LABEL_NAME)) {
case UNKNOWN:
getInputPort().addError(new SimpleMetaDataError(Severity.WARNING, getInputPort(), Collections.singletonList(new ChangeAttributeRoleQuickFix(getInputPort(), Attributes.LABEL_NAME, "change_attribute_role", Attributes.LABEL_NAME)), "special_unknown", new Object[] { Attributes.LABEL_NAME }));
break;
case NO:
getInputPort().addError(new SimpleMetaDataError(Severity.ERROR, getInputPort(), Collections.singletonList(new ChangeAttributeRoleQuickFix(getInputPort(), Attributes.LABEL_NAME, "change_attribute_role", Attributes.LABEL_NAME)), "special_missing", new Object[] { Attributes.LABEL_NAME }));
break;
case YES:
AttributeMetaData label = metaData.getLabelMetaData();
List<QuickFix> fixes = new LinkedList<QuickFix>();
if (label.isNominal()) {
if (capabilityProvider.supportsCapability(OperatorCapability.NUMERICAL_LABEL)) {
fixes.addAll(getFixesForClassificationWhenRegressionSupported());
}
if (capabilityProvider.supportsCapability(OperatorCapability.ONE_CLASS_LABEL) && !capabilityProvider.supportsCapability(OperatorCapability.BINOMINAL_LABEL) && !capabilityProvider.supportsCapability(OperatorCapability.POLYNOMINAL_LABEL)) {
// if it only supports one class label
if (label.getValueSet().size() > 1 && label.getValueSetRelation() != SetRelation.UNKNOWN) {
createError(Severity.ERROR, "one_class_label_invalid", label.getValueSetRelation().toString() + " " + label.getValueSet().size());
}
} else {
// if it supports two or more classes
if (label.getValueSet().size() == 1 && label.getValueSetRelation() == SetRelation.EQUAL) {
createError(Severity.ERROR, "no_polynomial_label");
} else {
// if two or more classes are present
if (label.isBinominal()) {
if (!capabilityProvider.supportsCapability(OperatorCapability.BINOMINAL_LABEL)) {
createLearnerError(OperatorCapability.BINOMINAL_LABEL.getDescription(), fixes);
}
} else {
if (!capabilityProvider.supportsCapability(OperatorCapability.POLYNOMINAL_LABEL)) {
// check if not nominal binominal label
if ((label.getValueSetRelation() != SetRelation.EQUAL) || (label.getValueSet().size() != 2)) {
if (capabilityProvider.supportsCapability(OperatorCapability.BINOMINAL_LABEL)) {
fixes.add(createToBinominalFix(label.getName()));
fixes.addAll(getFixesForPolynomialClassificationWhenBinominalSupported());
}
if (capabilityProvider.supportsCapability(OperatorCapability.NUMERICAL_LABEL)) {
fixes.add(createToNumericalFix(label.getName()));
}
createLearnerError(OperatorCapability.POLYNOMINAL_LABEL.getDescription(), fixes);
}
}
}
}
}
} else if (label.isNumerical() && !capabilityProvider.supportsCapability(OperatorCapability.NUMERICAL_LABEL)) {
createLearnerError(OperatorCapability.NUMERICAL_LABEL.getDescription(), getFixesForRegressionWhenClassificationSupported(label));
}
}
}
}
}
/**
* This method has to return a collection of quick fixes which are appropriate when classification is supported and
* the data needs regression. The default implementation will return fixes for discretization.
*/
protected List<QuickFix> getFixesForRegressionWhenClassificationSupported(AttributeMetaData labelMD) {
return AbstractDiscretizationOperator.createDiscretizationFixes(getInputPort(), labelMD.getName());
}
/**
* This method has to return a collection of quick fixes which are appropriate when regression is supported and
* the data needs classification.
*/
protected Collection<QuickFix> getFixesForClassificationWhenRegressionSupported() {
return Collections.emptyList();
}
/**
* This has to return a list of appropriate quick fixes in the case, that
* only binominal labels are supported but the data contains polynomials.
*/
protected Collection<QuickFix> getFixesForPolynomialClassificationWhenBinominalSupported() {
return Collections.emptyList();
}
/** Creates a quickfix to convert to nominal.
* @param labelName If null, regular attributes will be converted. Otherwise the special attribute with the given name will be converted. */
protected QuickFix createToBinominalFix(final String labelName) {
return new OperatorInsertionQuickFix("insert_nominal_to_binominal_" + ((labelName != null) ? "label" : "attributes"), new Object[0], 10, getInputPort()) {
@Override
public Operator createOperator() throws OperatorCreationException {
Operator op = OperatorService.createOperator(NominalToBinominal.class);
if (labelName != null) {
op.setParameter(AttributeSubsetSelector.PARAMETER_FILTER_TYPE, AttributeSubsetSelector.CONDITION_NAMES[AttributeSubsetSelector.CONDITION_SINGLE]);
op.setParameter(AttributeSubsetSelector.PARAMETER_INCLUDE_SPECIAL_ATTRIBUTES, "true");
op.setParameter(SingleAttributeFilter.PARAMETER_ATTRIBUTE, labelName);
}
return op;
}
};
}
/** Creates a quickfix to convert to numerical.
* @param labelName If null, regular attributes will be converted. Otherwise the special attribute with the given name will be converted. */
protected QuickFix createToNumericalFix(final String labelName) {
return new OperatorInsertionQuickFix("insert_nominal_to_numerical_" + ((labelName != null) ? "label" : "attributes"), new Object[0], 10, getInputPort()) {
@Override
public Operator createOperator() throws OperatorCreationException {
Operator op = OperatorService.createOperator(NominalToNumeric.class);
if (labelName != null) {
op.setParameter(AttributeSubsetSelector.PARAMETER_FILTER_TYPE, AttributeSubsetSelector.CONDITION_NAMES[AttributeSubsetSelector.CONDITION_REGULAR_EXPRESSION]);
op.setParameter(AttributeSubsetSelector.PARAMETER_INCLUDE_SPECIAL_ATTRIBUTES, "true");
op.setParameter(RegexpAttributeFilter.PARAMETER_REGULAR_EXPRESSION, labelName);
}
return op;
}
};
}
protected void createLearnerError(String description, List<? extends QuickFix> list) {
String id = "Learner";
if (capabilityProvider instanceof Operator) {
id = ((Operator)capabilityProvider).getOperatorDescription().getName();
}
createError(Severity.ERROR, list, "learner_cannot_handle", new Object[] { id, description });
}
}