# Copyright 2016 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Client for interacting with the `Google Stackdriver Monitoring API (V3)`_.

Example::

    >>> from google.cloud import monitoring
    >>> client = monitoring.Client()
    >>> query = client.query(minutes=5)
    >>> print(query.as_dataframe())  # Requires pandas.

At present, the client supports querying of time series, metric descriptors,
and monitored resource descriptors.

.. _Google Stackdriver Monitoring API (V3):
    https://cloud.google.com/monitoring/api/v3/
"""

import datetime

from google.cloud._helpers import _datetime_to_rfc3339
from google.cloud.client import ClientWithProject

from google.cloud.monitoring._http import Connection
from google.cloud.monitoring.group import Group
from google.cloud.monitoring.metric import Metric
from google.cloud.monitoring.metric import MetricDescriptor
from google.cloud.monitoring.metric import MetricKind
from google.cloud.monitoring.metric import ValueType
from google.cloud.monitoring.query import Query
from google.cloud.monitoring.resource import Resource
from google.cloud.monitoring.resource import ResourceDescriptor
from google.cloud.monitoring.timeseries import Point
from google.cloud.monitoring.timeseries import TimeSeries

_UTCNOW = datetime.datetime.utcnow  # To be replaced by tests.

class Client(ClientWithProject):
    """Client to bundle configuration needed for API requests.

    :type project: str
    :param project: The target project. If not passed, falls back to the
                    default inferred from the environment.

    :type credentials: :class:`~google.auth.credentials.Credentials`
    :param credentials: (Optional) The OAuth2 Credentials to use for this
                        client. If not passed (and if no ``_http`` object is
                        passed), falls back to the default inferred from the
                        environment.

    :type _http: :class:`~requests.Session`
    :param _http: (Optional) HTTP object to make requests. Can be any object
                  that defines ``request()`` with the same interface as
                  :meth:`requests.Session.request`. If not passed, an
                  ``_http`` object is created that is bound to the
                  ``credentials`` for the current object.
                  This parameter should be considered private, and could
                  change in the future.
    """

    SCOPE = ('https://www.googleapis.com/auth/monitoring.read',
             'https://www.googleapis.com/auth/monitoring',
             'https://www.googleapis.com/auth/cloud-platform')
    """The scopes required for authenticating as a Monitoring consumer."""

    def __init__(self, project=None, credentials=None, _http=None):
        super(Client, self).__init__(
            project=project, credentials=credentials, _http=_http)
        self._connection = Connection(self)

    def query(self,
              metric_type=Query.DEFAULT_METRIC_TYPE,
              end_time=None,
              days=0, hours=0, minutes=0):
        """Construct a query object for retrieving metric data.

        Example::

            >>> query = client.query(minutes=5)
            >>> print(query.as_dataframe())  # Requires pandas.

        :type metric_type: str
        :param metric_type: The metric type name. The default value is
            :data:`Query.DEFAULT_METRIC_TYPE
            <google.cloud.monitoring.query.Query.DEFAULT_METRIC_TYPE>`,
            but please note that this default value is provided only for
            demonstration purposes and is subject to change. See the
            `supported metrics`_.

        :type end_time: :class:`datetime.datetime`
        :param end_time:
            (Optional) The end time (inclusive) of the time interval
            for which results should be returned, as a datetime object.
            The default is the start of the current minute.

            The start time (exclusive) is determined by combining the
            values of  ``days``, ``hours``, and ``minutes``, and
            subtracting the resulting duration from the end time.

            It is also allowed to omit the end time and duration here,
            in which case
            :meth:`~google.cloud.monitoring.query.Query.select_interval`
            must be called before the query is executed.

        :type days: int
        :param days: The number of days in the time interval.

        :type hours: int
        :param hours: The number of hours in the time interval.

        :type minutes: int
        :param minutes: The number of minutes in the time interval.

        :rtype: :class:`~google.cloud.monitoring.query.Query`
        :returns: The query object.

        :raises: :exc:`ValueError` if ``end_time`` is specified but
            ``days``, ``hours``, and ``minutes`` are all zero.
            If you really want to specify a point in time, use
            :meth:`~google.cloud.monitoring.query.Query.select_interval`.

        .. _supported metrics: https://cloud.google.com/monitoring/api/metrics
        """
        return Query(self, metric_type,
                     end_time=end_time,
                     days=days, hours=hours, minutes=minutes)

    def metric_descriptor(self, type_,
                          metric_kind=MetricKind.METRIC_KIND_UNSPECIFIED,
                          value_type=ValueType.VALUE_TYPE_UNSPECIFIED,
                          labels=(), unit='', description='', display_name=''):
        """Construct a metric descriptor object.

        Metric descriptors specify the schema for a particular metric type.

        This factory method is used most often in conjunction with the metric
        descriptor
        :meth:`~google.cloud.monitoring.metric.MetricDescriptor.create`
        method to define custom metrics::

            >>> descriptor = client.metric_descriptor(
            ...     'custom.googleapis.com/my_metric',
            ...     metric_kind=MetricKind.GAUGE,
            ...     value_type=ValueType.DOUBLE,
            ...     description='This is a simple example of a custom metric.')
            >>> descriptor.create()

        Here is an example where the custom metric is parameterized by a
        metric label::

            >>> label = LabelDescriptor('response_code', LabelValueType.INT64,
            ...                         description='HTTP status code')
            >>> descriptor = client.metric_descriptor(
            ...     'custom.googleapis.com/my_app/response_count',
            ...     metric_kind=MetricKind.CUMULATIVE,
            ...     value_type=ValueType.INT64,
            ...     labels=[label],
            ...     description='Cumulative count of HTTP responses.')
            >>> descriptor.create()

        :type type_: str
        :param type_:
            The metric type including a DNS name prefix. For example:
            ``"custom.googleapis.com/my_metric"``

        :type metric_kind: str
        :param metric_kind:
            The kind of measurement. It must be one of
            :data:`MetricKind.GAUGE`, :data:`MetricKind.DELTA`,
            or :data:`MetricKind.CUMULATIVE`.
            See :class:`~google.cloud.monitoring.metric.MetricKind`.

        :type value_type: str
        :param value_type:
            The value type of the metric. It must be one of
            :data:`ValueType.BOOL`, :data:`ValueType.INT64`,
            :data:`ValueType.DOUBLE`, :data:`ValueType.STRING`,
            or :data:`ValueType.DISTRIBUTION`.
            See :class:`ValueType`.

        :type labels:
            list of :class:`~google.cloud.monitoring.label.LabelDescriptor`
        :param labels:
            A sequence of zero or more label descriptors specifying the labels
            used to identify a specific instance of this metric.

        :type unit: str
        :param unit: An optional unit in which the metric value is reported.

        :type description: str
        :param description: An optional detailed description of the metric.

        :type display_name: str
        :param display_name: An optional concise name for the metric.

        :rtype: :class:`MetricDescriptor`
        :returns: The metric descriptor created with the passed-in arguments.
        """
        return MetricDescriptor(
            self, type_,
            metric_kind=metric_kind,
            value_type=value_type,
            labels=labels,
            unit=unit,
            description=description,
            display_name=display_name,
        )

    @staticmethod
    def metric(type_, labels):
        """Factory for constructing metric objects.

        :class:`~google.cloud.monitoring.metric.Metric` objects are typically
        created to write custom metric values. The type should match the
        metric type specified in the
        :class:`~google.cloud.monitoring.metric.MetricDescriptor` used to
        create the custom metric::

             >>> metric = client.metric('custom.googleapis.com/my_metric',
             ...                        labels={
             ...                            'status': 'successful',
             ...                         })

        :type type_: str
        :param type_: The metric type name.

        :type labels: dict
        :param labels: A mapping from label names to values for all labels
                       enumerated in the associated
                       :class:`~.metric.MetricDescriptor`.

        :rtype: :class:`~google.cloud.monitoring.metric.Metric`
        :returns: The metric object.
        """
        return Metric(type=type_, labels=labels)

    @staticmethod
    def resource(type_, labels):
        """Factory for constructing monitored resource objects.

        A monitored resource object (
        :class:`~google.cloud.monitoring.resource.Resource`) is
        typically used to create a
        :class:`~google.cloud.monitoring.timeseries.TimeSeries` object.

        For a list of possible monitored resource types and their associated
        labels, see:

        https://cloud.google.com/monitoring/api/resources

        :type type_: str
        :param type_: The monitored resource type name.

        :type labels: dict
        :param labels: A mapping from label names to values for all labels
                       enumerated in the associated
                       :class:`~.resource.ResourceDescriptor`,
                       except that ``project_id`` can and should be omitted
                       when writing time series data.

        :rtype: :class:`~google.cloud.monitoring.resource.Resource`
        :returns: A monitored resource object.
        """
        return Resource(type_, labels)

    @staticmethod
    def time_series(metric, resource, value,
                    end_time=None, start_time=None):
        """Construct a time series object for a single data point.

        .. note::

           While :class:`~google.cloud.monitoring.timeseries.TimeSeries`
           objects returned by the API typically have multiple data points,
           :class:`~google.cloud.monitoring.timeseries.TimeSeries` objects
           sent to the API must have at most one point.

        For example::

            >>> timeseries = client.time_series(metric, resource, 1.23,
            ...                                 end_time=end)

        For more information, see:

        https://cloud.google.com/monitoring/api/ref_v3/rest/v3/TimeSeries

        :type metric: :class:`~google.cloud.monitoring.metric.Metric`
        :param metric: A :class:`~google.cloud.monitoring.metric.Metric`.

        :type resource: :class:`~google.cloud.monitoring.resource.Resource`
        :param resource: A :class:`~google.cloud.monitoring.resource.Resource`
                         object.

        :type value: bool, int, string, or float
        :param value:
            The value of the data point to create for the
            :class:`~google.cloud.monitoring.timeseries.TimeSeries`.

            .. note::

               The Python type of the value will determine the
               :class:`~ValueType` sent to the API, which must match the value
               type specified in the metric descriptor. For example, a Python
               float will be sent to the API as a :data:`ValueType.DOUBLE`.

        :type end_time: :class:`~datetime.datetime`
        :param end_time:
            The end time for the point to be included in the time series.
            Assumed to be UTC if no time zone information is present.
            Defaults to the current time, as obtained by calling
            :meth:`datetime.datetime.utcnow`.

        :type start_time: :class:`~datetime.datetime`
        :param start_time:
            The start time for the point to be included in the time series.
            Assumed to be UTC if no time zone information is present.
            Defaults to None. If the start time is unspecified,
            the API interprets the start time to be the same as the end time.

        :rtype: :class:`~google.cloud.monitoring.timeseries.TimeSeries`
        :returns: A time series object.
        """
        if end_time is None:
            end_time = _UTCNOW()

        end_time = _datetime_to_rfc3339(end_time, ignore_zone=False)
        if start_time:
            start_time = _datetime_to_rfc3339(start_time, ignore_zone=False)

        point = Point(value=value, start_time=start_time, end_time=end_time)
        return TimeSeries(metric=metric, resource=resource, metric_kind=None,
                          value_type=None, points=[point])

    def fetch_metric_descriptor(self, metric_type):
        """Look up a metric descriptor by type.

        Example::

            >>> METRIC = 'compute.googleapis.com/instance/cpu/utilization'
            >>> print(client.fetch_metric_descriptor(METRIC))

        :type metric_type: str
        :param metric_type: The metric type name.

        :rtype: :class:`~google.cloud.monitoring.metric.MetricDescriptor`
        :returns: The metric descriptor instance.

        :raises: :class:`google.cloud.exceptions.NotFound` if the metric
                 descriptor is not found.
        """
        return MetricDescriptor._fetch(self, metric_type)

    def list_metric_descriptors(self, filter_string=None, type_prefix=None):
        """List all metric descriptors for the project.

        Examples::

            >>> for descriptor in client.list_metric_descriptors():
            ...     print(descriptor.type)

            >>> for descriptor in client.list_metric_descriptors(
            ...         type_prefix='custom.'):
            ...     print(descriptor.type)

        :type filter_string: str
        :param filter_string:
            (Optional) An optional filter expression describing the metric
            descriptors to be returned. See the `filter documentation`_.

        :type type_prefix: str
        :param type_prefix:
            (Optional) An optional prefix constraining the selected metric
            types. This adds ``metric.type = starts_with("<prefix>")`` to the
            filter.

        :rtype:
            list of :class:`~google.cloud.monitoring.metric.MetricDescriptor`
        :returns: A list of metric descriptor instances.

        .. _filter documentation:
            https://cloud.google.com/monitoring/api/v3/filters
        """
        return MetricDescriptor._list(self, filter_string,
                                      type_prefix=type_prefix)

    def fetch_resource_descriptor(self, resource_type):
        """Look up a monitored resource descriptor by type.

        Example::

            >>> print(client.fetch_resource_descriptor('gce_instance'))

        :type resource_type: str
        :param resource_type: The resource type name.

        :rtype: :class:`~google.cloud.monitoring.resource.ResourceDescriptor`
        :returns: The resource descriptor instance.

        :raises: :class:`google.cloud.exceptions.NotFound` if the resource
                 descriptor is not found.
        """
        return ResourceDescriptor._fetch(self, resource_type)

    def list_resource_descriptors(self, filter_string=None):
        """List all monitored resource descriptors for the project.

        Example::

            >>> for descriptor in client.list_resource_descriptors():
            ...     print(descriptor.type)

        :type filter_string: str
        :param filter_string:
            (Optional) An optional filter expression describing the resource
            descriptors to be returned. See the `filter documentation`_.

        :rtype: list of
                :class:`~google.cloud.monitoring.resource.ResourceDescriptor`
        :returns: A list of resource descriptor instances.

        .. _filter documentation:
            https://cloud.google.com/monitoring/api/v3/filters
        """
        return ResourceDescriptor._list(self, filter_string)

    def group(self, group_id=None, display_name=None, parent_id=None,
              filter_string=None, is_cluster=False):
        """Factory constructor for group object.

        .. note::
          This will not make an HTTP request; it simply instantiates
          a group object owned by this client.

        :type group_id: str
        :param group_id: (Optional) The ID of the group.

        :type display_name: str
        :param display_name:
            (Optional) A user-assigned name for this group, used only for
            display purposes.

        :type parent_id: str
        :param parent_id:
            (Optional) The ID of the group's parent, if it has one.

        :type filter_string: str
        :param filter_string:
            (Optional) The filter string used to determine which monitored
            resources belong to this group.

        :type is_cluster: bool
        :param is_cluster:
            If true, the members of this group are considered to be a cluster.
            The system can perform additional analysis on groups that are
            clusters.

        :rtype: :class:`Group`
        :returns: The group created with the passed-in arguments.

        :raises:
            :exc:`ValueError` if both ``group_id`` and ``name`` are specified.
        """
        return Group(
            self,
            group_id=group_id,
            display_name=display_name,
            parent_id=parent_id,
            filter_string=filter_string,
            is_cluster=is_cluster,
        )

    def fetch_group(self, group_id):
        """Fetch a group from the API based on it's ID.

        Example::

            >>> try:
            >>>     group = client.fetch_group('1234')
            >>> except google.cloud.exceptions.NotFound:
            >>>     print('That group does not exist!')

        :type group_id: str
        :param group_id: The ID of the group.

        :rtype: :class:`~google.cloud.monitoring.group.Group`
        :returns: The group instance.

        :raises: :class:`google.cloud.exceptions.NotFound` if the group
                 is not found.
        """
        return Group._fetch(self, group_id)

    def list_groups(self):
        """List all groups for the project.

        Example::

            >>> for group in client.list_groups():
            ...     print((group.display_name, group.name))

        :rtype: list of :class:`~google.cloud.monitoring.group.Group`
        :returns: A list of group instances.
        """
        return Group._list(self)

    def write_time_series(self, timeseries_list):
        """Write a list of time series objects to the API.

        The recommended approach to creating time series objects is using
        the :meth:`~google.cloud.monitoring.client.Client.time_series` factory
        method.

        Example::

            >>> client.write_time_series([ts1, ts2])

        If you only need to write a single time series object, consider using
        the :meth:`~google.cloud.monitoring.client.Client.write_point` method
        instead.

        :type timeseries_list:
            list of :class:`~google.cloud.monitoring.timeseries.TimeSeries`
        :param timeseries_list:
            A list of time series object to be written
            to the API. Each time series must contain exactly one point.
        """
        path = '/projects/{project}/timeSeries/'.format(
            project=self.project)
        timeseries_dict = [timeseries._to_dict()
                           for timeseries in timeseries_list]
        self._connection.api_request(method='POST', path=path,
                                     data={'timeSeries': timeseries_dict})

    def write_point(self, metric, resource, value,
                    end_time=None,
                    start_time=None):
        """Write a single point for a metric to the API.

        This is a convenience method to write a single time series object to
        the API. To write multiple time series objects to the API as a batch
        operation, use the
        :meth:`~google.cloud.monitoring.client.Client.time_series`
        factory method to create time series objects and the
        :meth:`~google.cloud.monitoring.client.Client.write_time_series`
        method to write the objects.

        Example::

            >>> client.write_point(metric, resource, 3.14)

        :type metric: :class:`~google.cloud.monitoring.metric.Metric`
        :param metric: A :class:`~google.cloud.monitoring.metric.Metric`
                       object.

        :type resource: :class:`~google.cloud.monitoring.resource.Resource`
        :param resource: A :class:`~google.cloud.monitoring.resource.Resource`
                         object.

        :type value: bool, int, string, or float
        :param value:
            The value of the data point to create for the
            :class:`~google.cloud.monitoring.timeseries.TimeSeries`.

            .. note::

               The Python type of the value will determine the
               :class:`~ValueType` sent to the API, which must match the value
               type specified in the metric descriptor. For example, a Python
               float will be sent to the API as a :data:`ValueType.DOUBLE`.

        :type end_time: :class:`~datetime.datetime`
        :param end_time:
            The end time for the point to be included in the time series.
            Assumed to be UTC if no time zone information is present.
            Defaults to the current time, as obtained by calling
            :meth:`datetime.datetime.utcnow`.

        :type start_time: :class:`~datetime.datetime`
        :param start_time:
            The start time for the point to be included in the time series.
            Assumed to be UTC if no time zone information is present.
            Defaults to None. If the start time is unspecified,
            the API interprets the start time to be the same as the end time.
        """
        timeseries = self.time_series(
            metric, resource, value, end_time, start_time)
        self.write_time_series([timeseries])


"""
GTK+ Matplotlib interface using cairo (not GDK) drawing operations.
Author: Steve Chaplin
"""
import gtk
if gtk.pygtk_version < (2,7,0):
    import cairo.gtk

from matplotlib.backends import backend_cairo
from matplotlib.backends.backend_gtk import *

backend_version = 'PyGTK(%d.%d.%d) ' % gtk.pygtk_version + \
                  'Pycairo(%s)' % backend_cairo.backend_version

_debug = False
#_debug = True

def new_figure_manager(num, *args, **kwargs):
    """
    Create a new figure manager instance
    """
    if _debug: print 'backend_gtkcairo.%s()' % fn_name()
    FigureClass = kwargs.pop('FigureClass', Figure)
    thisFig = FigureClass(*args, **kwargs)
    canvas = FigureCanvasGTKCairo(thisFig)
    return FigureManagerGTK(canvas, num)

class RendererGTKCairo (backend_cairo.RendererCairo):
    if gtk.pygtk_version >= (2,7,0):
        def set_pixmap (self, pixmap):
            self.gc.ctx = pixmap.cairo_create()
    else:
        def set_pixmap (self, pixmap):
            self.gc.ctx = cairo.gtk.gdk_cairo_create (pixmap)

class FigureCanvasGTKCairo(backend_cairo.FigureCanvasCairo, FigureCanvasGTK):
    filetypes = FigureCanvasGTK.filetypes.copy()
    filetypes.update(backend_cairo.FigureCanvasCairo.filetypes)

    def _renderer_init(self):
        """Override to use cairo (rather than GDK) renderer"""
        if _debug: print '%s.%s()' % (self.__class__.__name__, _fn_name())
        self._renderer = RendererGTKCairo (self.figure.dpi)

class FigureManagerGTKCairo(FigureManagerGTK):
    def _get_toolbar(self, canvas):
        # must be inited after the window, drawingArea and figure
        # attrs are set
        if matplotlib.rcParams['toolbar']=='classic':
            toolbar = NavigationToolbar (canvas, self.window)
        elif matplotlib.rcParams['toolbar']=='toolbar2':
            toolbar = NavigationToolbar2GTKCairo (canvas, self.window)
        else:
            toolbar = None
        return toolbar

class NavigationToolbar2Cairo(NavigationToolbar2GTK):
    def _get_canvas(self, fig):
        return FigureCanvasGTKCairo(fig)


# Copyright 2017 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
# in compliance with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
# or implied. See the License for the specific language governing permissions and limitations under
# the License.
"""Test analyze, training, and prediction.
"""
from __future__ import absolute_import
from __future__ import print_function

import json
import logging
import os
import pandas as pd
import shutil
import six
import sys
import tempfile
import unittest

from . import e2e_functions
from tensorflow.python.lib.io import file_io

sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),
                                             '../../..')))

import mltoolbox.regression.linear as reglinear  # noqa: E402
import google.datalab.ml as dlml  # noqa: E402

class TestLinearRegression(unittest.TestCase):
  """Test linear regression works e2e locally.

  Note that there should be little need for testing the other scenarios (linear
  classification, dnn regression, dnn classification) as they should only
  differ at training time. The training coverage of task.py is already done in
  test_sd_trainer.
  """
  def __init__(self, *args, **kwargs):
    super(TestLinearRegression, self).__init__(*args, **kwargs)

    # Log everything
    self._logger = logging.getLogger('TestStructuredDataLogger')
    self._logger.setLevel(logging.DEBUG)
    if not self._logger.handlers:
      self._logger.addHandler(logging.StreamHandler(stream=sys.stdout))

  def _make_test_files(self):
    """Builds test files and folders"""

    # Make the output folders
    self._test_dir = tempfile.mkdtemp()
    self._preprocess_output = os.path.join(self._test_dir, 'preprocess')
    self._train_output = os.path.join(self._test_dir, 'train')
    self._batch_predict_output = os.path.join(self._test_dir, 'batch_predict')

    # Don't make train_output folder as it should not exist at training time.
    os.mkdir(self._preprocess_output)
    os.mkdir(self._batch_predict_output)

    # Make csv files
    self._csv_train_filename = os.path.join(self._test_dir,
                                            'train_csv_data.csv')
    self._csv_eval_filename = os.path.join(self._test_dir,
                                           'eval_csv_data.csv')
    self._csv_predict_filename = os.path.join(self._test_dir,
                                              'predict_csv_data.csv')
    e2e_functions.make_csv_data(self._csv_train_filename, 100, 'regression',
                                True)
    e2e_functions.make_csv_data(self._csv_eval_filename, 100, 'regression',
                                True)
    self._predict_num_rows = 10
    e2e_functions.make_csv_data(self._csv_predict_filename,
                                self._predict_num_rows, 'regression', False)

    # Make schema file
    self._schema_filename = os.path.join(self._test_dir, 'schema.json')
    e2e_functions.make_preprocess_schema(self._schema_filename, 'regression')

    # Make feature file
    self._input_features_filename = os.path.join(self._test_dir,
                                                 'input_features_file.json')
    transforms = {
        "num1": {"transform": "scale"},
        "num2": {"transform": "scale", "value": 4},
        "str1": {"transform": "one_hot"},
        "str2": {"transform": "embedding", "embedding_dim": 3},
        "target": {"transform": "target"},
        "key": {"transform": "key"},
    }
    file_io.write_string_to_file(
        self._input_features_filename,
        json.dumps(transforms, indent=2))

  def _run_analyze(self):
    reglinear.analyze(
        output_dir=self._preprocess_output,
        dataset=dlml.CsvDataSet(
            file_pattern=self._csv_train_filename,
            schema_file=self._schema_filename))

    self.assertTrue(os.path.isfile(
        os.path.join(self._preprocess_output, 'stats.json')))
    self.assertTrue(os.path.isfile(
        os.path.join(self._preprocess_output, 'vocab_str1.csv')))

  def _run_train(self):
    reglinear.train(
        train_dataset=dlml.CsvDataSet(
            file_pattern=self._csv_train_filename,
            schema_file=self._schema_filename),
        eval_dataset=dlml.CsvDataSet(
            file_pattern=self._csv_eval_filename,
            schema_file=self._schema_filename),
        analysis_dir=self._preprocess_output,
        output_dir=self._train_output,
        features=self._input_features_filename,
        max_steps=100,
        train_batch_size=100)

    self.assertTrue(os.path.isfile(
        os.path.join(self._train_output, 'model', 'saved_model.pb')))
    self.assertTrue(os.path.isfile(
        os.path.join(self._train_output, 'evaluation_model', 'saved_model.pb')))

  def _run_predict(self):
    data = pd.read_csv(self._csv_predict_filename,
                       header=None)
    df = reglinear.predict(data=data,
                           training_dir=self._train_output)

    self.assertEqual(len(df.index), self._predict_num_rows)
    self.assertEqual(list(df), ['key', 'predicted'])

  def _run_batch_prediction(self, output_dir, use_target):
    reglinear.batch_predict(
        training_dir=self._train_output,
        prediction_input_file=(self._csv_eval_filename if use_target
                               else self._csv_predict_filename),
        output_dir=output_dir,
        mode='evaluation' if use_target else 'prediction',
        batch_size=4,
        output_format='csv')

    # check errors file is empty
    errors = file_io.get_matching_files(os.path.join(output_dir, 'errors*'))
    self.assertEqual(len(errors), 1)
    self.assertEqual(os.path.getsize(errors[0]), 0)

    # check predictions files are not empty
    predictions = file_io.get_matching_files(os.path.join(output_dir,
                                                          'predictions*'))
    self.assertGreater(os.path.getsize(predictions[0]), 0)

    # check the schema is correct
    schema_file = os.path.join(output_dir, 'csv_schema.json')
    self.assertTrue(os.path.isfile(schema_file))
    schema = json.loads(file_io.read_file_to_string(schema_file))
    self.assertEqual(schema[0]['name'], 'key')
    self.assertEqual(schema[1]['name'], 'predicted')
    if use_target:
      self.assertEqual(schema[2]['name'], 'target')
      self.assertEqual(len(schema), 3)
    else:
      self.assertEqual(len(schema), 2)

  def _cleanup(self):
    shutil.rmtree(self._test_dir)

  def test_e2e(self):
    try:
      self._make_test_files()
      self._run_analyze()
      self._run_train()
      if six.PY2:
        # Dataflow is only supported by python 2. Prediction assumes Dataflow
        # is installed.
        self._run_predict()
        self._run_batch_prediction(
            os.path.join(self._batch_predict_output, 'with_target'),
            True)
        self._run_batch_prediction(
            os.path.join(self._batch_predict_output, 'without_target'),
            False)
      else:
        print('only tested analyze in TestLinearRegression')
    finally:
      self._cleanup()

if __name__ == '__main__':
    unittest.main()


# !/usr/bin/env python

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

# -*- coding: utf-8 -*-

from collections import Counter
import itertools
import iterators
import os
import numpy as np
import pandas as pd
import mxnet as mx
import argparse
import pickle
import logging

logging.basicConfig(level=logging.DEBUG)

parser = argparse.ArgumentParser(description="Deep neural network for multivariate time series forecasting",
                                 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--data-dir', type=str, default='../data',
                    help='relative path to input data')
parser.add_argument('--output-dir', type=str, default='../results',
                    help='directory to save model files to')
parser.add_argument('--max-records', type=int, default=None,
                    help='total records before data split')
parser.add_argument('--train_fraction', type=float, default=0.8,
                    help='fraction of data to use for training. remainder used for testing.')
parser.add_argument('--batch-size', type=int, default=128,
                    help='the batch size.')
parser.add_argument('--buckets', type=str, default="",
                    help='unique bucket sizes')
parser.add_argument('--char-embed', type=int, default=25,
                    help='Embedding size for each unique character.')
parser.add_argument('--char-filter-list', type=str, default="3,4,5",
                    help='unique filter sizes for char level cnn')
parser.add_argument('--char-filters', type=int, default=20,
                    help='number of each filter size')
parser.add_argument('--word-embed', type=int, default=500,
                    help='Embedding size for each unique character.')
parser.add_argument('--word-filter-list', type=str, default="3,4,5",
                    help='unique filter sizes for char level cnn')
parser.add_argument('--word-filters', type=int, default=200,
                    help='number of each filter size')
parser.add_argument('--lstm-state-size', type=int, default=100,
                    help='number of hidden units in each unrolled recurrent cell')
parser.add_argument('--lstm-layers', type=int, default=1,
                    help='number of recurrent layers')
parser.add_argument('--gpus', type=str, default='',
                    help='list of gpus to run, e.g. 0 or 0,2,5. empty means using cpu. ')
parser.add_argument('--optimizer', type=str, default='adam',
                    help='the optimizer type')
parser.add_argument('--lr', type=float, default=0.001,
                    help='initial learning rate')
parser.add_argument('--dropout', type=float, default=0.2,
                    help='dropout rate for network')
parser.add_argument('--num-epochs', type=int, default=100,
                    help='max num of epochs')
parser.add_argument('--save-period', type=int, default=20,
                    help='save checkpoint for every n epochs')
parser.add_argument('--model_prefix', type=str, default='electricity_model',
                    help='prefix for saving model params')

def save_obj(obj, name):
    with open(name + '.pkl', 'wb') as f:
        pickle.dump(obj, f, pickle.HIGHEST_PROTOCOL)

def save_model():
    if not os.path.exists(args.output_dir):
        os.mkdir(args.output_dir)
    return mx.callback.do_checkpoint(os.path.join(args.output_dir, "checkpoint"), args.save_period)

def build_vocab(nested_list):
    """
    :param nested_list: list of list of string
    :return: dictionary mapping from string to int, inverse of that dictionary
    """
    # Build vocabulary
    word_counts = Counter(itertools.chain(*nested_list))
    logging.info("build_vocab: word_counts=%d" % (len(word_counts)))

    # Mapping from index to label
    vocabulary_inv = [x[0] for x in word_counts.most_common()]

    # Mapping from label to index
    vocabulary = {x: i for i, x in enumerate(vocabulary_inv)}
    return vocabulary, vocabulary_inv

def build_iters(data_dir, max_records, train_fraction, batch_size, buckets=None):
    """
    Reads a csv of sentences/tag sequences into a pandas dataframe.
    Converts into X = array(list(int)) & Y = array(list(int))
    Splits into training and test sets
    Builds dictionaries mapping from index labels to labels/ indexed features to features
    :param data_dir: directory to read in csv data from
    :param max_records: total number of records to randomly select from input data
    :param train_fraction: fraction of the data to use for training
    :param batch_size: records in mini-batches during training
    :param buckets: size of each bucket in the iterators
    :return: train_iter, val_iter, word_to_index, index_to_word, pos_to_index, index_to_pos
    """

    # Read in data as numpy array
    df = pd.read_pickle(os.path.join(data_dir, "ner_data.pkl"))[:max_records]

    # Get feature lists
    entities=[list(array) for array in df["BILOU_tag"].values]
    sentences = [list(array) for array in df["token"].values]
    chars=[[[c for c in word] for word in sentence] for sentence in sentences]

    # Build vocabularies
    entity_to_index, index_to_entity = build_vocab(entities)
    word_to_index, index_to_word = build_vocab(sentences)
    char_to_index, index_to_char = build_vocab([np.array([c for c in word]) for word in index_to_word])
    save_obj(entity_to_index, os.path.join(args.data_dir, "tag_to_index"))

    # Map strings to integer values
    indexed_entities=[list(map(entity_to_index.get, l)) for l in entities]
    indexed_tokens=[list(map(word_to_index.get, l)) for l in sentences]
    indexed_chars=[[list(map(char_to_index.get, word)) for word in sentence] for sentence in chars]

    # Split into training and testing data
    idx=int(len(indexed_tokens)*train_fraction)
    logging.info("Preparing train/test datasets splitting at idx %d on total %d sentences using a batchsize of %d", idx, len(indexed_tokens), batch_size)
    X_token_train, X_char_train, Y_train = indexed_tokens[:idx], indexed_chars[:idx], indexed_entities[:idx]
    X_token_test, X_char_test, Y_test = indexed_tokens[idx:], indexed_chars[idx:], indexed_entities[idx:]

    # build iterators to feed batches to network
    train_iter = iterators.BucketNerIter(sentences=X_token_train, characters=X_char_train, label=Y_train,
                                         max_token_chars=5, batch_size=batch_size, buckets=buckets)
    logging.info("Creating the val_iter using %d sentences", len(X_token_test))
    val_iter = iterators.BucketNerIter(sentences=X_token_test, characters=X_char_test, label=Y_test,
                                         max_token_chars=train_iter.max_token_chars, batch_size=batch_size, buckets=train_iter.buckets)
    return train_iter, val_iter, word_to_index, char_to_index, entity_to_index

def sym_gen(seq_len):
    """
    Build NN symbol depending on the length of the input sequence
    """
    sentence_shape = train_iter.provide_data[0][1]
    char_sentence_shape = train_iter.provide_data[1][1]
    entities_shape = train_iter.provide_label[0][1]

    X_sent = mx.symbol.Variable(train_iter.provide_data[0].name)
    X_char_sent = mx.symbol.Variable(train_iter.provide_data[1].name)
    Y = mx.sym.Variable(train_iter.provide_label[0].name)

    ###############################
    # Character embedding component
    ###############################
    char_embeddings = mx.sym.Embedding(data=X_char_sent, input_dim=len(char_to_index), output_dim=args.char_embed, name='char_embed')
    char_embeddings = mx.sym.reshape(data=char_embeddings, shape=(0,1,seq_len,-1,args.char_embed), name='char_embed2')

    char_cnn_outputs = []
    for i, filter_size in enumerate(args.char_filter_list):
        # Kernel that slides over entire words resulting in a 1d output
        convi = mx.sym.Convolution(data=char_embeddings, kernel=(1, filter_size, args.char_embed), stride=(1, 1, 1),
                                   num_filter=args.char_filters, name="char_conv_layer_" + str(i))
        acti = mx.sym.Activation(data=convi, act_type='tanh')
        pooli = mx.sym.Pooling(data=acti, pool_type='max', kernel=(1, char_sentence_shape[2] - filter_size + 1, 1),
                               stride=(1, 1, 1), name="char_pool_layer_" + str(i))
        pooli = mx.sym.transpose(mx.sym.Reshape(pooli, shape=(0, 0, 0)), axes=(0, 2, 1), name="cchar_conv_layer_" + str(i))
        char_cnn_outputs.append(pooli)

    # combine features from all filters & apply dropout
    cnn_char_features = mx.sym.Concat(*char_cnn_outputs, dim=2, name="cnn_char_features")
    regularized_cnn_char_features = mx.sym.Dropout(data=cnn_char_features, p=args.dropout, mode='training',
                                                   name='regularized charCnn features')

    ##################################
    # Combine char and word embeddings
    ##################################
    word_embeddings = mx.sym.Embedding(data=X_sent, input_dim=len(word_to_index), output_dim=args.word_embed, name='word_embed')
    rnn_features = mx.sym.Concat(*[word_embeddings, regularized_cnn_char_features], dim=2, name='rnn input')

    ##############################
    # Bidirectional LSTM component
    ##############################

    # unroll the lstm cell in time, merging outputs
    bi_cell.reset()
    output, states = bi_cell.unroll(length=seq_len, inputs=rnn_features, merge_outputs=True)

    # Map to num entity classes
    rnn_output = mx.sym.Reshape(output, shape=(-1, args.lstm_state_size * 2), name='r_output')
    fc = mx.sym.FullyConnected(data=rnn_output, num_hidden=len(entity_to_index), name='fc_layer')

    # reshape back to same shape as loss will be
    reshaped_fc = mx.sym.transpose(mx.sym.reshape(fc, shape=(-1, seq_len, len(entity_to_index))), axes=(0, 2, 1))
    sm = mx.sym.SoftmaxOutput(data=reshaped_fc, label=Y, ignore_label=-1, use_ignore=True, multi_output=True, name='softmax')
    return sm, [v.name for v in train_iter.provide_data], [v.name for v in train_iter.provide_label]

def train(train_iter, val_iter):
    import metrics
    devs = mx.cpu() if args.gpus is None or args.gpus is '' else [mx.gpu(int(i)) for i in args.gpus.split(',')]
    logging.info("train on device %s using optimizer %s at learningrate %f for %d epochs using %d records: lstm_state_size=%d ...",
          devs, args.optimizer, args.lr, args.num_epochs, args.max_records, args.lstm_state_size)
    module = mx.mod.BucketingModule(sym_gen, train_iter.default_bucket_key, context=devs)
    module.fit(train_data=train_iter,
               eval_data=val_iter,
               eval_metric=metrics.composite_classifier_metrics(),
               optimizer=args.optimizer,
               optimizer_params={'learning_rate': args.lr },
               initializer=mx.initializer.Uniform(0.1),
               num_epoch=args.num_epochs,
               epoch_end_callback=save_model())

if __name__ == '__main__':
    # parse args
    args = parser.parse_args()
    args.buckets = list(map(int, args.buckets.split(','))) if len(args.buckets) > 0 else None
    args.char_filter_list = list(map(int, args.char_filter_list.split(',')))

    # Build data iterators
    train_iter, val_iter, word_to_index, char_to_index, entity_to_index = build_iters(args.data_dir, args.max_records,
                                                                     args.train_fraction, args.batch_size, args.buckets)

    logging.info("validation iterator: %s", val_iter)

    # Define the recurrent layer
    bi_cell = mx.rnn.SequentialRNNCell()
    for layer_num in range(args.lstm_layers):
        bi_cell.add(mx.rnn.BidirectionalCell(
            mx.rnn.LSTMCell(num_hidden=args.lstm_state_size, prefix="forward_layer_" + str(layer_num)),
            mx.rnn.LSTMCell(num_hidden=args.lstm_state_size, prefix="backward_layer_" + str(layer_num))))
        bi_cell.add(mx.rnn.DropoutCell(args.dropout))

    train(train_iter, val_iter)


# -*- coding: utf-8 -*-
"""
Created on Thu Jun 18 19:04:51 2015

