# -*- coding: utf-8 -*-
"""
Created on Sun Feb 21 23:43:37 2016

@author: kiko
"""
from __future__ import division, absolute_import

from .settings import RICH_DISPLAY

import numpy as np
if RICH_DISPLAY:
    from IPython.display import display

def axes_set_better_defaults(ax,
                             axes_color = '#777777',
                             grid = False,
                             show = False):
    """
    Enter an Axes instance and it will change the defaults to an opinionated
    version of how a simple plot should be.

    Parameters:
    -----------
    ax : matplotlib.axes.Axes or matplotlib.axes.Subplot instance
    axes_color : str
        A string indicating a valid matplotlib color.
    grid : bool
        If `True` the grid of the axes will be shown, if `False` (default)
        the grid, if active, will be supressed.
    show : bool
        if `True` the figure will be shown.
            If you are working in a rich display environment like the IPython
            qtconsole or the Jupyter notebook it will use
            `IPython.display.display` to show the figure.
            If you are working otherwise it will call the `show` of the
            `Figure` instance.
    """
    ax.set_axis_bgcolor((1, 1, 1))
    ax.grid(grid)
    for key in ax.spines.keys():
        if ax.spines[key].get_visible():
            ax.spines[key].set_color(axes_color)
    ax.tick_params(axis = 'x', colors = axes_color)
    ax.tick_params(axis = 'y', colors = axes_color)
    ax.figure.set_facecolor('white')
    ax.figure.canvas.draw()
    if show:
        if RICH_DISPLAY:
            display(ax.figure)
        else:
            ax.figure.show()

# http://matplotlib.org/examples/pylab_examples/spine_placement_demo.html
def axes_set_axis_position(ax,
                           spines = ['bottom', 'left'],
                           pan = 0,
                           show = False):
    """
    Enter an Axes instance and depending the options it will display the
    axis where you selected.

    Parameters:
    -----------
    ax : matplotlib.axes.Axes or matplotlib.axes.Subplot instance
    spines : str or iterable
        A string  or an iterable of strings with the following valid options:
            'bottom' : To active the bottom x-axis.
            'top' : To active the top x-axis.
            'left' : To active the left y-axis.
            'right' : To active the right y-axis.
    pan : int or iterable
        A integer value or an iterable of integer values indicating the value
        to pan the axis. It has to have the same lenght and the same order
        than the spines input.
    show : bool
        if `True` the figure will be shown.
            If you are working in a rich display environment like the IPython
            qtconsole or the Jupyter notebook it will use
            `IPython.display.display` to show the figure.
            If you are working otherwise it will call the `show` of the
            `Figure` instance.
    """
    if np.isscalar(spines):
        spines = (spines,)
        len_spines = 1
    else:
        len_spines = len(spines)
    if np.isscalar(pan):
        pan = np.repeat(pan, len_spines)
        len_pan = 1
    else:
        len_pan = len(pan)
    if len_pan > 1 and len_pan != len_spines:
        raise ValueError(('Length of `spines` and `pan` mismatch. `pan` ')
        ('should be a scalar or should have the same length than `spines`.'))

    i = 0
    for loc, spine in ax.spines.items():
        if loc in spines:
            spine.set_position(('outward', pan[i]))  # outward by `pan` points
            spine.set_smart_bounds(True)
            i += 1
        else:
            #spine.set_color('none')  # don't draw spine
            spine.set_visible(False)

    # turn off ticks where there is no spine
    if 'left' in spines:
        ax.yaxis.set_ticks_position('left')
        ax.tick_params(labelleft = True)
    if 'right' in spines:
        ax.yaxis.set_ticks_position('right')
        ax.tick_params(labelright = True)
    if 'left' in spines and 'right' in spines:
        ax.yaxis.set_ticks_position('both')
        ax.tick_params(labelleft = True, labelright = True)
    if 'left' not in spines and 'right' not in spines:
        ax.yaxis.set_ticks([])

    if 'bottom' in spines:
        ax.xaxis.set_ticks_position('bottom')
        ax.tick_params(labelbottom = True)
    if 'top' in spines:
        ax.xaxis.set_ticks_position('top')
        ax.tick_params(labeltop = True)
    if 'bottom' in spines and 'top' in spines:
        ax.xaxis.set_ticks_position('both')
        ax.tick_params(labelbottom = True, labeltop = True)
    if 'bottom' not in spines and 'top' not in spines:
        ax.xaxis.set_ticks([])
    ax.figure.canvas.draw()
    if show:
        if RICH_DISPLAY:
            display(ax.figure)
        else:
            ax.figure.show()

def axes_set_origin(ax,
                    x = 0,
                    y = 0,
                    xticks_position = 'bottom',
                    yticks_position = 'left',
                    xticks_visible = True,
                    yticks_visible = True,
                    show = False):
    """
    function to locate x-axis and y-axis on the position you want.

    Parameters:
    -----------
    ax : matplotlib.axes.Axes or matplotlib.axes.Subplot instance
    x : int or float
        Value indicating the position on the y-axis where you want the x-axis
        to be located.
    y : int or float
        Value indicating the position on the x-axis where you want the y-axis
        to be located.
    xticks_position : str
        Default value is 'bottom' if you want the ticks to be located below
        the x-axis. 'top' if you want the ticks to be located above the x-axis.
    yticks_position : str
        Default value is 'left' if you want the ticks to be located on the left
        side of the y-axis. 'right' if you want the ticks to be located on the
        right side of the y-axis.
    xticks_visible : bool
        Default value is True if you want ticks visible on the x-axis. False
        if you don't want to see the ticks on the x-axis.
    yticks_visible : bool
        Default value is True if you want ticks visible on the y-axis. False
        if you don't want to see the ticks on the y-axis.
    show : bool
        if `True` the figure will be shown.
            If you are working in a rich display environment like the IPython
            qtconsole or the Jupyter notebook it will use
            `IPython.display.display` to show the figure.
            If you are working otherwise it will call the `show` of the
            `Figure` instance.
    """
    ax.spines['right'].set_visible(False)
    ax.spines['top'].set_visible(False)
    ax.xaxis.set_ticks_position(xticks_position)
    ax.spines['bottom'].set_position(('data', x))
    ax.yaxis.set_ticks_position(yticks_position)
    ax.spines['left'].set_position(('data', y))
    if not xticks_visible:
        ax.set_xticks([])
    if not yticks_visible:
        ax.set_yticks([])
    ax.figure.canvas.draw()
    if show:
        if RICH_DISPLAY:
            display(ax.figure)
        else:
            ax.figure.show()

def axes_set_aspect_ratio(ax, ratio = 'equal', show = True):
    """
    function that accepts an Axes instance and update the information
    setting the aspect ratio of the axis to the defined quantity

    Parameters:
    -----------
    ax : matplotlib.axes.Axes or matplotlib.axes.Subplot instance
    ratio : str or int/float
        The value can be a string with the following values:
            'equal' : (default) same scaling from data to plot units for x and y
            'auto' : automatic; fill position rectangle with data
        Or a:
            number (int or float) : a circle will be stretched such that the
            height is num times the width. aspec t =1 is the same as
            aspect='equal'.
    show : bool
        if `True` the figure will be shown.
            If you are working in a rich display environment like the IPython
            qtconsole or the Jupyter notebook it will use
            `IPython.display.display` to show the figure.
            If you are working otherwise it will call the `show` of the
            `Figure` instance.
    """
    ax.set_aspect(ratio, adjustable = None)
    if show:
        if RICH_DISPLAY:
            display(ax.figure)
        else:
            ax.figure.show()


"""
The :mod:`sklearn.linear_model` module implements generalized linear models. It
includes Ridge regression, Bayesian Regression, Lasso and Elastic Net
estimators computed with Least Angle Regression and coordinate descent. It also
implements Stochastic Gradient Descent related algorithms.
"""

# See http://scikit-learn.sourceforge.net/modules/sgd.html and
# http://scikit-learn.sourceforge.net/modules/linear_model.html for
# complete documentation.

from .base import LinearRegression

from .bayes import BayesianRidge, ARDRegression
from .least_angle import (Lars, LassoLars, lars_path, LarsCV, LassoLarsCV,
                          LassoLarsIC)
from .coordinate_descent import (Lasso, ElasticNet, LassoCV, ElasticNetCV,
                                 lasso_path, enet_path, MultiTaskLasso,
                                 MultiTaskElasticNet, MultiTaskElasticNetCV,
                                 MultiTaskLassoCV)
from .sgd_fast import Hinge, Log, ModifiedHuber, SquaredLoss, Huber
from .stochastic_gradient import SGDClassifier, SGDRegressor
from .ridge import (Ridge, RidgeCV, RidgeClassifier, RidgeClassifierCV,
                    ridge_regression)
from .logistic import (LogisticRegression, LogisticRegressionCV,
                       logistic_regression_path)
from .omp import (orthogonal_mp, orthogonal_mp_gram, OrthogonalMatchingPursuit,
                  OrthogonalMatchingPursuitCV)
from .passive_aggressive import PassiveAggressiveClassifier
from .passive_aggressive import PassiveAggressiveRegressor
from .perceptron import Perceptron
from .randomized_l1 import (RandomizedLasso, RandomizedLogisticRegression,
                            lasso_stability_path)
from .ransac import RANSACRegressor
from .theil_sen import TheilSenRegressor

__all__ = ['ARDRegression',
           'BayesianRidge',
           'ElasticNet',
           'ElasticNetCV',
           'Hinge',
           'Huber',
           'Lars',
           'LarsCV',
           'Lasso',
           'LassoCV',
           'LassoLars',
           'LassoLarsCV',
           'LassoLarsIC',
           'LinearRegression',
           'Log',
           'LogisticRegression',
           'LogisticRegressionCV',
           'ModifiedHuber',
           'MultiTaskElasticNet',
           'MultiTaskElasticNetCV',
           'MultiTaskLasso',
           'MultiTaskLassoCV',
           'OrthogonalMatchingPursuit',
           'OrthogonalMatchingPursuitCV',
           'PassiveAggressiveClassifier',
           'PassiveAggressiveRegressor',
           'Perceptron',
           'RandomizedLasso',
           'RandomizedLogisticRegression',
           'Ridge',
           'RidgeCV',
           'RidgeClassifier',
           'RidgeClassifierCV',
           'SGDClassifier',
           'SGDRegressor',
           'SquaredLoss',
           'TheilSenRegressor',
           'enet_path',
           'lars_path',
           'lasso_path',
           'lasso_stability_path',
           'logistic_regression_path',
           'orthogonal_mp',
           'orthogonal_mp_gram',
           'ridge_regression',
           'RANSACRegressor']


# -*- coding: utf-8 -*-
"""
Created on Sun Apr 15 19:47:45 2012

@author: Tillsten
"""
import numpy as np
from lmfit import Parameters, Minimizer, conf_interval, report_fit, report_ci

from numpy import linspace, zeros, sin, exp, random, sqrt, pi, sign
from scipy.optimize import leastsq

try:
    import matplotlib.pyplot as plt
    import pylab
    HASPYLAB = True
except ImportError:
    HASPYLAB = False

p_true = Parameters()
p_true.add('amp', value=14.0)
p_true.add('period', value=5.33)
p_true.add('shift', value=0.123)
p_true.add('decay', value=0.010)

