code: import operator
  
  import numpy as np
  import pytest
  
  import pandas.util._test_decorators as td
  
  import pandas as pd
  from pandas import DataFrame, Series
  import pandas._testing as tm
  
  
  class TestSeriesAnalytics:
      def test_prod_numpy16_bug(self):
          s = Series([1.0, 1.0, 1.0], index=range(3))
          result = s.prod()
  
          assert not isinstance(result, Series)
  
      def test_matmul(self):
          # matmul test is for GH #10259
          a = Series(np.random.randn(4), index=["p", "q", "r", "s"])
          b = DataFrame(
              np.random.randn(3, 4), index=["1", "2", "3"], columns=["p", "q", "r", "s"]
          ).T
  
          # Series @ DataFrame -> Series
          result = operator.matmul(a, b)
          expected = Series(np.dot(a.values, b.values), index=["1", "2", "3"])
          tm.assert_series_equal(result, expected)
  
          # DataFrame @ Series -> Series
          result = operator.matmul(b.T, a)
          expected = Series(np.dot(b.T.values, a.T.values), index=["1", "2", "3"])
          tm.assert_series_equal(result, expected)
  
          # Series @ Series -> scalar
          result = operator.matmul(a, a)
          expected = np.dot(a.values, a.values)
          tm.assert_almost_equal(result, expected)
  
          # GH 21530
          # vector (1D np.array) @ Series (__rmatmul__)
          result = operator.matmul(a.values, a)
          expected = np.dot(a.values, a.values)
          tm.assert_almost_equal(result, expected)
  
          # GH 21530
          # vector (1D list) @ Series (__rmatmul__)
          result = operator.matmul(a.values.tolist(), a)
          expected = np.dot(a.values, a.values)
          tm.assert_almost_equal(result, expected)
  
          # GH 21530
          # matrix (2D np.array) @ Series (__rmatmul__)
          result = operator.matmul(b.T.values, a)
          expected = np.dot(b.T.values, a.values)
          tm.assert_almost_equal(result, expected)
  
          # GH 21530
          # matrix (2D nested lists) @ Series (__rmatmul__)
          result = operator.matmul(b.T.values.tolist(), a)
          expected = np.dot(b.T.values, a.values)
          tm.assert_almost_equal(result, expected)
  
          # mixed dtype DataFrame @ Series
          a["p"] = int(a.p)
          result = operator.matmul(b.T, a)
          expected = Series(np.dot(b.T.values, a.T.values), index=["1", "2", "3"])
          tm.assert_series_equal(result, expected)
  
          # different dtypes DataFrame @ Series
          a = a.astype(int)
          result = operator.matmul(b.T, a)
          expected = Series(np.dot(b.T.values, a.T.values), index=["1", "2", "3"])
          tm.assert_series_equal(result, expected)
  
          msg = r"Dot product shape mismatch, \(4,\) vs \(3,\)"
          # exception raised is of type Exception
          with pytest.raises(Exception, match=msg):
              a.dot(a.values[:3])
          msg = "matrices are not aligned"
          with pytest.raises(ValueError, match=msg):
              a.dot(b.T)
  
      def test_ptp(self):
          # GH21614
          N = 1000
          arr = np.random.randn(N)
          ser = Series(arr)
          assert np.ptp(ser) == np.ptp(arr)
  
      def test_repeat(self):
          s = Series(np.random.randn(3), index=["a", "b", "c"])
  
          reps = s.repeat(5)
          exp = Series(s.values.repeat(5), index=s.index.values.repeat(5))
          tm.assert_series_equal(reps, exp)
  
          to_rep = [2, 3, 4]
          reps = s.repeat(to_rep)
          exp = Series(s.values.repeat(to_rep), index=s.index.values.repeat(to_rep))
          tm.assert_series_equal(reps, exp)
  
      def test_numpy_repeat(self):
          s = Series(np.arange(3), name="x")
          expected = Series(s.values.repeat(2), name="x", index=s.index.values.repeat(2))
          tm.assert_series_equal(np.repeat(s, 2), expected)
  
          msg = "the 'axis' parameter is not supported"
          with pytest.raises(ValueError, match=msg):
              np.repeat(s, 2, axis=0)
  
      def test_is_monotonic(self):
  
          s = Series(np.random.randint(0, 10, size=1000))
          assert not s.is_monotonic
          s = Series(np.arange(1000))
          assert s.is_monotonic is True
          assert s.is_monotonic_increasing is True
          s = Series(np.arange(1000, 0, -1))
          assert s.is_monotonic_decreasing is True
  
          s = Series(pd.date_range("20130101", periods=10))
          assert s.is_monotonic is True
          assert s.is_monotonic_increasing is True
          s = Series(list(reversed(s.tolist())))
          assert s.is_monotonic is False
          assert s.is_monotonic_decreasing is True
  
      @pytest.mark.parametrize("func", [np.any, np.all])
      @pytest.mark.parametrize("kwargs", [dict(keepdims=True), dict(out=object())])
      @td.skip_if_np_lt("1.15")
      def test_validate_any_all_out_keepdims_raises(self, kwargs, func):
          s = pd.Series([1, 2])
          param = list(kwargs)[0]
          name = func.__name__
  
          msg = (
              f"the '{param}' parameter is not "
              "supported in the pandas "
              fr"implementation of {name}\(\)"
          )
          with pytest.raises(ValueError, match=msg):
              func(s, **kwargs)
  
      @td.skip_if_np_lt("1.15")
      def test_validate_sum_initial(self):
          s = pd.Series([1, 2])
          msg = (
              r"the 'initial' parameter is not "
              r"supported in the pandas "
              r"implementation of sum\(\)"
          )
          with pytest.raises(ValueError, match=msg):
              np.sum(s, initial=10)
  
      def test_validate_median_initial(self):
          s = pd.Series([1, 2])
          msg = (
              r"the 'overwrite_input' parameter is not "
              r"supported in the pandas "
              r"implementation of median\(\)"
          )
          with pytest.raises(ValueError, match=msg):
              # It seems like np.median doesn't dispatch, so we use the
              # method instead of the ufunc.
              s.median(overwrite_input=True)
  
      @td.skip_if_np_lt("1.15")
      def test_validate_stat_keepdims(self):
          s = pd.Series([1, 2])
          msg = (
              r"the 'keepdims' parameter is not "
              r"supported in the pandas "
              r"implementation of sum\(\)"
          )
          with pytest.raises(ValueError, match=msg):
              np.sum(s, keepdims=True)
  
      def test_td64_summation_overflow(self):
          # GH 9442
          s = pd.Series(pd.date_range("20130101", periods=100000, freq="H"))
          s[0] += pd.Timedelta("1s 1ms")
  
          # mean
          result = (s - s.min()).mean()
          expected = pd.Timedelta((pd.TimedeltaIndex((s - s.min())).asi8 / len(s)).sum())
  
          # the computation is converted to float so
          # might be some loss of precision
          assert np.allclose(result.value / 1000, expected.value / 1000)
  
          # sum
          msg = "overflow in timedelta operation"
          with pytest.raises(ValueError, match=msg):
              (s - s.min()).sum()
  
          s1 = s[0:10000]
          with pytest.raises(ValueError, match=msg):
              (s1 - s1.min()).sum()
          s2 = s[0:1000]
          (s2 - s2.min()).sum()
   """ Non-negative matrix factorization.
  """
  # Author: Vlad Niculae
  #         Lars Buitinck
  #         Mathieu Blondel <mathieu@mblondel.org>
  #         Tom Dupre la Tour
  # License: BSD 3 clause
  
  import numbers
  import numpy as np
  import scipy.sparse as sp
  import time
  import warnings
  from math import sqrt
  
  from ._cdnmf_fast import _update_cdnmf_fast
  from .._config import config_context
  from ..base import BaseEstimator, TransformerMixin
  from ..exceptions import ConvergenceWarning
  from ..utils import check_random_state, check_array
  from ..utils.extmath import randomized_svd, safe_sparse_dot, squared_norm
  from ..utils.validation import check_is_fitted, check_non_negative
  from ..utils.validation import _deprecate_positional_args
  
  EPSILON = np.finfo(np.float32).eps
  
  
  def norm(x):
      """Dot product-based Euclidean norm implementation.
  
      See: http://fseoane.net/blog/2011/computing-the-vector-norm/
  
      Parameters
      ----------
      x : array-like
          Vector for which to compute the norm.
      """
      return sqrt(squared_norm(x))
  
  
  def trace_dot(X, Y):
      """Trace of np.dot(X, Y.T).
  
      Parameters
      ----------
      X : array-like
          First matrix.
      Y : array-like
          Second matrix.
      """
      return np.dot(X.ravel(), Y.ravel())
  
  
  def _check_init(A, shape, whom):
      A = check_array(A)
      if np.shape(A) != shape:
          raise ValueError('Array with wrong shape passed to %s. Expected %s, '
                           'but got %s ' % (whom, shape, np.shape(A)))
      check_non_negative(A, whom)
      if np.max(A) == 0:
          raise ValueError('Array passed to %s is full of zeros.' % whom)
  
  
  def _beta_divergence(X, W, H, beta, square_root=False):
      """Compute the beta-divergence of X and dot(W, H).
  
      Parameters
      ----------
      X : float or array-like of shape (n_samples, n_features)
  
      W : float or array-like of shape (n_samples, n_components)
  
      H : float or array-like of shape (n_components, n_features)
  
      beta : float or {'frobenius', 'kullback-leibler', 'itakura-saito'}
          Parameter of the beta-divergence.
          If beta == 2, this is half the Frobenius *squared* norm.
          If beta == 1, this is the generalized Kullback-Leibler divergence.
          If beta == 0, this is the Itakura-Saito divergence.
          Else, this is the general beta-divergence.
  
      square_root : bool, default=False
          If True, return np.sqrt(2 * res)
          For beta == 2, it corresponds to the Frobenius norm.
  
      Returns
      -------
          res : float
              Beta divergence of X and np.dot(X, H).
      """
      beta = _beta_loss_to_float(beta)
  
      # The method can be called with scalars
      if not sp.issparse(X):
          X = np.atleast_2d(X)
      W = np.atleast_2d(W)
      H = np.atleast_2d(H)
  
      # Frobenius norm
      if beta == 2:
          # Avoid the creation of the dense np.dot(W, H) if X is sparse.
          if sp.issparse(X):
              norm_X = np.dot(X.data, X.data)
              norm_WH = trace_dot(np.linalg.multi_dot([W.T, W, H]), H)
              cross_prod = trace_dot((X * H.T), W)
              res = (norm_X + norm_WH - 2. * cross_prod) / 2.
          else:
              res = squared_norm(X - np.dot(W, H)) / 2.
  
          if square_root:
              return np.sqrt(res * 2)
          else:
              return res
  
      if sp.issparse(X):
          # compute np.dot(W, H) only where X is nonzero
          WH_data = _special_sparse_dot(W, H, X).data
          X_data = X.data
      else:
          WH = np.dot(W, H)
          WH_data = WH.ravel()
          X_data = X.ravel()
  
      # do not affect the zeros: here 0 ** (-1) = 0 and not infinity
      indices = X_data > EPSILON
      WH_data = WH_data[indices]
      X_data = X_data[indices]
  
      # used to avoid division by zero
      WH_data[WH_data == 0] = EPSILON
  
      # generalized Kullback-Leibler divergence
      if beta == 1:
          # fast and memory efficient computation of np.sum(np.dot(W, H))
          sum_WH = np.dot(np.sum(W, axis=0), np.sum(H, axis=1))
          # computes np.sum(X * log(X / WH)) only where X is nonzero
          div = X_data / WH_data
          res = np.dot(X_data, np.log(div))
          # add full np.sum(np.dot(W, H)) - np.sum(X)
          res += sum_WH - X_data.sum()
  
      # Itakura-Saito divergence
      elif beta == 0:
          div = X_data / WH_data
          res = np.sum(div) - np.product(X.shape) - np.sum(np.log(div))
  
      # beta-divergence, beta not in (0, 1, 2)
      else:
          if sp.issparse(X):
              # slow loop, but memory efficient computation of :
              # np.sum(np.dot(W, H) ** beta)
              sum_WH_beta = 0
              for i in range(X.shape[1]):
                  sum_WH_beta += np.sum(np.dot(W, H[:, i]) ** beta)
  
          else:
              sum_WH_beta = np.sum(WH ** beta)
  
          sum_X_WH = np.dot(X_data, WH_data ** (beta - 1))
          res = (X_data ** beta).sum() - beta * sum_X_WH
          res += sum_WH_beta * (beta - 1)
          res /= beta * (beta - 1)
  
      if square_root:
          return np.sqrt(2 * res)
      else:
          return res
  
  
  def _special_sparse_dot(W, H, X):
      """Computes np.dot(W, H), only where X is non zero."""
      if sp.issparse(X):
          ii, jj = X.nonzero()
          n_vals = ii.shape[0]
          dot_vals = np.empty(n_vals)
          n_components = W.shape[1]
  
          batch_size = max(n_components, n_vals // n_components)
          for start in range(0, n_vals, batch_size):
              batch = slice(start, start + batch_size)
              dot_vals[batch] = np.multiply(W[ii[batch], :],
                                            H.T[jj[batch], :]).sum(axis=1)
  
          WH = sp.coo_matrix((dot_vals, (ii, jj)), shape=X.shape)
          return WH.tocsr()
      else:
          return np.dot(W, H)
  
  
  def _compute_regularization(alpha, l1_ratio, regularization):
      """Compute L1 and L2 regularization coefficients for W and H."""
      alpha_H = 0.
      alpha_W = 0.
      if regularization in ('both', 'components'):
          alpha_H = float(alpha)
      if regularization in ('both', 'transformation'):
          alpha_W = float(alpha)
  
      l1_reg_W = alpha_W * l1_ratio
      l1_reg_H = alpha_H * l1_ratio
      l2_reg_W = alpha_W * (1. - l1_ratio)
      l2_reg_H = alpha_H * (1. - l1_ratio)
      return l1_reg_W, l1_reg_H, l2_reg_W, l2_reg_H
  
  
  def _check_string_param(solver, regularization, beta_loss, init):
      allowed_solver = ('cd', 'mu')
      if solver not in allowed_solver:
          raise ValueError(
              'Invalid solver parameter: got %r instead of one of %r' %
              (solver, allowed_solver))
  
      allowed_regularization = ('both', 'components', 'transformation', None)
      if regularization not in allowed_regularization:
          raise ValueError(
              'Invalid regularization parameter: got %r instead of one of %r' %
              (regularization, allowed_regularization))
  
      # 'mu' is the only solver that handles other beta losses than 'frobenius'
      if solver != 'mu' and beta_loss not in (2, 'frobenius'):
          raise ValueError(
              'Invalid beta_loss parameter: solver %r does not handle beta_loss'
              ' = %r' % (solver, beta_loss))
  
      if solver == 'mu' and init == 'nndsvd':
          warnings.warn("The multiplicative update ('mu') solver cannot update "
                        "zeros present in the initialization, and so leads to "
                        "poorer results when used jointly with init='nndsvd'. "
                        "You may try init='nndsvda' or init='nndsvdar' instead.",
                        UserWarning)
  
      beta_loss = _beta_loss_to_float(beta_loss)
      return beta_loss
  
  
  def _beta_loss_to_float(beta_loss):
      """Convert string beta_loss to float."""
      allowed_beta_loss = {'frobenius': 2,
                           'kullback-leibler': 1,
                           'itakura-saito': 0}
      if isinstance(beta_loss, str) and beta_loss in allowed_beta_loss:
          beta_loss = allowed_beta_loss[beta_loss]
  
      if not isinstance(beta_loss, numbers.Number):
          raise ValueError('Invalid beta_loss parameter: got %r instead '
                           'of one of %r, or a float.' %
                           (beta_loss, allowed_beta_loss.keys()))
      return beta_loss
  
  
  def _initialize_nmf(X, n_components, init='warn', eps=1e-6,
                      random_state=None):
      """Algorithms for NMF initialization.
  
      Computes an initial guess for the non-negative
      rank k matrix approximation for X: X = WH.
  
      Parameters
      ----------
      X : array-like of shape (n_samples, n_features)
          The data matrix to be decomposed.
  
      n_components : int
          The number of components desired in the approximation.
  
      init :  {'random', 'nndsvd', 'nndsvda', 'nndsvdar'}, default=None
          Method used to initialize the procedure.
          Default: None.
          Valid options:
  
          - None: 'nndsvd' if n_components <= min(n_samples, n_features),
              otherwise 'random'.
  
          - 'random': non-negative random matrices, scaled with:
              sqrt(X.mean() / n_components)
  
          - 'nndsvd': Nonnegative Double Singular Value Decomposition (NNDSVD)
              initialization (better for sparseness)
  
          - 'nndsvda': NNDSVD with zeros filled with the average of X
              (better when sparsity is not desired)
  
          - 'nndsvdar': NNDSVD with zeros filled with small random values
              (generally faster, less accurate alternative to NNDSVDa
              for when sparsity is not desired)
  
          - 'custom': use custom matrices W and H
  
      eps : float, default=1e-6
          Truncate all values less then this in output to zero.
  
      random_state : int, RandomState instance or None, default=None
          Used when ``init`` == 'nndsvdar' or 'random'. Pass an int for
          reproducible results across multiple function calls.
          See :term:`Glossary <random_state>`.
  
      Returns
      -------
      W : array-like of shape (n_samples, n_components)
          Initial guesses for solving X ~= WH.
  
      H : array-like of shape (n_components, n_features)
          Initial guesses for solving X ~= WH.
  
      References
      ----------
      C. Boutsidis, E. Gallopoulos: SVD based initialization: A head start for
      nonnegative matrix factorization - Pattern Recognition, 2008
      http://tinyurl.com/nndsvd
      """
      if init == 'warn':
          warnings.warn(("The 'init' value, when 'init=None' and "
                         "n_components is less than n_samples and "
                         "n_features, will be changed from 'nndsvd' to "
                         "'nndsvda' in 1.1 (renaming of 0.26)."), FutureWarning)
          init = None
  
      check_non_negative(X, "NMF initialization")
      n_samples, n_features = X.shape
  
      if (init is not None and init != 'random'
              and n_components > min(n_samples, n_features)):
          raise ValueError("init = '{}' can only be used when "
                           "n_components <= min(n_samples, n_features)"
                           .format(init))
  
      if init is None:
          if n_components <= min(n_samples, n_features):
              init = 'nndsvd'
          else:
              init = 'random'
  
      # Random initialization
      if init == 'random':
          avg = np.sqrt(X.mean() / n_components)
          rng = check_random_state(random_state)
          H = avg * rng.randn(n_components, n_features).astype(X.dtype,
                                                               copy=False)
          W = avg * rng.randn(n_samples, n_components).astype(X.dtype,
                                                              copy=False)
          np.abs(H, out=H)
          np.abs(W, out=W)
          return W, H
  
      # NNDSVD initialization
      U, S, V = randomized_svd(X, n_components, random_state=random_state)
      W = np.zeros_like(U)
      H = np.zeros_like(V)
  
      # The leading singular triplet is non-negative
      # so it can be used as is for initialization.
      W[:, 0] = np.sqrt(S[0]) * np.abs(U[:, 0])
      H[0, :] = np.sqrt(S[0]) * np.abs(V[0, :])
  
      for j in range(1, n_components):
          x, y = U[:, j], V[j, :]
  
          # extract positive and negative parts of column vectors
          x_p, y_p = np.maximum(x, 0), np.maximum(y, 0)
          x_n, y_n = np.abs(np.minimum(x, 0)), np.abs(np.minimum(y, 0))
  
          # and their norms
          x_p_nrm, y_p_nrm = norm(x_p), norm(y_p)
          x_n_nrm, y_n_nrm = norm(x_n), norm(y_n)
  
          m_p, m_n = x_p_nrm * y_p_nrm, x_n_nrm * y_n_nrm
  
          # choose update
          if m_p > m_n:
              u = x_p / x_p_nrm
              v = y_p / y_p_nrm
              sigma = m_p
          else:
              u = x_n / x_n_nrm
              v = y_n / y_n_nrm
              sigma = m_n
  
          lbd = np.sqrt(S[j] * sigma)
          W[:, j] = lbd * u
          H[j, :] = lbd * v
  
      W[W < eps] = 0
      H[H < eps] = 0
  
      if init == "nndsvd":
          pass
      elif init == "nndsvda":
          avg = X.mean()
          W[W == 0] = avg
          H[H == 0] = avg
      elif init == "nndsvdar":
          rng = check_random_state(random_state)
          avg = X.mean()
          W[W == 0] = abs(avg * rng.randn(len(W[W == 0])) / 100)
          H[H == 0] = abs(avg * rng.randn(len(H[H == 0])) / 100)
      else:
          raise ValueError(
              'Invalid init parameter: got %r instead of one of %r' %
              (init, (None, 'random', 'nndsvd', 'nndsvda', 'nndsvdar')))
  
      return W, H
  
  
  def _update_coordinate_descent(X, W, Ht, l1_reg, l2_reg, shuffle,
                                 random_state):
      """Helper function for _fit_coordinate_descent.
  
      Update W to minimize the objective function, iterating once over all
      coordinates. By symmetry, to update H, one can call
      _update_coordinate_descent(X.T, Ht, W, ...).
  
      """
      n_components = Ht.shape[1]
  
      HHt = np.dot(Ht.T, Ht)
      XHt = safe_sparse_dot(X, Ht)
  
      # L2 regularization corresponds to increase of the diagonal of HHt
      if l2_reg != 0.:
          # adds l2_reg only on the diagonal
          HHt.flat[::n_components + 1] += l2_reg
      # L1 regularization corresponds to decrease of each element of XHt
      if l1_reg != 0.:
          XHt -= l1_reg
  
      if shuffle:
          permutation = random_state.permutation(n_components)
      else:
          permutation = np.arange(n_components)
      # The following seems to be required on 64-bit Windows w/ Python 3.5.
      permutation = np.asarray(permutation, dtype=np.intp)
      return _update_cdnmf_fast(W, HHt, XHt, permutation)
  
  
  def _fit_coordinate_descent(X, W, H, tol=1e-4, max_iter=200, l1_reg_W=0,
                              l1_reg_H=0, l2_reg_W=0, l2_reg_H=0, update_H=True,
                              verbose=0, shuffle=False, random_state=None):
      """Compute Non-negative Matrix Factorization (NMF) with Coordinate Descent
  
      The objective function is minimized with an alternating minimization of W
      and H. Each minimization is done with a cyclic (up to a permutation of the
      features) Coordinate Descent.
  
      Parameters
      ----------
      X : array-like of shape (n_samples, n_features)
          Constant matrix.
  
      W : array-like of shape (n_samples, n_components)
          Initial guess for the solution.
  
      H : array-like of shape (n_components, n_features)
          Initial guess for the solution.
  
      tol : float, default=1e-4
          Tolerance of the stopping condition.
  
      max_iter : int, default=200
          Maximum number of iterations before timing out.
  
      l1_reg_W : float, default=0.
          L1 regularization parameter for W.
  
      l1_reg_H : float, default=0.
          L1 regularization parameter for H.
  
      l2_reg_W : float, default=0.
          L2 regularization parameter for W.
  
      l2_reg_H : float, default=0.
          L2 regularization parameter for H.
  
      update_H : bool, default=True
          Set to True, both W and H will be estimated from initial guesses.
          Set to False, only W will be estimated.
  
      verbose : int, default=0
          The verbosity level.
  
      shuffle : bool, default=False
          If true, randomize the order of coordinates in the CD solver.
  
      random_state : int, RandomState instance or None, default=None
          Used to randomize the coordinates in the CD solver, when
          ``shuffle`` is set to ``True``. Pass an int for reproducible
          results across multiple function calls.
          See :term:`Glossary <random_state>`.
  
      Returns
      -------
      W : ndarray of shape (n_samples, n_components)
          Solution to the non-negative least squares problem.
  
      H : ndarray of shape (n_components, n_features)
          Solution to the non-negative least squares problem.
  
      n_iter : int
          The number of iterations done by the algorithm.
  
      References
      ----------
      Cichocki, Andrzej, and Phan, Anh-Huy. "Fast local algorithms for
      large scale nonnegative matrix and tensor factorizations."
      IEICE transactions on fundamentals of electronics, communications and
      computer sciences 92.3: 708-721, 2009.
      """
      # so W and Ht are both in C order in memory
      Ht = check_array(H.T, order='C')
      X = check_array(X, accept_sparse='csr')
  
      rng = check_random_state(random_state)
  
      for n_iter in range(1, max_iter + 1):
          violation = 0.
  
          # Update W
          violation += _update_coordinate_descent(X, W, Ht, l1_reg_W,
                                                  l2_reg_W, shuffle, rng)
          # Update H
          if update_H:
              violation += _update_coordinate_descent(X.T, Ht, W, l1_reg_H,
                                                      l2_reg_H, shuffle, rng)
  
          if n_iter == 1:
              violation_init = violation
  
          if violation_init == 0:
              break
  
          if verbose:
              print("violation:", violation / violation_init)
  
          if violation / violation_init <= tol:
              if verbose:
                  print("Converged at iteration", n_iter + 1)
              break
  
      return W, Ht.T, n_iter
  
  
  def _multiplicative_update_w(X, W, H, beta_loss, l1_reg_W, l2_reg_W, gamma,
                               H_sum=None, HHt=None, XHt=None, update_H=True):
      """Update W in Multiplicative Update NMF."""
      if beta_loss == 2:
          # Numerator
          if XHt is None:
              XHt = safe_sparse_dot(X, H.T)
          if update_H:
              # avoid a copy of XHt, which will be re-computed (update_H=True)
              numerator = XHt
          else:
              # preserve the XHt, which is not re-computed (update_H=False)
              numerator = XHt.copy()
  
          # Denominator
          if HHt is None:
              HHt = np.dot(H, H.T)
          denominator = np.dot(W, HHt)
  
      else:
          # Numerator
          # if X is sparse, compute WH only where X is non zero
          WH_safe_X = _special_sparse_dot(W, H, X)
          if sp.issparse(X):
              WH_safe_X_data = WH_safe_X.data
              X_data = X.data
          else:
              WH_safe_X_data = WH_safe_X
              X_data = X
              # copy used in the Denominator
              WH = WH_safe_X.copy()
              if beta_loss - 1. < 0:
                  WH[WH == 0] = EPSILON
  
          # to avoid taking a negative power of zero
          if beta_loss - 2. < 0:
              WH_safe_X_data[WH_safe_X_data == 0] = EPSILON
  
          if beta_loss == 1:
              np.divide(X_data, WH_safe_X_data, out=WH_safe_X_data)
          elif beta_loss == 0:
              # speeds up computation time
              # refer to /numpy/numpy/issues/9363
              WH_safe_X_data **= -1
              WH_safe_X_data **= 2
              # element-wise multiplication
              WH_safe_X_data *= X_data
          else:
              WH_safe_X_data **= beta_loss - 2
              # element-wise multiplication
              WH_safe_X_data *= X_data
  
          # here numerator = dot(X * (dot(W, H) ** (beta_loss - 2)), H.T)
          numerator = safe_sparse_dot(WH_safe_X, H.T)
  
          # Denominator
          if beta_loss == 1:
              if H_sum is None:
                  H_sum = np.sum(H, axis=1)  # shape(n_components, )
              denominator = H_sum[np.newaxis, :]
  
          else:
              # computation of WHHt = dot(dot(W, H) ** beta_loss - 1, H.T)
              if sp.issparse(X):
                  # memory efficient computation
                  # (compute row by row, avoiding the dense matrix WH)
                  WHHt = np.empty(W.shape)
                  for i in range(X.shape[0]):
                      WHi = np.dot(W[i, :], H)
                      if beta_loss - 1 < 0:
                          WHi[WHi == 0] = EPSILON
                      WHi **= beta_loss - 1
                      WHHt[i, :] = np.dot(WHi, H.T)
              else:
                  WH **= beta_loss - 1
                  WHHt = np.dot(WH, H.T)
              denominator = WHHt
  
      # Add L1 and L2 regularization
      if l1_reg_W > 0:
          denominator += l1_reg_W
      if l2_reg_W > 0:
          denominator = denominator + l2_reg_W * W
      denominator[denominator == 0] = EPSILON
  
      numerator /= denominator
      delta_W = numerator
  
      # gamma is in ]0, 1]
      if gamma != 1:
          delta_W **= gamma
  
      return delta_W, H_sum, HHt, XHt
  
  
  def _multiplicative_update_h(X, W, H, beta_loss, l1_reg_H, l2_reg_H, gamma):
      """Update H in Multiplicative Update NMF."""
      if beta_loss == 2:
          numerator = safe_sparse_dot(W.T, X)
          denominator = np.linalg.multi_dot([W.T, W, H])
  
      else:
          # Numerator
          WH_safe_X = _special_sparse_dot(W, H, X)
          if sp.issparse(X):
              WH_safe_X_data = WH_safe_X.data
              X_data = X.data
          else:
              WH_safe_X_data = WH_safe_X
              X_data = X
              # copy used in the Denominator
              WH = WH_safe_X.copy()
              if beta_loss - 1. < 0:
                  WH[WH == 0] = EPSILON
  
          # to avoid division by zero
          if beta_loss - 2. < 0:
              WH_safe_X_data[WH_safe_X_data == 0] = EPSILON
  
          if beta_loss == 1:
              np.divide(X_data, WH_safe_X_data, out=WH_safe_X_data)
          elif beta_loss == 0:
              # speeds up computation time
              # refer to /numpy/numpy/issues/9363
              WH_safe_X_data **= -1
              WH_safe_X_data **= 2
              # element-wise multiplication
              WH_safe_X_data *= X_data
          else:
              WH_safe_X_data **= beta_loss - 2
              # element-wise multiplication
              WH_safe_X_data *= X_data
  
          # here numerator = dot(W.T, (dot(W, H) ** (beta_loss - 2)) * X)
          numerator = safe_sparse_dot(W.T, WH_safe_X)
  
          # Denominator
          if beta_loss == 1:
              W_sum = np.sum(W, axis=0)  # shape(n_components, )
              W_sum[W_sum == 0] = 1.
              denominator = W_sum[:, np.newaxis]
  
          # beta_loss not in (1, 2)
          else:
              # computation of WtWH = dot(W.T, dot(W, H) ** beta_loss - 1)
              if sp.issparse(X):
                  # memory efficient computation
                  # (compute column by column, avoiding the dense matrix WH)
                  WtWH = np.empty(H.shape)
                  for i in range(X.shape[1]):
                      WHi = np.dot(W, H[:, i])
                      if beta_loss - 1 < 0:
                          WHi[WHi == 0] = EPSILON
                      WHi **= beta_loss - 1
                      WtWH[:, i] = np.dot(W.T, WHi)
              else:
                  WH **= beta_loss - 1
                  WtWH = np.dot(W.T, WH)
              denominator = WtWH
  
      # Add L1 and L2 regularization
      if l1_reg_H > 0:
          denominator += l1_reg_H
      if l2_reg_H > 0:
          denominator = denominator + l2_reg_H * H
      denominator[denominator == 0] = EPSILON
  
      numerator /= denominator
      delta_H = numerator
  
      # gamma is in ]0, 1]
      if gamma != 1:
          delta_H **= gamma
  
      return delta_H
  
  
  def _fit_multiplicative_update(X, W, H, beta_loss='frobenius',
                                 max_iter=200, tol=1e-4,
                                 l1_reg_W=0, l1_reg_H=0, l2_reg_W=0, l2_reg_H=0,
                                 update_H=True, verbose=0):
      """Compute Non-negative Matrix Factorization with Multiplicative Update.
  
      The objective function is _beta_divergence(X, WH) and is minimized with an
      alternating minimization of W and H. Each minimization is done with a
      Multiplicative Update.
  
      Parameters
      ----------
      X : array-like of shape (n_samples, n_features)
          Constant input matrix.
  
      W : array-like of shape (n_samples, n_components)
          Initial guess for the solution.
  
      H : array-like of shape (n_components, n_features)
          Initial guess for the solution.
  
      beta_loss : float or {'frobenius', 'kullback-leibler', \
              'itakura-saito'}, default='frobenius'
          String must be in {'frobenius', 'kullback-leibler', 'itakura-saito'}.
          Beta divergence to be minimized, measuring the distance between X
          and the dot product WH. Note that values different from 'frobenius'
          (or 2) and 'kullback-leibler' (or 1) lead to significantly slower
          fits. Note that for beta_loss <= 0 (or 'itakura-saito'), the input
          matrix X cannot contain zeros.
  
      max_iter : int, default=200
          Number of iterations.
  
      tol : float, default=1e-4
          Tolerance of the stopping condition.
  
      l1_reg_W : float, default=0.
          L1 regularization parameter for W.
  
      l1_reg_H : float, default=0.
          L1 regularization parameter for H.
  
      l2_reg_W : float, default=0.
          L2 regularization parameter for W.
  
      l2_reg_H : float, default=0.
          L2 regularization parameter for H.
  
      update_H : bool, default=True
          Set to True, both W and H will be estimated from initial guesses.
          Set to False, only W will be estimated.
  
      verbose : int, default=0
          The verbosity level.
  
      Returns
      -------
      W : ndarray of shape (n_samples, n_components)
          Solution to the non-negative least squares problem.
  
      H : ndarray of shape (n_components, n_features)
          Solution to the non-negative least squares problem.
  
      n_iter : int
          The number of iterations done by the algorithm.
  
      References
      ----------
      Fevotte, C., & Idier, J. (2011). Algorithms for nonnegative matrix
      factorization with the beta-divergence. Neural Computation, 23(9).
      """
      start_time = time.time()
  
      beta_loss = _beta_loss_to_float(beta_loss)
  
      # gamma for Maximization-Minimization (MM) algorithm [Fevotte 2011]
      if beta_loss < 1:
          gamma = 1. / (2. - beta_loss)
      elif beta_loss > 2:
          gamma = 1. / (beta_loss - 1.)
      else:
          gamma = 1.
  
      # used for the convergence criterion
      error_at_init = _beta_divergence(X, W, H, beta_loss, square_root=True)
      previous_error = error_at_init
  
      H_sum, HHt, XHt = None, None, None
      for n_iter in range(1, max_iter + 1):
          # update W
          # H_sum, HHt and XHt are saved and reused if not update_H
          delta_W, H_sum, HHt, XHt = _multiplicative_update_w(
              X, W, H, beta_loss, l1_reg_W, l2_reg_W, gamma,
              H_sum, HHt, XHt, update_H)
          W *= delta_W
  
          # necessary for stability with beta_loss < 1
          if beta_loss < 1:
              W[W < np.finfo(np.float64).eps] = 0.
  
          # update H
          if update_H:
              delta_H = _multiplicative_update_h(X, W, H, beta_loss, l1_reg_H,
                                                 l2_reg_H, gamma)
              H *= delta_H
  
              # These values will be recomputed since H changed
              H_sum, HHt, XHt = None, None, None
  
              # necessary for stability with beta_loss < 1
              if beta_loss <= 1:
                  H[H < np.finfo(np.float64).eps] = 0.
  
          # test convergence criterion every 10 iterations
          if tol > 0 and n_iter % 10 == 0:
              error = _beta_divergence(X, W, H, beta_loss, square_root=True)
  
              if verbose:
                  iter_time = time.time()
                  print("Epoch %02d reached after %.3f seconds, error: %f" %
                        (n_iter, iter_time - start_time, error))
  
              if (previous_error - error) / error_at_init < tol:
                  break
              previous_error = error
  
      # do not print if we have already printed in the convergence test
      if verbose and (tol == 0 or n_iter % 10 != 0):
          end_time = time.time()
          print("Epoch %02d reached after %.3f seconds." %
                (n_iter, end_time - start_time))
  
      return W, H, n_iter
  
  
  @_deprecate_positional_args
  def non_negative_factorization(X, W=None, H=None, n_components=None, *,
                                 init='warn', update_H=True, solver='cd',
                                 beta_loss='frobenius', tol=1e-4,
                                 max_iter=200, alpha=0., l1_ratio=0.,
                                 regularization=None, random_state=None,
                                 verbose=0, shuffle=False):
      """Compute Non-negative Matrix Factorization (NMF).
  
      Find two non-negative matrices (W, H) whose product approximates the non-
      negative matrix X. This factorization can be used for example for
      dimensionality reduction, source separation or topic extraction.
  
      The objective function is:
  
          .. math::
  
              0.5 * ||X - WH||_{Fro}^2 + alpha * l1_{ratio} * ||vec(W)||_1
  
              + alpha * l1_{ratio} * ||vec(H)||_1
  
              + 0.5 * alpha * (1 - l1_{ratio}) * ||W||_{Fro}^2
  
              + 0.5 * alpha * (1 - l1_{ratio}) * ||H||_{Fro}^2
  
      Where:
  
      :math:`||A||_{Fro}^2 = \\sum_{i,j} A_{ij}^2` (Frobenius norm)
  
      :math:`||vec(A)||_1 = \\sum_{i,j} abs(A_{ij})` (Elementwise L1 norm)
  
      For multiplicative-update ('mu') solver, the Frobenius norm
      :math:`(0.5 * ||X - WH||_{Fro}^2)` can be changed into another
      beta-divergence loss, by changing the beta_loss parameter.
  
      The objective function is minimized with an alternating minimization of W
      and H. If H is given and update_H=False, it solves for W only.
  
      Parameters
      ----------
      X : array-like of shape (n_samples, n_features)
          Constant matrix.
  
      W : array-like of shape (n_samples, n_components), default=None
          If init='custom', it is used as initial guess for the solution.
  
      H : array-like of shape (n_components, n_features), default=None
          If init='custom', it is used as initial guess for the solution.
          If update_H=False, it is used as a constant, to solve for W only.
  
      n_components : int, default=None
          Number of components, if n_components is not set all features
          are kept.
  
      init : {'random', 'nndsvd', 'nndsvda', 'nndsvdar', 'custom'}, default=None
          Method used to initialize the procedure.
  
          Valid options:
  
          - None: 'nndsvd' if n_components < n_features, otherwise 'random'.
  
          - 'random': non-negative random matrices, scaled with:
              sqrt(X.mean() / n_components)
  
          - 'nndsvd': Nonnegative Double Singular Value Decomposition (NNDSVD)
              initialization (better for sparseness)
  
          - 'nndsvda': NNDSVD with zeros filled with the average of X
              (better when sparsity is not desired)
  
          - 'nndsvdar': NNDSVD with zeros filled with small random values
              (generally faster, less accurate alternative to NNDSVDa
              for when sparsity is not desired)
  
          - 'custom': use custom matrices W and H if `update_H=True`. If
            `update_H=False`, then only custom matrix H is used.
  
          .. versionchanged:: 0.23
              The default value of `init` changed from 'random' to None in 0.23.
  
      update_H : bool, default=True
          Set to True, both W and H will be estimated from initial guesses.
          Set to False, only W will be estimated.
  
      solver : {'cd', 'mu'}, default='cd'
          Numerical solver to use:
  
          - 'cd' is a Coordinate Descent solver that uses Fast Hierarchical
              Alternating Least Squares (Fast HALS).
  
          - 'mu' is a Multiplicative Update solver.
  
          .. versionadded:: 0.17
             Coordinate Descent solver.
  
          .. versionadded:: 0.19
             Multiplicative Update solver.
  
      beta_loss : float or {'frobenius', 'kullback-leibler', \
              'itakura-saito'}, default='frobenius'
          Beta divergence to be minimized, measuring the distance between X
          and the dot product WH. Note that values different from 'frobenius'
          (or 2) and 'kullback-leibler' (or 1) lead to significantly slower
          fits. Note that for beta_loss <= 0 (or 'itakura-saito'), the input
          matrix X cannot contain zeros. Used only in 'mu' solver.
  
          .. versionadded:: 0.19
  
      tol : float, default=1e-4
          Tolerance of the stopping condition.
  
      max_iter : int, default=200
          Maximum number of iterations before timing out.
  
      alpha : float, default=0.
          Constant that multiplies the regularization terms.
  
      l1_ratio : float, default=0.
          The regularization mixing parameter, with 0 <= l1_ratio <= 1.
          For l1_ratio = 0 the penalty is an elementwise L2 penalty
          (aka Frobenius Norm).
          For l1_ratio = 1 it is an elementwise L1 penalty.
          For 0 < l1_ratio < 1, the penalty is a combination of L1 and L2.
  
      regularization : {'both', 'components', 'transformation'}, default=None
          Select whether the regularization affects the components (H), the
          transformation (W), both or none of them.
  
      random_state : int, RandomState instance or None, default=None
          Used for NMF initialisation (when ``init`` == 'nndsvdar' or
          'random'), and in Coordinate Descent. Pass an int for reproducible
          results across multiple function calls.
          See :term:`Glossary <random_state>`.
  
      verbose : int, default=0
          The verbosity level.
  
      shuffle : bool, default=False
          If true, randomize the order of coordinates in the CD solver.
  
      Returns
      -------
      W : ndarray of shape (n_samples, n_components)
          Solution to the non-negative least squares problem.
  
      H : ndarray of shape (n_components, n_features)
          Solution to the non-negative least squares problem.
  
      n_iter : int
          Actual number of iterations.
  
      Examples
      --------
      >>> import numpy as np
      >>> X = np.array([[1,1], [2, 1], [3, 1.2], [4, 1], [5, 0.8], [6, 1]])
      >>> from sklearn.decomposition import non_negative_factorization
      >>> W, H, n_iter = non_negative_factorization(X, n_components=2,
      ... init='random', random_state=0)
  
      References
      ----------
      Cichocki, Andrzej, and P. H. A. N. Anh-Huy. "Fast local algorithms for
      large scale nonnegative matrix and tensor factorizations."
      IEICE transactions on fundamentals of electronics, communications and
      computer sciences 92.3: 708-721, 2009.
  
      Fevotte, C., & Idier, J. (2011). Algorithms for nonnegative matrix
      factorization with the beta-divergence. Neural Computation, 23(9).
      """
      X = check_array(X, accept_sparse=('csr', 'csc'),
                      dtype=[np.float64, np.float32])
      check_non_negative(X, "NMF (input X)")
      beta_loss = _check_string_param(solver, regularization, beta_loss, init)
  
      if X.min() == 0 and beta_loss <= 0:
          raise ValueError("When beta_loss <= 0 and X contains zeros, "
                           "the solver may diverge. Please add small values to "
                           "X, or use a positive beta_loss.")
  
      n_samples, n_features = X.shape
      if n_components is None:
          n_components = n_features
  
      if not isinstance(n_components, numbers.Integral) or n_components <= 0:
          raise ValueError("Number of components must be a positive integer;"
                           " got (n_components=%r)" % n_components)
      if not isinstance(max_iter, numbers.Integral) or max_iter < 0:
          raise ValueError("Maximum number of iterations must be a positive "
                           "integer; got (max_iter=%r)" % max_iter)
      if not isinstance(tol, numbers.Number) or tol < 0:
          raise ValueError("Tolerance for stopping criteria must be "
                           "positive; got (tol=%r)" % tol)
  
      # check W and H, or initialize them
      if init == 'custom' and update_H:
          _check_init(H, (n_components, n_features), "NMF (input H)")
          _check_init(W, (n_samples, n_components), "NMF (input W)")
          if H.dtype != X.dtype or W.dtype != X.dtype:
              raise TypeError("H and W should have the same dtype as X. Got "
                              "H.dtype = {} and W.dtype = {}."
                              .format(H.dtype, W.dtype))
      elif not update_H:
          _check_init(H, (n_components, n_features), "NMF (input H)")
          if H.dtype != X.dtype:
              raise TypeError("H should have the same dtype as X. Got H.dtype = "
                              "{}.".format(H.dtype))
          # 'mu' solver should not be initialized by zeros
          if solver == 'mu':
              avg = np.sqrt(X.mean() / n_components)
              W = np.full((n_samples, n_components), avg, dtype=X.dtype)
          else:
              W = np.zeros((n_samples, n_components), dtype=X.dtype)
      else:
          W, H = _initialize_nmf(X, n_components, init=init,
                                 random_state=random_state)
  
      l1_reg_W, l1_reg_H, l2_reg_W, l2_reg_H = _compute_regularization(
          alpha, l1_ratio, regularization)
  
      if solver == 'cd':
          W, H, n_iter = _fit_coordinate_descent(X, W, H, tol, max_iter,
                                                 l1_reg_W, l1_reg_H,
                                                 l2_reg_W, l2_reg_H,
                                                 update_H=update_H,
                                                 verbose=verbose,
                                                 shuffle=shuffle,
                                                 random_state=random_state)
      elif solver == 'mu':
          W, H, n_iter = _fit_multiplicative_update(X, W, H, beta_loss, max_iter,
                                                    tol, l1_reg_W, l1_reg_H,
                                                    l2_reg_W, l2_reg_H, update_H,
                                                    verbose)
  
      else:
          raise ValueError("Invalid solver parameter '%s'." % solver)
  
      if n_iter == max_iter and tol > 0:
          warnings.warn("Maximum number of iterations %d reached. Increase it to"
                        " improve convergence." % max_iter, ConvergenceWarning)
  
      return W, H, n_iter
  
  
  class NMF(TransformerMixin, BaseEstimator):
      """Non-Negative Matrix Factorization (NMF).
  
      Find two non-negative matrices (W, H) whose product approximates the non-
      negative matrix X. This factorization can be used for example for
      dimensionality reduction, source separation or topic extraction.
  
      The objective function is:
  
          .. math::
  
              0.5 * ||X - WH||_{Fro}^2 + alpha * l1_{ratio} * ||vec(W)||_1
  
              + alpha * l1_{ratio} * ||vec(H)||_1
  
              + 0.5 * alpha * (1 - l1_{ratio}) * ||W||_{Fro}^2
  
              + 0.5 * alpha * (1 - l1_{ratio}) * ||H||_{Fro}^2
  
      Where:
  
      :math:`||A||_{Fro}^2 = \\sum_{i,j} A_{ij}^2` (Frobenius norm)
  
      :math:`||vec(A)||_1 = \\sum_{i,j} abs(A_{ij})` (Elementwise L1 norm)
  
      For multiplicative-update ('mu') solver, the Frobenius norm
      (:math:`0.5 * ||X - WH||_{Fro}^2`) can be changed into another
      beta-divergence loss, by changing the beta_loss parameter.
  
      The objective function is minimized with an alternating minimization of W
      and H.
  
      Read more in the :ref:`User Guide <NMF>`.
  
      Parameters
      ----------
      n_components : int, default=None
          Number of components, if n_components is not set all features
          are kept.
  
      init : {'random', 'nndsvd', 'nndsvda', 'nndsvdar', 'custom'}, default=None
          Method used to initialize the procedure.
          Default: None.
          Valid options:
  
          - `None`: 'nndsvd' if n_components <= min(n_samples, n_features),
            otherwise random.
  
          - `'random'`: non-negative random matrices, scaled with:
            sqrt(X.mean() / n_components)
  
          - `'nndsvd'`: Nonnegative Double Singular Value Decomposition (NNDSVD)
            initialization (better for sparseness)
  
          - `'nndsvda'`: NNDSVD with zeros filled with the average of X
            (better when sparsity is not desired)
  
          - `'nndsvdar'` NNDSVD with zeros filled with small random values
            (generally faster, less accurate alternative to NNDSVDa
            for when sparsity is not desired)
  
          - `'custom'`: use custom matrices W and H
  
      solver : {'cd', 'mu'}, default='cd'
          Numerical solver to use:
          'cd' is a Coordinate Descent solver.
          'mu' is a Multiplicative Update solver.
  
          .. versionadded:: 0.17
             Coordinate Descent solver.
  
          .. versionadded:: 0.19
             Multiplicative Update solver.
  
      beta_loss : float or {'frobenius', 'kullback-leibler', \
              'itakura-saito'}, default='frobenius'
          Beta divergence to be minimized, measuring the distance between X
          and the dot product WH. Note that values different from 'frobenius'
          (or 2) and 'kullback-leibler' (or 1) lead to significantly slower
          fits. Note that for beta_loss <= 0 (or 'itakura-saito'), the input
          matrix X cannot contain zeros. Used only in 'mu' solver.
  
          .. versionadded:: 0.19
  
      tol : float, default=1e-4
          Tolerance of the stopping condition.
  
      max_iter : int, default=200
          Maximum number of iterations before timing out.
  
      random_state : int, RandomState instance or None, default=None
          Used for initialisation (when ``init`` == 'nndsvdar' or
          'random'), and in Coordinate Descent. Pass an int for reproducible
          results across multiple function calls.
          See :term:`Glossary <random_state>`.
  
      alpha : float, default=0.
          Constant that multiplies the regularization terms. Set it to zero to
          have no regularization.
  
          .. versionadded:: 0.17
             *alpha* used in the Coordinate Descent solver.
  
      l1_ratio : float, default=0.
          The regularization mixing parameter, with 0 <= l1_ratio <= 1.
          For l1_ratio = 0 the penalty is an elementwise L2 penalty
          (aka Frobenius Norm).
          For l1_ratio = 1 it is an elementwise L1 penalty.
          For 0 < l1_ratio < 1, the penalty is a combination of L1 and L2.
  
          .. versionadded:: 0.17
             Regularization parameter *l1_ratio* used in the Coordinate Descent
             solver.
  
      verbose : int, default=0
          Whether to be verbose.
  
      shuffle : bool, default=False
          If true, randomize the order of coordinates in the CD solver.
  
          .. versionadded:: 0.17
             *shuffle* parameter used in the Coordinate Descent solver.
  
      regularization : {'both', 'components', 'transformation', None}, \
                       default='both'
          Select whether the regularization affects the components (H), the
          transformation (W), both or none of them.
  
          .. versionadded:: 0.24
  
      Attributes
      ----------
      components_ : ndarray of shape (n_components, n_features)
          Factorization matrix, sometimes called 'dictionary'.
  
      n_components_ : int
          The number of components. It is same as the `n_components` parameter
          if it was given. Otherwise, it will be same as the number of
          features.
  
      reconstruction_err_ : float
          Frobenius norm of the matrix difference, or beta-divergence, between
          the training data ``X`` and the reconstructed data ``WH`` from
          the fitted model.
  
      n_iter_ : int
          Actual number of iterations.
  
      Examples
      --------
      >>> import numpy as np
      >>> X = np.array([[1, 1], [2, 1], [3, 1.2], [4, 1], [5, 0.8], [6, 1]])
      >>> from sklearn.decomposition import NMF
      >>> model = NMF(n_components=2, init='random', random_state=0)
      >>> W = model.fit_transform(X)
      >>> H = model.components_
  
      References
      ----------
      Cichocki, Andrzej, and P. H. A. N. Anh-Huy. "Fast local algorithms for
      large scale nonnegative matrix and tensor factorizations."
      IEICE transactions on fundamentals of electronics, communications and
      computer sciences 92.3: 708-721, 2009.
  
      Fevotte, C., & Idier, J. (2011). Algorithms for nonnegative matrix
      factorization with the beta-divergence. Neural Computation, 23(9).
      """
      @_deprecate_positional_args
      def __init__(self, n_components=None, *, init='warn', solver='cd',
                   beta_loss='frobenius', tol=1e-4, max_iter=200,
                   random_state=None, alpha=0., l1_ratio=0., verbose=0,
                   shuffle=False, regularization='both'):
          self.n_components = n_components
          self.init = init
          self.solver = solver
          self.beta_loss = beta_loss
          self.tol = tol
          self.max_iter = max_iter
          self.random_state = random_state
          self.alpha = alpha
          self.l1_ratio = l1_ratio
          self.verbose = verbose
          self.shuffle = shuffle
          self.regularization = regularization
  
      def _more_tags(self):
          return {'requires_positive_X': True}
  
      def fit_transform(self, X, y=None, W=None, H=None):
          """Learn a NMF model for the data X and returns the transformed data.
  
          This is more efficient than calling fit followed by transform.
  
          Parameters
          ----------
          X : {array-like, sparse matrix} of shape (n_samples, n_features)
              Data matrix to be decomposed
  
          y : Ignored
  
          W : array-like of shape (n_samples, n_components)
              If init='custom', it is used as initial guess for the solution.
  
          H : array-like of shape (n_components, n_features)
              If init='custom', it is used as initial guess for the solution.
  
          Returns
          -------
          W : ndarray of shape (n_samples, n_components)
              Transformed data.
          """
          X = self._validate_data(X, accept_sparse=('csr', 'csc'),
                                  dtype=[np.float64, np.float32])
  
          with config_context(assume_finite=True):
              W, H, n_iter_ = non_negative_factorization(
                  X=X, W=W, H=H, n_components=self.n_components, init=self.init,
                  update_H=True, solver=self.solver, beta_loss=self.beta_loss,
                  tol=self.tol, max_iter=self.max_iter, alpha=self.alpha,
                  l1_ratio=self.l1_ratio, regularization=self.regularization,
                  random_state=self.random_state, verbose=self.verbose,
                  shuffle=self.shuffle)
  
          self.reconstruction_err_ = _beta_divergence(X, W, H, self.beta_loss,
                                                      square_root=True)
  
          self.n_components_ = H.shape[0]
          self.components_ = H
          self.n_iter_ = n_iter_
  
          return W
  
      def fit(self, X, y=None, **params):
          """Learn a NMF model for the data X.
  
          Parameters
          ----------
          X : {array-like, sparse matrix} of shape (n_samples, n_features)
              Data matrix to be decomposed
  
          y : Ignored
  
          Returns
          -------
          self
          """
          self.fit_transform(X, **params)
          return self
  
      def transform(self, X):
          """Transform the data X according to the fitted NMF model.
  
          Parameters
          ----------
          X : {array-like, sparse matrix} of shape (n_samples, n_features)
              Data matrix to be transformed by the model.
  
          Returns
          -------
          W : ndarray of shape (n_samples, n_components)
              Transformed data.
          """
          check_is_fitted(self)
          X = self._validate_data(X, accept_sparse=('csr', 'csc'),
                                  dtype=[np.float64, np.float32],
                                  reset=False)
  
          with config_context(assume_finite=True):
              W, _, n_iter_ = non_negative_factorization(
                  X=X, W=None, H=self.components_,
                  n_components=self.n_components_,
                  init=self.init, update_H=False, solver=self.solver,
                  beta_loss=self.beta_loss, tol=self.tol, max_iter=self.max_iter,
                  alpha=self.alpha, l1_ratio=self.l1_ratio,
                  regularization=self.regularization,
                  random_state=self.random_state,
                  verbose=self.verbose, shuffle=self.shuffle)
  
          return W
  
      def inverse_transform(self, W):
          """Transform data back to its original space.
  
          Parameters
          ----------
          W : {ndarray, sparse matrix} of shape (n_samples, n_components)
              Transformed data matrix.
  
          Returns
          -------
          X : {ndarray, sparse matrix} of shape (n_samples, n_features)
              Data matrix of original shape.
  
          .. versionadded:: 0.18
          """
          check_is_fitted(self)
          return np.dot(W, self.components_)
   import warnings
  import unittest
  import sys
  import os
  import atexit
  
  import numpy as np
  
  from scipy import sparse
  
  import pytest
  
  from sklearn.utils.deprecation import deprecated
  from sklearn.utils.metaestimators import if_delegate_has_method
  from sklearn.utils._testing import (
      assert_raises,
      assert_warns,
      assert_no_warnings,
      set_random_state,
      assert_raise_message,
      ignore_warnings,
      check_docstring_parameters,
      assert_allclose_dense_sparse,
      assert_raises_regex,
      TempMemmap,
      create_memmap_backed_data,
      _delete_folder,
      _convert_container,
      raises,
  )
  
  from sklearn.tree import DecisionTreeClassifier
  from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
  
  
  def test_set_random_state():
      lda = LinearDiscriminantAnalysis()
      tree = DecisionTreeClassifier()
      # Linear Discriminant Analysis doesn't have random state: smoke test
      set_random_state(lda, 3)
      set_random_state(tree, 3)
      assert tree.random_state == 3
  
  
  def test_assert_allclose_dense_sparse():
      x = np.arange(9).reshape(3, 3)
      msg = "Not equal to tolerance "
      y = sparse.csc_matrix(x)
      for X in [x, y]:
          # basic compare
          with pytest.raises(AssertionError, match=msg):
              assert_allclose_dense_sparse(X, X*2)
          assert_allclose_dense_sparse(X, X)
  
      with pytest.raises(ValueError, match="Can only compare two sparse"):
          assert_allclose_dense_sparse(x, y)
  
      A = sparse.diags(np.ones(5), offsets=0).tocsr()
      B = sparse.csr_matrix(np.ones((1, 5)))
      with pytest.raises(AssertionError, match="Arrays are not equal"):
          assert_allclose_dense_sparse(B, A)
  
  
  def test_assert_raises_msg():
      with assert_raises_regex(AssertionError, 'Hello world'):
          with assert_raises(ValueError, msg='Hello world'):
              pass
  
  
  def test_assert_raise_message():
      def _raise_ValueError(message):
          raise ValueError(message)
  
      def _no_raise():
          pass
  
      assert_raise_message(ValueError, "test",
                           _raise_ValueError, "test")
  
      assert_raises(AssertionError,
                    assert_raise_message, ValueError, "something else",
                    _raise_ValueError, "test")
  
      assert_raises(ValueError,
                    assert_raise_message, TypeError, "something else",
                    _raise_ValueError, "test")
  
      assert_raises(AssertionError,
                    assert_raise_message, ValueError, "test",
                    _no_raise)
  
      # multiple exceptions in a tuple
      assert_raises(AssertionError,
                    assert_raise_message, (ValueError, AttributeError),
                    "test", _no_raise)
  
  
  def test_ignore_warning():
      # This check that ignore_warning decorateur and context manager are working
      # as expected
      def _warning_function():
          warnings.warn("deprecation warning", DeprecationWarning)
  
      def _multiple_warning_function():
          warnings.warn("deprecation warning", DeprecationWarning)
          warnings.warn("deprecation warning")
  
      # Check the function directly
      assert_no_warnings(ignore_warnings(_warning_function))
      assert_no_warnings(ignore_warnings(_warning_function,
                                         category=DeprecationWarning))
      assert_warns(DeprecationWarning, ignore_warnings(_warning_function,
                                                       category=UserWarning))
      assert_warns(UserWarning,
                   ignore_warnings(_multiple_warning_function,
                                   category=FutureWarning))
      assert_warns(DeprecationWarning,
                   ignore_warnings(_multiple_warning_function,
                                   category=UserWarning))
      assert_no_warnings(ignore_warnings(_warning_function,
                                         category=(DeprecationWarning,
                                                   UserWarning)))
  
      # Check the decorator
      @ignore_warnings
      def decorator_no_warning():
          _warning_function()
          _multiple_warning_function()
  
      @ignore_warnings(category=(DeprecationWarning, UserWarning))
      def decorator_no_warning_multiple():
          _multiple_warning_function()
  
      @ignore_warnings(category=DeprecationWarning)
      def decorator_no_deprecation_warning():
          _warning_function()
  
      @ignore_warnings(category=UserWarning)
      def decorator_no_user_warning():
          _warning_function()
  
      @ignore_warnings(category=DeprecationWarning)
      def decorator_no_deprecation_multiple_warning():
          _multiple_warning_function()
  
      @ignore_warnings(category=UserWarning)
      def decorator_no_user_multiple_warning():
          _multiple_warning_function()
  
      assert_no_warnings(decorator_no_warning)
      assert_no_warnings(decorator_no_warning_multiple)
      assert_no_warnings(decorator_no_deprecation_warning)
      assert_warns(DeprecationWarning, decorator_no_user_warning)
      assert_warns(UserWarning, decorator_no_deprecation_multiple_warning)
      assert_warns(DeprecationWarning, decorator_no_user_multiple_warning)
  
      # Check the context manager
      def context_manager_no_warning():
          with ignore_warnings():
              _warning_function()
  
      def context_manager_no_warning_multiple():
          with ignore_warnings(category=(DeprecationWarning, UserWarning)):
              _multiple_warning_function()
  
      def context_manager_no_deprecation_warning():
          with ignore_warnings(category=DeprecationWarning):
              _warning_function()
  
      def context_manager_no_user_warning():
          with ignore_warnings(category=UserWarning):
              _warning_function()
  
      def context_manager_no_deprecation_multiple_warning():
          with ignore_warnings(category=DeprecationWarning):
              _multiple_warning_function()
  
      def context_manager_no_user_multiple_warning():
          with ignore_warnings(category=UserWarning):
              _multiple_warning_function()
  
      assert_no_warnings(context_manager_no_warning)
      assert_no_warnings(context_manager_no_warning_multiple)
      assert_no_warnings(context_manager_no_deprecation_warning)
      assert_warns(DeprecationWarning, context_manager_no_user_warning)
      assert_warns(UserWarning, context_manager_no_deprecation_multiple_warning)
      assert_warns(DeprecationWarning, context_manager_no_user_multiple_warning)
  
      # Check that passing warning class as first positional argument
      warning_class = UserWarning
      match = "'obj' should be a callable.+you should use 'category=UserWarning'"
  
      with pytest.raises(ValueError, match=match):
          silence_warnings_func = ignore_warnings(warning_class)(
              _warning_function)
          silence_warnings_func()
  
      with pytest.raises(ValueError, match=match):
          @ignore_warnings(warning_class)
          def test():
              pass
  
  
  class TestWarns(unittest.TestCase):
      def test_warn(self):
          def f():
              warnings.warn("yo")
              return 3
  
          with warnings.catch_warnings():
              warnings.simplefilter("ignore", UserWarning)
              filters_orig = warnings.filters[:]
              assert assert_warns(UserWarning, f) == 3
              # test that assert_warns doesn't have side effects on warnings
              # filters
              assert warnings.filters == filters_orig
          with pytest.raises(AssertionError):
              assert_no_warnings(f)
          assert assert_no_warnings(lambda x: x, 1) == 1
  
      def test_warn_wrong_warning(self):
          def f():
              warnings.warn("yo", FutureWarning)
  
          failed = False
          filters = sys.modules['warnings'].filters[:]
          try:
              try:
                  # Should raise an AssertionError
  
                  # assert_warns has a special handling of "FutureWarning" that
                  # pytest.warns does not have
                  assert_warns(UserWarning, f)
                  failed = True
              except AssertionError:
                  pass
          finally:
              sys.modules['warnings'].filters = filters
  
          if failed:
              raise AssertionError("wrong warning caught by assert_warn")
  
  
  # Tests for docstrings:
  
  def f_ok(a, b):
      """Function f
  
      Parameters
      ----------
      a : int
          Parameter a
      b : float
          Parameter b
  
      Returns
      -------
      c : list
          Parameter c
      """
      c = a + b
      return c
  
  
  def f_bad_sections(a, b):
      """Function f
  
      Parameters
      ----------
      a : int
          Parameter a
      b : float
          Parameter b
  
      Results
      -------
      c : list
          Parameter c
      """
      c = a + b
      return c
  
  
  def f_bad_order(b, a):
      """Function f
  
      Parameters
      ----------
      a : int
          Parameter a
      b : float
          Parameter b
  
      Returns
      -------
      c : list
          Parameter c
      """
      c = a + b
      return c
  
  
  def f_too_many_param_docstring(a, b):
      """Function f
  
      Parameters
      ----------
      a : int
          Parameter a
      b : int
          Parameter b
      c : int
          Parameter c
  
      Returns
      -------
      d : list
          Parameter c
      """
      d = a + b
      return d
  
  
  def f_missing(a, b):
      """Function f
  
      Parameters
      ----------
      a : int
          Parameter a
  
      Returns
      -------
      c : list
          Parameter c
      """
      c = a + b
      return c
  
  
  def f_check_param_definition(a, b, c, d, e):
      """Function f
  
      Parameters
      ----------
      a: int
          Parameter a
      b:
          Parameter b
      c :
          Parameter c
      d:int
          Parameter d
      e
          No typespec is allowed without colon
      """
      return a + b + c + d
  
  
  class Klass:
      def f_missing(self, X, y):
          pass
  
      def f_bad_sections(self, X, y):
          """Function f
  
          Parameter
          ----------
          a : int
              Parameter a
          b : float
              Parameter b
  
          Results
          -------
          c : list
              Parameter c
          """
          pass
  
  
  class MockEst:
      def __init__(self):
          """MockEstimator"""
      def fit(self, X, y):
          return X
  
      def predict(self, X):
          return X
  
      def predict_proba(self, X):
          return X
  
      def score(self, X):
          return 1.
  
  
  class MockMetaEstimator:
      def __init__(self, delegate):
          """MetaEstimator to check if doctest on delegated methods work.
  
          Parameters
          ---------
          delegate : estimator
              Delegated estimator.
          """
          self.delegate = delegate
  
      @if_delegate_has_method(delegate=('delegate'))
      def predict(self, X):
          """This is available only if delegate has predict.
  
          Parameters
          ----------
          y : ndarray
              Parameter y
          """
          return self.delegate.predict(X)
  
      @if_delegate_has_method(delegate=('delegate'))
      @deprecated("Testing a deprecated delegated method")
      def score(self, X):
          """This is available only if delegate has score.
  
          Parameters
          ---------
          y : ndarray
              Parameter y
          """
  
      @if_delegate_has_method(delegate=('delegate'))
      def predict_proba(self, X):
          """This is available only if delegate has predict_proba.
  
          Parameters
          ---------
          X : ndarray
              Parameter X
          """
          return X
  
      @deprecated('Testing deprecated function with wrong params')
      def fit(self, X, y):
          """Incorrect docstring but should not be tested"""
  
  
  def test_check_docstring_parameters():
      pytest.importorskip('numpydoc',
                          reason="numpydoc is required to test the docstrings")
  
      incorrect = check_docstring_parameters(f_ok)
      assert incorrect == []
      incorrect = check_docstring_parameters(f_ok, ignore=['b'])
      assert incorrect == []
      incorrect = check_docstring_parameters(f_missing, ignore=['b'])
      assert incorrect == []
      with pytest.raises(RuntimeError, match="Unknown section Results"):
          check_docstring_parameters(f_bad_sections)
      with pytest.raises(RuntimeError, match="Unknown section Parameter"):
          check_docstring_parameters(Klass.f_bad_sections)
  
      incorrect = check_docstring_parameters(f_check_param_definition)
      assert (
          incorrect == [
              "sklearn.utils.tests.test_testing.f_check_param_definition There "
              "was no space between the param name and colon ('a: int')",
  
              "sklearn.utils.tests.test_testing.f_check_param_definition There "
              "was no space between the param name and colon ('b:')",
  
              "sklearn.utils.tests.test_testing.f_check_param_definition "
              "Parameter 'c :' has an empty type spec. Remove the colon",
  
              "sklearn.utils.tests.test_testing.f_check_param_definition There "
              "was no space between the param name and colon ('d:int')",
          ])
  
      messages = [
              ["In function: sklearn.utils.tests.test_testing.f_bad_order",
               "There's a parameter name mismatch in function docstring w.r.t."
               " function signature, at index 0 diff: 'b' != 'a'",
               "Full diff:",
               "- ['b', 'a']",
               "+ ['a', 'b']"],
  
              ["In function: " +
                  "sklearn.utils.tests.test_testing.f_too_many_param_docstring",
               "Parameters in function docstring have more items w.r.t. function"
               " signature, first extra item: c",
               "Full diff:",
               "- ['a', 'b']",
               "+ ['a', 'b', 'c']",
               "?          +++++"],
  
              ["In function: sklearn.utils.tests.test_testing.f_missing",
               "Parameters in function docstring have less items w.r.t. function"
               " signature, first missing item: b",
               "Full diff:",
               "- ['a', 'b']",
               "+ ['a']"],
  
              ["In function: sklearn.utils.tests.test_testing.Klass.f_missing",
               "Parameters in function docstring have less items w.r.t. function"
               " signature, first missing item: X",
               "Full diff:",
               "- ['X', 'y']",
               "+ []"],
  
              ["In function: " +
               "sklearn.utils.tests.test_testing.MockMetaEstimator.predict",
               "There's a parameter name mismatch in function docstring w.r.t."
               " function signature, at index 0 diff: 'X' != 'y'",
               "Full diff:",
               "- ['X']",
               "?   ^",
               "+ ['y']",
               "?   ^"],
  
              ["In function: " +
               "sklearn.utils.tests.test_testing.MockMetaEstimator."
               + "predict_proba",
               "Parameters in function docstring have less items w.r.t. function"
               " signature, first missing item: X",
               "Full diff:",
               "- ['X']",
               "+ []"],
  
              ["In function: " +
                  "sklearn.utils.tests.test_testing.MockMetaEstimator.score",
               "Parameters in function docstring have less items w.r.t. function"
               " signature, first missing item: X",
               "Full diff:",
               "- ['X']",
               "+ []"],
  
              ["In function: " +
                  "sklearn.utils.tests.test_testing.MockMetaEstimator.fit",
               "Parameters in function docstring have less items w.r.t. function"
               " signature, first missing item: X",
               "Full diff:",
               "- ['X', 'y']",
               "+ []"],
  
              ]
  
      mock_meta = MockMetaEstimator(delegate=MockEst())
  
      for msg, f in zip(messages,
                        [f_bad_order,
                         f_too_many_param_docstring,
                         f_missing,
                         Klass.f_missing,
                         mock_meta.predict,
                         mock_meta.predict_proba,
                         mock_meta.score,
                         mock_meta.fit]):
          incorrect = check_docstring_parameters(f)
          assert msg == incorrect, ('\n"%s"\n not in \n"%s"' % (msg, incorrect))
  
  
  class RegistrationCounter:
      def __init__(self):
          self.nb_calls = 0
  
      def __call__(self, to_register_func):
          self.nb_calls += 1
          assert to_register_func.func is _delete_folder
  
  
  def check_memmap(input_array, mmap_data, mmap_mode='r'):
      assert isinstance(mmap_data, np.memmap)
      writeable = mmap_mode != 'r'
      assert mmap_data.flags.writeable is writeable
      np.testing.assert_array_equal(input_array, mmap_data)
  
  
  def test_tempmemmap(monkeypatch):
      registration_counter = RegistrationCounter()
      monkeypatch.setattr(atexit, 'register', registration_counter)
  
      input_array = np.ones(3)
      with TempMemmap(input_array) as data:
          check_memmap(input_array, data)
          temp_folder = os.path.dirname(data.filename)
      if os.name != 'nt':
          assert not os.path.exists(temp_folder)
      assert registration_counter.nb_calls == 1
  
      mmap_mode = 'r+'
      with TempMemmap(input_array, mmap_mode=mmap_mode) as data:
          check_memmap(input_array, data, mmap_mode=mmap_mode)
          temp_folder = os.path.dirname(data.filename)
      if os.name != 'nt':
          assert not os.path.exists(temp_folder)
      assert registration_counter.nb_calls == 2
  
  
  def test_create_memmap_backed_data(monkeypatch):
      registration_counter = RegistrationCounter()
      monkeypatch.setattr(atexit, 'register', registration_counter)
  
      input_array = np.ones(3)
      data = create_memmap_backed_data(input_array)
      check_memmap(input_array, data)
      assert registration_counter.nb_calls == 1
  
      data, folder = create_memmap_backed_data(input_array,
                                               return_folder=True)
      check_memmap(input_array, data)
      assert folder == os.path.dirname(data.filename)
      assert registration_counter.nb_calls == 2
  
      mmap_mode = 'r+'
      data = create_memmap_backed_data(input_array, mmap_mode=mmap_mode)
      check_memmap(input_array, data, mmap_mode)
      assert registration_counter.nb_calls == 3
  
      input_list = [input_array, input_array + 1, input_array + 2]
      mmap_data_list = create_memmap_backed_data(input_list)
      for input_array, data in zip(input_list, mmap_data_list):
          check_memmap(input_array, data)
      assert registration_counter.nb_calls == 4
  
  
  @pytest.mark.parametrize(
      "constructor_name, container_type",
      [('list', list),
       ('tuple', tuple),
       ('array', np.ndarray),
       ('sparse', sparse.csr_matrix),
       ('dataframe', pytest.importorskip('pandas').DataFrame),
       ('series', pytest.importorskip('pandas').Series),
       ('index', pytest.importorskip('pandas').Index),
       ('slice', slice)]
  )
  def test_convert_container(constructor_name, container_type):
      container = [0, 1]
      assert isinstance(_convert_container(container, constructor_name),
                        container_type)
  
  
  def test_raises():
      # Tests for the raises context manager
  
      # Proper type, no match
      with raises(TypeError):
          raise TypeError()
  
      # Proper type, proper match
      with raises(TypeError, match="how are you") as cm:
          raise TypeError("hello how are you")
      assert cm.raised_and_matched
  
      # Proper type, proper match with multiple patterns
      with raises(TypeError, match=["not this one", "how are you"]) as cm:
          raise TypeError("hello how are you")
      assert cm.raised_and_matched
  
      # bad type, no match
      with pytest.raises(ValueError, match="this will be raised"):
          with raises(TypeError) as cm:
              raise ValueError("this will be raised")
      assert not cm.raised_and_matched
  
      # Bad type, no match, with a err_msg
      with pytest.raises(AssertionError, match="the failure message"):
          with raises(TypeError, err_msg="the failure message") as cm:
              raise ValueError()
      assert not cm.raised_and_matched
  
      # bad type, with match (is ignored anyway)
      with pytest.raises(ValueError, match="this will be raised"):
          with raises(TypeError, match="this is ignored") as cm:
              raise ValueError("this will be raised")
      assert not cm.raised_and_matched
  
      # proper type but bad match
      with pytest.raises(
          AssertionError, match="should contain one of the following patterns"
      ):
          with raises(TypeError, match="hello") as cm:
              raise TypeError("Bad message")
      assert not cm.raised_and_matched
  
      # proper type but bad match, with err_msg
      with pytest.raises(AssertionError, match="the failure message"):
          with raises(
              TypeError, match="hello", err_msg="the failure message"
          ) as cm:
              raise TypeError("Bad message")
      assert not cm.raised_and_matched
  
      # no raise with default may_pass=False
      with pytest.raises(AssertionError, match="Did not raise"):
          with raises(TypeError) as cm:
              pass
      assert not cm.raised_and_matched
  
      # no raise with may_pass=True
      with raises(TypeError, match="hello", may_pass=True) as cm:
          pass  # still OK
      assert not cm.raised_and_matched
  
      # Multiple exception types:
      with raises((TypeError, ValueError)):
          raise TypeError()
      with raises((TypeError, ValueError)):
          raise ValueError()
      with pytest.raises(AssertionError):
          with raises((TypeError, ValueError)):
              pass
   import numpy as np
  import pytest
  
  import pandas as pd
  import pandas._testing as tm
  
  
  @pytest.mark.parametrize("sort", [True, False])
  def test_factorize(index_or_series_obj, sort):
      obj = index_or_series_obj
      result_codes, result_uniques = obj.factorize(sort=sort)
  
      constructor = pd.Index
      if isinstance(obj, pd.MultiIndex):
          constructor = pd.MultiIndex.from_tuples
      expected_uniques = constructor(obj.unique())
  
      if sort:
          expected_uniques = expected_uniques.sort_values()
  
      # construct an integer ndarray so that
      # `expected_uniques.take(expected_codes)` is equal to `obj`
      expected_uniques_list = list(expected_uniques)
      expected_codes = [expected_uniques_list.index(val) for val in obj]
      expected_codes = np.asarray(expected_codes, dtype=np.intp)
  
      tm.assert_numpy_array_equal(result_codes, expected_codes)
      tm.assert_index_equal(result_uniques, expected_uniques)
  
  
  def test_series_factorize_na_sentinel_none():
      # GH35667
      values = np.array([1, 2, 1, np.nan])
      ser = pd.Series(values)
      codes, uniques = ser.factorize(na_sentinel=None)
  
      expected_codes = np.array([0, 1, 0, 2], dtype=np.intp)
      expected_uniques = pd.Index([1.0, 2.0, np.nan])
  
      tm.assert_numpy_array_equal(codes, expected_codes)
      tm.assert_index_equal(uniques, expected_uniques)
   """Utilities for meta-estimators"""
  # Author: Joel Nothman
  #         Andreas Mueller
  # License: BSD
  from typing import List, Any
  
  from abc import ABCMeta, abstractmethod
  from operator import attrgetter
  from functools import update_wrapper
  import numpy as np
  
  from ..utils import _safe_indexing
  from ..base import BaseEstimator
  from ..base import _is_pairwise
  
  __all__ = ['if_delegate_has_method']
  
  
  class _BaseComposition(BaseEstimator, metaclass=ABCMeta):
      """Handles parameter management for classifiers composed of named estimators.
      """
      steps: List[Any]
  
      @abstractmethod
      def __init__(self):
          pass
  
      def _get_params(self, attr, deep=True):
          out = super().get_params(deep=deep)
          if not deep:
              return out
          estimators = getattr(self, attr)
          out.update(estimators)
          for name, estimator in estimators:
              if hasattr(estimator, 'get_params'):
                  for key, value in estimator.get_params(deep=True).items():
                      out['%s__%s' % (name, key)] = value
          return out
  
      def _set_params(self, attr, **params):
          # Ensure strict ordering of parameter setting:
          # 1. All steps
          if attr in params:
              setattr(self, attr, params.pop(attr))
          # 2. Step replacement
          items = getattr(self, attr)
          names = []
          if items:
              names, _ = zip(*items)
          for name in list(params.keys()):
              if '__' not in name and name in names:
                  self._replace_estimator(attr, name, params.pop(name))
          # 3. Step parameters and other initialisation arguments
          super().set_params(**params)
          return self
  
      def _replace_estimator(self, attr, name, new_val):
          # assumes `name` is a valid estimator name
          new_estimators = list(getattr(self, attr))
          for i, (estimator_name, _) in enumerate(new_estimators):
              if estimator_name == name:
                  new_estimators[i] = (name, new_val)
                  break
          setattr(self, attr, new_estimators)
  
      def _validate_names(self, names):
          if len(set(names)) != len(names):
              raise ValueError('Names provided are not unique: '
                               '{0!r}'.format(list(names)))
          invalid_names = set(names).intersection(self.get_params(deep=False))
          if invalid_names:
              raise ValueError('Estimator names conflict with constructor '
                               'arguments: {0!r}'.format(sorted(invalid_names)))
          invalid_names = [name for name in names if '__' in name]
          if invalid_names:
              raise ValueError('Estimator names must not contain __: got '
                               '{0!r}'.format(invalid_names))
  
  
  class _IffHasAttrDescriptor:
      """Implements a conditional property using the descriptor protocol.
  
      Using this class to create a decorator will raise an ``AttributeError``
      if none of the delegates (specified in ``delegate_names``) is an attribute
      of the base object or the first found delegate does not have an attribute
      ``attribute_name``.
  
      This allows ducktyping of the decorated method based on
      ``delegate.attribute_name``. Here ``delegate`` is the first item in
      ``delegate_names`` for which ``hasattr(object, delegate) is True``.
  
      See https://docs.python.org/3/howto/descriptor.html for an explanation of
      descriptors.
      """
      def __init__(self, fn, delegate_names, attribute_name):
          self.fn = fn
          self.delegate_names = delegate_names
          self.attribute_name = attribute_name
  
          # update the docstring of the descriptor
          update_wrapper(self, fn)
  
      def __get__(self, obj, type=None):
          # raise an AttributeError if the attribute is not present on the object
          if obj is not None:
              # delegate only on instances, not the classes.
              # this is to allow access to the docstrings.
              for delegate_name in self.delegate_names:
                  try:
                      delegate = attrgetter(delegate_name)(obj)
                  except AttributeError:
                      continue
                  else:
                      getattr(delegate, self.attribute_name)
                      break
              else:
                  attrgetter(self.delegate_names[-1])(obj)
  
          # lambda, but not partial, allows help() to work with update_wrapper
          out = lambda *args, **kwargs: self.fn(obj, *args, **kwargs)
          # update the docstring of the returned function
          update_wrapper(out, self.fn)
          return out
  
  
  def if_delegate_has_method(delegate):
      """Create a decorator for methods that are delegated to a sub-estimator
  
      This enables ducktyping by hasattr returning True according to the
      sub-estimator.
  
      Parameters
      ----------
      delegate : string, list of strings or tuple of strings
          Name of the sub-estimator that can be accessed as an attribute of the
          base object. If a list or a tuple of names are provided, the first
          sub-estimator that is an attribute of the base object will be used.
  
      """
      if isinstance(delegate, list):
          delegate = tuple(delegate)
      if not isinstance(delegate, tuple):
          delegate = (delegate,)
  
      return lambda fn: _IffHasAttrDescriptor(fn, delegate,
                                              attribute_name=fn.__name__)
  
  
  def _safe_split(estimator, X, y, indices, train_indices=None):
      """Create subset of dataset and properly handle kernels.
  
      Slice X, y according to indices for cross-validation, but take care of
      precomputed kernel-matrices or pairwise affinities / distances.
  
      If ``estimator._pairwise is True``, X needs to be square and
      we slice rows and columns. If ``train_indices`` is not None,
      we slice rows using ``indices`` (assumed the test set) and columns
      using ``train_indices``, indicating the training set.
  
      .. deprecated:: 0.24
  
          The _pairwise attribute is deprecated in 0.24. From 1.1
          (renaming of 0.26) and onward, this function will check for the
          pairwise estimator tag.
  
      Labels y will always be indexed only along the first axis.
  
      Parameters
      ----------
      estimator : object
          Estimator to determine whether we should slice only rows or rows and
          columns.
  
      X : array-like, sparse matrix or iterable
          Data to be indexed. If ``estimator._pairwise is True``,
          this needs to be a square array-like or sparse matrix.
  
      y : array-like, sparse matrix or iterable
          Targets to be indexed.
  
      indices : array of int
          Rows to select from X and y.
          If ``estimator._pairwise is True`` and ``train_indices is None``
          then ``indices`` will also be used to slice columns.
  
      train_indices : array of int or None, default=None
          If ``estimator._pairwise is True`` and ``train_indices is not None``,
          then ``train_indices`` will be use to slice the columns of X.
  
      Returns
      -------
      X_subset : array-like, sparse matrix or list
          Indexed data.
  
      y_subset : array-like, sparse matrix or list
          Indexed targets.
  
      """
      if _is_pairwise(estimator):
          if not hasattr(X, "shape"):
              raise ValueError("Precomputed kernels or affinity matrices have "
                               "to be passed as arrays or sparse matrices.")
          # X is a precomputed square kernel matrix
          if X.shape[0] != X.shape[1]:
              raise ValueError("X should be a square kernel matrix")
          if train_indices is None:
              X_subset = X[np.ix_(indices, indices)]
          else:
              X_subset = X[np.ix_(indices, train_indices)]
      else:
          X_subset = _safe_indexing(X, indices)
  
      if y is not None:
          y_subset = _safe_indexing(y, indices)
      else:
          y_subset = None
  
      return X_subset, y_subset
  
apis: numpy.allclose numpy.sum pandas.Series pandas.date_range numpy.random.randn numpy.repeat pandas.Timedelta numpy.arange pandas._testing.assert_almost_equal pandas._testing.assert_series_equal numpy.ptp numpy.dot numpy.random.randint pandas.util._test_decorators.skip_if_np_lt
apis: numpy.sum numpy.multiply numpy.asarray numpy.log numpy.abs numpy.minimum numpy.atleast_2d numpy.zeros numpy.linalg.multi_dot numpy.arange scipy.sparse.coo_matrix numpy.max numpy.maximum numpy.finfo numpy.zeros_like numpy.divide numpy.empty scipy.sparse.issparse numpy.shape numpy.product numpy.sqrt numpy.dot numpy.full
apis: numpy.ones sklearn.utils._testing.TempMemmap sklearn.utils.deprecation.deprecated sklearn.utils._testing.check_docstring_parameters sklearn.tree.DecisionTreeClassifier scipy.sparse.csc_matrix numpy.testing.assert_array_equal sklearn.utils._testing.assert_warns sklearn.utils.metaestimators.if_delegate_has_method sklearn.utils._testing.assert_raises_regex sklearn.utils._testing.set_random_state sklearn.utils._testing.create_memmap_backed_data sklearn.utils._testing.assert_allclose_dense_sparse numpy.arange sklearn.utils._testing.ignore_warnings sklearn.utils._testing.assert_no_warnings sklearn.utils._testing.raises sklearn.utils._testing.assert_raises sklearn.utils._testing._convert_container sklearn.utils._testing.assert_raise_message sklearn.discriminant_analysis.LinearDiscriminantAnalysis
apis: pandas._testing.assert_numpy_array_equal pandas.Series numpy.asarray pandas._testing.assert_index_equal numpy.array pandas.Index
apis: numpy.ix_