Repository: Craigacp/FEAST (language: python)

Issue:
Improve portability
Link MIToolbox library from a sibling directory instead of previously installing it to the system.


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 890c624..9694ed0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,7 @@
 
 # IDE files
 .idea
+
+*.pyo
+*.pyc
+__pycache__/
diff --git a/Makefile b/Makefile
index fdc6181..fcc9f7e 100644
--- a/Makefile
+++ b/Makefile
@@ -45,7 +45,7 @@ JNI_INCLUDES = -I/usr/lib/jvm/java-8-openjdk-amd64/include/ -I/usr/lib/jvm/java-
 JAVA_INCLUDES = -Ijava/native/include
 objects = build/BetaGamma.o build/CMIM.o build/CondMI.o build/DISR.o build/ICAP.o build/JMI.o build/MIM.o build/mRMR_D.o build/WeightedCMIM.o build/WeightedCondMI.o build/WeightedDISR.o build/WeightedJMI.o build/WeightedMIM.o
 
-libFSToolbox.so : libMIToolbox.so $(objects)
+libFSToolbox.so : $(objects)
 	$(LINKER) $(CFLAGS) -shared -o libFSToolbox.so $(objects) -lm -lMIToolbox
 
 libFSToolbox.dll : libMIToolbox.dll $(objects)