def residual(pars, x, data=None):
    amp = pars['amp'].value
    per = pars['period'].value
    shift = pars['shift'].value
    decay = pars['decay'].value

    if abs(shift) > pi/2:
        shift = shift - sign(shift)*pi
    model = amp*sin(shift + x/per) * exp(-x*x*decay*decay)
    if data is None:
        return model
    return (model - data)

n = 2500
xmin = 0.
xmax = 250.0
noise = random.normal(scale=0.7215, size=n)
x     = linspace(xmin, xmax, n)
data  = residual(p_true, x) + noise

fit_params = Parameters()
fit_params.add('amp', value=13.0)
fit_params.add('period', value=2)
fit_params.add('shift', value=0.0)
fit_params.add('decay', value=0.02)

mini = Minimizer(residual, fit_params, fcn_args=(x,),
                 fcn_kws={'data':data})
out = mini.leastsq()

fit = residual(out.params, x)

print( ' N fev = ', out.nfev)
print( out.chisqr, out.redchi, out.nfree)

report_fit(out.params)
#ci=calc_ci(out)

ci, tr = conf_interval(mini, out, trace=True)
report_ci(ci)

if HASPYLAB:
    names=out.params.keys()
    i=0
    gs=pylab.GridSpec(4,4)
    sx={}
    sy={}
    for fixed in names:
        j=0
        for free in names:
            if j in sx and i in sy:
                ax=pylab.subplot(gs[i,j],sharex=sx[j],sharey=sy[i])
            elif i in sy:
                ax=pylab.subplot(gs[i,j],sharey=sy[i])
                sx[j]=ax
            elif j in sx:
                ax=pylab.subplot(gs[i,j],sharex=sx[j])
                sy[i]=ax
            else:
                ax=pylab.subplot(gs[i,j])
                sy[i]=ax
                sx[j]=ax
            if i<3:
                pylab.setp( ax.get_xticklabels(), visible=False)
            else:
                ax.set_xlabel(free)

            if j>0:
                pylab.setp( ax.get_yticklabels(), visible=False)
            else:
                ax.set_ylabel(fixed)

            res=tr[fixed]
            prob=res['prob']
            f=prob<0.96

            x,y=res[free], res[fixed]
            ax.scatter(x[f],y[f],
                  c=1-prob[f],s=200*(1-prob[f]+0.5))
            ax.autoscale(1,1)

            j=j+1
        i=i+1

    pylab.show()


# (c) Nelen & Schuurmans.  GPL licensed, see LICENSE.rst.
# -*- coding: utf-8 -*-
"""
Convert shapefiles with z coordinates. Choose from the following formats:
'inp' to create an inp file, 'img' to create an image with a plot of the
feature, or 'shp' to output a shapefile with the average height of a
feature stored in an extra attribute.
"""

from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import
from __future__ import division

import argparse
import math
import os
import shutil
import tempfile

from matplotlib.backends import backend_agg
from matplotlib import figure
from osgeo import gdal
from osgeo import ogr
from PIL import Image

ogr.UseExceptions()

def get_parser():
    """ Return argument parser. """
    parser = argparse.ArgumentParser(
        description=__doc__,
    )
    parser.add_argument('source_path',
                        metavar='SOURCE',
                        help=('Path to source shapefile.'))
    parser.add_argument('target_path',
                        metavar='TARGET',
                        help=('Path to target file.'))
    parser.add_argument('-of', '--output-format',
                        metavar='FORMAT',
                        choices=['inp', 'img', 'shp'],
                        default='shp',
                        help=("Path to output."))
    return parser

class InputFileWriter(object):
    """ Writer for input files. """
    def __init__(self, path):
        """
        Init the counters and tmpdirs
        """
        self.path = path
        self.node_count = 0
        self.link_count = 0

    def __enter__(self):
        """ Setup tempfiles. """
        self.temp_directory = tempfile.mkdtemp()
        self.node_file = open(
            os.path.join(self.temp_directory, 'nodes'), 'a+',
        )
        self.link_file = open(
            os.path.join(self.temp_directory, 'links'), 'a+',
        )
        return self

    def __exit__(self, type, value, traceback):
        """ Write 'inputfile' at path. """
        with open(self.path, 'w') as input_file:
            self.node_file.seek(0)
            input_file.write(self.node_file.read())
            input_file.write('-1\n')
            self.link_file.seek(0)
            input_file.write(self.link_file.read())
        self.node_file.close()
        self.link_file.close()
        shutil.rmtree(self.temp_directory)

    def _write_node(self, node):
        """ Write a node. """
        self.node_count += 1
        self.node_file.write('{} {} {} {}\n'.format(
            self.node_count, node[0], node[1], -node[2]  # Depth, not height!
        ))

    def _write_link(self):
        """ Write a link between previous node and next node."""
        self.link_count += 1
        self.link_file.write('{} {} {}\n'.format(
            self.link_count, self.node_count, self.node_count + 1,
        ))

    def _add_wkb_line_string(self, wkb_line_string):
        """ Add linestring as nodes and links. """
        nodes = [wkb_line_string.GetPoint(i)
                 for i in range(wkb_line_string.GetPointCount())]
        # Add nodes and links up to the last node
        for i in range(len(nodes) - 1):
            self._write_node(nodes[i])
            self._write_link()
        # Add last node, link already covered.
        self._write_node(nodes[-1])

    def add_feature(self, feature):
        """ Add feature as nodes and links. """
        geometry = feature.geometry()
        geometry_type = geometry.GetGeometryType()
        if geometry_type == ogr.wkbLineString25D:
            self._add_wkb_line_string(geometry)
        elif geometry_type == ogr.wkbMultiLineString25D:
            for wkb_line_string in geometry:
                self._add_wkb_line_string(wkb_line_string)

class ImageWriter(object):
    """ Writer for images. """

    def __init__(self, path):
        self.count = 0
        self.path = path

    def __enter__(self):
        return self

    def _add_wkb_line_string(self, wkb_line_string, label):
        """ Plot linestring as separate image. """
        # Get data
        x, y, z = zip(*[wkb_line_string.GetPoint(i)
                        for i in range(wkb_line_string.GetPointCount())])
        # Determine distance along line
        l = [0]
        for i in range(len(z) - 1):
            l.append(l[-1] + math.sqrt(
                (x[i + 1] - x[i]) ** 2 + (y[i + 1] - y[i]) ** 2,
            ))
        # Plot in matplotlib
        fig = figure.Figure()
        axes = fig.add_axes([0.1, 0.1, 0.8, 0.8])
        axes.plot(l, z, label=label)
        axes.legend(loc='best', frameon=False)
        # Write to image
        backend_agg.FigureCanvasAgg(fig)
        buf, size = fig.canvas.print_to_buffer()
        image = Image.fromstring('RGBA', size, buf)
        root, ext = os.path.splitext(self.path)
        image.save(root + '{:00.0f}'.format(self.count) + ext)
        self.count += 1

    def add_feature(self, feature):
        """ Currently saves every feature in a separate image. """
        # Plotlabel
        label = '\n'.join([': '.join(str(v) for v in item)
                           for item in feature.items().items()])
        # Plot according to geometry type
        geometry = feature.geometry()
        geometry_type = geometry.GetGeometryType()
        if geometry_type == ogr.wkbLineString25D:
            self._add_wkb_line_string(geometry, label=label)
        elif geometry_type == ogr.wkbMultiLineString25D:
            for wkb_line_string in geometry:
                self._add_wkb_line_string(wkb_line_string, label=label)

    def __exit__(self, type, value, traceback):
        pass

class ShapefileWriter(object):
    """ Writer for shapefiles. """
    ATTRIBUTE = b'kruinhoogt'

    def __init__(self, path):
        self.count = 0
        self.path = path
        self.datasource = None
        self.layer = None

    def __enter__(self):
        return self

    def create_datasource(self, feature):
        """ Create a datasource based on feature. """
        root, ext = os.path.splitext(os.path.basename(self.path))
        driver = ogr.GetDriverByName(b'ESRI Shapefile')
        datasource = driver.CreateDataSource(self.path)
        layer = datasource.CreateLayer(root)
        for i in range(feature.GetFieldCount()):
            layer.CreateField(feature.GetFieldDefnRef(i))
        field_defn = ogr.FieldDefn(self.ATTRIBUTE, ogr.OFTReal)
        layer.CreateField(field_defn)

        self.datasource = datasource
        self.layer = layer

    def add_feature(self, feature):
        """ Currently saves every feature in a separate image. """
        if self.layer is None:
            self.create_datasource(feature)
        layer_defn = self.layer.GetLayerDefn()

        # elevation
        geometry = feature.geometry().Clone()
        geometry_type = geometry.GetGeometryType()
        if geometry_type == ogr.wkbLineString25D:
            elevation = min([p[2] for p in geometry.GetPoints()])
        else:
            # multilinestring
            elevation = min([p[2]
                             for g in geometry
                             for p in g.GetPoints()])
        geometry.FlattenTo2D()

        new_feature = ogr.Feature(layer_defn)
        new_feature.SetGeometry(geometry)
        for k, v in feature.items().items():
            new_feature[k] = v
        new_feature[self.ATTRIBUTE] = elevation
        self.layer.CreateFeature(new_feature)

    def __exit__(self, type, value, traceback):
        pass

def convert(source_path, target_path, output_format):
    """ Convert shapefile to inp file."""

    source_dataset = ogr.Open(str(source_path))

    writers = dict(
        inp=InputFileWriter,
        img=ImageWriter,
        shp=ShapefileWriter,
    )
    with writers[output_format](target_path) as writer:
        for source_layer in source_dataset:
            total = source_layer.GetFeatureCount()
            for count, source_feature in enumerate(source_layer, 1):
                writer.add_feature(source_feature)
                gdal.TermProgress_nocb(count / total)

def main():
    """ Call convert() with commandline args. """
    convert(**vars(get_parser().parse_args()))

if __name__ == '__main__':
    exit(main())


from __future__ import print_function, division
import matplotlib
matplotlib.use('Agg') # Must be before importing matplotlib.pyplot or pylab!
from neuralnilm import Net, RealApplianceSource, BLSTMLayer, SubsampleLayer, DimshuffleLayer
from lasagne.nonlinearities import sigmoid, rectify
from lasagne.objectives import crossentropy, mse
from lasagne.init import Uniform, Normal
from lasagne.layers import LSTMLayer, DenseLayer, Conv1DLayer, ReshapeLayer
from lasagne.updates import adagrad, nesterov_momentum
from functools import partial
import os
from neuralnilm.source import standardise
from neuralnilm.experiment import run_experiment
from neuralnilm.net import TrainingError
import __main__

NAME = os.path.splitext(os.path.split(__main__.__file__)[1])[0]
PATH = "/homes/dk3810/workspace/python/neuralnilm/figures"
SAVE_PLOT_INTERVAL = 250
GRADIENT_STEPS = 100

