Repository: scikit-learn/scikit-learn
File: sklearn/semi_supervised/_label_propagation.py (Python)

Code before:
    ):
        super().__init__(
            kernel=kernel,
            gamma=gamma,
            n_neighbors=n_neighbors,
            max_iter=max_iter,
            tol=tol,
            n_jobs=n_jobs,
            alpha=None,
        )

    def _build_graph(self):
        """Matrix representing a fully connected graph between each sample."""
        if self.kernel == "knn":
            self.nn_fit = None
        affinity_matrix = self._get_kernel(self.X_)
        normalizer = affinity_matrix.sum(axis=1)
        if sparse.issparse(affinity_matrix):
            # handle spmatrix (make 1D)
            if sparse.isspmatrix(affinity_matrix):
                normalizer = np.ravel(normalizer)
            # common case: knn method gives row sum k for all rows but
            # a user-kernel may have varied row sums so "else" handles that
            if np.all(normalizer == normalizer[0]):
                affinity_matrix.data /= normalizer[0]
            else:
                if affinity_matrix.format == "csr":
                    repeats = np.diff(affinity_matrix.indptr)
                    rows = np.repeat(np.arange(affinity_matrix.shape[0]), repeats)
                else:  # CSC format
                    rows = affinity_matrix.indices
                affinity_matrix.data /= normalizer[rows]
        else:  # Dense affinity_matrix
            affinity_matrix /= normalizer[:, np.newaxis]
        return affinity_matrix

    def fit(self, X, y):
        """Fit a semi-supervised label propagation model to X.

        Parameters
        ----------
        X : {array-like, sparse matrix} of shape (n_samples, n_features)
            Training data, where `n_samples` is the number of samples
            and `n_features` is the number of features.

        y : array-like of shape (n_samples,)
            Target class values with unlabeled points marked as -1.
            All unlabeled samples will be transductively assigned labels
            internally, which are stored in `transduction_`.

        Returns

Review comment (performance):
I wonder if we could use sparse multiplication instead. The code is much easier to read, and I believe it's pretty efficient for row normalization (to be confirmed). Have you tried and compared sparse multiplication with your alternatives for csr/csc formats ? 
```python
normalizer = affinity_matrix.sum(axis=1)
# handle spmatrix (make 1D)
if sparse.isspmatrix(affinity_matrix):
    normalizer = np.ravel(normalizer)
if sparse.issparse(affinity_matrix):
    inv_normalizer = sparse.diags(1.0 / normalizer)
    affinity_matrix = inv_normalizer @ affinity_matrix
else:
    affinity_matrix /= normalizer[:, np.newaxis]
```

Code after:
    ):
        super().__init__(
            kernel=kernel,
            gamma=gamma,
            n_neighbors=n_neighbors,
            max_iter=max_iter,
            tol=tol,
            n_jobs=n_jobs,
            alpha=None,
        )

    def _build_graph(self):
        """Matrix representing a fully connected graph between each sample."""
        if self.kernel == "knn":
            self.nn_fit = None
        affinity_matrix = self._get_kernel(self.X_)
        normalizer = affinity_matrix.sum(axis=1)
        # handle spmatrix (make normalizer 1D)
        if sparse.isspmatrix(affinity_matrix):
            normalizer = np.ravel(normalizer)
        # TODO: when SciPy 1.12+ is min dependence, replace up to ---- with:
        # affinity_matrix /= normalizer[:, np.newaxis]
        if sparse.issparse(affinity_matrix):
            inv_normalizer = sparse.diags(1.0 / normalizer)
            affinity_matrix = inv_normalizer @ affinity_matrix
        else:  # Dense affinity_matrix
            affinity_matrix /= normalizer[:, np.newaxis]
        # ----
        return affinity_matrix

    def fit(self, X, y):
        """Fit a semi-supervised label propagation model to X.

        Parameters
        ----------
        X : {array-like, sparse matrix} of shape (n_samples, n_features)
            Training data, where `n_samples` is the number of samples
            and `n_features` is the number of features.

        y : array-like of shape (n_samples,)
            Target class values with unlabeled points marked as -1.
            All unlabeled samples will be transductively assigned labels
            internally, which are stored in `transduction_`.

        Returns
        -------
        self : object
            Returns the instance itself.
        """
        return super().fit(X, y)