/**
* Copyright (C) 2001-2017 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.preprocessing;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.rapidminer.example.Attribute;
import com.rapidminer.example.AttributeRole;
import com.rapidminer.example.Attributes;
import com.rapidminer.example.Example;
import com.rapidminer.example.ExampleSet;
import com.rapidminer.example.table.AttributeFactory;
import com.rapidminer.example.utils.ExampleSetBuilder;
import com.rapidminer.example.utils.ExampleSets;
import com.rapidminer.operator.AbstractExampleSetProcessing;
import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.operator.OperatorException;
import com.rapidminer.operator.annotation.ResourceConsumptionEstimator;
import com.rapidminer.operator.ports.metadata.ExampleSetMetaData;
import com.rapidminer.operator.ports.metadata.MetaData;
import com.rapidminer.operator.preprocessing.filter.ChangeAttributeRole;
import com.rapidminer.tools.Ontology;
import com.rapidminer.tools.OperatorResourceConsumptionHandler;
/**
* <p>
* This operator transposes an example set, i.e. the columns with become the new rows and the old
* rows will become the columns. Hence, this operator works very similar to the well know transpose
* operation for matrices.
* </p>
*
* <p>
* If an Id attribute is part of the given example set, the ids will become the names of the new
* attributes. The names of the old attributes will be transformed into the id values of a new
* special Id attribute. Since no other "special" examples or data rows exist, all other
* new attributes will be regular after the transformation. You can use the
* {@link ChangeAttributeRole} operator in order to change one of these into a special type
* afterwards.
* </p>
*
* <p>
* If all old attribute have the same value type, all new attributes will have this value type.
* Otherwise, the new value types will all be "nominal" if at least one nominal attribute
* was part of the given example set and "real" if the types contained mixed numbers.
* </p>
*
* <p>
* This operator produces a copy of the data in the main memory and it therefore not suggested to
* use it on very large data sets.
* </p>
*
* @author Ingo Mierswa
*/
public class ExampleSetTranspose extends AbstractExampleSetProcessing {
public ExampleSetTranspose(OperatorDescription description) {
super(description);
}
@Override
protected MetaData modifyMetaData(ExampleSetMetaData metaData) {
return metaData.transpose();
}
@Override
public ExampleSet apply(ExampleSet exampleSet) throws OperatorException {
// init operator progress
int numberOfAllAttributeRoles = 0;
Iterator<AttributeRole> aIter = exampleSet.getAttributes().allAttributeRoles();
while (aIter.hasNext()) {
aIter.next();
numberOfAllAttributeRoles++;
}
getProgress().setTotal(numberOfAllAttributeRoles);
// determine new value types
int valueType = Ontology.REAL;
Iterator<AttributeRole> a = exampleSet.getAttributes().allAttributeRoles();
while (a.hasNext()) {
AttributeRole attributeRole = a.next();
if (!attributeRole.isSpecial() || !attributeRole.getSpecialName().equals(Attributes.ID_NAME)) {
if (attributeRole.getAttribute().isNominal()) {
valueType = Ontology.NOMINAL;
break;
}
}
}
// create new attributes
List<Attribute> newAttributes = new ArrayList<Attribute>(exampleSet.size());
Attribute newIdAttribute = AttributeFactory.createAttribute(Attributes.ID_NAME, Ontology.NOMINAL);
newAttributes.add(newIdAttribute);
Attribute oldIdAttribute = exampleSet.getAttributes().getId();
if (oldIdAttribute != null) {
for (Example e : exampleSet) {
double idValue = e.getValue(oldIdAttribute);
String attributeName = "att_" + idValue;
if (oldIdAttribute.isNominal()) {
if (Double.isNaN(idValue)) {
newAttributes.add(AttributeFactory.createAttribute(valueType));
} else {
attributeName = oldIdAttribute.getMapping().mapIndex((int) idValue);
newAttributes.add(AttributeFactory.createAttribute(attributeName, valueType));
}
} else {
newAttributes.add(AttributeFactory.createAttribute(attributeName, valueType));
}
}
} else {
for (int i = 0; i < exampleSet.size(); i++) {
newAttributes.add(AttributeFactory.createAttribute("att_" + (i + 1), valueType));
}
}
// create and fill table
ExampleSetBuilder builder = ExampleSets.from(newAttributes);
a = exampleSet.getAttributes().allAttributeRoles();
while (a.hasNext()) {
AttributeRole attributeRole = a.next();
if (!attributeRole.isSpecial() || !attributeRole.getSpecialName().equals(Attributes.ID_NAME)) {
Attribute attribute = attributeRole.getAttribute();
double[] data = new double[exampleSet.size() + 1];
data[0] = newIdAttribute.getMapping().mapString(attribute.getName());
int counter = 1;
for (Example e : exampleSet) {
double currentValue = e.getValue(attribute);
data[counter] = currentValue;
Attribute newAttribute = newAttributes.get(counter);
if (newAttribute.isNominal()) {
if (!Double.isNaN(currentValue)) {
String currentValueString = currentValue + "";
if (attribute.isNominal()) {
currentValueString = attribute.getMapping().mapIndex((int) currentValue);
}
data[counter] = newAttribute.getMapping().mapString(currentValueString);
}
}
counter++;
}
builder.addRow(data);
}
getProgress().step();
}
// create and deliver example set
getProgress().complete();
ExampleSet result = builder.withRole(newIdAttribute, Attributes.ID_NAME).build();
result.getAnnotations().addAll(exampleSet.getAnnotations());
return result;
}
@Override
public boolean writesIntoExistingData() {
return false;
}
@Override
public ResourceConsumptionEstimator getResourceConsumptionEstimator() {
return OperatorResourceConsumptionHandler.getResourceConsumptionEstimator(getInputPort(), ExampleSetTranspose.class,
null);
}
}