@author: Paco
"""

"""
Information Theoretic Metric Learning, Kulis et al., ICML 2007
"""

import numpy as np
from sklearn.metrics import pairwise_distances
from base_metric import BaseMetricLearner

class ITML(BaseMetricLearner):
  """
  Information Theoretic Metric Learning (ITML)
  """
  def __init__(self, gamma=1., max_iters=1000, convergence_threshold=1e-3):
    """
    gamma: value for slack variables
    """
    self.gamma = gamma
    self.max_iters = max_iters
    self.convergence_threshold = convergence_threshold

  def _process_inputs(self, X, constraints, bounds, A0):
    self.X = X
    # check to make sure that no two constrained vectors are identical
    a,b,c,d = constraints
    ident = _vector_norm(self.X[a] - self.X[b]) > 1e-9
    a, b = a[ident], b[ident]
    ident = _vector_norm(self.X[c] - self.X[d]) > 1e-9
    c, d = c[ident], d[ident]
    # init bounds
    if bounds is None:
      self.bounds = np.percentile(pairwise_distances(X), (5, 95))
    else:
      assert len(bounds) == 2
      self.bounds = bounds
    # init metric
    if A0 is None:
      self.A = np.identity(X.shape[1])
    else:
      self.A = A0
    return a,b,c,d

  def fit(self, X, constraints, bounds=None, A0=None, verbose=False):
    """
    X: (n x d) data matrix - each row corresponds to a single instance
    constraints: tuple of arrays: (a,b,c,d) indices into X, such that:
      d(X[a],X[b]) < d(X[c],X[d])
    bounds: (pos,neg) pair of bounds on similarity, such that:
      d(X[a],X[b]) < pos
      d(X[c],X[d]) > neg
    A0: [optional] (d x d) initial regularization matrix, defaults to identity
    """
    a,b,c,d = self._process_inputs(X, constraints, bounds, A0)
    gamma = self.gamma
    num_pos = len(a)
    num_neg = len(c)
    _lambda = np.zeros(num_pos + num_neg)
    lambdaold = np.zeros_like(_lambda)
    gamma_proj = 1. if gamma is np.inf else gamma/(gamma+1.)
    pos_bhat = np.zeros(num_pos) + self.bounds[0]
    neg_bhat = np.zeros(num_neg) + self.bounds[1]
    A = self.A

    for it in xrange(self.max_iters):
      # update positives
      vv = self.X[a] - self.X[b]
      for i,v in enumerate(vv):
        wtw = v.dot(A).dot(v)  # scalar
        alpha = min(_lambda[i], gamma_proj*(1./wtw - 1./pos_bhat[i]))
        _lambda[i] -= alpha
        beta = alpha/(1 - alpha*wtw)
        pos_bhat[i] = 1./((1 / pos_bhat[i]) + (alpha / gamma))
        A += beta * A.dot(np.outer(v,v)).dot(A)

      # update negatives
      vv = self.X[c] - self.X[d]
      for i,v in enumerate(vv):
        wtw = v.dot(A).dot(v)  # scalar
        alpha = min(_lambda[i+num_pos],gamma_proj*(1./neg_bhat[i] - 1./wtw))
        _lambda[i+num_pos] -= alpha
        beta = -alpha/(1 + alpha*wtw)
        neg_bhat[i] = 1./((1 / neg_bhat[i]) - (alpha / gamma))
        A += beta * A.dot(np.outer(v,v)).dot(A)

      normsum = np.linalg.norm(_lambda) + np.linalg.norm(lambdaold)
      if normsum == 0:
        conv = np.inf
        break
      conv = np.abs(lambdaold - _lambda).sum() / normsum
      if conv < self.convergence_threshold:
        break
      lambdaold = _lambda.copy()
      if verbose:
        print 'itml iter: %d, conv = %f' % (it, conv)
    if verbose:
      print 'itml converged at iter: %d, conv = %f' % (it, conv)
    return self

  def metric(self):
    return self.A

  @classmethod
  def prepare_constraints(self, labels, num_points, num_constraints):
    ac,bd = np.random.randint(num_points, size=(2,num_constraints))
    pos = labels[ac] == labels[bd]
    a,c = ac[pos], ac[~pos]
    b,d = bd[pos], bd[~pos]
    return a,b,c,d

# hack around lack of axis kwarg in older numpy versions
try:
  np.linalg.norm([[4]], axis=1)
except TypeError:
  def _vector_norm(X):
    return np.apply_along_axis(np.linalg.norm, 1, X)
else:
  def _vector_norm(X):
    return np.linalg.norm(X, axis=1)


"""
=========================
Bayesian Ridge Regression
=========================

