Return the predicted class for each observation in *dataset*. This
        prediction is made based on the closest neighbors stored in the nearest
        neighbors classifier model.

        Parameters
        ----------
        dataset : SFrame
            Dataset of new observations. Must include columns with the same
            names as the features used for model training, but does not require
            a target column. Additional columns are ignored.

        verbose : bool, optional
            If True, print progress updates.

        max_neighbors : int, optional
            Maximum number of neighbors to consider for each point.

        radius : float, optional
            Maximum distance from each point to a neighbor in the reference
            dataset.

        Returns
        -------
        out : SFrame
            An SFrame with model predictions. The first column is the most
            likely class according to the model, and the second column is the
            predicted probability for that class.

        See Also
        --------
        create, predict, predict_topk

        Notes
        -----
        - If the 'radius' parameter is small, it is possible that a query point
          has no qualified neighbors in the training dataset. In this case, the
          resulting class and probability for that query are 'None' in the
          SFrame output by this method. If the target column in the training
          dataset has missing values, these predictions will be ambiguous.

        - Ties between predicted classes are broken randomly.

        Examples
        --------
        >>> sf_train = turicreate.SFrame({'species': ['cat', 'dog', 'fossa', 'dog'],
        ...                             'height': [9, 25, 20, 23],
        ...                             'weight': [13, 28, 33, 22]})
        ...
        >>> sf_new = turicreate.SFrame({'height': [26, 19],
        ...                           'weight': [25, 35]})
        ...
        >>> m = turicreate.nearest_neighbor_classifier.create(sf, target='species')
        >>> ystar = m.classify(sf_new, max_neighbors=2)
        >>> print ystar
        +-------+-------------+
        | class | probability |
        +-------+-------------+
        |  dog  |     1.0     |
        | fossa |     0.5     |
        +-------+-------------+

def classify(self, dataset, max_neighbors=10, radius=None, verbose=True):
        """
        Return the predicted class for each observation in *dataset*. This
        prediction is made based on the closest neighbors stored in the nearest
        neighbors classifier model.

        Parameters
        ----------
        dataset : SFrame
            Dataset of new observations. Must include columns with the same
            names as the features used for model training, but does not require
            a target column. Additional columns are ignored.

        verbose : bool, optional
            If True, print progress updates.

        max_neighbors : int, optional
            Maximum number of neighbors to consider for each point.

        radius : float, optional
            Maximum distance from each point to a neighbor in the reference
            dataset.

        Returns
        -------
        out : SFrame
            An SFrame with model predictions. The first column is the most
            likely class according to the model, and the second column is the
            predicted probability for that class.

        See Also
        --------
        create, predict, predict_topk

        Notes
        -----
        - If the 'radius' parameter is small, it is possible that a query point
          has no qualified neighbors in the training dataset. In this case, the
          resulting class and probability for that query are 'None' in the
          SFrame output by this method. If the target column in the training
          dataset has missing values, these predictions will be ambiguous.

        - Ties between predicted classes are broken randomly.

        Examples
        --------
        >>> sf_train = turicreate.SFrame({'species': ['cat', 'dog', 'fossa', 'dog'],
        ...                             'height': [9, 25, 20, 23],
        ...                             'weight': [13, 28, 33, 22]})
        ...
        >>> sf_new = turicreate.SFrame({'height': [26, 19],
        ...                           'weight': [25, 35]})
        ...
        >>> m = turicreate.nearest_neighbor_classifier.create(sf, target='species')
        >>> ystar = m.classify(sf_new, max_neighbors=2)
        >>> print ystar
        +-------+-------------+
        | class | probability |
        +-------+-------------+
        |  dog  |     1.0     |
        | fossa |     0.5     |
        +-------+-------------+
        """

        ## Validate the query 'dataset'. Note that the 'max_neighbors' and
        #  'radius' parameters are validated by the nearest neighbor model's
        #  query method.
        _raise_error_if_not_sframe(dataset, "dataset")
        _raise_error_if_sframe_empty(dataset, "dataset")
        n_query = dataset.num_rows()

        ## Validate neighborhood parameters 'max_neighbors'.
        # - NOTE: when the parameter name is changed in nearest neighbors, the
        #   query call will do this itself, and this block can be removed.
        if max_neighbors is not None:
            if not isinstance(max_neighbors, int):
                raise ValueError("Input 'max_neighbors' must be an integer.")

            if max_neighbors <= 0:
                raise ValueError("Input 'max_neighbors' must be larger than 0.")


        ## Find the nearest neighbors for each query and count the number of
        #  votes for each class.
        knn = self._knn_model.query(dataset, k=max_neighbors, radius=radius,
                                    verbose=verbose)

        ## If there are *no* results for *any* query make an SFrame of nothing.
        if knn.num_rows() == 0:
            ystar = _tc.SFrame(
                    {'class': _tc.SArray([None] * n_query, self._target_type),
                     'probability': _tc.SArray([None] * n_query, int)})

        else:
            ## Find the class with the most votes for each query and postprocess.
            grp = knn.groupby(['query_label', 'reference_label'], _tc.aggregate.COUNT)

            ystar = grp.groupby('query_label',
                                {'class': _tc.aggregate.ARGMAX('Count', 'reference_label'),
                                 'max_votes': _tc.aggregate.MAX('Count'),
                                 'total_votes': _tc.aggregate.SUM('Count')})

            ystar['probability'] = ystar['max_votes'] / ystar['total_votes']

            ## Fill in 'None' for query points that don't have any near neighbors.
            row_ids = _tc.SFrame({'query_label': range(n_query)})
            ystar = ystar.join(row_ids, how='right')

            ## Sort by row number (because row number is not returned) and return
            ystar = ystar.sort('query_label', ascending=True)
            ystar = ystar[['class', 'probability']]

        return ystar