我们首先解释一个非常简单的 biner。bin 是等距间隔的,这样某个属性的整个范围被划分为 n 个区间。在第 k 个区间内具有属性值的数据点被认为属于第 k 个 bin。因此,输出是原始表,并为每个实例(即行)附加了分箱信息。该节点还需要一个对话框,因为用户应该能够确定 bin 的数量,并指定应该对值进行分箱的列。
节点模型:
在我们开始在 execute 方法中实现实际的分箱算法之前,我们必须在 NodeModel 中定义我们需要的字段。(创建后,NodeModel 已经包含可以删除的示例代码)。SettingsModel 提供了一种从 NodeModel 到 NodeDialog 交换设置的便捷方式。正如您稍后将看到的,NodeDialog 还与 SettingsModel 一起使用,这就是为什么我们将它们用于 bin 的数量和值应该被分箱的列的原因:
// bin 数量的设置模型
私有最终 SettingsModelIntegerBounded m_numberOfBins =
新的 SettingsModelIntegerBounded(NumericBinnerNodeModel.CFGKEY_NR_OF_BINS,
NumericBinnerNodeModel.DEFAULT_NR_OF_BINS,
1, Integer.MAX_VALUE);
// 将列存储到 bin 的设置模型
private final SettingsModelString m_column = new SettingsModelString(
NumericBinnerNodeModel.CFGKEY_COLUMN_NAME, "");
为了从对话框中获取设置,必须将它们写入 NodeSettings 对象。NodeSettings 将设置从对话框传输到模型,反之亦然。每个字段都需要一个键来标识和从 NodeSettings 中检索它。定义用作 NodeModel 键的静态最终字符串是一种很好的做法。
/** bin 数量的配置键。*/
public static final String CFGKEY_NR_OF_BINS = "numberOfbins";
/** 所选列的配置键。*/
public static final String CFGKEY_COLUMN_NAME = "columnName";
将设置从 NodeModel 传输到 NodeDialog 是通过实现 validateSettings、loadValidatedSettings 和 saveSettings 方法来实现的。所有这些方法都可以安全地委托给 SettingsModels。在 validateSettings 方法中,会检查这些值是否存在且有效(例如在有效范围内等)。
/**
* @see org.knime.core.node.NodeModel
* #validateSettings(org.knime.core.node.NodeSettingsRO)
*/
@覆盖
protected void validateSettings(最终的 NodeSettingsRO 设置)
抛出 InvalidSettingsException {
// 将此委托给设置模型
m_numberOfBins.validateSettings(settings);
m_column.validateSettings(settings);
}
当 loadValidatedSettings 方法被调用时,设置已经被验证并且可以加载到本地字段中,在这种情况下是 bin 数量和所选列的 SettingsModels。
/**
* @see org.knime.core.node.NodeModel
* #loadValidatedSettingsFrom(org.knime.core.node.NodeSettingsRO)
*/
@覆盖
protected void loadValidatedSettingsFrom(最终 NodeSettingsRO 设置)
抛出 InvalidSettingsException {
// 将设置中的值加载到模型中。
// 可以安全地假设设置由
// 下面的方法。
m_numberOfBins.loadSettingsFrom(settings);
m_column.loadSettingsFrom(settings);
}
在 saveSettings 方法中,本地字段被写入设置,以便对话框显示当前值。
/**
* @see org.knime.core.node.NodeModel
* #saveSettingsTo(org.knime.core.node.NodeSettings)
*/
@覆盖
protected void saveSettingsTo(最终 NodeSettingsWO 设置){
// 将设置保存到配置对象。
m_numberOfBins.saveSettingsTo(settings);
m_column.saveSettingsTo(settings);
}
上述方法只是检查节点是否可以使用当前设置执行的一个步骤。检查它是否适用于传入的数据表也非常重要。这是通过配置方法完成的。一旦连接了输入端口,就会执行配置方法。在我们的数字合并器的小示例中,执行检查以查看是否至少有一个数字列可用,以及传入的数据表是否包含具有所选列名称的列。否则节点不可执行。DataTableSpec 包含所需的信息并传递给 configure 方法。
/**
* @see org.knime.core.node.NodeModel
* #configure(org.knime.core.data.DataTableSpec[])
*/
受保护的 DataTableSpec[] 配置(最终 DataTableSpec[] inSpecs)
抛出 InvalidSettingsException {
// 首先验证传入的数据表规范
boolean hasNumericColumn = false;
boolean containsName = false;
for (int i = 0; i < inSpecs[IN_PORT].getNumColumns(); i++) {
DataColumnSpec columnSpec = inSpecs[IN_PORT].getColumnSpec(i);
// 我们只能使用它,如果它至少包含一个
// 数字列
如果 (columnSpec.getType().isCompatible(DoubleValue.class)) {
// 找到一个数字列
hasNumericColumn = true;
}
// 如果设置了列名,它必须包含在数据中
// 表格规格
如果 (m_column != null
&& columnSpec.getName().equals(m_column.getStringValue())) {
containsName = true;
}
}
如果(!hasNumericColumn){
throw new InvalidSettingsException("输入表必须包含在"
+ "最少一列数字");
}
如果(!包含名称){
throw new InvalidSettingsException("输入表不包含"
+ "column " + m_column.getStringValue() + " 。请(重新)配置 "
+ "节点");
}
// 到目前为止,输入被检查并且算法可以与
// 传入数据
...
正如我们依赖传入的数据规范一样,后继节点也需要有关数据格式的信息,这些信息在执行后提供。出于这个原因,我们节点的输出规范也必须在配置方法中创建。
...
// 现在生成输出表规范,
// 即指定这个节点的输出
DataColumnSpec newColumnSpec = createOutputColumnSpec();
// 和附加部分的 DataTableSpec
DataTableSpec appendSpec = new DataTableSpec(newColumnSpec);
// 因为它只是附加的,所以新的输出规范包含两个:
// 原始规范和附加规范
DataTableSpec outputSpec = new DataTableSpec(inSpecs[IN_PORT],
附加规格);
返回新的 DataTableSpec[]{outputSpec};
...
由于必须在 configure 和 execute 方法中为新添加的列创建 DataColumnSpec,因此在单独的方法中提取用于创建 DataColumnSpec 的代码:
私有数据列规范 createOutputColumnSpec() {
// 我们要添加一个带有 bin 编号的列
DataColumnSpecCreator colSpecCreator = new DataColumnSpecCreator(
"Bin Number", IntCell.TYPE);
// 如果我们知道 bin 的数量,我们也知道可能的数量
// 该新列的值
DataColumnDomainCreator domainCreator = new DataColumnDomainCreator(
new IntCell(0), new IntCell(m_numberOfBins.getIntValue() - 1));
// 并且可以将此域信息添加到输出规范中
colSpecCreator.setDomain(domainCreator.createDomain());
// 现在可以创建列规范
DataColumnSpec newColumnSpec = colSpecCreator.createSpec();
返回新列规范;
}
一旦完成并实施,就可以编写等距分箱的实际算法。对数据进行操作的算法必须放在 execute 方法中。在此示例中,只有一列附加到原始数据。为此,使用了所谓的 ColumnRearranger。它需要一个 CellFactory,它返回给定行的附加单元格。
...
// 实例化细胞工厂
CellFactory cellFactory = new NumericBinnerCellFactory(
createOutputColumnSpec(), splitPoints, colIndex);
// 创建列重新排列器
ColumnRearranger outputTable = new ColumnRearranger(
inData[IN_PORT].getDataTableSpec());
// 添加新列
outputTable.append(cellFactory);
...
创建了 ColumnRearranger 后,它可以与输入表一起传输到 ExecutionContext 以创建一个 BufferedDataTable,该表由 execute 方法返回,即在输出端口提供。每个节点在 BufferedDataTable 中缓冲数据。为了避免相同数据的冗余缓冲,使用了 ColumnRearranger。通过这种方式,只有附加的列在我们的节点中被缓冲。这就是我们必须从 ExecutionContext 中检索 BufferedDataTable 的原因:
...
// 并创建实际的输出表
BufferedDataTable bufferedOutput = exec.createColumnRearrangeTable(
inData[IN_PORT], outputTable, exec);
// 把它返还
返回新的 BufferedDataTable[]{bufferedOutput};
...
为了 CellFactory 的目的,有必要实现一个 NumericBinnerCellFactory。这扩展了 SingleCellFactory 并且只实现了 getCell 方法。检查传递的行以找出哪个 bin 包含来自所选列的值。它将 bin 的编号作为 DataCell 返回。
/**
* @see org.knime.core.data.container.SingleCellFactory#getCell(
* org.knime.core.data.DataRow)
*/
@覆盖
public DataCell getCell(DataRow 行){
DataCell currCell = row.getCell(m_colIndex);
// 检查单元格是否有缺失值
如果(currCell.isMissing()){
返回 DataType.getMissingCell();
}
double currValue = ((DoubleValue)currCell).getDoubleValue();
int binNr = 0;
for (Double intervalBound : m_intervalUpperBounds) {
如果(currValue <= intervalBound){
返回新的 IntCell(binNr);
}
binNr++;
}
返回 DataType.getMissingCell();
}
节点对话框:
创建 NumericBinnerNodeDialog 后,您将看到构造函数已包含一些示例代码。您可以删除它并添加所需控制元素的代码。对于 NumericBinnerNodeDialog,我们需要两个 GUI 元素:一个用于设置 bin 的数量,另一个用于选择 binning 的列。KNIME 框架提供了一个非常方便的设置来将标准对话框元素应用到 NodeDialog。因此,您的 NumericBinnerNodeDialog 默认扩展 DefaultNodeSettingsPane。如果默认对话框组件不适合您的需要,例如,如果某些组件应该根据用户的设置启用或禁用,您可以直接扩展 NodeDialogPane。在我们的例子中,需要添加一个表示 bin 数量的 DialogComponentNumber 和一个 DialogComponentColumnSelection。每个组件’ s 构造函数需要一个 SettingsModel 的新实例。SettingsModel 需要一个字符串标识符,它用于存储和加载组件的值,以及一个默认值,它在加载新值之前一直保持。额外的参数是必要的,这取决于组件的类型。从设置加载和保存到设置是通过构造函数中传递的键自动执行的。我们建议使用 NodeModel 中定义的键。如果你这样做,你必须在此时公开它。从设置加载和保存到设置是通过构造函数中传递的键自动执行的。我们建议使用 NodeModel 中定义的键。如果你这样做,你必须在此时公开它。从设置加载和保存到设置是通过构造函数中传递的键自动执行的。我们建议使用 NodeModel 中定义的键。如果你这样做,你必须在此时公开它。
公共类 NumericBinnerNodeDialog 扩展了 DefaultNodeSettingsPane {
/**
* 用于配置 NumericBinner 节点对话框的新窗格。
* 包含用于调整 bin 数量的控制元素
* 并选择要合并的列。
* 在这里禁止警告:这是不可避免的,因为
* 允许类型作为泛型数组传递。
*/
@SuppressWarnings(“未选中”)
受保护的 NumericBinnerNodeDialog() {
极好的();
// bins 控制元素的数量
addDialogComponent(新的DialogComponentNumber(
新的 SettingsModelIntegerBounded(
NumericBinnerNodeModel.CFGKEY_NR_OF_BINS,
NumericBinnerNodeModel.DEFAULT_NR_OF_BINS,
1, Integer.MAX_VALUE),
"箱数:", /*step*/ 1));
// 列到 bin
添加DialogComponent(新的DialogComponentColumnNameSelection(
新的设置模型字符串(
NumericBinnerNodeModel.CFGKEY_COLUMN_NAME,
"选择一列"),
"选择要装箱的列",
NumericBinnerNodeModel.IN_PORT,
DoubleValue.class));
}
}
创建节点并实现 NodeModel 和 NodeDialog 后,不要忘记编辑 XML 文件中的节点描述(与 NodeFactory 同名)。描述您的节点、对话框设置、输入和输出端口以及稍后的视图。这在第 8 节中有详细解释