Computes a Bayesian Ridge Regression on a synthetic dataset.

See :ref:`bayesian_ridge_regression` for more information on the regressor.

Compared to the OLS (ordinary least squares) estimator, the coefficient
weights are slightly shifted toward zeros, which stabilises them.

As the prior on the weights is a Gaussian prior, the histogram of the
estimated weights is Gaussian.

The estimation of the model is done by iteratively maximizing the
marginal log-likelihood of the observations.
"""
print(__doc__)

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

from sklearn.linear_model import BayesianRidge, LinearRegression

###############################################################################
# Generating simulated data with Gaussian weigthts
np.random.seed(0)
n_samples, n_features = 100, 100
X = np.random.randn(n_samples, n_features)  # Create Gaussian data
# Create weigts with a precision lambda_ of 4.
lambda_ = 4.
w = np.zeros(n_features)
# Only keep 10 weights of interest
relevant_features = np.random.randint(0, n_features, 10)
for i in relevant_features:
    w[i] = stats.norm.rvs(loc=0, scale=1. / np.sqrt(lambda_))
# Create noise with a precision alpha of 50.
alpha_ = 50.
noise = stats.norm.rvs(loc=0, scale=1. / np.sqrt(alpha_), size=n_samples)
# Create the target
y = np.dot(X, w) + noise

###############################################################################
# Fit the Bayesian Ridge Regression and an OLS for comparison
clf = BayesianRidge(compute_score=True)
clf.fit(X, y)

ols = LinearRegression()
ols.fit(X, y)

###############################################################################
# Plot true weights, estimated weights and histogram of the weights
plt.figure(figsize=(6, 5))
plt.title("Weights of the model")
plt.plot(clf.coef_, 'b-', label="Bayesian Ridge estimate")
plt.plot(w, 'g-', label="Ground truth")
plt.plot(ols.coef_, 'r--', label="OLS estimate")
plt.xlabel("Features")
plt.ylabel("Values of the weights")
plt.legend(loc="best", prop=dict(size=12))

plt.figure(figsize=(6, 5))
plt.title("Histogram of the weights")
plt.hist(clf.coef_, bins=n_features, log=True)
plt.plot(clf.coef_[relevant_features], 5 * np.ones(len(relevant_features)),
         'ro', label="Relevant features")
plt.ylabel("Features")
plt.xlabel("Values of the weights")
plt.legend(loc="lower left")

plt.figure(figsize=(6, 5))
plt.title("Marginal log-likelihood")
plt.plot(clf.scores_)
plt.ylabel("Score")
plt.xlabel("Iterations")
plt.show()


"""
========================
Partial Dependence Plots
========================

