Source code for node_hjoin_tables

# Copyright (c) 2013, System Engineering Software Society
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of the System Engineering Software Society nor the
#       names of its contributors may be used to endorse or promote products
#       derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.
# IN NO EVENT SHALL SYSTEM ENGINEERING SOFTWARE SOCIETY BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
The operation of horizontal join, or HJoin, stacks the columns in the incoming
:ref:`Tables` horizontally beside each other. The outgoing Table will have all
the columns from all the incoming Tables. Note that all Tables that should be
hjoined must have the same number of rows.

If a column name exists in both inputs the latter Table (or lower port) will
take precedence and the corresponding column from the former Table (or upper
port) will be lost.

The node always tries to give the output table a name, so if the chosen
port has a table without name, the other port will be used. This is
to preserve backwards compatibility.
"""
from __future__ import (print_function, division, unicode_literals,
                        absolute_import)
from sympathy.api import node as synode
from sympathy.api.nodeconfig import Port, Ports, Tag, Tags
from sympathy.api.exceptions import SyDataError


def merge_attributes(tables):
    attributes = {}
    for table in tables:
        attributes.update(table.get_table_attributes())
    return attributes


def _join_table(table1, table2, first_name):
    if ((table1.number_of_rows() != table2.number_of_rows()) and
            table1.number_of_columns() and table2.number_of_columns()):
        raise SyDataError(
            'Number of rows mismatch in tables ({} vs {})'.format(
                table1.number_of_rows(), table2.number_of_rows()))
    attributes = merge_attributes([table1, table2])
    name1 = table1.get_name()
    name2 = table2.get_name()
    if name1 is None:
        name = name2
    elif name2 is None:
        name = name1
    else:
        name = name1 if first_name else name2
    table1.hjoin(table2)
    table1.set_table_attributes(attributes)
    table1.set_name(name)
    return table1


class HJoinTableSuper(synode.Node):
    author = "Alexander Busck <alexander.busck@sysess.org>"
    copyright = "(C) 2013 System Engineering Software Society"
    version = '1.1'
    icon = 'hjoin_table.svg'
    tags = Tags(Tag.DataProcessing.TransformStructure)

    parameters = {}
    parameter_root = synode.parameters(parameters)
    parameter_root.set_list(
        'name',
        plist=['Lower', 'Upper'],
        label='Input port name for joined table',
        description='Select which port decides the output table(s) names',
        value=[0],
        editor=synode.Util.combo_editor().value())


[docs]class HJoinTable(HJoinTableSuper): """ Horizontal join of two Tables into a single Table. :Opposite node: :ref:`HSplit Table` """ name = 'HJoin Table' description = 'Horizontal join of two Tables' nodeid = 'org.sysess.sympathy.data.table.hjointable' inputs = Ports([ Port.Table('Input Table 1', name='port1'), Port.Table('Input Table 2', name='port2')]) outputs = Ports([Port.Table( 'Table with horizontally joined data', name='port1')]) def execute(self, node_context): """Execute""" node_context.output['port1'].source(_join_table( node_context.input['port1'], node_context.input['port2'], node_context.parameters['name'].value[0]))
[docs]class HJoinTables(HJoinTableSuper): """ Pairwise horizontal join of two lists of Tables into a single list of Tables. I.e. The first Table on the upper port is hjoined with the first Table on the lower port and so on. :Opposite node: :ref:`HSplit Tables` """ name = 'HJoin Tables pairwise' description = 'Pairwise horizontal join of two list of Tables.' nodeid = 'org.sysess.sympathy.data.table.hjointables' inputs = Ports([ Port.Tables('Input Tables 1', name='port1'), Port.Tables('Input Tables 2', name='port2')]) outputs = Ports([ Port.Tables( 'List of Tables with pairwise horizontally joined data from the ' 'incoming lists of Tables.', name='port1')]) def execute(self, node_context): """Execute""" for table1, table2 in zip( node_context.input['port1'], node_context.input['port2']): node_context.output['port1'].append(_join_table( table1, table2, node_context.parameters['name'].value[0]))
[docs]class HJoinTablesSingle(synode.Node): """ Horizontal join of all incoming Tables into a single outgoing Table. Columns from Tables later in the list will take precedence in the case when a certain column name exists in two or more Tables. :Opposite node: :ref:`HSplit Table` """ author = "Greger Cronquist <greger.cronquist@sysess.org>" copyright = "(C) 2013 System Engineering Software Society" version = '1.0' icon = 'hjoin_table.svg' name = 'HJoin Tables' description = 'HJoin Tables to Table' nodeid = 'org.sysess.sympathy.data.table.hjointablessingle' tags = Tags(Tag.DataProcessing.TransformStructure) inputs = Ports([Port.Tables('Input Tables', name='port1')]) outputs = Ports([ Port.Table('Table with horizontally joined data from the incoming ' 'list of Tables.', name='port1')]) def execute(self, node_context): """Execute""" in_files = node_context.input['port1'] out_tablefile = node_context.output['port1'] for item in in_files: out_tablefile.hjoin(item) out_tablefile.set_table_attributes(merge_attributes(in_files)) out_tablefile.set_name(in_files[-1].get_name())