Source code for node_index_table

# This file is part of Sympathy for Data.
# Copyright (c) 2018, Combine Control Systems AB
#
# SYMPATHY FOR DATA COMMERCIAL LICENSE
# You should have received a link to the License with Sympathy for Data.
import numpy as np

from sympathy.api import node
from sympathy.api.exceptions import SyDataError, NoDataError
from sympathy.api.nodeconfig import Port, Ports, Tag, Tags, adjust
from sympathy.api import node_helper
import sylib.index


def _get_single_col_editor():
    return node.editors.combo_editor('', filter=True, edit=True)


[docs] class IndexTable(node.Node): name = 'Index table' description = ( 'Uses index-table to perform row indexation from value-table.\n' 'No other datatypes than integer or boolean are allowed in the index ' 'column') author = 'Mathias Broxvall' icon = 'lookup.svg' nodeid = 'org.sysess.sympathy.data.table.indextable' tags = Tags(Tag.DataProcessing.TransformStructure) inputs = Ports([ Port.Table('Value table', name='value'), Port.Table('Index table', name='index') ]) outputs = Ports([ Port.Table('Result table', name='out') ]) editor = _get_single_col_editor() parameters = node.parameters() parameters.set_list( 'index column', label='Select indexing column', description='Select column used for indexing.', value=[], editor=editor) parameters.set_boolean( 'at_one', label='Start at one', description='Start indexing at one, otherwise at zero', value=False) parameters.set_list( 'operation', label='Operation', list=['Include', 'Exclude'], value=[0], description='If to include or exclude rows', editor=node.editors.combo_editor()) def update_parameters(self, old_params): param = 'index column' if param in old_params: old_params[param].editor = _get_single_col_editor() def execute(self, node_context): index_tbl = node_context.input['index'] value_tbl = node_context.input['value'] out_tbl = node_context.output['out'] at_one = node_context.parameters['at_one'].value exclude = node_context.parameters['operation'].selected == 'Exclude' index_param = node_context.parameters['index column'] index_col = index_param.selected if not index_col: column_names = index_tbl.column_names() if column_names: index_col = column_names[0] indices = index_tbl._require_column(index_param, name=index_col) if indices.dtype.kind == 'i': try: fail = at_one and np.min(np.ma.compressed(indices)) < 1 except ValueError: fail = False if fail: raise SyDataError('Start at one requires integer column ' 'with values >= 1.') indices = indices - at_one elif at_one: raise SyDataError('Invalid datatype {} in index column. ' 'Start at one requires integer column.' .format(indices.dtype)) if indices.dtype.kind not in ['b', 'i']: raise SyDataError('Invalid datatype {} in index column' .format(indices.dtype)) for col in value_tbl.cols(): if exclude: mask = np.ones(col.data.shape[0], dtype=bool) mask[indices] = False out_tbl.set_column_from_array(col.name, col.data[mask]) else: out_tbl.set_column_from_array(col.name, col.data[indices]) def adjust_parameters(self, node_context): adjust(node_context.parameters['index column'], node_context.input['index'])
[docs] class CreateIndexTable(node.Node): """ Create an index column for table data. The name of the resulting index column depends on the *name* parameter and its values are computed depending on the *method* used and the *columns* selected. """ name = 'Create Index Table' description = 'Create an index column for table data' icon = 'lookup.svg' nodeid = 'org.sysess.sympathy.data.table.createindextable' tags = Tags(Tag.DataProcessing.Index) inputs = Ports([ Port.Table('Input table', name='input'), ]) outputs = Ports([ Port.Table('Output table', name='output') ]) _enumerate_rows, _enumerate_unique = _options = [ 'Enumerate rows', 'Enumerate unique rows'] parameters = node.parameters() parameters.set_string( 'method', label='Select index creation method', description='Select method used for index creation.', value=_enumerate_rows, editor=node.editors.combo_editor(options=_options)) parameters.set_list( 'columns', label='Select columns', description='Select columns used for building the index.', value=[], editor=node.editors.multilist_editor(edit=True)) parameters.set_string( 'name', label='Name of index column', description='Select name for index column.', value='index') controllers = node.controller( when=node.field('method', 'value', value=_enumerate_rows), action=node.field('columns', 'disabled')) def execute(self, node_context): input_ = node_context.input['input'] output = node_context.output['output'] output.source(input_) method = node_context.parameters['method'].value name = node_context.parameters['name'].value method = node_context.parameters['method'].value if method == self._enumerate_rows: output[name] = np.arange(input_.number_of_rows()) elif method == self._enumerate_unique: selected_names = node_context.parameters['columns'].selected_names( input_.column_names()) unique = {} indices = [] count = 0 for values in zip(*[input_[col].tolist() for col in selected_names]): values = tuple(values) i = unique.get(values) if i is not None: indices.append(i) else: unique[values] = count indices.append(count) count += 1 output[name] = np.array(indices) def adjust_parameters(self, node_context): adjust(node_context.parameters['columns'], node_context.input['input'])
def _negative_lookup(length): return {-length + i: i for i in range(length)} def _str_list(items): return [str(i) for i in items] def _non_negative_index(index, length): index = index.from_dict(index.to_dict()) lookup = {str(k): str(v) for k, v in _negative_lookup(length).items()} positive_names = [lookup.get(v, v) for v in index.value_names] unique_names = list(dict.fromkeys(positive_names)) index.value_names = unique_names return index
[docs] class IndexColumnsTable(node.Node): __doc__ = f""" Index columns in Table. {sylib.index.index_doc} """ author = "Erik der Hagopian" name = 'Index columns in Table' nodeid = 'org.sysess.sympathy.index.columns.table' icon = 'lookup.svg' tags = Tags(Tag.DataProcessing.TransformStructure) related = ['org.sysess.sympathy.index.rows.table'] inputs = Ports([Port.Table('Input Table', name='data')]) outputs = Ports([Port.Table('Output Table', name='data')]) parameters = node.parameters() sylib.index.set_parameters(parameters) def _column_indices(self, table): return range(table.number_of_columns()) def adjust_parameters(self, ctx): indices = [] try: indices = _str_list(self._column_indices(ctx.input['data'])) except NoDataError: pass ctx.parameters['index'].adjust(indices) def execute(self, ctx): input_data = ctx.input['data'] output_data = ctx.output['data'] index = ctx.parameters['index'] lookup = {str(i): name for i, name in enumerate(input_data.column_names())} names = list(lookup.keys()) index = _non_negative_index(index, len(names)) selected = index.selected_names(names) output_data.set_name(input_data.get_name()) output_data.set_table_attributes(input_data.get_table_attributes()) for i in selected: output_data.update_column(lookup[i], input_data)
[docs] @node_helper.list_node_decorator(['data'], ['data']) class IndexColumnsTables(IndexColumnsTable): name = 'Index columns in Tables' nodeid = 'org.sysess.sympathy.index.columns.tables' def _column_indices(self, tables): rows = [t.number_of_rows() for t in tables] return range(max(rows, default=0))
[docs] class IndexRowsTable(node.Node): __doc__ = f""" Index rows in Table. {sylib.index.index_doc} Because the number of rows can become very large, only selected indices are shown. To add, right-click and press Insert. Then rename the entry to the desired integer index and check it. """ author = "Erik der Hagopian" name = 'Index rows in Table' nodeid = 'org.sysess.sympathy.index.rows.table' icon = 'lookup.svg' tags = Tags(Tag.DataProcessing.TransformStructure) related = ['org.sysess.sympathy.index.columns.table'] inputs = Ports([Port.Table('Input Table', name='data')]) outputs = Ports([Port.Table('Output Table', name='data')]) parameters = node.parameters() sylib.index.set_parameters( parameters, description=( f'{sylib.index.index_description}\n' 'Because the number of rows can become ' 'too large, only selected indices are shown. To add, right-click ' 'and press Insert.\nThe values must be edited to integer values')) def _row_indices(self, table): return _str_list(range(table.number_of_rows())) def execute(self, ctx): input_data = ctx.input['data'] output_data = ctx.output['data'] index = ctx.parameters['index'] names = self._row_indices(input_data) index = _non_negative_index(index, len(names)) indices = [int(i) for i in index.selected_names(names)] output_data.update(input_data[np.array(indices)]) output_data.set_name(input_data.get_name()) output_data.set_table_attributes(input_data.get_table_attributes())
[docs] @node_helper.list_node_decorator(['data'], ['data']) class IndexRowsTables(IndexRowsTable): name = 'Index rows in Tables' nodeid = 'org.sysess.sympathy.index.rows.tables'