diff --git a/python/README.md b/python/README.md
new file mode 100644
index 0000000..b5df9dc
--- /dev/null
+++ b/python/README.md
@@ -0,0 +1,55 @@
+# FEAST for Python
+
+The information provided here is only relavant to PyFeast. PyFeast is the python extension to the [FEAST](https://github.com/Craigacp/FEAST) library.
+
+# Installation
+
+The user may opt to install FEAST and MIToolbox on their own, but a build script is supplied to automatically build the information as well. Please see instructions for installing FEAST and MIToolbox [here](https://github.com/Craigacp/FEAST). 
+
+After the installation is complete for FEAST and MIToolbox, the user can run the installation (locally) for PyFeast. Navigate to the the python folder of the [FEAST](https://github.com/Craigacp/FEAST) library and execute `pip3 install .`
+
+A _requirements.txt_ file is supplied in the event that a user would like to install all requirements before installing the package. 
+
+**Note**: _MIToolbox should be installed prior to FEAST_.
+**Note**: _The [build.sh](./build.sh) script will only work on linux systems_.
+
+# Example
+
+The following example will illustrate the (simple) usage of the library.
+
+```python
+from feast.feast import CMIM
+import pandas as pd
+import numpy as np
+from sklearn.datasets import load_boston
+
+
+boston = load_boston()
+
+# Read the dataset and set the appropriate columns
+data_set = pd.DataFrame(data=boston.data, columns=boston.feature_names).to_numpy()
+
+# labels is the regression target for the dataset
+labels = pd.DataFrame(data=boston.target, columns=['Labels']).to_numpy()
+```
+
+In the code snippet above, the data set is loaded from a [known set](https://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_boston.html).
+The data set is trimmed from the raw input and the labels are set to the regression targets for the data set. 
+
+**Note**: _Upon investigation the size of data set is X by Y and the size of labels is X by 1_.
+
+
+```python
+selected_features, feature_scores = CMIM(data_set, labels, 2)
+````
+
+Continuing from the imports/provisioning example, the snippet above will execute the [CMIM](https://github.com/Craigacp/FEAST/blob/master/src/CMIM.c) source code
+and expects `2` features to be returned. The indexes of the returned features and the scores of those features are provided by **all** functions in the 
+python extension.
+
+```python
+feature_names = dataset.columns.tolist()
+selected_features = [feature_names[idx] for idx in fn_cols]
+```
+
+If the user wants to determine what those features amount to, they can execute a similar set of lines to those in the snippet above.
diff --git a/python/build.sh b/python/build.sh
new file mode 100755
index 0000000..a63cd6d
--- /dev/null
+++ b/python/build.sh
@@ -0,0 +1,78 @@
+# Script to build all of the dependencies from scratch in the event that
+# nothing has been previously established.
+
+me=$(whoami)
+
+echo "[INFO] Running script as ${me}"
+
+original_dir=$(pwd)
+
+DEP_DIR="../MIToolbox"
+
+if [ -d "$DEP_DIR" ]; then
+    if [ "$(ls -A $DEP_DIR)" ]; then
+	echo "[DEBUG] Submodule already initialized";
+    else
+	cd ..;
+	git submodule init;
+	git submodule update;
+	cd -;
+    fi
+fi
+
+# build the dependencies first
+cd $DEP_DIR;
+make clean && make;
+
+if [ "$me" = "root" ] ; then
+    echo "[INFO] Running make install as root";
+    make install;
+else
+    sudo make install;
+fi
+
+# Move to the home directory
+cd ..;
+make clean && make;
+
+if [ "$me" = "root" ] ; then
+    make install;
+else
+    echo "[INFO] Running make install as root";
+    sudo make install;
+fi
+
+if [[ -z "${LD_LIBRARY_PATH}" ]] ; then
+    # These were more than likely added to /usr/local/lib
+    echo "export LD_LIBRARY_PATH=/lib:/usr/lib:/usr/local/lib" >> $HOME/.bashrc
+else
+    if [[ ":$LD_LIBRARY_PATH:" == *":/lib:"* ]] ; then
+	echo "[DEBUG] /lib is already part of LD_LIBRARY_PATH"
+    else
+	echo "export LD_LIBRARY_PATH=/lib:$LD_LIBRARY_PATH" >> $HOME/.bashrc
+    fi
+
+    if [[ ":$LD_LIBRARY_PATH:" == *":/usr/lib:"* ]] ; then
+	echo "[DEBUG] /usr/lib is already part of LD_LIBRARY_PATH"
+    else
+	echo "export LD_LIBRARY_PATH=/usr/lib:$LD_LIBRARY_PATH" >> $HOME/.bashrc
+    fi
+
+    if [[ ":$LD_LIBRARY_PATH:" == *":/usr/local/lib:"* ]] ; then
+	echo "[DEBUG] /usr/local/lib is already part of LD_LIBRARY_PATH"
+    else
+	echo "export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH" >> $HOME/.bashrc
+    fi
+fi
+
+source $HOME/.bashrc
+
+# build the python data !
+cd $original_dir;
+
+if [ "$me" = "root" ] ; then
+    python3 -m pip install . --upgrade;
+else
+    echo "[INFO] Running python install as root";
+    sudo python3 -m pip install . --upgrade;
+fi
diff --git a/python/feast/CMIM.py b/python/feast/CMIM.py
deleted file mode 100644
index a46498b..0000000
--- a/python/feast/CMIM.py
+++ /dev/null
@@ -1,113 +0,0 @@
-#######################################################################
-##  Contributed by Evgeni Dubov from Intel Advanced Analitics group  ##
-#######################################################################
-
-import os
-import sys
-import pandas as pd
-import numpy as np
-from numpy.ctypeslib import ndpointer
-import ctypes as c
-
-
-class CMIM():
-    """ 
-    Python wrapper for CMIM feature selection
-
-    Parameters
-    ----------
-    num_of_features : integer, default = 20
-        the amount of features to choose.
-
-    Attributes
-    ----------
-    feature_names : list of strings
-        Names of the selected features.
-        * will be available only in case data for fit is pandas DataFrame
-    feature_scores : list of floats
-        Scores of selected feature. The score is calculated by min{I(Xn ; Y | Xk)}
-    feature_indexes : list of floats
-        Indexes of selected feature.
-    """
-
-    def __init__(self, num_of_features=20):
-        if sys.platform.startswith('linux'):
-            # in case of Linux OS load feast linux library (.so file)
-            self.libFSToolbox = c.CDLL('{}/{}/{}'.format(os.path.dirname(__file__), 'lib', 'libFSToolbox.so'))
-        elif sys.platform.startswith('win'):
-            # in case of Windows OS load feast windows library (.dll file)
-            self.libFSToolbox = c.WinDLL('{}/{}/{}'.format(os.path.dirname(__file__), 'lib', 'libFSToolbox.dll'))
-        else:
-            # OSs other than Linux and Windows are not supported
-            raise NotImplementedError('FeastCMIM is not supported on {} operating system'.format(sys.platform))
-
-        self.num_of_features = num_of_features
-        self.feature_indexes = None
-        self.feature_scores = None
-        self.feature_names = None
-
-    def fit(self, data, labels):
-        """
-        Fits a defined CMIM filter to a given data set.
-        data and labels are expected to be discretized
-
-        Parameters
-        ----------
-        data : pandas or numpy data object
-            The training input samples.
-        labels : pandas or numpy data object. labels should contain a binary label.
-            The label of the training samples.
-        """
-
-        # in case the data is a pandas dataframe we can store features by name
-        data_labels = None
-        if isinstance(data, pd.DataFrame):
-            data_labels = data.columns.tolist()
-
-        # python variables adaptation for C parameters initialization
-        data = np.array(data, dtype=np.uint32, order="F")
-        labels = np.array(labels, dtype=np.uint32)
-        n_samples, n_features = data.shape
-        output = np.zeros(self.num_of_features).astype(np.uint)
-        selected_features_score = np.zeros(self.num_of_features)
-
-        # cast as C types
-        _uintpp = ndpointer(dtype=np.uintp, ndim=1, flags='F')
-        c_k = c.c_uint(self.num_of_features)
-        c_no_of_samples = c.c_uint(n_samples)
-        c_no_of_features = c.c_uint(n_features)
-        c_feature_matrix = (data.__array_interface__['data'][0] + np.arange(data.shape[1]) * (data.strides[1])).astype(
-            np.uintp)
-        c_class_column = labels.ctypes.data_as(c.POINTER(c.c_uint))
-        c_output_features = output.ctypes.data_as(c.POINTER(c.c_uint))
-        c_feature_scores = selected_features_score.ctypes.data_as(c.POINTER(c.c_double))
-
-        self.libFSToolbox.CMIM.argtypes = [c.c_uint, c.c_uint, c.c_uint, _uintpp, c.POINTER(c.c_uint),
-                                           c.POINTER(c.c_uint), c.POINTER(c.c_double)]
-        self.libFSToolbox.CMIM.restype = c.POINTER(c.c_uint)
-
-        # call the C lib implementation
-        c_selected_features = self.libFSToolbox.CMIM(
-            c_k, c_no_of_samples, c_no_of_features, c_feature_matrix,
-            c_class_column, c_output_features, c_feature_scores)
-
-        # result transition from C to Python
-        features_iterator = np.fromiter(c_selected_features, dtype=np.uint, count=self.num_of_features)
-        selected_features = []
-        for c_selected_feature_index in features_iterator:
-            selected_features.append(c_selected_feature_index)
-
-        # store the selection results
-        self.feature_scores = [c_feature_scores[idx] for idx in range(self.num_of_features)]
-        self.feature_indexes = selected_features
-
-        if data_labels is not None:
-            self.feature_names = [data_labels[idx] for idx in self.feature_indexes]
-
-    def transform(self, X):
-        if isinstance(X, np.ndarray):
-            return(X[:, self.feature_indexes])
-        elif isinstance(X, pd.DataFrame):
-            return(X.loc[:, self.feature_names])
-        else:
-            raise AttributeError('Transform accepts data of type numpy.ndarray or pandas.DataFrame')
diff --git a/python/feast/feast.py b/python/feast/feast.py
new file mode 100644
index 0000000..ea38a8a
--- /dev/null
+++ b/python/feast/feast.py
@@ -0,0 +1,616 @@
+'''
+Copyright (c) 2010-2017, A. Pocock, G. Brown, The University of Manchester
+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 University of Manchester 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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.
+'''
+from ctypes import cdll, POINTER, c_uint, c_double
+import inspect
+from numpy import zeros
+import numpy as np
+from numpy.ctypeslib import ndpointer
+from logging import getLogger
+
+
+log = getLogger()
+
+# load the library
+fstoolbox_lib = cdll.LoadLibrary("libFSToolbox.so")
+
+
+def _load(config):
+    # Set the expected argtypes and return types for library functions
+    for func_name in config.func_names:
+        f = getattr(fstoolbox_lib, func_name, None)
+        if f is not None:
+            f.argtypes = config.argtypes
+            f.restype = config.restype
+        else:
+            print("Failed to set types for %s", func_name)
+
+class Config:
+    def __init__(self, func_names, argtypes, restype):
+        self.func_names = func_names
+        self.argtypes = argtypes
+        self.restype = restype
+
+# Declare useful return types that are a bit special (**)
+_uintpp = ndpointer(dtype=np.uintp, ndim=1, flags='F')
+_doublepp = ndpointer(dtype=np.uintp, ndim=1, flags='C')
+
+# Create the configurations to load
+normLibConfig = Config(
+    ["CMIM", "CondMI", "DISR", "ICAP", "JMI", "MIM", "mRMR_D"],
+    [c_uint, c_uint, c_uint, _uintpp, POINTER(c_uint), POINTER(c_uint), POINTER(c_double)],
+    POINTER(c_uint)
+)
+discLibConfig = Config(
+    ["discCMIM", "discCondMI", "discDISR", "discICAP", "discJMI", "discMIM", "disc_mRMR_D"],
+    [c_uint, c_uint, c_uint, _doublepp, POINTER(c_double), POINTER(c_double), POINTER(c_double)],
+    POINTER(c_double)
+)
+weightedLibConfig = Config(
+    ["weightedCMIM", "weightedCondMI", "weightedDISR", "weightedJMI", "weightedMIM"],
+    [c_uint, c_uint, c_uint, _uintpp, POINTER(c_uint), POINTER(c_double), POINTER(c_uint), POINTER(c_double)],
+    POINTER(c_uint)
+)
+discWeightedLibConfig = Config(
+    ["discWeightedCMIM", "discWeightedCondMI", "discWeightedDISR", "discWeightedJMI", "discWeightedMIM"],
+    [c_uint, c_uint, c_uint, _doublepp, POINTER(c_double), POINTER(c_double), POINTER(c_double), POINTER(c_double)],
+    POINTER(c_double)
+)
+betaGammaConfig = Config(
+    ["BetaGamma"],
+    [c_uint, c_uint, c_uint, _uintpp, POINTER(c_uint), POINTER(c_uint), POINTER(c_double), c_double, c_double],
+    POINTER(c_uint)
+)
+discBetaGammaConfig = Config(
+    ["discBetaGamma"],
+    [c_uint, c_uint, c_uint, _doublepp, POINTER(c_double), POINTER(c_double), POINTER(c_double), c_double, c_double],
+    POINTER(c_double)
+)
+
+# list and load all configs
+configs = [
+    normLibConfig, 
+    discLibConfig, 
+    weightedLibConfig,
+    discWeightedLibConfig, 
+    betaGammaConfig, 
+    discBetaGammaConfig
+]
+for config in configs:
+    _load(config)
+
+
+
+def _FeatureSelectionCommon(data_set, labels, num_features_to_select, *args, **kwargs):
+    '''Base function for FEAST algorithms. Convert the parameters from the types listed below
+    into the types that can be sent to the C/C++ library. 
+        
+    :param data_set: ND Array where len() == number of observations and len(transpose()) ==
+    number of features
+    :param labels: ND Array where len() == number of observations. The length of labels
+    sould be equal to the length of the `data_set`
+    :param num_features_to_select: Number of features to select. The value must be less than
+    or equal to the length of the `data_set`
+    
+    -- Supported ARGS --
+    
+    :param beta: Penalty attached to I(X_j;X_k); Value between 0.0 and 1.0 
+    :param gamma: Positive weight attached to I(X_k;X_j|Y); Value between 0.0 and 1.0 
+    :return: `num_features_to_select` Features in order that they were selected
+    '''
+    # grab the function name as it matches the one from the C-library
+    internal_function = getattr(fstoolbox_lib, inspect.stack()[1][3], None)
+    
+    if internal_function is None:
+        raise RuntimeError("Failed to find %s in fstoolbox", str(inspect.stack()[1][3]))
+    else:
+        log.info("Running %s", str(inspect.stack()[1][3]))
+
+    _data_set = np.array(data_set, dtype=np.uint32, order="F")
+    _labels = np.array(labels, dtype=np.uint32)
+    
+    num_observations, num_features = _data_set.shape
+    output = zeros(num_features_to_select).astype(np.uint)
+    selected_features_scores = zeros(num_features)
+
+    # convert/create the types to C-Types
+    c_num_features_to_select = c_uint(num_features_to_select)
+    c_num_observations = c_uint(num_observations)
+    c_num_features = c_uint(num_features)    
+    c_output_features = output.ctypes.data_as(POINTER(c_uint))
+    
+    # filled in feature scores through the library
+    selected_features_scores = zeros(num_features)
+    c_selected_feature_types = selected_features_scores.ctypes.data_as(POINTER(c_double))
+    
+    # convert the parameters to the C-Type variables expected
+    c_labels = _labels.ctypes.data_as(POINTER(c_uint))
+    c_feature_matrix = (
+        _data_set.__array_interface__['data'][0] +\
+            np.arange(_data_set.shape[1]) * (_data_set.strides[1])).astype(np.uintp)
+    
+    if len(args) > 0:
+        features = internal_function(
+            c_num_features_to_select,
+            c_num_observations,
+            c_num_features, 
+            c_feature_matrix,
+            c_labels,
+            c_output_features,
+            c_selected_feature_types,
+            *args
+        )
+    else:    
+        features = internal_function(
+            c_num_features_to_select,
+            c_num_observations,
+            c_num_features, 
+            c_feature_matrix,
+            c_labels,
+            c_output_features,
+            c_selected_feature_types
+        )
+
+    # result transition from C to Python
+    features_iterator = np.fromiter(features, dtype=np.uint, count=num_features_to_select)
+    selected_features = []
+    for c_selected_feature_index in features_iterator:
+        selected_features.append(c_selected_feature_index)
+
+    # store the selection results
+    feature_scores = [c_selected_feature_types[idx] for idx in range(num_features_to_select)]
+
+    return selected_features, feature_scores
+
+
+def _discFeatureSelectionCommon(data_set, labels, num_features_to_select, *args, **kwargs):
+    '''Base function for FEAST discretized algorithms. Convert the parameters from the types 
+    listed below into the types that can be sent to the C/C++ library. 
+        
+    :param data_set: ND Array where len() == number of observations and len(transpose()) ==
+    number of features
+    :param labels: ND Array where len() == number of observations. The length of labels
+    sould be equal to the length of the `data_set`
+    :param num_features_to_select: Number of features to select. The value must be less than
+    or equal to the length of the `data_set`
+    
+    -- Supported ARGS --
+    
+    :param beta: Penalty attached to I(X_j;X_k); Value between 0.0 and 1.0 
+    :param gamma: Positive weight attached to I(X_k;X_j|Y); Value between 0.0 and 1.0 
+    :return: `num_features_to_select` Features in order that they were selected
+    '''
+    # grab the function name as it matches the one from the C-library
+    internal_function = getattr(fstoolbox_lib, inspect.stack()[1][3], None)
+    
+    if internal_function is None:
+        raise RuntimeError("Failed to find %s in fstoolbox", str(inspect.stack()[1][3]))
+    else:
+        log.info("Running %s", str(inspect.stack()[1][3]))
+        
+    _data_set = np.array(data_set, dtype=np.uint32, order="C")
+    _labels = np.array(labels, dtype=np.uint32)
+    
+    num_observations, num_features = _data_set.shape
+    output = zeros(num_features_to_select).astype(np.uint)
+    selected_features_scores = zeros(num_features)
+
+    # convert/create the types to C-Types
+    c_num_features_to_select = c_uint(num_features_to_select)
+    c_num_observations = c_uint(num_observations)
+    c_num_features = c_uint(num_features)    
+    c_output_features = output.ctypes.data_as(POINTER(c_double))
+    
+    # filled in feature scores through the library
+    selected_features_scores = zeros(num_features)
+    c_selected_feature_types = selected_features_scores.ctypes.data_as(POINTER(c_double))
+    
+    # convert the parameters to the C-Type variables expected
+    c_labels = _labels.ctypes.data_as(POINTER(c_double))
+    c_feature_matrix = (_data_set.__array_interface__['data'][0] +\
+        np.arange(_data_set.shape[0]) * (_data_set.strides[0])).astype(np.uintp)
+    
+    if len(args) > 0:
+        features = internal_function(
+            c_num_features_to_select,
+            c_num_observations,
+            c_num_features, 
+            c_feature_matrix,
+            c_labels,
+            c_output_features,
+            c_selected_feature_types,
+            *args
+        )
+    else:    
+        features = internal_function(
+            c_num_features_to_select,
+            c_num_observations,
+            c_num_features, 
+            c_feature_matrix,
+            c_labels,
+            c_output_features,
+            c_selected_feature_types
+        )
+
+    # result transition from C to Python
+    features_iterator = np.fromiter(features, dtype=np.double, count=num_features_to_select)
+    selected_features = []
+    for c_selected_feature_index in features_iterator:
+        selected_features.append(c_selected_feature_index)
+
+    # store the selection results
+    feature_scores = [c_selected_feature_types[idx] for idx in range(num_features_to_select)]
+
+    return selected_features, feature_scores
+
+
+def _WeightedFeatureSelectionCommon(data_set, labels, weights, num_features_to_select):
+    '''Base function for FEAST weighted algorithms. Convert the parameters from the types 
+    listed below into the types that can be sent to the C/C++ library. 
+    
+    :param data_set: ND Array where len() == number of observations and len(transpose()) ==
+    number of features
+    :param labels: ND Array where len() == number of observations. The length of labels
+    sould be equal to the length of the `data_set`
+    :param weights: ND Array of weights for each label where len() == len(labels)
+    :param num_features_to_select: Number of features to select. The value must be less than
+    or equal to the length of the `data_set`
+    :return: `num_features_to_select` Features in order that they were selected
+    '''
+    # grab the function name as it matches the one from the C-library
+    internal_function = getattr(fstoolbox_lib, inspect.stack()[1][3], None)
+    
+    if internal_function is None:
+        raise RuntimeError("Failed to find %s in fstoolbox", str(inspect.stack()[1][3]))
+    else:
+        log.info("Running %s", str(inspect.stack()[1][3]))
+
+    _data_set = np.array(data_set, dtype=np.uint32, order="F")
+    _labels = np.array(labels, dtype=np.uint32)
+    _weights = np.array(weights, dtype=np.double)
+    
+    num_observations, num_features = _data_set.shape
+    output = zeros(num_features_to_select).astype(np.uint)
+    selected_features_scores = zeros(num_features)
+
+    # convert/create the types to C-Types
+    c_num_features_to_select = c_uint(num_features_to_select)
+    c_num_observations = c_uint(num_observations)
+    c_num_features = c_uint(num_features)    
+    c_output_features = output.ctypes.data_as(POINTER(c_uint))
+    
+    # filled in feature scores through the library
+    selected_features_scores = zeros(num_features)
+    c_selected_feature_types = selected_features_scores.ctypes.data_as(POINTER(c_double))
+    
+    # convert the parameters to the C-Type variables expected
+    c_labels = _labels.ctypes.data_as(POINTER(c_uint))
+    c_weights = _weights.ctypes.data_as(POINTER(c_double))
+    c_feature_matrix = (
+        _data_set.__array_interface__['data'][0] +\
+            np.arange(_data_set.shape[1]) * (_data_set.strides[1])).astype(np.uintp)
+      
+    features = internal_function(
+        c_num_features_to_select,
+        c_num_observations,
+        c_num_features, 
+        c_feature_matrix,
+        c_labels,
+        c_weights,
+        c_output_features,
+        c_selected_feature_types
+    )
+
+    # result transition from C to Python
+    features_iterator = np.fromiter(features, dtype=np.uint, count=num_features_to_select)
+    selected_features = []
+    for c_selected_feature_index in features_iterator:
+        selected_features.append(c_selected_feature_index)
+
+    # store the selection results
+    feature_scores = [c_selected_feature_types[idx] for idx in range(num_features_to_select)]
+
+    return selected_features, feature_scores
+
+
+def _discWeightedFeatureSelectionCommon(data_set, labels, weights, num_features_to_select):
+    '''Base function for FEAST discretized weighted algorithms. Convert the parameters from the types 
+    listed below into the types that can be sent to the C/C++ library. 
+    
+    :param data_set: ND Array where len() == number of observations and len(transpose()) ==
+    number of features
+    :param labels: ND Array where len() == number of observations. The length of labels
+    sould be equal to the length of the `data_set`
+    :param weights: ND Array of weights for each label where len() == len(labels)
+    :param num_features_to_select: Number of features to select. The value must be less than
+    or equal to the length of the `data_set`
+    :return: `num_features_to_select` Features in order that they were selected
+    '''
+    # grab the function name as it matches the one from the C-library
+    internal_function = getattr(fstoolbox_lib, inspect.stack()[1][3], None)
+    
+    if internal_function is None:
+        raise RuntimeError("Failed to find %s in fstoolbox", str(inspect.stack()[1][3]))
+    else:
+        log.info("Running %s", str(inspect.stack()[1][3]))
+    
+    _data_set = np.array(data_set, dtype=np.uint32, order="C")
+    _labels = np.array(labels, dtype=np.uint32)
+    _weights = np.array(weights, dtype=np.double)
+    
+    num_observations, num_features = _data_set.shape
+    output = zeros(num_features_to_select).astype(np.uint)
+    selected_features_scores = zeros(num_features)
+
+    # convert/create the types to C-Types
+    c_num_features_to_select = c_uint(num_features_to_select)
+    c_num_observations = c_uint(num_observations)
+    c_num_features = c_uint(num_features)    
+    c_output_features = output.ctypes.data_as(POINTER(c_double))
+    
+    # filled in feature scores through the library
+    selected_features_scores = zeros(num_features)
+    c_selected_feature_types = selected_features_scores.ctypes.data_as(POINTER(c_double))
+    
+    # convert the parameters to the C-Type variables expected
+    c_labels = _labels.ctypes.data_as(POINTER(c_double))
+    c_weights = _weights.ctypes.data_as(POINTER(c_double))
+    c_feature_matrix = (_data_set.__array_interface__['data'][0] +\
+        np.arange(_data_set.shape[0]) * (_data_set.strides[0])).astype(np.uintp)
+    
+    features = internal_function(
+        c_num_features_to_select,
+        c_num_observations,
+        c_num_features, 
+        c_feature_matrix,
+        c_labels,
+        c_weights,
+        c_output_features,
+        c_selected_feature_types
+    )
+
+    # result transition from C to Python
+    features_iterator = np.fromiter(features, dtype=np.double, count=num_features_to_select)
+    selected_features = []
+    for c_selected_feature_index in features_iterator:
+        selected_features.append(c_selected_feature_index)
+
+    # store the selection results
+    feature_scores = [c_selected_feature_types[idx] for idx in range(num_features_to_select)]
+
+    return selected_features, feature_scores
+
+def BetaGamma(data_set, labels, num_features_to_select, beta, gamma):
+    '''Python implementation of the FEAST::BetaGamma Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/BetaGamma.c` for more
+    information.
+    '''
+    c_beta = c_double(beta)
+    c_gamma = c_double(gamma)
+    return _FeatureSelectionCommon(data_set, labels, num_features_to_select, c_beta, c_gamma)
+
+
+def discBetaGamma(data_set, labels, num_features_to_select, beta, gamma):
+    '''Python implementation of the FEAST::discBetaGamma Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/BetaGamma.c` for more
+    information.
+    '''
+    c_beta = c_double(beta)
+    c_gamma = c_double(gamma)
+    return _discFeatureSelectionCommon(data_set, labels, num_features_to_select, c_beta, c_gamma)
+
+
+def CMIM(data_set, labels, num_features_to_select):
+    '''Python implementation of the FEAST::CMIM Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/CMIM.c` for more
+    information.
+    '''
+    return _FeatureSelectionCommon(data_set, labels, num_features_to_select)
+
+
+def discCMIM(data_set, labels, num_features_to_select):
+    '''Python implementation of the FEAST::discCMIM Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/CMIM.c` for more
+    information.
+    '''
+    return _discFeatureSelectionCommon(data_set, labels, num_features_to_select)
+
+
+def CondMI(data_set, labels, num_features_to_select):
+    '''Python implementation of the FEAST::CondMI Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/CondMI.c` for more
+    information.
+    '''
+    return _FeatureSelectionCommon(data_set, labels, num_features_to_select)
+
+
+def discCondMI(data_set, labels, num_features_to_select):
+    '''Python implementation of the FEAST::discCondMI Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/CondMI.c` for more
+    information.
+    '''
+    return _discFeatureSelectionCommon(data_set, labels, num_features_to_select)
+
+
+def DISR(data_set, labels, num_features_to_select):
+    '''Python implementation of the FEAST::DISR Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/DISR.c` for more
+    information.
+    '''
+    return _FeatureSelectionCommon(data_set, labels, num_features_to_select)
+
+
+def discDISR(data_set, labels, num_features_to_select):
+    '''Python implementation of the FEAST::discDISR Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/DISR.c` for more
+    information.
+    '''
+    return _discFeatureSelectionCommon(data_set, labels, num_features_to_select)
+
+
+def ICAP(data_set, labels, num_features_to_select):
+    '''Python implementation of the FEAST::ICAP Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/ICAP.c` for more
+    information.
+    '''
+    return _FeatureSelectionCommon(data_set, labels, num_features_to_select)
+
+
+def discICAP(data_set, labels, num_features_to_select):
+    '''Python implementation of the FEAST::discICAP Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/ICAP.c` for more
+    information.
+    '''
+    return _discFeatureSelectionCommon(data_set, labels, num_features_to_select)
+
+
+def JMI(data_set, labels, num_features_to_select):
+    '''Python implementation of the FEAST::JMI Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/JMI.c` for more
+    information.
+    '''
+    return _FeatureSelectionCommon(data_set, labels, num_features_to_select)
+
+
+def discJMI(data_set, labels, num_features_to_select):
+    '''Python implementation of the FEAST::discJMI Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/JMI.c` for more
+    information.
+    '''
+    return _discFeatureSelectionCommon(data_set, labels, num_features_to_select)
+
+
+def MIM(data_set, labels, num_features_to_select):
+    '''Python implementation of the FEAST::MIM Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/MIM.c` for more
+    information.
+    '''
+    return _FeatureSelectionCommon(data_set, labels, num_features_to_select)
+
+
+def discMIM(data_set, labels, num_features_to_select):
+    '''Python implementation of the FEAST::discMIM Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/MIM.c` for more
+    information.
+    '''
+    return _discFeatureSelectionCommon(data_set, labels, num_features_to_select)
+
+
+def mRMR_D(data_set, labels, num_features_to_select):
+    '''Python implementation of the FEAST::mRMR_D Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/mRMR_D.c` for more
+    information.
+    '''
+    return _FeatureSelectionCommon(data_set, labels, num_features_to_select)
+
+
+def disc_mRMR_D(data_set, labels, num_features_to_select):
+    '''Python implementation of the FEAST::disc_mRMR_D Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/mRMR_D.c` for more
+    information.
+    '''
+    return _discFeatureSelectionCommon(data_set, labels, num_features_to_select)
+
+
+def weightedCMIM(data_set, labels, weights, num_features_to_select):
+    '''Python implementation of the FEAST::WeightedCMIM Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/WeightedCMIM.c` for more
+    information.
+    '''
+    return _WeightedFeatureSelectionCommon(data_set, labels, weights, num_features_to_select)
+
+
+def discWeightedCMIM(data_set, labels, weights, num_features_to_select):
+    '''Python implementation of the FEAST::discWeightedCMIM Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/WeightedCMIM.c` for more
+    information.
+    '''
+    return _discWeightedFeatureSelectionCommon(data_set, labels, weights, num_features_to_select)
+
+
+def weightedCondMI(data_set, labels, weights, num_features_to_select):
+    '''Python implementation of the FEAST::WeightedCondMI Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/WeightedCondMI.c` for more
+    information.
+    '''
+    return _WeightedFeatureSelectionCommon(data_set, labels, weights, num_features_to_select)
+
+
+def discWeightedCondMI(data_set, labels, weights, num_features_to_select):
+    '''Python implementation of the FEAST::discWeightedCondMI Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/WeightedCondMI.c` for more
+    information.
+    '''
+    return _discWeightedFeatureSelectionCommon(data_set, labels, weights, num_features_to_select)
+
+
+def weightedDISR(data_set, labels, weights, num_features_to_select):
+    '''Python implementation of the FEAST::WeightedCondMI Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/WeightedDISR.c` for more
+    information.
+    '''
+    return _WeightedFeatureSelectionCommon(data_set, labels, weights, num_features_to_select)
+
+
+def discWeightedDISR(data_set, labels, weights, num_features_to_select):
+    '''Python implementation of the FEAST::discWeightedDISR Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/WeightedDISR.c` for more
+    information.
+    '''
+    return _discWeightedFeatureSelectionCommon(data_set, labels, weights, num_features_to_select)
+
+
+def weightedJMI(data_set, labels, weights, num_features_to_select):
+    '''Python implementation of the FEAST::WeightedJMI Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/WeightedJMI.c` for more
+    information.
+    '''
+    return _WeightedFeatureSelectionCommon(data_set, labels, weights, num_features_to_select)
+
+
+def discWeightedJMI(data_set, labels, weights, num_features_to_select):
+    '''Python implementation of the FEAST::discWeightedJMI Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/WeightedJMI.c` for more
+    information.
+    '''
+    return _discWeightedFeatureSelectionCommon(data_set, labels, weights, num_features_to_select)
+
+
+def weightedMIM(data_set, labels, weights, num_features_to_select):
+    '''Python implementation of the FEAST::WeightedMIM Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/WeightedMIM.c` for more
+    information.
+    '''
+    return _WeightedFeatureSelectionCommon(data_set, labels, weights, num_features_to_select)
+
+
+def discWeightedMIM(data_set, labels, weights, num_features_to_select):
+    '''Python implementation of the FEAST::discWeightedMIM Selection Algorithm.
+    See `https://github.com/Craigacp/FEAST/blob/master/src/WeightedMIM.c` for more
+    information.
+    '''
+    return _discWeightedFeatureSelectionCommon(data_set, labels, weights, num_features_to_select)
diff --git a/python/feast/weightedCMIM.py b/python/feast/weightedCMIM.py
deleted file mode 100644
index ce3b9fd..0000000
--- a/python/feast/weightedCMIM.py
+++ /dev/null
@@ -1,191 +0,0 @@
-#######################################################################
-##  Contributed by Evgeni Dubov from Intel Advanced Analitics group  ##
-#######################################################################
-
-import os
-import sys
-import pandas as pd
-import numpy as np
-from numpy.ctypeslib import ndpointer
-import ctypes as c
-
-
-class weightedCMIM():
-    """
-    Python wrapper for weightedCMIM feature selection
-
-    Parameters
-    ----------
-    num_of_features : integer, default = 20
-        the amount of features to choose
-    	weights			: list
-    		list of samples weights
-
-    Attributes
-    ----------
-    	num_of_features	: integer
-    		Number of features to select
-    	weight_vector	: list of floats
-    		Samples weights
-    feature_indexes	: list of floats
-        Indexes of selected feature
-    feature_scores	: list of floats
-        Scores of selected feature. The score is calculated by min{I(Xn ; Y | Xk)}
-    	feature_names	: list of strings
-        Names of the selected features
-        * will be available only in case data for fit is pandas DataFrame
-    """
-
-    def __init__(self, num_of_features=20, weights):
-        if sys.platform.startswith('linux'):
-            # in case of Linux OS load feast linux library (.so file)
-            self.libFSToolbox = c.CDLL('{}/{}/{}'.format(os.path.dirname(__file__), 'lib', 'libFSToolbox.so'))
-        elif sys.platform.startswith('win'):
-            # in case of Windows OS load feast windows library (.dll file)
-            self.libFSToolbox = c.WinDLL('{}/{}/{}'.format(os.path.dirname(__file__), 'lib', 'libFSToolbox.dll'))
-        else:
-            # OSs other than Linux and Windows are not supported
-            raise NotImplementedError('FeastCMIM is not supported on {} operating system'.format(sys.platform))
-
-        self.num_of_features = num_of_features
-        self.weight_vector = weights
-        self.feature_indexes = None
-        self.feature_scores = None
-        self.feature_names = None		
-
-    def fit(self, data, labels):
-        """
-        Fits a defined weightedCMIM filter to a given data set.
-        data and labels are expected to be discretized
-
-        Parameters
-        ----------
-        data : pandas or numpy data object
-            The training input samples.
-        labels : pandas or numpy data object. labels should contain a binary label.
-            The label of the training samples.
-        """
-
-        # in case the data is a pandas dataframe we can store features by name
-        data_labels = None
-        if isinstance(data, pd.DataFrame):
-            data_labels = data.columns.tolist()
-
-        # python variables adaptation for C parameters initialization
-        data = np.array(data, dtype=np.uint32, order="F")
-        labels = np.array(labels, dtype=np.uint32)
-        n_samples, n_features = data.shape
-        output = np.zeros(self.num_of_features).astype(np.uint)
-        selected_features_score = np.zeros(self.num_of_features)
-        weight_vector = self.weight_vector.values
-
-        # cast as C types
-        _uintpp = ndpointer(dtype=np.uintp, ndim=1, flags='F')
-        c_k = c.c_uint(self.num_of_features)
-        c_no_of_samples = c.c_uint(n_samples)
-        c_no_of_features = c.c_uint(n_features)
-        c_feature_matrix = (data.__array_interface__['data'][0] + np.arange(data.shape[1]) * (data.strides[1])).astype(
-            np.uintp)
-        c_class_column = labels.ctypes.data_as(c.POINTER(c.c_uint))
-        c_output_features = output.ctypes.data_as(c.POINTER(c.c_uint))
-        c_weight_vector = weight_vector.ctypes.data_as(c.POINTER(c.c_double))
-        c_feature_scores = selected_features_score.ctypes.data_as(c.POINTER(c.c_double))
-
-        self.libFSToolbox.weightedCMIM.argtypes = [c.c_uint, c.c_uint, c.c_uint, _uintpp, c.POINTER(c.c_uint), c.POINTER(c.c_double), c.POINTER(c.c_uint), c.POINTER(c.c_double)]
-        self.libFSToolbox.weightedCMIM.restype = c.POINTER(c.c_uint)
-
-        # call the C lib implementation
-        c_selected_features = self.libFSToolbox.CMIM(
-            c_k, c_no_of_samples, c_no_of_features, c_feature_matrix,
-            c_class_column, c_output_features, c_feature_scores)
-
-        # result transition from C to Python
-        features_iterator = np.fromiter(c_selected_features, dtype=np.uint, count=self.num_of_features)
-        selected_features = []
-        for c_selected_feature_index in features_iterator:
-            selected_features.append(c_selected_feature_index)
-
-        # store the selection results
-        self.feature_scores = [c_feature_scores[idx] for idx in range(self.num_of_features)]
-        self.feature_indexes = selected_features
-
-        if data_labels is not None:
-            self.feature_names = [data_labels[idx] for idx in self.feature_indexes]
-
-    def transform(self, X):
-        if isinstance(X, np.ndarray):
-            return(X[:, self.feature_indexes])
-        elif isinstance(X, pd.DataFrame):
-            return(X.loc[:, self.feature_names])
-        else:
-            raise AttributeError('Transform accepts data of type numpy.ndarray or pandas.DataFrame')
-
-
-
-class weightedCMIM(BaseEstimator, TransformerMixin, Transformer):
-
-
-
-
-    def fit(self, data, labels):
-        """
-        Fits a defined CMIM filter to a given data set.
-        data and labels are expected to be discretized
-
-        Parameters
-        ----------
-        data : pandas or numpy data object
-            The training input samples.
-        labels : pandas or numpy data object. labels should contain a binary label.
-            The label of the training samples.
-        """
-
-        # in case the data is a pandas data frame we can store features by name
-        data_labels = None
-        if type(data) is pd.DataFrame:
-            data_labels = data.columns.tolist()
-
-        # python variables adaptation for C parameters initialization
-        data = np.array(data, dtype=np.uint32, order="F")
-        labels = np.array(labels, dtype=np.uint32)
-        n_samples, n_features = data.shape
-        output = np.zeros(self.num_of_features).astype(np.uint)
-        selected_features_score = np.zeros(self.num_of_features)
-        weight_vector = self.weight_vector.values
-
-        # cast as C types
-        _uintpp = ndpointer(dtype=np.uintp, ndim=1, flags='F')
-        c_k = c.c_uint(self.num_of_features)
-        c_no_of_samples = c.c_uint(n_samples)
-        c_no_of_features = c.c_uint(n_features)
-        c_feature_matrix = (data.__array_interface__['data'][0] + np.arange(data.shape[1]) * (data.strides[1])).astype(np.uintp)
-        c_class_column = labels.ctypes.data_as(c.POINTER(c.c_uint))
-        c_output_features = output.ctypes.data_as(c.POINTER(c.c_uint))
-        c_weight_vector = weight_vector.ctypes.data_as(c.POINTER(c.c_double))
-        c_feature_scores = selected_features_score.ctypes.data_as(c.POINTER(c.c_double))
-
-        self.libFSToolbox.weightedCMIM.argtypes = [c.c_uint, c.c_uint, c.c_uint, _uintpp, c.POINTER(c.c_uint), c.POINTER(c.c_double), c.POINTER(c.c_uint), c.POINTER(c.c_double)]
-        self.libFSToolbox.weightedCMIM.restype = c.POINTER(c.c_uint)
-
-        # call the C implementation
-        c_selected_features = self.libFSToolbox.weightedCMIM(c_k, c_no_of_samples, c_no_of_features, c_feature_matrix, c_class_column, c_weight_vector, c_output_features, c_feature_scores)
-
-        # result transition from C to Python
-        features_iterator = np.fromiter(c_selected_features, dtype=np.uint, count=self.num_of_features)
-        selected_features = []
-        for c_selected_feature_index in features_iterator:
-            selected_features.append(c_selected_feature_index)
-
-        # store the selection results
-        self.feature_scores = [c_feature_scores[idx] for idx in range(self.num_of_features)]
-        self.feature_indexes = selected_features
-
-        if data_labels is not None:
-            self.feature_names = [data_labels[idx] for idx in self.feature_indexes]
-			
-        return self
-
-
-
-
-
diff --git a/python/setup.py b/python/setup.py
index 50c8748..ba98013 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -2,6 +2,13 @@
 import sys
 from setuptools import setup
 from setuptools.command.install import install
+from os.path import join
+from os import getcwd
+
+
+# Read license data to match the dependent project
+with open(join(getcwd(), "../LICENSE"), "r") as license_file:
+    license_data = license_file.read()
 
 
 class FEASTInstall(install):
@@ -18,7 +25,7 @@ def run(self):
     version='2.1.0',
     packages=['feast'],
     url='https://github.com/Craigacp/FEAST',
-    license='BSD',
+    license=license_data,
     author='Adam Pocock',
     author_email='adam@adampocock.com',
     description='An information theoretic feature selection toolbox.',


Test patch:
diff --git a/python/tests/testCMIM.py b/python/tests/testCMIM.py
deleted file mode 100644
index 6c5e638..0000000
--- a/python/tests/testCMIM.py
+++ /dev/null
@@ -1,107 +0,0 @@
-#######################################################################
-##  Contributed by Evgeni Dubov from Intel Advanced Analitics group  ##
-#######################################################################
-
-import unittest
-import pandas as pd
-import numpy as np
-from sklearn.datasets import load_boston
-from sklearn.preprocessing import MinMaxScaler
-from feast.CMIM import CMIM
-
-class TestCMIM(unittest.TestCase):
-
-    def setUp(self):
-
-        # prepare testing dataset
-        boston = load_boston()
-        self.X = pd.DataFrame(data=boston.data, columns=boston.feature_names)
-        y = pd.DataFrame(data=boston.target, columns=['Labels'])
-        
-        # feast CMIM expects discretized data
-
-        # normalize each feature
-        min_max_scaler = MinMaxScaler()
-        X_normalized = min_max_scaler.fit_transform(self.X)
-        y_normalized = min_max_scaler.fit_transform(y)
-
-        # discretize
-        n_bins = 6
-        X_n_samples, X_n_features = self.X.shape
-        y_n_samples, y_n_features = y.shape
-        self.X_discrete = np.zeros((X_n_samples, X_n_features))
-        self.y_discrete = np.zeros((y_n_samples, y_n_features))
-        bins = np.linspace(0, 1, n_bins)
-        for i in range(X_n_features):
-            self.X_discrete[:, i] = np.digitize(X_normalized[:, i], bins)
-        for i in range(y_n_features):
-            self.y_discrete[:, i] = np.digitize(y_normalized[:, i], bins)
-        self.X_discrete = self.X_discrete - 1
-        self.y_discrete = self.y_discrete - 1
-
-    def test_CMIM_boston_feature_names(self):
-
-        # run feast CMIM fit
-        feast_cmim = CMIM(num_of_features=2)
-        feast_cmim.fit(self.X_discrete, self.y_discrete)
-
-        # get selected features names
-        if feast_cmim.feature_names is not None:
-            selected_features = feast_cmim.feature_names
-        else:
-            feature_names = self.X.columns.tolist()
-            selected_features = [feature_names[idx] for idx in feast_cmim.feature_indexes]
-
-        # validate selected features names
-        expected_result = ['LSTAT', 'RM']
-        self.assertEqual(set(selected_features), set(expected_result))
-
-    def test_CMIM_boston_numpy_scores(self):
-
-        # run feast CMIM fit
-        feast_cmim = CMIM(num_of_features=2)
-        feast_cmim.fit(self.X_discrete, self.y_discrete)
-
-        # validate selected features scores
-        expected_scores = [0.5788220081872999, 0.2731987912650301]
-        self.assertEqual(set(feast_cmim.feature_scores), set(expected_scores))
-
-    def test_CMIM_boston_pandas_scores(self):
-
-        # run feast CMIM fit
-        feast_cmim = CMIM(num_of_features=2)
-        feast_cmim.fit(pd.DataFrame(self.X_discrete), self.y_discrete)
-
-        # validate selected features scores
-        expected_scores = [0.5788220081872999, 0.2731987912650301]
-        self.assertEqual(set(feast_cmim.feature_scores), set(expected_scores))
-
-    def test_CMIM_boston_pandas_transform(self):
-
-        # prepare data pandas dataframe
-        df_column_names = ['column{}'.format(i) for i in range(self.X_discrete.shape[1])]
-        X_discrete_df = pd.DataFrame(data=self.X_discrete, columns=df_column_names)
-
-        # run feast CMIM
-        feast_cmim = CMIM(num_of_features=2)
-        feast_cmim.fit(X_discrete_df, self.y_discrete)
-
-        # transform the data according to features selection
-        X_df_transform = feast_cmim.transform(X_discrete_df)
-
-        # validate transform with features names
-        expected_columns = ['column12', 'column5']
-        self.assertTrue(X_discrete_df.loc[:, expected_columns].equals(X_df_transform))
-
-    def test_CMIM_boston_pandas_transform(self):
-
-        # run feast CMIM fit
-        feast_cmim = CMIM(num_of_features=2)
-        feast_cmim.fit(self.X_discrete, self.y_discrete)
-
-        # transform the data according to features selection
-        X_transform = feast_cmim.transform(self.X_discrete)
-
-        # validate transform with features indexes 
-        expected_columns = [12, 5]
-        self.assertTrue(np.array_equal(self.X_discrete[:, expected_columns], X_transform))