"""
e103
Discovered that bottom layer is hardly changing.  So will try
just a single lstm layer

e104
standard init
lower learning rate

e106
lower learning rate to 0.001

e108
is e107 but with batch size of 5

e109
Normal(1) for LSTM

e110
* Back to Uniform(5) for LSTM
* Using nntools eb17bd923ef9ff2cacde2e92d7323b4e51bb5f1f
RESULTS: Seems to run fine again!

e111
* Try with nntools head
* peepholes=False
RESULTS: appears to be working well.  Haven't seen a NaN,
even with training rate of 0.1

e112
* n_seq_per_batch = 50

e114
* Trying looking at layer by layer training again.
* Start with single LSTM layer

e115
* Learning rate = 1

"""

def exp_a(name):
    source = RealApplianceSource(
        filename='/data/dk3810/ukdale.h5',
        appliances=[
            ['fridge freezer', 'fridge', 'freezer'],
            'hair straighteners',
            'television'
            # 'dish washer',
            # ['washer dryer', 'washing machine']
        ],
        max_appliance_powers=[300, 500, 200], #, 2500, 2400],
        on_power_thresholds=[20, 20, 20], #, 20, 20],
        max_input_power=1000,
        min_on_durations=[60, 60, 60], #, 1800, 1800],
        window=("2013-06-01", "2014-07-01"),
        seq_length=1000,
        output_one_appliance=False,
        boolean_targets=False,
        min_off_duration=60,
        train_buildings=[1],
        validation_buildings=[1],
        skip_probability=0,
        n_seq_per_batch=50
    )

    net = Net(
        experiment_name=name,
        source=source,
        save_plot_interval=SAVE_PLOT_INTERVAL,
        loss_function=crossentropy,
        updates=partial(nesterov_momentum, learning_rate=1.0),
        layers_config=[
            {
                'type': LSTMLayer,
                'num_units': 50,
                'W_in_to_cell': Uniform(5),
                'gradient_steps': GRADIENT_STEPS,
                'peepholes': False
            },
            {
                'type': DenseLayer,
                'num_units': source.n_outputs,
                'nonlinearity': sigmoid
            }
        ]
    )
    return net

def init_experiment(experiment):
    full_exp_name = NAME + experiment
    func_call = 'exp_{:s}(full_exp_name)'.format(experiment)
    print("***********************************")
    print("Preparing", full_exp_name, "...")
    net = eval(func_call)
    return net

def main():
    for experiment in list('a'):
        full_exp_name = NAME + experiment
        path = os.path.join(PATH, full_exp_name)
        try:
            net = init_experiment(experiment)
            run_experiment(net, path, epochs=5000)
        except KeyboardInterrupt:
            break
        except TrainingError as e:
            print("EXCEPTION:", e)

if __name__ == "__main__":
    main()


"""
=======================================================
Comparison of LDA and PCA 2D projection of Iris dataset
=======================================================

The Iris dataset represents 3 kind of Iris flowers (Setosa, Versicolour
and Virginica) with 4 attributes: sepal length, sepal width, petal length
and petal width.

Principal Component Analysis (PCA) applied to this data identifies the
combination of attributes (principal components, or directions in the
feature space) that account for the most variance in the data. Here we
plot the different samples on the 2 first principal components.

Linear Discriminant Analysis (LDA) tries to identify attributes that
account for the most variance *between classes*. In particular,
LDA, in contrast to PCA, is a supervised method, using known class labels.
"""
print(__doc__)

import matplotlib.pyplot as plt

from sklearn import datasets
from sklearn.decomposition import PCA
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis

iris = datasets.load_iris()

X = iris.data
y = iris.target
target_names = iris.target_names

pca = PCA(n_components=2)
X_r = pca.fit(X).transform(X)

lda = LinearDiscriminantAnalysis(n_components=2)
X_r2 = lda.fit(X, y).transform(X)

# Percentage of variance explained for each components
print('explained variance ratio (first two components): %s'
      % str(pca.explained_variance_ratio_))

plt.figure()
colors = ['navy', 'turquoise', 'darkorange']
lw = 2

for color, i, target_name in zip(colors, [0, 1, 2], target_names):
    plt.scatter(X_r[y == i, 0], X_r[y == i, 1], color=color, alpha=.8, lw=lw,
                label=target_name)
plt.legend(loc='best', shadow=False, scatterpoints=1)
plt.title('PCA of IRIS dataset')

plt.figure()
for color, i, target_name in zip(colors, [0, 1, 2], target_names):
    plt.scatter(X_r2[y == i, 0], X_r2[y == i, 1], alpha=.8, color=color,
                label=target_name)
plt.legend(loc='best', shadow=False, scatterpoints=1)
plt.title('LDA of IRIS dataset')

plt.show()


#! /usr/bin/env python
#
# Author: Damian Eads
# Date: April 17, 2008
#
# Copyright (C) 2008 Damian Eads
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
# 2. 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.
#
# 3. The name of the author may not be used to endorse or promote
#    products derived from this software without specific prior
#    written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 __future__ import division, print_function, absolute_import

import numpy as np
from numpy.testing import (TestCase, run_module_suite, dec, assert_raises,
                           assert_allclose, assert_equal, assert_)

from scipy.lib.six import xrange, u

import scipy.cluster.hierarchy
from scipy.cluster.hierarchy import (
    linkage, from_mlab_linkage, to_mlab_linkage, num_obs_linkage, inconsistent,
    cophenet, fclusterdata, fcluster, is_isomorphic, single, leaders,
    correspond, is_monotonic, maxdists, maxinconsts, maxRstat,
    is_valid_linkage, is_valid_im, to_tree, leaves_list, dendrogram)
from scipy.spatial.distance import pdist

import hierarchy_test_data

# Matplotlib is not a scipy dependency but is optionally used in dendrogram, so
# check if it's available
try:
    # import matplotlib
    import matplotlib
    # and set the backend to be Agg (no gui)
    matplotlib.use('Agg')
    # before importing pyplot
    import matplotlib.pyplot as plt
    have_matplotlib = True
except:
    have_matplotlib = False

class TestLinkage(object):
    def test_linkage_empty_distance_matrix(self):
        # Tests linkage(Y) where Y is a 0x4 linkage matrix. Exception expected.
        y = np.zeros((0,))
        assert_raises(ValueError, linkage, y)

    ################### linkage
    def test_linkage_tdist(self):
        for method in ['single', 'complete', 'average', 'weighted', u('single')]:
            yield self.check_linkage_tdist, method

    def check_linkage_tdist(self, method):
        # Tests linkage(Y, method) on the tdist data set.
        Z = linkage(hierarchy_test_data.ytdist, method)
        expectedZ = getattr(hierarchy_test_data, 'linkage_ytdist_' + method)
        assert_allclose(Z, expectedZ, atol=1e-10)

    ################### linkage on Q
    def test_linkage_X(self):
        for method in ['centroid', 'median', 'ward']:
            yield self.check_linkage_q, method

    def check_linkage_q(self, method):
        # Tests linkage(Y, method) on the Q data set.
        Z = linkage(hierarchy_test_data.X, method)
        expectedZ = getattr(hierarchy_test_data, 'linkage_X_' + method)
        assert_allclose(Z, expectedZ, atol=1e-06)

class TestInconsistent(object):
    def test_inconsistent_tdist(self):
        for depth in hierarchy_test_data.inconsistent_ytdist:
            yield self.check_inconsistent_tdist, depth

    def check_inconsistent_tdist(self, depth):
        Z = hierarchy_test_data.linkage_ytdist_single
        assert_allclose(inconsistent(Z, depth),
                        hierarchy_test_data.inconsistent_ytdist[depth])

class TestCopheneticDistance(object):
    def test_linkage_cophenet_tdist_Z(self):
        # Tests cophenet(Z) on tdist data set.
        expectedM = np.array([268, 295, 255, 255, 295, 295, 268, 268, 295, 295,
                              295, 138, 219, 295, 295])
        Z = hierarchy_test_data.linkage_ytdist_single
        M = cophenet(Z)
        assert_allclose(M, expectedM, atol=1e-10)

    def test_linkage_cophenet_tdist_Z_Y(self):
        # Tests cophenet(Z, Y) on tdist data set.
        Z = hierarchy_test_data.linkage_ytdist_single
        (c, M) = cophenet(Z, hierarchy_test_data.ytdist)
        expectedM = np.array([268, 295, 255, 255, 295, 295, 268, 268, 295, 295,
                              295, 138, 219, 295, 295])
        expectedc = 0.639931296433393415057366837573
        assert_allclose(c, expectedc, atol=1e-10)
        assert_allclose(M, expectedM, atol=1e-10)

class TestMLabLinkageConversion(object):
    def test_mlab_linkage_conversion_empty(self):
        # Tests from/to_mlab_linkage on empty linkage array.
        X = np.asarray([])
        assert_equal(from_mlab_linkage([]), X)
        assert_equal(to_mlab_linkage([]), X)

    def test_mlab_linkage_conversion_single_row(self):
        # Tests from/to_mlab_linkage on linkage array with single row.
        Z = np.asarray([[0., 1., 3., 2.]])
        Zm = [[1, 2, 3]]
        assert_equal(from_mlab_linkage(Zm), Z)
        assert_equal(to_mlab_linkage(Z), Zm)

    def test_mlab_linkage_conversion_multiple_rows(self):
        # Tests from/to_mlab_linkage on linkage array with multiple rows.
        Zm = np.asarray([[3, 6, 138], [4, 5, 219],
                         [1, 8, 255], [2, 9, 268], [7, 10, 295]])
        Z = np.array([[2., 5., 138., 2.],
                      [3., 4., 219., 2.],
                      [0., 7., 255., 3.],
                      [1., 8., 268., 4.],
                      [6., 9., 295., 6.]],
                      dtype=np.double)
        assert_equal(from_mlab_linkage(Zm), Z)
        assert_equal(to_mlab_linkage(Z), Zm)

class TestFcluster(object):
    def test_fclusterdata(self):
        for t in hierarchy_test_data.fcluster_inconsistent:
            yield self.check_fclusterdata, t, 'inconsistent'
        for t in hierarchy_test_data.fcluster_distance:
            yield self.check_fclusterdata, t, 'distance'
        for t in hierarchy_test_data.fcluster_maxclust:
            yield self.check_fclusterdata, t, 'maxclust'

    def check_fclusterdata(self, t, criterion):
        # Tests fclusterdata(X, criterion=criterion, t=t) on a random 3-cluster data set.
        expectedT = getattr(hierarchy_test_data, 'fcluster_' + criterion)[t]
        X = hierarchy_test_data.Q_X
        T = fclusterdata(X, criterion=criterion, t=t)
        assert_(is_isomorphic(T, expectedT))

    def test_fcluster(self):
        for t in hierarchy_test_data.fcluster_inconsistent:
            yield self.check_fcluster, t, 'inconsistent'
        for t in hierarchy_test_data.fcluster_distance:
            yield self.check_fcluster, t, 'distance'
        for t in hierarchy_test_data.fcluster_maxclust:
            yield self.check_fcluster, t, 'maxclust'

    def check_fcluster(self, t, criterion):
        # Tests fcluster(Z, criterion=criterion, t=t) on a random 3-cluster data set.
        expectedT = getattr(hierarchy_test_data, 'fcluster_' + criterion)[t]
        Z = single(hierarchy_test_data.Q_X)
        T = fcluster(Z, criterion=criterion, t=t)
        assert_(is_isomorphic(T, expectedT))

    def test_fcluster_monocrit(self):
        for t in hierarchy_test_data.fcluster_distance:
            yield self.check_fcluster_monocrit, t
        for t in hierarchy_test_data.fcluster_maxclust:
            yield self.check_fcluster_maxclust_monocrit, t

    def check_fcluster_monocrit(self, t):
        expectedT = hierarchy_test_data.fcluster_distance[t]
        Z = single(hierarchy_test_data.Q_X)
        T = fcluster(Z, t, criterion='monocrit', monocrit=maxdists(Z))
        assert_(is_isomorphic(T, expectedT))

    def check_fcluster_maxclust_monocrit(self, t):
        expectedT = hierarchy_test_data.fcluster_maxclust[t]
        Z = single(hierarchy_test_data.Q_X)
        T = fcluster(Z, t, criterion='maxclust_monocrit', monocrit=maxdists(Z))
        assert_(is_isomorphic(T, expectedT))