Partial dependence plots show the dependence between the target function [2]_
and a set of 'target' features, marginalizing over the
values of all other features (the complement features). Due to the limits
of human perception the size of the target feature set must be small (usually,
one or two) thus the target features are usually chosen among the most
important features
(see :attr:`~sklearn.ensemble.GradientBoostingRegressor.feature_importances_`).

This example shows how to obtain partial dependence plots from a
:class:`~sklearn.ensemble.GradientBoostingRegressor` trained on the California
housing dataset. The example is taken from [1]_.

The plot shows four one-way and one two-way partial dependence plots.
The target variables for the one-way PDP are:
median income (`MedInc`), avg. occupants per household (`AvgOccup`),
median house age (`HouseAge`), and avg. rooms per household (`AveRooms`).

We can clearly see that the median house price shows a linear relationship
with the median income (top left) and that the house price drops when the
avg. occupants per household increases (top middle).
The top right plot shows that the house age in a district does not have
a strong influence on the (median) house price; so does the average rooms
per household.
The tick marks on the x-axis represent the deciles of the feature values
in the training data.

Partial dependence plots with two target features enable us to visualize
interactions among them. The two-way partial dependence plot shows the
dependence of median house price on joint values of house age and avg.
occupants per household. We can clearly see an interaction between the
two features:
For an avg. occupancy greater than two, the house price is nearly independent
of the house age, whereas for values less than two there is a strong dependence
on age.

