Categories

Versions

You are viewing the RapidMiner Developers documentation for version 8.1 - Check here for latest version

Adding Your Own Data Objects

You might find that the standard data objects don't fulfil all your requirements. For example, maybe you are going to analyze data recorded from a game engine. The format of the original data can't directly be expressed as a table. Although you could write a single operator that reads in data from a file and does all the translation and feature extraction, you may decide, that it's best to split the task. Instead, create multiple operators - one that can handle the new data object and one that extracts part of the data as example set. With this modularity, it is much easier to extend the mechanism later on and optimize steps separately.

Defining the object class

First, you must define a new class that holds the information you need. This class implements the interface IOObject, but it is best to extend ResultObjectAdapter instead. The abstract class has already implemented much of the non-special functionality and is suitable for the most cases. In special circumstances, when you already have a class that might hold the data and provide some important functionality, it might be better to extend this class and let it implement the interface. An empty implementation would look like:

import com.rapidminer.operator.ResultObjectAdapter;

public class DemoDataIOObject extends ResultObjectAdapter {

    private static final long serialVersionUID = 1725159059797569345L;

}

The above is only an empty object that doesn't hold any information. Now, add some content:

import com.rapidminer.operator.ResultObjectAdapter;

public class DemoDataIOObject extends ResultObjectAdapter {

    private static final long serialVersionUID = 1725159059797569345L;

    private DemoData data;

    public DemoDataIOObject(DemoData data) {
        this.data = data;
    }

    public DemoData getDemoData() {
        return data;
    }
}

This class gives access to an object of the class DemoData, which is the representative for everything you want to access. While it might be more complex to implement in real-world applications, this example helps to illustrate how things work in general. You want to extract attribute values from the demo data, which an operator can then store in a table. You need a mechanism to add data to the IOObject. (For simplicity, assume you only have numerical attributes to save the effort of remembering the correct types of the data.) Add a map for storing the values with identifier as a local variable:

private Map<String, Double> valueMap = new HashMap<String, Double>();

Then we extend the DemoDataIOObject with two methods for accessing the map:

public void setValue(String identifier, double value) {
    valueMap.put(identifier, value);
}

public Map<String, Double> getValueMap() {
    return valueMap;
}

Configuration

To make your data object accessible, adapt the file ioobjectsNAME.xml (in the resources folder), which contains some examples of how to define your IOObject. This is how the DemoDataIOObject is defined:

<ioobjects>
    <ioobject
    name="DemoData"
    class="com.rapidminer.operator.demo.DemoDataIOObject"
    reportable="false">
        <renderer>com.rapidminer.gui.renderer.DefaultTextRenderer</renderer>
    </ioobject>
</ioobjects>

The renderer is a simple text renderer, which calls the toString() method of your IOObject to display the object in the Results perspective.

Similarly to operators, you can also give your own data objects a color. The connection between two operators that exchange your data object will be colored in the assigned color. To control colors, change the file groupsNAME.properties in the resources folder to add a line defining the color:

# IOObjects
io.com.rapidminer.operator.demo.DemoDataIOObject.color = #28C68C

Processing your own IOObjects

Using these methods, you can now implement your first operator, which extracts values of the DemoData.

import java.util.List;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.operator.OperatorException;
import com.rapidminer.operator.ports.OutputPort;

/**
 * Operator that generates {@link DemoData}
 */
public class GenerateDemoDataOperator extends Operator {

    private OutputPort outputPortDemoData = getOutputPorts().createPort("demo data");

    /**
     * Operator to create {@link DemoData}
     *
     * @param description
     *            of the operator
     */
    public GenerateDemoDataOperator(OperatorDescription description) {
        super(description);
        getTransformer().addGenerationRule(outputPortDemoData, DemoDataIOObject.class);
    }

    @Override
    public void doWork() throws OperatorException {

        DemoDataIOObject ioobject = new DemoDataIOObject(new DemoData());
        List<Double> values = ioobject.getDemoData().getValues();
        for (int i = 0; i < values.size(); i++) {
            ioobject.setValue("" + (i + 1), values.get(i));
        }
        outputPortDemoData.deliver(ioobject);
    }
}

The example fetches the values from the DemoData object and sets the values of the IOObject. Then, the output port delivers the DemoDataIOObject.

Of course, it's possible to build more complex constructions. You might, for example, use one super operator that handles your DemoData with different inner operators. You could build operators that get DemoData as input and transform them to an example set. Every method of treating your own IOObjects is possible by combining what we have learned.

Looking into your IOObject