class TestLeaders(object):
    def test_leaders_single(self):
        # Tests leaders using a flat clustering generated by single linkage.
        X = hierarchy_test_data.Q_X
        Y = pdist(X)
        Z = linkage(Y)
        T = fcluster(Z, criterion='maxclust', t=3)
        Lright = (np.array([53, 55, 56]), np.array([2, 3, 1]))
        L = leaders(Z, T)
        assert_equal(L, Lright)

class TestIsIsomorphic(object):
    def test_is_isomorphic_1(self):
        # Tests is_isomorphic on test case #1 (one flat cluster, different labellings)
        a = [1, 1, 1]
        b = [2, 2, 2]
        assert_(is_isomorphic(a, b))
        assert_(is_isomorphic(b, a))

    def test_is_isomorphic_2(self):
        # Tests is_isomorphic on test case #2 (two flat clusters, different labelings)
        a = [1, 7, 1]
        b = [2, 3, 2]
        assert_(is_isomorphic(a, b))
        assert_(is_isomorphic(b, a))

    def test_is_isomorphic_3(self):
        # Tests is_isomorphic on test case #3 (no flat clusters)
        a = []
        b = []
        assert_(is_isomorphic(a, b))

    def test_is_isomorphic_4A(self):
        # Tests is_isomorphic on test case #4A (3 flat clusters, different labelings, isomorphic)
        a = [1, 2, 3]
        b = [1, 3, 2]
        assert_(is_isomorphic(a, b))
        assert_(is_isomorphic(b, a))

    def test_is_isomorphic_4B(self):
        # Tests is_isomorphic on test case #4B (3 flat clusters, different labelings, nonisomorphic)
        a = [1, 2, 3, 3]
        b = [1, 3, 2, 3]
        assert_(is_isomorphic(a, b) == False)
        assert_(is_isomorphic(b, a) == False)

    def test_is_isomorphic_4C(self):
        # Tests is_isomorphic on test case #4C (3 flat clusters, different labelings, isomorphic)
        a = [7, 2, 3]
        b = [6, 3, 2]
        assert_(is_isomorphic(a, b))
        assert_(is_isomorphic(b, a))

    def test_is_isomorphic_5(self):
        # Tests is_isomorphic on test case #5 (1000 observations, 2/3/5 random
        # clusters, random permutation of the labeling).
        for nc in [2, 3, 5]:
            yield self.help_is_isomorphic_randperm, 1000, nc

    def test_is_isomorphic_6(self):
        # Tests is_isomorphic on test case #5A (1000 observations, 2/3/5 random
        # clusters, random permutation of the labeling, slightly
        # nonisomorphic.)
        for nc in [2, 3, 5]:
            yield self.help_is_isomorphic_randperm, 1000, nc, True, 5

    def help_is_isomorphic_randperm(self, nobs, nclusters, noniso=False, nerrors=0):
        for k in range(3):
            a = np.int_(np.random.rand(nobs) * nclusters)
            b = np.zeros(a.size, dtype=np.int_)
            P = np.random.permutation(nclusters)
            for i in xrange(0, a.shape[0]):
                b[i] = P[a[i]]
            if noniso:
                Q = np.random.permutation(nobs)
                b[Q[0:nerrors]] += 1
                b[Q[0:nerrors]] %= nclusters
            assert_(is_isomorphic(a, b) == (not noniso))
            assert_(is_isomorphic(b, a) == (not noniso))

