/* * RapidMiner * * Copyright (C) 2001-2011 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.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.learner.functions; import static com.rapidminer.operator.learner.functions.linear.LinearRegression.*; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import Jama.Matrix; import com.rapidminer.example.Attribute; import com.rapidminer.example.Attributes; import com.rapidminer.example.Example; import com.rapidminer.example.ExampleSet; import com.rapidminer.operator.Model; import com.rapidminer.operator.OperatorCapability; import com.rapidminer.operator.OperatorCreationException; import com.rapidminer.operator.OperatorDescription; import com.rapidminer.operator.OperatorException; import com.rapidminer.operator.ProcessStoppedException; import com.rapidminer.operator.UserError; import com.rapidminer.operator.learner.AbstractLearner; import com.rapidminer.operator.learner.functions.linear.LinearRegression; import com.rapidminer.operator.learner.functions.linear.LinearRegressionMethod; import com.rapidminer.operator.ports.InputPortExtender; import com.rapidminer.parameter.ParameterType; import com.rapidminer.parameter.ParameterTypeBoolean; import com.rapidminer.parameter.ParameterTypeCategory; import com.rapidminer.parameter.ParameterTypeDouble; import com.rapidminer.parameter.UndefinedParameterError; import com.rapidminer.parameter.conditions.BooleanParameterCondition; import com.rapidminer.parameter.conditions.EqualTypeCondition; import com.rapidminer.tools.OperatorService; import com.rapidminer.tools.container.Pair; import com.rapidminer.tools.math.matrix.CovarianceMatrix; /** * This operator performs a seemingly unrelated regression from several data sets to make use of common effects in the * label that are not explainable from attributes. * * @author Sebastian Land */ public class SeeminglyUnrelatedRegressionOperator extends AbstractLearner { private InputPortExtender unrelatedExampleSets = new InputPortExtender("unrelated example sets", getInputPorts()); public SeeminglyUnrelatedRegressionOperator(OperatorDescription description) { super(description); unrelatedExampleSets.start(); } @Override public Model learn(ExampleSet mainSet) throws OperatorException { List<ExampleSet> dataSets = unrelatedExampleSets.getData(true); return learn(mainSet, dataSets); } public SeeminglyUnrelatedRegressionModel learn(ExampleSet mainSet, List<ExampleSet> dataSets) throws UserError, UndefinedParameterError, OperatorException, ProcessStoppedException { // check if each data set is part of the mainSet and has the same size int numberOfExamples = mainSet.size(); int numberOfSets = dataSets.size(); for (ExampleSet exampleSet : dataSets) { if (exampleSet.size() != numberOfExamples) throw new UserError(this, 951); } Attributes mainAttributes = mainSet.getAttributes(); int exampleSetIndex = 1; for (ExampleSet testSet : dataSets) { Attributes attributes = testSet.getAttributes(); for (Attribute attribute : attributes) { if (mainAttributes.get(attribute.getName()) == null) { throw new UserError(this, 952, attribute.getName(), exampleSetIndex + ""); } } exampleSetIndex++; } // first perform linear regression on each dataSet ArrayList<ExampleSet> residualSets = new ArrayList<ExampleSet>(dataSets.size()); ArrayList<Pair<Attribute, Attribute>> labelPredictionAttributes = new ArrayList<Pair<Attribute, Attribute>>(dataSets.size()); ArrayList<Iterator<Example>> setIterators = new ArrayList<Iterator<Example>>(dataSets.size()); ArrayList<String[]> usedAttributeNames = new ArrayList<String[]>(dataSets.size()); ArrayList<String> labelNames = new ArrayList<String>(dataSets.size()); try { // create linear regression and set parameters LinearRegression regression = OperatorService.createOperator(LinearRegression.class); regression.setParameter(PARAMETER_ELIMINATE_COLINEAR_FEATURES, getParameterAsString(PARAMETER_ELIMINATE_COLINEAR_FEATURES)); regression.setParameter(PARAMETER_FEATURE_SELECTION, getParameterAsString(PARAMETER_FEATURE_SELECTION)); regression.setParameter(PARAMETER_MIN_TOLERANCE, getParameterAsString(PARAMETER_MIN_TOLERANCE)); regression.setParameter(PARAMETER_RIDGE, getParameterAsString(PARAMETER_RIDGE)); regression.setParameter(PARAMETER_USE_BIAS, "true"); for (ExampleSet exampleSet : dataSets) { LinearRegressionModel model = (LinearRegressionModel) regression.learn(exampleSet); // now apply for getting residuals ExampleSet resultSet = model.apply(exampleSet); residualSets.add(resultSet); Attribute label = resultSet.getAttributes().getLabel(); labelPredictionAttributes.add(new Pair<Attribute, Attribute>(label, resultSet.getAttributes().getPredictedLabel())); labelNames.add(label.getName()); usedAttributeNames.add(model.getSelectedAttributeNames()); setIterators.add(resultSet.iterator()); } } catch (OperatorCreationException e) { throw new UserError(this, 950, LinearRegression.class.getSimpleName(), e.getMessage()); } // now build covariance matrix of residuals checkForStop(); double[][] residualMatrix = new double[numberOfExamples][numberOfSets]; for (int i = 0; i < numberOfExamples; i++) { // search residuals from examples of each set for (int j = 0; j < numberOfSets; j++) { Example example = setIterators.get(j).next(); Pair<Attribute, Attribute> labelPredictionPair = labelPredictionAttributes.get(j); residualMatrix[i][j] = example.getValue(labelPredictionPair.getFirst()) - example.getValue(labelPredictionPair.getSecond()); } } Matrix covarianceMatrix = CovarianceMatrix.getCovarianceMatrix(residualMatrix); Matrix wMatrixInverse = new Matrix(numberOfSets * numberOfExamples, numberOfSets * numberOfExamples, 0d); for (int i = 0; i < numberOfSets; i++) { for (int j = 0; j < numberOfSets; j++) { Matrix partlyResult = Matrix.identity(numberOfExamples, numberOfExamples).times(covarianceMatrix.get(i, j)); wMatrixInverse.setMatrix(i * numberOfExamples, (i + 1) * numberOfExamples - 1, j * numberOfExamples, (j + 1) * numberOfExamples - 1, partlyResult); } } checkForStop(); wMatrixInverse = wMatrixInverse.times(1d / numberOfExamples); wMatrixInverse = wMatrixInverse.inverse(); // now build data matrices and merge to single matrix checkForStop(); int width = 0; for (String[] selectedAttributes : usedAttributeNames) width += selectedAttributes.length + 1; // bias int height = numberOfSets * numberOfExamples; Matrix xMatrix = new Matrix(height, width, 0d); // fill matrix int i = 0; int currentColumn = 0; for (ExampleSet unrelatedSet : dataSets) { checkForStop(); Attributes attributes = unrelatedSet.getAttributes(); Attribute[] selectedAttributes = new Attribute[usedAttributeNames.get(i).length]; int j = 0; for (String selectedAttributeName : usedAttributeNames.get(i)) { selectedAttributes[j] = attributes.get(selectedAttributeName); j++; } double[][] subMatrixData = new double[numberOfExamples][selectedAttributes.length + 1]; int y = 0; for (Example example : unrelatedSet) { subMatrixData[y][0] = 1d; // bias int x = 1; for (Attribute attribute : selectedAttributes) { subMatrixData[y][x] = example.getValue(attribute); x++; } y++; } // now set submatrix xMatrix.setMatrix(i * numberOfExamples, (i + 1) * numberOfExamples - 1, currentColumn, currentColumn + selectedAttributes.length, new Matrix(subMatrixData)); currentColumn += selectedAttributes.length + 1; i++; } // build y matrix double[][] yMatrixData = new double[height][1]; int currentRow = 0; for (ExampleSet unrelatedSet : dataSets) { checkForStop(); Attribute label = unrelatedSet.getAttributes().getLabel(); for (Example example : unrelatedSet) { yMatrixData[currentRow][0] = example.getValue(label); currentRow++; } } Matrix yMatrix = new Matrix(yMatrixData); // now make matrix calculations checkForStop(); Matrix result = xMatrix.transpose().times(wMatrixInverse); checkForStop(); result = result.times(xMatrix).inverse(); checkForStop(); result = result.times(xMatrix.transpose()); checkForStop(); result = result.times(wMatrixInverse); checkForStop(); result = result.times(yMatrix); checkForStop(); return new SeeminglyUnrelatedRegressionModel(mainSet, usedAttributeNames, labelNames, result.getRowPackedCopy()); } @Override public boolean supportsCapability(OperatorCapability lc) { if (lc.equals(OperatorCapability.NUMERICAL_ATTRIBUTES)) return true; if (lc.equals(OperatorCapability.NUMERICAL_LABEL)) return true; return false; } @Override /** * This is a copy of the linear regression parameters, because we can't just copy them because of the dependencies */ public List<ParameterType> getParameterTypes() { List<ParameterType> types = super.getParameterTypes(); String[] availableSelectionMethods = SELECTION_METHODS.keySet().toArray(new String[0]); types.add(new ParameterTypeCategory(PARAMETER_FEATURE_SELECTION, "The feature selection method used during regression.", availableSelectionMethods, M5_PRIME)); // adding parameter of methods int i = 0; for (Entry<String, Class<? extends LinearRegressionMethod>> entry : SELECTION_METHODS.entrySet()) { try { LinearRegressionMethod method = entry.getValue().newInstance(); for (ParameterType methodType : method.getParameterTypes()) { types.add(methodType); methodType.registerDependencyCondition(new EqualTypeCondition(this, PARAMETER_FEATURE_SELECTION, availableSelectionMethods, true, i)); } } catch (InstantiationException e) { // can't do anything about this } catch (IllegalAccessException e) { } i++; } types.add(new ParameterTypeBoolean(PARAMETER_ELIMINATE_COLINEAR_FEATURES, "Indicates if the algorithm should try to delete colinear features during the regression.", true)); ParameterType type = new ParameterTypeDouble(PARAMETER_MIN_TOLERANCE, "The minimum tolerance for the removal of colinear features.", 0.0d, 1.0d, 0.05d); type.registerDependencyCondition(new BooleanParameterCondition(this, PARAMETER_ELIMINATE_COLINEAR_FEATURES, true, true)); types.add(type); types.add(new ParameterTypeBoolean(PARAMETER_USE_BIAS, "Indicates if an intercept value should be calculated.", true)); types.add(new ParameterTypeDouble(PARAMETER_RIDGE, "The ridge parameter used for ridge regression. A value of zero switches to ordinary least squares estimate.", 0.0d, Double.POSITIVE_INFINITY, 1.0E-8)); return types; } }