When building a process for your IOObjects, it's an excellent idea to set breakpoints with the process and take a look at what's contained in the objects. To continue with the example above, it would be interesting to see which values have been extracted. If you set a breakpoint, RapidMiner will display the result of the toString method as the default fallback.

There is plenty of space you could fill with information about the object. How can you do this? The simplest approach is to override the toString method of the IOObject. However, it is better to override the toResultString method, which, by default, only calls the toString method.

Although text output has its advantages, writing Courier characters to the screen is a bit outdated. How do you add nice representations to the output as is done with nearly all core RapidMiner IOObjects?

RapidMiner uses a renderer concept for displaying the various types of IOObjects. So, you should implement a renderer for your DemoDataIOObject.

You must implement the Renderer interface for this purpose. Extend the AbstractRenderer, which has most of the methods already implemented. Most of the methods are used for handling parameters, since renderers might have parameters, just as operators do. They are used during automatic object reporting and control the output. The handling of these parameters and their values is done by the abstract class, you just need to take their values into account when rendering. Here are the methods to implement:

import java.awt.Component;
import com.rapidminer.gui.renderer.AbstractRenderer;
import com.rapidminer.operator.IOContainer;
import com.rapidminer.report.Reportable;

public class DemoDataRenderer extends AbstractRenderer {

    @Override
    public String getName() {
        return "DemoData";
    }

    @Override
    public Component getVisualizationComponent(Object renderable, IOContainer ioContainer) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Reportable createReportable(Object renderable, IOContainer ioContainer, int desiredWidth, int desiredHeight) {
        // TODO Auto-generated method stub
        return null;
    }
}

The first method must return an object of a class implementing one of the sub interfaces of Reportable, but this should not be handled here. For an example, look at the interfaces and some of the implementations in the core.

The second method returns an arbitrary Java component used for displaying content in Swing. While there is great flexibility, because you want to see the values as a table, render it as such. You don't have to implement everything yourself, though. You can use a subclass of the AbstractRenderer - the AbstractTableModelTableRenderer. As the name indicates, it shows a table based upon a table model. All you need to do is to return this table model:

import java.util.ArrayList;
import java.util.List;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import com.rapidminer.gui.renderer.AbstractTableModelTableRenderer;
import com.rapidminer.operator.IOContainer;
import com.rapidminer.operator.demo.DemoDataIOObject;
import com.rapidminer.tools.container.Pair;

public class NewDemoDataRenderer extends AbstractTableModelTableRenderer {

    @Override
    public String getName() {
        return "DemoData";
    }

    @Override
    public TableModel getTableModel(Object renderable, IOContainer ioContainer, boolean isReporting) {
        if (renderable instanceof DemoDataIOObject) {
            DemoDataIOObject object = (DemoDataIOObject) renderable;
            final List<Pair<String, Double>> values = new ArrayList<>();
            for (String key : object.getValueMap().keySet()) {
                values.add(new Pair<String, Double>(key, object.getValueMap().get(key)));
            }

            return new AbstractTableModel() {

                private static final long serialVersionUID = 1L;

                @Override
                public int getColumnCount() {
                    return 2;
                }

                @Override
                public String getColumnName(int column) {
                    if (column == 0) {
                        return "Id";
                    }
                    return "Value";
                }

                @Override
                public int getRowCount() {
                    return values.size();
                }

                @Override
                public Object getValueAt(int rowIndex, int columnIndex) {
                    Pair<String, Double> pair = values.get(rowIndex);
                    if (columnIndex == 0) {
                        return pair.getFirst();
                    }
                    return pair.getSecond();
                }
            };
        }
        return new DefaultTableModel();
    }
}

There are other convenience methods in the AbstractTableModelTableRenderer for changing the appearance of the table. For example, the following methods change the behavior of the table by enabling or disabling certain features:

@Override
public boolean isSortable() {
    return true;
}

@Override
public boolean isAutoresize() {
    return false;
}

@Override
public boolean isColumnMovable() {
    return true;
}

To use the new renderer, you must change the file ioobjectsNAME.xml in the resources folder again. Just add the renderer before the default renderer:

<ioobjects>
    <ioobject
    name="DemoData"
    class="com.rapidminer.operator.demo.DemoDataIOObject"
    reportable="false">
            <renderer>com.rapidminer.gui.renderer.demo.DemoDataRenderer</renderer>
            <renderer>com.rapidminer.gui.renderer.DefaultTextRenderer</renderer>
    </ioobject>
</ioobjects>

You can now see the result of your efforts in building a table representation of the DemoData values.