class TestIsValidLinkage(object):
    def test_is_valid_linkage_various_size(self):
        for nrow, ncol, valid in [(2, 5, False), (2, 3, False),
                                  (1, 4, True), (2, 4, True)]:
            yield self.check_is_valid_linkage_various_size, nrow, ncol, valid

    def check_is_valid_linkage_various_size(self, nrow, ncol, valid):
        # Tests is_valid_linkage(Z) with linkage matrics of various sizes
        Z = np.asarray([[0, 1, 3.0, 2, 5],
                        [3, 2, 4.0, 3, 3]], dtype=np.double)
        Z = Z[:nrow, :ncol]
        assert_(is_valid_linkage(Z) == valid)
        if not valid:
            assert_raises(ValueError, is_valid_linkage, Z, throw=True)

    def test_is_valid_linkage_int_type(self):
        # Tests is_valid_linkage(Z) with integer type.
        Z = np.asarray([[0, 1, 3.0, 2],
                        [3, 2, 4.0, 3]], dtype=np.int)
        assert_(is_valid_linkage(Z) == False)
        assert_raises(TypeError, is_valid_linkage, Z, throw=True)

    def test_is_valid_linkage_empty(self):
        # Tests is_valid_linkage(Z) with empty linkage.
        Z = np.zeros((0, 4), dtype=np.double)
        assert_(is_valid_linkage(Z) == False)
        assert_raises(ValueError, is_valid_linkage, Z, throw=True)

    def test_is_valid_linkage_4_and_up(self):
        # Tests is_valid_linkage(Z) on linkage on observation sets between
        # sizes 4 and 15 (step size 3).
        for i in xrange(4, 15, 3):
            y = np.random.rand(i*(i-1)//2)
            Z = linkage(y)
            assert_(is_valid_linkage(Z) == True)

    def test_is_valid_linkage_4_and_up_neg_index_left(self):
        # Tests is_valid_linkage(Z) on linkage on observation sets between
        # sizes 4 and 15 (step size 3) with negative indices (left).
        for i in xrange(4, 15, 3):
            y = np.random.rand(i*(i-1)//2)
            Z = linkage(y)
            Z[i//2,0] = -2
            assert_(is_valid_linkage(Z) == False)
            assert_raises(ValueError, is_valid_linkage, Z, throw=True)

    def test_is_valid_linkage_4_and_up_neg_index_right(self):
        # Tests is_valid_linkage(Z) on linkage on observation sets between
        # sizes 4 and 15 (step size 3) with negative indices (right).
        for i in xrange(4, 15, 3):
            y = np.random.rand(i*(i-1)//2)
            Z = linkage(y)
            Z[i//2,1] = -2
            assert_(is_valid_linkage(Z) == False)
            assert_raises(ValueError, is_valid_linkage, Z, throw=True)

    def test_is_valid_linkage_4_and_up_neg_dist(self):
        # Tests is_valid_linkage(Z) on linkage on observation sets between
        # sizes 4 and 15 (step size 3) with negative distances.
        for i in xrange(4, 15, 3):
            y = np.random.rand(i*(i-1)//2)
            Z = linkage(y)
            Z[i//2,2] = -0.5
            assert_(is_valid_linkage(Z) == False)
            assert_raises(ValueError, is_valid_linkage, Z, throw=True)

    def test_is_valid_linkage_4_and_up_neg_counts(self):
        # Tests is_valid_linkage(Z) on linkage on observation sets between
        # sizes 4 and 15 (step size 3) with negative counts.
        for i in xrange(4, 15, 3):
            y = np.random.rand(i*(i-1)//2)
            Z = linkage(y)
            Z[i//2,3] = -2
            assert_(is_valid_linkage(Z) == False)
            assert_raises(ValueError, is_valid_linkage, Z, throw=True)

class TestIsValidInconsistent(object):
    def test_is_valid_im_int_type(self):
        # Tests is_valid_im(R) with integer type.
        R = np.asarray([[0, 1, 3.0, 2],
                        [3, 2, 4.0, 3]], dtype=np.int)
        assert_(is_valid_im(R) == False)
        assert_raises(TypeError, is_valid_im, R, throw=True)

    def test_is_valid_im_various_size(self):
        for nrow, ncol, valid in [(2, 5, False), (2, 3, False),
                                  (1, 4, True), (2, 4, True)]:
            yield self.check_is_valid_im_various_size, nrow, ncol, valid

    def check_is_valid_im_various_size(self, nrow, ncol, valid):
        # Tests is_valid_im(R) with linkage matrics of various sizes
        R = np.asarray([[0, 1, 3.0, 2, 5],
                        [3, 2, 4.0, 3, 3]], dtype=np.double)
        R = R[:nrow, :ncol]
        assert_(is_valid_im(R) == valid)
        if not valid:
            assert_raises(ValueError, is_valid_im, R, throw=True)

    def test_is_valid_im_empty(self):
        # Tests is_valid_im(R) with empty inconsistency matrix.
        R = np.zeros((0, 4), dtype=np.double)
        assert_(is_valid_im(R) == False)
        assert_raises(ValueError, is_valid_im, R, throw=True)

    def test_is_valid_im_4_and_up(self):
        # Tests is_valid_im(R) on im on observation sets between sizes 4 and 15
        # (step size 3).
        for i in xrange(4, 15, 3):
            y = np.random.rand(i*(i-1)//2)
            Z = linkage(y)
            R = inconsistent(Z)
            assert_(is_valid_im(R) == True)

    def test_is_valid_im_4_and_up_neg_index_left(self):
        # Tests is_valid_im(R) on im on observation sets between sizes 4 and 15
        # (step size 3) with negative link height means.
        for i in xrange(4, 15, 3):
            y = np.random.rand(i*(i-1)//2)
            Z = linkage(y)
            R = inconsistent(Z)
            R[i//2,0] = -2.0
            assert_(is_valid_im(R) == False)
            assert_raises(ValueError, is_valid_im, R, throw=True)

    def test_is_valid_im_4_and_up_neg_index_right(self):
        # Tests is_valid_im(R) on im on observation sets between sizes 4 and 15
        # (step size 3) with negative link height standard deviations.
        for i in xrange(4, 15, 3):
            y = np.random.rand(i*(i-1)//2)
            Z = linkage(y)
            R = inconsistent(Z)
            R[i//2,1] = -2.0
            assert_(is_valid_im(R) == False)
            assert_raises(ValueError, is_valid_im, R, throw=True)

    def test_is_valid_im_4_and_up_neg_dist(self):
        # Tests is_valid_im(R) on im on observation sets between sizes 4 and 15
        # (step size 3) with negative link counts.
        for i in xrange(4, 15, 3):
            y = np.random.rand(i*(i-1)//2)
            Z = linkage(y)
            R = inconsistent(Z)
            R[i//2,2] = -0.5
            assert_(is_valid_im(R) == False)
            assert_raises(ValueError, is_valid_im, R, throw=True)

class TestNumObsLinkage(TestCase):
    def test_num_obs_linkage_empty(self):
        # Tests num_obs_linkage(Z) with empty linkage.
        Z = np.zeros((0, 4), dtype=np.double)
        self.assertRaises(ValueError, num_obs_linkage, Z)

    def test_num_obs_linkage_1x4(self):
        # Tests num_obs_linkage(Z) on linkage over 2 observations.
        Z = np.asarray([[0, 1, 3.0, 2]], dtype=np.double)
        self.assertTrue(num_obs_linkage(Z) == 2)

    def test_num_obs_linkage_2x4(self):
        # Tests num_obs_linkage(Z) on linkage over 3 observations.
        Z = np.asarray([[0, 1, 3.0, 2],
                        [3, 2, 4.0, 3]], dtype=np.double)
        self.assertTrue(num_obs_linkage(Z) == 3)

    def test_num_obs_linkage_4_and_up(self):
        # Tests num_obs_linkage(Z) on linkage on observation sets between sizes
        # 4 and 15 (step size 3).
        for i in xrange(4, 15, 3):
            y = np.random.rand(i*(i-1)//2)
            Z = linkage(y)
            self.assertTrue(num_obs_linkage(Z) == i)

class TestLeavesList(object):
    def test_leaves_list_1x4(self):
        # Tests leaves_list(Z) on a 1x4 linkage.
        Z = np.asarray([[0, 1, 3.0, 2]], dtype=np.double)
        to_tree(Z)
        assert_equal(leaves_list(Z), [0, 1])

    def test_leaves_list_2x4(self):
        # Tests leaves_list(Z) on a 2x4 linkage.
        Z = np.asarray([[0, 1, 3.0, 2],
                        [3, 2, 4.0, 3]], dtype=np.double)
        to_tree(Z)
        assert_equal(leaves_list(Z), [0, 1, 2])

    def test_leaves_list_Q(self):
        for method in ['single', 'complete', 'average', 'weighted', 'centroid',
                       'median', 'ward']:
            yield self.check_leaves_list_Q, method

    def check_leaves_list_Q(self, method):
        # Tests leaves_list(Z) on the Q data set
        X = hierarchy_test_data.Q_X
        Z = linkage(X, method)
        node = to_tree(Z)
        assert_equal(node.pre_order(), leaves_list(Z))

    def test_Q_subtree_pre_order(self):
        # Tests that pre_order() works when called on sub-trees.
        X = hierarchy_test_data.Q_X
        Z = linkage(X, 'single')
        node = to_tree(Z)
        assert_equal(node.pre_order(), (node.get_left().pre_order()
                                        + node.get_right().pre_order()))

class TestCorrespond(TestCase):
    def test_correspond_empty(self):
        # Tests correspond(Z, y) with empty linkage and condensed distance matrix.
        y = np.zeros((0,))
        Z = np.zeros((0,4))
        self.assertRaises(ValueError, correspond, Z, y)

    def test_correspond_2_and_up(self):
        # Tests correspond(Z, y) on linkage and CDMs over observation sets of
        # different sizes.
        for i in xrange(2, 4):
            y = np.random.rand(i*(i-1)//2)
            Z = linkage(y)
            self.assertTrue(correspond(Z, y))
        for i in xrange(4, 15, 3):
            y = np.random.rand(i*(i-1)//2)
            Z = linkage(y)
            self.assertTrue(correspond(Z, y))

    def test_correspond_4_and_up(self):
        # Tests correspond(Z, y) on linkage and CDMs over observation sets of
        # different sizes. Correspondance should be false.
        for (i, j) in (list(zip(list(range(2, 4)), list(range(3, 5)))) +
                       list(zip(list(range(3, 5)), list(range(2, 4))))):
            y = np.random.rand(i*(i-1)//2)
            y2 = np.random.rand(j*(j-1)//2)
            Z = linkage(y)
            Z2 = linkage(y2)
            self.assertTrue(correspond(Z, y2) == False)
            self.assertTrue(correspond(Z2, y) == False)

    def test_correspond_4_and_up_2(self):
        # Tests correspond(Z, y) on linkage and CDMs over observation sets of
        # different sizes. Correspondance should be false.
        for (i, j) in (list(zip(list(range(2, 7)), list(range(16, 21)))) +
                       list(zip(list(range(2, 7)), list(range(16, 21))))):
            y = np.random.rand(i*(i-1)//2)
            y2 = np.random.rand(j*(j-1)//2)
            Z = linkage(y)
            Z2 = linkage(y2)
            self.assertTrue(correspond(Z, y2) == False)
            self.assertTrue(correspond(Z2, y) == False)

    def test_num_obs_linkage_multi_matrix(self):
        # Tests num_obs_linkage with observation matrices of multiple sizes.
        for n in xrange(2, 10):
            X = np.random.rand(n, 4)
            Y = pdist(X)
            Z = linkage(Y)
            self.assertTrue(num_obs_linkage(Z) == n)

class TestIsMonotonic(TestCase):
    def test_is_monotonic_empty(self):
        # Tests is_monotonic(Z) on an empty linkage.
        Z = np.zeros((0, 4))
        self.assertRaises(ValueError, is_monotonic, Z)

    def test_is_monotonic_1x4(self):
        # Tests is_monotonic(Z) on 1x4 linkage. Expecting True.
        Z = np.asarray([[0, 1, 0.3, 2]], dtype=np.double)
        self.assertTrue(is_monotonic(Z) == True)

    def test_is_monotonic_2x4_T(self):
        # Tests is_monotonic(Z) on 2x4 linkage. Expecting True.
        Z = np.asarray([[0, 1, 0.3, 2],
                        [2, 3, 0.4, 3]], dtype=np.double)
        self.assertTrue(is_monotonic(Z) == True)

    def test_is_monotonic_2x4_F(self):
        # Tests is_monotonic(Z) on 2x4 linkage. Expecting False.
        Z = np.asarray([[0, 1, 0.4, 2],
                        [2, 3, 0.3, 3]], dtype=np.double)
        self.assertTrue(is_monotonic(Z) == False)

    def test_is_monotonic_3x4_T(self):
        # Tests is_monotonic(Z) on 3x4 linkage. Expecting True.
        Z = np.asarray([[0, 1, 0.3, 2],
                        [2, 3, 0.4, 2],
                        [4, 5, 0.6, 4]], dtype=np.double)
        self.assertTrue(is_monotonic(Z) == True)

    def test_is_monotonic_3x4_F1(self):
        # Tests is_monotonic(Z) on 3x4 linkage (case 1). Expecting False.
        Z = np.asarray([[0, 1, 0.3, 2],
                        [2, 3, 0.2, 2],
                        [4, 5, 0.6, 4]], dtype=np.double)
        self.assertTrue(is_monotonic(Z) == False)

    def test_is_monotonic_3x4_F2(self):
        # Tests is_monotonic(Z) on 3x4 linkage (case 2). Expecting False.
        Z = np.asarray([[0, 1, 0.8, 2],
                        [2, 3, 0.4, 2],
                        [4, 5, 0.6, 4]], dtype=np.double)
        self.assertTrue(is_monotonic(Z) == False)

    def test_is_monotonic_3x4_F3(self):
        # Tests is_monotonic(Z) on 3x4 linkage (case 3). Expecting False
        Z = np.asarray([[0, 1, 0.3, 2],
                        [2, 3, 0.4, 2],
                        [4, 5, 0.2, 4]], dtype=np.double)
        self.assertTrue(is_monotonic(Z) == False)

    def test_is_monotonic_tdist_linkage1(self):
        # Tests is_monotonic(Z) on clustering generated by single linkage on
        # tdist data set. Expecting True.
        Z = linkage(hierarchy_test_data.ytdist, 'single')
        self.assertTrue(is_monotonic(Z) == True)

    def test_is_monotonic_tdist_linkage2(self):
        # Tests is_monotonic(Z) on clustering generated by single linkage on
        # tdist data set. Perturbing. Expecting False.
        Z = linkage(hierarchy_test_data.ytdist, 'single')
        Z[2,2] = 0.0
        self.assertTrue(is_monotonic(Z) == False)

    def test_is_monotonic_Q_linkage(self):
        # Tests is_monotonic(Z) on clustering generated by single linkage on
        # Q data set. Expecting True.
        X = hierarchy_test_data.Q_X
        Z = linkage(X, 'single')
        self.assertTrue(is_monotonic(Z) == True)

class TestMaxDists(object):
    def test_maxdists_empty_linkage(self):
        # Tests maxdists(Z) on empty linkage. Expecting exception.
        Z = np.zeros((0, 4), dtype=np.double)
        assert_raises(ValueError, maxdists, Z)

    def test_maxdists_one_cluster_linkage(self):
        # Tests maxdists(Z) on linkage with one cluster.
        Z = np.asarray([[0, 1, 0.3, 4]], dtype=np.double)
        MD = maxdists(Z)
        expectedMD = calculate_maximum_distances(Z)
        assert_allclose(MD, expectedMD, atol=1e-15)

    def test_maxdists_Q_linkage(self):
        for method in ['single', 'complete', 'ward', 'centroid', 'median']:
            yield self.check_maxdists_Q_linkage, method

    def check_maxdists_Q_linkage(self, method):
        # Tests maxdists(Z) on the Q data set
        X = hierarchy_test_data.Q_X
        Z = linkage(X, method)
        MD = maxdists(Z)
        expectedMD = calculate_maximum_distances(Z)
        assert_allclose(MD, expectedMD, atol=1e-15)

class TestMaxInconsts(object):
    def test_maxinconsts_empty_linkage(self):
        # Tests maxinconsts(Z, R) on empty linkage. Expecting exception.
        Z = np.zeros((0, 4), dtype=np.double)
        R = np.zeros((0, 4), dtype=np.double)
        assert_raises(ValueError, maxinconsts, Z, R)

    def test_maxinconsts_difrow_linkage(self):
        # Tests maxinconsts(Z, R) on linkage and inconsistency matrices with
        # different numbers of clusters. Expecting exception.
        Z = np.asarray([[0, 1, 0.3, 4]], dtype=np.double)
        R = np.random.rand(2, 4)
        assert_raises(ValueError, maxinconsts, Z, R)

    def test_maxinconsts_one_cluster_linkage(self):
        # Tests maxinconsts(Z, R) on linkage with one cluster.
        Z = np.asarray([[0, 1, 0.3, 4]], dtype=np.double)
        R = np.asarray([[0, 0, 0, 0.3]], dtype=np.double)
        MD = maxinconsts(Z, R)
        expectedMD = calculate_maximum_inconsistencies(Z, R)
        assert_allclose(MD, expectedMD, atol=1e-15)

    def test_maxinconsts_Q_linkage(self):
        for method in ['single', 'complete', 'ward', 'centroid', 'median']:
            yield self.check_maxinconsts_Q_linkage, method

    def check_maxinconsts_Q_linkage(self, method):
        # Tests maxinconsts(Z, R) on the Q data set
        X = hierarchy_test_data.Q_X
        Z = linkage(X, method)
        R = inconsistent(Z)
        MD = maxinconsts(Z, R)
        expectedMD = calculate_maximum_inconsistencies(Z, R)
        assert_allclose(MD, expectedMD, atol=1e-15)

class TestMaxRStat(object):
    def test_maxRstat_invalid_index(self):
        for i in [3.3, -1, 4]:
            yield self.check_maxRstat_invalid_index, i

    def check_maxRstat_invalid_index(self, i):
        # Tests maxRstat(Z, R, i). Expecting exception.
        Z = np.asarray([[0, 1, 0.3, 4]], dtype=np.double)
        R = np.asarray([[0, 0, 0, 0.3]], dtype=np.double)
        if isinstance(i, int):
            assert_raises(ValueError, maxRstat, Z, R, i)
        else:
            assert_raises(TypeError, maxRstat, Z, R, i)

    def test_maxRstat_empty_linkage(self):
        for i in range(4):
            yield self.check_maxRstat_empty_linkage, i

    def check_maxRstat_empty_linkage(self, i):
        # Tests maxRstat(Z, R, i) on empty linkage. Expecting exception.
        Z = np.zeros((0, 4), dtype=np.double)
        R = np.zeros((0, 4), dtype=np.double)
        assert_raises(ValueError, maxRstat, Z, R, i)

    def test_maxRstat_difrow_linkage(self):
        for i in range(4):
            yield self.check_maxRstat_difrow_linkage, i

    def check_maxRstat_difrow_linkage(self, i):
        # Tests maxRstat(Z, R, i) on linkage and inconsistency matrices with
        # different numbers of clusters. Expecting exception.
        Z = np.asarray([[0, 1, 0.3, 4]], dtype=np.double)
        R = np.random.rand(2, 4)
        assert_raises(ValueError, maxRstat, Z, R, i)

    def test_maxRstat_one_cluster_linkage(self):
        for i in range(4):
            yield self.check_maxRstat_one_cluster_linkage, i

    def check_maxRstat_one_cluster_linkage(self, i):
        # Tests maxRstat(Z, R, i) on linkage with one cluster.
        Z = np.asarray([[0, 1, 0.3, 4]], dtype=np.double)
        R = np.asarray([[0, 0, 0, 0.3]], dtype=np.double)
        MD = maxRstat(Z, R, 1)
        expectedMD = calculate_maximum_inconsistencies(Z, R, 1)
        assert_allclose(MD, expectedMD, atol=1e-15)

    def test_maxRstat_Q_linkage(self):
        for method in ['single', 'complete', 'ward', 'centroid', 'median']:
            for i in range(4):
                yield self.check_maxRstat_Q_linkage, method, i

    def check_maxRstat_Q_linkage(self, method, i):
        # Tests maxRstat(Z, R, i) on the Q data set
        X = hierarchy_test_data.Q_X
        Z = linkage(X, method)
        R = inconsistent(Z)
        MD = maxRstat(Z, R, 1)
        expectedMD = calculate_maximum_inconsistencies(Z, R, 1)
        assert_allclose(MD, expectedMD, atol=1e-15)

class TestDendrogram(object):
    def test_dendrogram_single_linkage_tdist(self):
        # Tests dendrogram calculation on single linkage of the tdist data set.
        Z = linkage(hierarchy_test_data.ytdist, 'single')
        R = dendrogram(Z, no_plot=True)
        leaves = R["leaves"]
        assert_equal(leaves, [2, 5, 1, 0, 3, 4])

    def test_valid_orientation(self):
        Z = linkage(hierarchy_test_data.ytdist, 'single')
        assert_raises(ValueError, dendrogram, Z, orientation="foo")

    @dec.skipif(not have_matplotlib)
    def test_dendrogram_plot(self):
        for orientation in ['top', 'bottom', 'left', 'right']:
            yield self.check_dendrogram_plot, orientation

    def check_dendrogram_plot(self, orientation):
        # Tests dendrogram plotting.
        Z = linkage(hierarchy_test_data.ytdist, 'single')
        expected = {'color_list': ['g', 'b', 'b', 'b', 'b'],
                    'dcoord': [[0.0, 138.0, 138.0, 0.0],
                               [0.0, 219.0, 219.0, 0.0],
                               [0.0, 255.0, 255.0, 219.0],
                               [0.0, 268.0, 268.0, 255.0],
                               [138.0, 295.0, 295.0, 268.0]],
                    'icoord': [[5.0, 5.0, 15.0, 15.0],
                               [45.0, 45.0, 55.0, 55.0],
                               [35.0, 35.0, 50.0, 50.0],
                               [25.0, 25.0, 42.5, 42.5],
                               [10.0, 10.0, 33.75, 33.75]],
                    'ivl': ['2', '5', '1', '0', '3', '4'],
                    'leaves': [2, 5, 1, 0, 3, 4]}

        fig = plt.figure()
        ax = fig.add_subplot(111)

        # test that dendrogram accepts ax keyword
        R1 = dendrogram(Z, ax=ax, orientation=orientation)
        plt.close()
        assert_equal(R1, expected)

        # test plotting to gca (will import pylab)
        R2 = dendrogram(Z, orientation=orientation)
        plt.close()
        assert_equal(R2, expected)

    @dec.skipif(not have_matplotlib)
    def test_dendrogram_truncate_mode(self):
        Z = linkage(hierarchy_test_data.ytdist, 'single')

        R = dendrogram(Z, 2, 'lastp', show_contracted=True)
        plt.close()
        assert_equal(R, {'color_list': ['b'],
                         'dcoord': [[0.0, 295.0, 295.0, 0.0]],
                         'icoord': [[5.0, 5.0, 15.0, 15.0]],
                         'ivl': ['(2)', '(4)'],
                         'leaves': [6, 9]})

        R = dendrogram(Z, 2, 'mtica', show_contracted=True)
        plt.close()
        assert_equal(R, {'color_list': ['g', 'b', 'b', 'b'],
                         'dcoord': [[0.0, 138.0, 138.0, 0.0],
                                    [0.0, 255.0, 255.0, 0.0],
                                    [0.0, 268.0, 268.0, 255.0],
                                    [138.0, 295.0, 295.0, 268.0]],
                         'icoord': [[5.0, 5.0, 15.0, 15.0],
                                    [35.0, 35.0, 45.0, 45.0],
                                    [25.0, 25.0, 40.0, 40.0],
                                    [10.0, 10.0, 32.5, 32.5]],
                         'ivl': ['2', '5', '1', '0', '(2)'],
                         'leaves': [2, 5, 1, 0, 7]})

def calculate_maximum_distances(Z):
    # Used for testing correctness of maxdists.
    n = Z.shape[0] + 1
    B = np.zeros((n-1,))
    q = np.zeros((3,))
    for i in xrange(0, n - 1):
        q[:] = 0.0
        left = Z[i, 0]
        right = Z[i, 1]
        if left >= n:
            q[0] = B[int(left) - n]
        if right >= n:
            q[1] = B[int(right) - n]
        q[2] = Z[i, 2]
        B[i] = q.max()
    return B

def calculate_maximum_inconsistencies(Z, R, k=3):
    # Used for testing correctness of maxinconsts.
    n = Z.shape[0] + 1
    B = np.zeros((n-1,))
    q = np.zeros((3,))
    for i in xrange(0, n - 1):
        q[:] = 0.0
        left = Z[i, 0]
        right = Z[i, 1]
        if left >= n:
            q[0] = B[int(left) - n]
        if right >= n:
            q[1] = B[int(right) - n]
        q[2] = R[i, k]
        B[i] = q.max()
    return B

def test_euclidean_linkage_value_error():
    for method in scipy.cluster.hierarchy._cpy_euclid_methods:
        assert_raises(ValueError,
                linkage, [[1, 1], [1, 1]], method=method, metric='cityblock')

def test_2x2_linkage():
    Z1 = linkage([1], method='single', metric='euclidean')
    Z2 = linkage([[0, 1], [0, 0]], method='single', metric='euclidean')
    assert_allclose(Z1, Z2)

if __name__ == "__main__":
    run_module_suite()


# Authors: Lars Buitinck
#          Dan Blanchard <dblanchard@ets.org>
# License: BSD 3 clause

from array import array
from collections import Mapping
from operator import itemgetter

import numpy as np
import scipy.sparse as sp

from ..base import BaseEstimator, TransformerMixin
from ..externals import six
from ..externals.six.moves import xrange
from ..utils import check_array, tosequence
from ..utils.fixes import frombuffer_empty

def _tosequence(X):
    """Turn X into a sequence or ndarray, avoiding a copy if possible."""
    if isinstance(X, Mapping):  # single sample
        return [X]
    else:
        return tosequence(X)

class DictVectorizer(BaseEstimator, TransformerMixin):
    """Transforms lists of feature-value mappings to vectors.

    This transformer turns lists of mappings (dict-like objects) of feature
    names to feature values into Numpy arrays or scipy.sparse matrices for use
    with scikit-learn estimators.

    When feature values are strings, this transformer will do a binary one-hot
    (aka one-of-K) coding: one boolean-valued feature is constructed for each
    of the possible string values that the feature can take on. For instance,
    a feature "f" that can take on the values "ham" and "spam" will become two
    features in the output, one signifying "f=ham", the other "f=spam".

    Features that do not occur in a sample (mapping) will have a zero value
    in the resulting array/matrix.

    Read more in the :ref:`User Guide <dict_feature_extraction>`.

    Parameters
    ----------
    dtype : callable, optional
        The type of feature values. Passed to Numpy array/scipy.sparse matrix
        constructors as the dtype argument.
    separator: string, optional
        Separator string used when constructing new features for one-hot
        coding.
    sparse: boolean, optional.
        Whether transform should produce scipy.sparse matrices.
        True by default.
    sort: boolean, optional.
        Whether ``feature_names_`` and ``vocabulary_`` should be sorted when fitting.
        True by default.

    Attributes
    ----------
    vocabulary_ : dict
        A dictionary mapping feature names to feature indices.

    feature_names_ : list
        A list of length n_features containing the feature names (e.g., "f=ham"
        and "f=spam").

    Examples
    --------
    >>> from sklearn.feature_extraction import DictVectorizer
    >>> v = DictVectorizer(sparse=False)
    >>> D = [{'foo': 1, 'bar': 2}, {'foo': 3, 'baz': 1}]
    >>> X = v.fit_transform(D)
    >>> X
    array([[ 2.,  0.,  1.],
           [ 0.,  1.,  3.]])
    >>> v.inverse_transform(X) == \
        [{'bar': 2.0, 'foo': 1.0}, {'baz': 1.0, 'foo': 3.0}]
    True
    >>> v.transform({'foo': 4, 'unseen_feature': 3})
    array([[ 0.,  0.,  4.]])

    See also
    --------
    FeatureHasher : performs vectorization using only a hash function.
    sklearn.preprocessing.OneHotEncoder : handles nominal/categorical features
      encoded as columns of integers.
    """

    def __init__(self, dtype=np.float64, separator="=", sparse=True,
                 sort=True):
        self.dtype = dtype
        self.separator = separator
        self.sparse = sparse
        self.sort = sort

    def fit(self, X, y=None):
        """Learn a list of feature name -> indices mappings.

        Parameters
        ----------
        X : Mapping or iterable over Mappings
            Dict(s) or Mapping(s) from feature names (arbitrary Python
            objects) to feature values (strings or convertible to dtype).
        y : (ignored)

        Returns
        -------
        self
        """
        feature_names = []
        vocab = {}

        for x in X:
            for f, v in six.iteritems(x):
                if isinstance(v, six.string_types):
                    f = "%s%s%s" % (f, self.separator, v)
                if f not in vocab:
                    feature_names.append(f)
                    vocab[f] = len(vocab)

        if self.sort:
            feature_names.sort()
            vocab = dict((f, i) for i, f in enumerate(feature_names))

        self.feature_names_ = feature_names
        self.vocabulary_ = vocab

        return self

    def _transform(self, X, fitting):
        # Sanity check: Python's array has no way of explicitly requesting the
        # signed 32-bit integers that scipy.sparse needs, so we use the next
        # best thing: typecode "i" (int). However, if that gives larger or
        # smaller integers than 32-bit ones, np.frombuffer screws up.
        assert array("i").itemsize == 4, (
            "sizeof(int) != 4 on your platform; please report this at"
            " https://github.com/scikit-learn/scikit-learn/issues and"
            " include the output from platform.platform() in your bug report")

        dtype = self.dtype
        if fitting:
            feature_names = []
            vocab = {}
        else:
            feature_names = self.feature_names_
            vocab = self.vocabulary_

        # Process everything as sparse regardless of setting
        X = [X] if isinstance(X, Mapping) else X

        indices = array("i")
        indptr = array("i", [0])
        # XXX we could change values to an array.array as well, but it
        # would require (heuristic) conversion of dtype to typecode...
        values = []

        # collect all the possible feature names and build sparse matrix at
        # same time
        for x in X:
            for f, v in six.iteritems(x):
                if isinstance(v, six.string_types):
                    f = "%s%s%s" % (f, self.separator, v)
                    v = 1
                if f in vocab:
                    indices.append(vocab[f])
                    values.append(dtype(v))
                else:
                    if fitting:
                        feature_names.append(f)
                        vocab[f] = len(vocab)
                        indices.append(vocab[f])
                        values.append(dtype(v))

            indptr.append(len(indices))

        if len(indptr) == 1:
            raise ValueError("Sample sequence X is empty.")

        indices = frombuffer_empty(indices, dtype=np.intc)
        indptr = np.frombuffer(indptr, dtype=np.intc)
        shape = (len(indptr) - 1, len(vocab))

        result_matrix = sp.csr_matrix((values, indices, indptr),
                                      shape=shape, dtype=dtype)

        # Sort everything if asked
        if fitting and self.sort:
            feature_names.sort()
            map_index = np.empty(len(feature_names), dtype=np.int32)
            for new_val, f in enumerate(feature_names):
                map_index[new_val] = vocab[f]
                vocab[f] = new_val
            result_matrix = result_matrix[:, map_index]

        if self.sparse:
            result_matrix.sort_indices()
        else:
            result_matrix = result_matrix.toarray()

        if fitting:
            self.feature_names_ = feature_names
            self.vocabulary_ = vocab

        return result_matrix

    def fit_transform(self, X, y=None):
        """Learn a list of feature name -> indices mappings and transform X.

        Like fit(X) followed by transform(X), but does not require
        materializing X in memory.

        Parameters
        ----------
        X : Mapping or iterable over Mappings
            Dict(s) or Mapping(s) from feature names (arbitrary Python
            objects) to feature values (strings or convertible to dtype).
        y : (ignored)

        Returns
        -------
        Xa : {array, sparse matrix}
            Feature vectors; always 2-d.
        """
        return self._transform(X, fitting=True)

    def inverse_transform(self, X, dict_type=dict):
        """Transform array or sparse matrix X back to feature mappings.

        X must have been produced by this DictVectorizer's transform or
        fit_transform method; it may only have passed through transformers
        that preserve the number of features and their order.

        In the case of one-hot/one-of-K coding, the constructed feature
        names and values are returned rather than the original ones.

        Parameters
        ----------
        X : {array-like, sparse matrix}, shape = [n_samples, n_features]
            Sample matrix.
        dict_type : callable, optional
            Constructor for feature mappings. Must conform to the
            collections.Mapping API.

        Returns
        -------
        D : list of dict_type objects, length = n_samples
            Feature mappings for the samples in X.
        """
        # COO matrix is not subscriptable
        X = check_array(X, accept_sparse=['csr', 'csc'])
        n_samples = X.shape[0]

        names = self.feature_names_
        dicts = [dict_type() for _ in xrange(n_samples)]

        if sp.issparse(X):
            for i, j in zip(*X.nonzero()):
                dicts[i][names[j]] = X[i, j]
        else:
            for i, d in enumerate(dicts):
                for j, v in enumerate(X[i, :]):
                    if v != 0:
                        d[names[j]] = X[i, j]

        return dicts

    def transform(self, X, y=None):
        """Transform feature->value dicts to array or sparse matrix.

        Named features not encountered during fit or fit_transform will be
        silently ignored.

        Parameters
        ----------
        X : Mapping or iterable over Mappings, length = n_samples
            Dict(s) or Mapping(s) from feature names (arbitrary Python
            objects) to feature values (strings or convertible to dtype).
        y : (ignored)

        Returns
        -------
        Xa : {array, sparse matrix}
            Feature vectors; always 2-d.
        """
        if self.sparse:
            return self._transform(X, fitting=False)

        else:
            dtype = self.dtype
            vocab = self.vocabulary_
            X = _tosequence(X)
            Xa = np.zeros((len(X), len(vocab)), dtype=dtype)

            for i, x in enumerate(X):
                for f, v in six.iteritems(x):
                    if isinstance(v, six.string_types):
                        f = "%s%s%s" % (f, self.separator, v)
                        v = 1
                    try:
                        Xa[i, vocab[f]] = dtype(v)
                    except KeyError:
                        pass

            return Xa

    def get_feature_names(self):
        """Returns a list of feature names, ordered by their indices.

        If one-of-K coding is applied to categorical features, this will
        include the constructed feature names but not the original ones.
        """
        return self.feature_names_

    def restrict(self, support, indices=False):
        """Restrict the features to those in support using feature selection.

        This function modifies the estimator in-place.

        Parameters
        ----------
        support : array-like
            Boolean mask or list of indices (as returned by the get_support
            member of feature selectors).
        indices : boolean, optional
            Whether support is a list of indices.

        Returns
        -------
        self

        Examples
        --------
        >>> from sklearn.feature_extraction import DictVectorizer
        >>> from sklearn.feature_selection import SelectKBest, chi2
        >>> v = DictVectorizer()
        >>> D = [{'foo': 1, 'bar': 2}, {'foo': 3, 'baz': 1}]
        >>> X = v.fit_transform(D)
        >>> support = SelectKBest(chi2, k=2).fit(X, [0, 1])
        >>> v.get_feature_names()
        ['bar', 'baz', 'foo']
        >>> v.restrict(support.get_support()) # doctest: +ELLIPSIS
        DictVectorizer(dtype=..., separator='=', sort=True,
                sparse=True)
        >>> v.get_feature_names()
        ['bar', 'foo']
        """
        if not indices:
            support = np.where(support)[0]

        names = self.feature_names_
        new_vocab = {}
        for i in support:
            new_vocab[names[i]] = len(new_vocab)

        self.vocabulary_ = new_vocab
        self.feature_names_ = [f for f, i in sorted(six.iteritems(new_vocab),
                                                    key=itemgetter(1))]

        return self


"""
Functions which are common and require SciPy Base and Level 1 SciPy
(special, linalg)
"""

from __future__ import division, print_function, absolute_import

from scipy.lib.six.moves import xrange

from numpy import exp, log, asarray, arange, newaxis, hstack, product, array, \
                  where, zeros, extract, place, pi, sqrt, eye, poly1d, dot, \
                  r_, rollaxis, sum, fromstring

__all__ = ['logsumexp', 'factorial','factorial2','factorialk','comb',
           'central_diff_weights', 'derivative', 'pade', 'lena', 'ascent', 'face']

# XXX: the factorial functions could move to scipy.special, and the others
# to numpy perhaps?

def logsumexp(a, axis=None, b=None):
    """Compute the log of the sum of exponentials of input elements.

    Parameters
    ----------
    a : array_like
        Input array.
    axis : int, optional
        Axis over which the sum is taken. By default `axis` is None,
        and all elements are summed.

        .. versionadded:: 0.11.0
    b : array-like, optional
        Scaling factor for exp(`a`) must be of the same shape as `a` or
        broadcastable to `a`.

        .. versionadded:: 0.12.0

    Returns
    -------
    res : ndarray
        The result, ``np.log(np.sum(np.exp(a)))`` calculated in a numerically
        more stable way. If `b` is given then ``np.log(np.sum(b*np.exp(a)))``
        is returned.

    See Also
    --------
    numpy.logaddexp, numpy.logaddexp2

    Notes
    -----
    Numpy has a logaddexp function which is very similar to `logsumexp`, but
    only handles two arguments. `logaddexp.reduce` is similar to this
    function, but may be less stable.

    Examples
    --------
    >>> from scipy.misc import logsumexp
    >>> a = np.arange(10)
    >>> np.log(np.sum(np.exp(a)))
    9.4586297444267107
    >>> logsumexp(a)
    9.4586297444267107

    With weights

    >>> a = np.arange(10)
    >>> b = np.arange(10, 0, -1)
    >>> logsumexp(a, b=b)
    9.9170178533034665
    >>> np.log(np.sum(b*np.exp(a)))
    9.9170178533034647
    """
    a = asarray(a)
    if axis is None:
        a = a.ravel()
    else:
        a = rollaxis(a, axis)
    a_max = a.max(axis=0)
    if b is not None:
        b = asarray(b)
        if axis is None:
            b = b.ravel()
        else:
            b = rollaxis(b, axis)
        out = log(sum(b * exp(a - a_max), axis=0))
    else:
        out = log(sum(exp(a - a_max), axis=0))
    out += a_max
    return out

def factorial(n,exact=0):
    """
    The factorial function, n! = special.gamma(n+1).

    If exact is 0, then floating point precision is used, otherwise
    exact long integer is computed.

    - Array argument accepted only for exact=0 case.
    - If n<0, the return value is 0.

    Parameters
    ----------
    n : int or array_like of ints
        Calculate ``n!``.  Arrays are only supported with `exact` set
        to False.  If ``n < 0``, the return value is 0.
    exact : bool, optional
        The result can be approximated rapidly using the gamma-formula
        above.  If `exact` is set to True, calculate the
        answer exactly using integer arithmetic. Default is False.

    Returns
    -------
    nf : float or int
        Factorial of `n`, as an integer or a float depending on `exact`.

    Examples
    --------
    >>> arr = np.array([3,4,5])
    >>> sc.factorial(arr, exact=False)
    array([   6.,   24.,  120.])
    >>> sc.factorial(5, exact=True)
    120L

    """
    if exact:
        if n < 0:
            return 0
        val = 1
        for k in xrange(1,n+1):
            val *= k
        return val
    else:
        from scipy import special
        n = asarray(n)
        sv = special.errprint(0)
        vals = special.gamma(n+1)
        sv = special.errprint(sv)
        return where(n>=0,vals,0)

def factorial2(n, exact=False):
    """
    Double factorial.

    This is the factorial with every second value skipped, i.e.,
    ``7!! = 7 * 5 * 3 * 1``.  It can be approximated numerically as::

      n!! = special.gamma(n/2+1)*2**((m+1)/2)/sqrt(pi)  n odd
          = 2**(n/2) * (n/2)!                           n even

    Parameters
    ----------
    n : int or array_like
        Calculate ``n!!``.  Arrays are only supported with `exact` set
        to False.  If ``n < 0``, the return value is 0.
    exact : bool, optional
        The result can be approximated rapidly using the gamma-formula
        above (default).  If `exact` is set to True, calculate the
        answer exactly using integer arithmetic.

    Returns
    -------
    nff : float or int
        Double factorial of `n`, as an int or a float depending on
        `exact`.

    Examples
    --------
    >>> factorial2(7, exact=False)
    array(105.00000000000001)
    >>> factorial2(7, exact=True)
    105L

    """
    if exact:
        if n < -1:
            return 0
        if n <= 0:
            return 1
        val = 1
        for k in xrange(n,0,-2):
            val *= k
        return val
    else:
        from scipy import special
        n = asarray(n)
        vals = zeros(n.shape,'d')
        cond1 = (n % 2) & (n >= -1)
        cond2 = (1-(n % 2)) & (n >= -1)
        oddn = extract(cond1,n)
        evenn = extract(cond2,n)
        nd2o = oddn / 2.0
        nd2e = evenn / 2.0
        place(vals,cond1,special.gamma(nd2o+1)/sqrt(pi)*pow(2.0,nd2o+0.5))
        place(vals,cond2,special.gamma(nd2e+1) * pow(2.0,nd2e))
        return vals

def factorialk(n,k,exact=1):
    """
    n(!!...!)  = multifactorial of order k
    k times

    Parameters
    ----------
    n : int, array_like
        Calculate multifactorial. Arrays are only supported with exact
        set to False. If `n` < 0, the return value is 0.
    exact : bool, optional
        If exact is set to True, calculate the answer exactly using
        integer arithmetic.

    Returns
    -------
    val : int
        Multi factorial of `n`.

    Raises
    ------
    NotImplementedError
        Raises when exact is False

    Examples
    --------
    >>> sc.factorialk(5, 1, exact=True)
    120L
    >>> sc.factorialk(5, 3, exact=True)
    10L

    """
    if exact:
        if n < 1-k:
            return 0
        if n<=0:
            return 1
        val = 1
        for j in xrange(n,0,-k):
            val = val*j
        return val
    else:
        raise NotImplementedError

def comb(N,k,exact=0):
    """
    The number of combinations of N things taken k at a time.

    This is often expressed as "N choose k".

    Parameters
    ----------
    N : int, ndarray
        Number of things.
    k : int, ndarray
        Number of elements taken.
    exact : int, optional
        If `exact` is 0, then floating point precision is used, otherwise
        exact long integer is computed.

    Returns
    -------
    val : int, ndarray
        The total number of combinations.

    Notes
    -----
    - Array arguments accepted only for exact=0 case.
    - If k > N, N < 0, or k < 0, then a 0 is returned.

    Examples
    --------
    >>> k = np.array([3, 4])
    >>> n = np.array([10, 10])
    >>> sc.comb(n, k, exact=False)
    array([ 120.,  210.])
    >>> sc.comb(10, 3, exact=True)
    120L

    """
    if exact:
        if (k > N) or (N < 0) or (k < 0):
            return 0
        val = 1
        for j in xrange(min(k, N-k)):
            val = (val*(N-j))//(j+1)
        return val
    else:
        from scipy import special
        k,N = asarray(k), asarray(N)
        lgam = special.gammaln
        cond = (k <= N) & (N >= 0) & (k >= 0)
        sv = special.errprint(0)
        vals = exp(lgam(N+1) - lgam(N-k+1) - lgam(k+1))
        sv = special.errprint(sv)
        return where(cond, vals, 0.0)

def central_diff_weights(Np, ndiv=1):
    """
    Return weights for an Np-point central derivative.

    Assumes equally-spaced function points.

    If weights are in the vector w, then
    derivative is w[0] * f(x-ho*dx) + ... + w[-1] * f(x+h0*dx)

    Parameters
    ----------
    Np : int
        Number of points for the central derivative.
    ndiv : int, optional
        Number of divisions.  Default is 1.

    Notes
    -----
    Can be inaccurate for large number of points.

    """
    if Np < ndiv + 1:
        raise ValueError("Number of points must be at least the derivative order + 1.")
    if Np % 2 == 0:
        raise ValueError("The number of points must be odd.")
    from scipy import linalg
    ho = Np >> 1
    x = arange(-ho,ho+1.0)
    x = x[:,newaxis]
    X = x**0.0
    for k in range(1,Np):
        X = hstack([X,x**k])
    w = product(arange(1,ndiv+1),axis=0)*linalg.inv(X)[ndiv]
    return w

def derivative(func, x0, dx=1.0, n=1, args=(), order=3):
    """
    Find the n-th derivative of a function at a point.

    Given a function, use a central difference formula with spacing `dx` to
    compute the `n`-th derivative at `x0`.

    Parameters
    ----------
    func : function
        Input function.
    x0 : float
        The point at which `n`-th derivative is found.
    dx : int, optional
        Spacing.
    n : int, optional
        Order of the derivative. Default is 1.
    args : tuple, optional
        Arguments
    order : int, optional
        Number of points to use, must be odd.

    Notes
    -----
    Decreasing the step size too small can result in round-off error.

    Examples
    --------
    >>> def x2(x):
    ...     return x*x
    ...
    >>> derivative(x2, 2)
    4.0

    """
    if order < n + 1:
        raise ValueError("'order' (the number of points used to compute the derivative), "
                         "must be at least the derivative order 'n' + 1.")
    if order % 2 == 0:
        raise ValueError("'order' (the number of points used to compute the derivative) "
                         "must be odd.")
    # pre-computed for n=1 and 2 and low-order for speed.
    if n==1:
        if order == 3:
            weights = array([-1,0,1])/2.0
        elif order == 5:
            weights = array([1,-8,0,8,-1])/12.0
        elif order == 7:
            weights = array([-1,9,-45,0,45,-9,1])/60.0
        elif order == 9:
            weights = array([3,-32,168,-672,0,672,-168,32,-3])/840.0
        else:
            weights = central_diff_weights(order,1)
    elif n==2:
        if order == 3:
            weights = array([1,-2.0,1])
        elif order == 5:
            weights = array([-1,16,-30,16,-1])/12.0
        elif order == 7:
            weights = array([2,-27,270,-490,270,-27,2])/180.0
        elif order == 9:
            weights = array([-9,128,-1008,8064,-14350,8064,-1008,128,-9])/5040.0
        else:
            weights = central_diff_weights(order,2)
    else:
        weights = central_diff_weights(order, n)
    val = 0.0
    ho = order >> 1
    for k in range(order):
        val += weights[k]*func(x0+(k-ho)*dx,*args)
    return val / product((dx,)*n,axis=0)

def pade(an, m):
    """
    Return Pade approximation to a polynomial as the ratio of two polynomials.

    Parameters
    ----------
    an : (N,) array_like
        Taylor series coefficients.
    m : int
        The order of the returned approximating polynomials.

    Returns
    -------
    p, q : Polynomial class
        The pade approximation of the polynomial defined by `an` is
        `p(x)/q(x)`.

    Examples
    --------
    >>> from scipy import misc
    >>> e_exp = [1.0, 1.0, 1.0/2.0, 1.0/6.0, 1.0/24.0, 1.0/120.0]
    >>> p, q = misc.pade(e_exp, 2)

    >>> e_exp.reverse()
    >>> e_poly = np.poly1d(e_exp)

    Compare ``e_poly(x)`` and the pade approximation ``p(x)/q(x)``

    >>> e_poly(1)
    2.7166666666666668

    >>> p(1)/q(1)
    2.7179487179487181

    """
    from scipy import linalg
    an = asarray(an)
    N = len(an) - 1
    n = N - m
    if n < 0:
        raise ValueError("Order of q <m> must be smaller than len(an)-1.")
    Akj = eye(N+1, n+1)
    Bkj = zeros((N+1, m), 'd')
    for row in range(1, m+1):
        Bkj[row,:row] = -(an[:row])[::-1]
    for row in range(m+1, N+1):
        Bkj[row,:] = -(an[row-m:row])[::-1]
    C = hstack((Akj, Bkj))
    pq = linalg.solve(C, an)
    p = pq[:n+1]
    q = r_[1.0, pq[n+1:]]
    return poly1d(p[::-1]), poly1d(q[::-1])

def lena():
    """
    Get classic image processing example image, Lena, at 8-bit grayscale
    bit-depth, 512 x 512 size.

    Parameters
    ----------
    None

    Returns
    -------
    lena : ndarray
        Lena image

    Examples
    --------
    >>> import scipy.misc
    >>> lena = scipy.misc.lena()
    >>> lena.shape
    (512, 512)
    >>> lena.max()
    245
    >>> lena.dtype
    dtype('int32')

    >>> import matplotlib.pyplot as plt
    >>> plt.gray()
    >>> plt.imshow(lena)
    >>> plt.show()

    """
    import pickle, os
    fname = os.path.join(os.path.dirname(__file__),'lena.dat')
    f = open(fname,'rb')
    lena = array(pickle.load(f))
    f.close()
    return lena

def ascent():
    """
    Get an 8-bit grayscale bit-depth, 512 x 512 derived image for easy use in demos

    The image is derived from accent-to-the-top.jpg at
    http://www.public-domain-image.com/people-public-domain-images-pictures/

    Parameters
    ----------
    None

    Returns
    -------
    ascent : ndarray
       convenient image to use for testing and demonstration

    Examples
    --------
    >>> import scipy.misc
    >>> ascent = scipy.misc.ascent()
    >>> ascent.shape
    (512, 512)
    >>> ascent.max()
    255

    >>> import matplotlib.pyplot as plt
    >>> plt.gray()
    >>> plt.imshow(ascent)
    >>> plt.show()

    """
    import pickle, os
    fname = os.path.join(os.path.dirname(__file__),'ascent.dat')
    f = open(fname,'rb')
    ascent = array(pickle.load(f))
    f.close()
    return ascent

def face(gray=False):
    """
    Get a 1024 x 768, color image of a raccoon face.

    raccoon-procyon-lotor.jpg at http://www.public-domain-image.com

    Parameters
    ----------
    gray : bool, optional
        If True then return color image, otherwise return an 8-bit gray-scale

    Returns
    -------
    face : ndarray
        image of a racoon face

    Examples
    --------
    >>> import scipy.misc
    >>> face = scipy.misc.face()
    >>> face.shape
    (768, 1024, 3)
    >>> face.max()
    230
    >>> face.dtype
    dtype('uint8')

    >>> import matplotlib.pyplot as plt
    >>> plt.gray()
    >>> plt.imshow(face)
    >>> plt.show()

    """
    import bz2, os
    rawdata = open(os.path.join(os.path.dirname(__file__), 'face.dat')).read()
    data = bz2.decompress(rawdata)
    face = fromstring(data, dtype='uint8')
    face.shape = (768, 1024, 3)
    if gray is True:
        face = (0.21 * face[:,:,0] + 0.71 * face[:,:,1] + 0.07 * face[:,:,2]).astype('uint8')
    return face