.. [1] T. Hastie, R. Tibshirani and J. Friedman,
    "Elements of Statistical Learning Ed. 2", Springer, 2009.

.. [2] For classification you can think of it as the regression score before
       the link function.
"""
print(__doc__)

import numpy as np
import matplotlib.pyplot as plt

from mpl_toolkits.mplot3d import Axes3D
from six.moves.urllib.error import HTTPError

from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.ensemble.partial_dependence import plot_partial_dependence
from sklearn.ensemble.partial_dependence import partial_dependence
from sklearn.datasets.california_housing import fetch_california_housing

def main():
    # fetch California housing dataset
    try:
        cal_housing = fetch_california_housing()
    except HTTPError:
        print("Failed downloading california housing data.")
        return

    # split 80/20 train-test
    X_train, X_test, y_train, y_test = train_test_split(cal_housing.data,
                                                        cal_housing.target,
                                                        test_size=0.2,
                                                        random_state=1)
    names = cal_housing.feature_names

    print('_' * 80)
    print("Training GBRT...")
    clf = GradientBoostingRegressor(n_estimators=100, max_depth=4,
                                    learning_rate=0.1, loss='huber',
                                    random_state=1)
    clf.fit(X_train, y_train)
    print("done.")

    print('_' * 80)
    print('Convenience plot with ``partial_dependence_plots``')
    print

    features = [0, 5, 1, 2, (5, 1)]
    fig, axs = plot_partial_dependence(clf, X_train, features, feature_names=names,
                                       n_jobs=3, grid_resolution=50)
    fig.suptitle('Partial dependence of house value on nonlocation features\n'
                 'for the California housing dataset')
    plt.subplots_adjust(top=0.9)  # tight_layout causes overlap with suptitle

    print('_' * 80)
    print('Custom 3d plot via ``partial_dependence``')
    print
    fig = plt.figure()

    target_feature = (1, 5)
    pdp, (x_axis, y_axis) = partial_dependence(clf, target_feature,
                                               X=X_train, grid_resolution=50)
    XX, YY = np.meshgrid(x_axis, y_axis)
    Z = pdp.T.reshape(XX.shape).T
    ax = Axes3D(fig)
    surf = ax.plot_surface(XX, YY, Z, rstride=1, cstride=1, cmap=plt.cm.BuPu)
    ax.set_xlabel(names[target_feature[0]])
    ax.set_ylabel(names[target_feature[1]])
    ax.set_zlabel('Partial dependence')
    #  pretty init view
    ax.view_init(elev=22, azim=122)
    plt.colorbar(surf)
    plt.suptitle('Partial dependence of house value on median age and '
                 'average occupancy')
    plt.subplots_adjust(top=0.9)

    plt.show()

if __name__ == "__main__":
    main()