@@ -187,6 +187,7 @@ def _preprocess_data(
187187 fit_intercept ,
188188 normalize = False ,
189189 copy = True ,
190+ copy_y = True ,
190191 sample_weight = None ,
191192 check_input = True ,
192193):
@@ -230,13 +231,14 @@ def _preprocess_data(
230231
231232 if check_input :
232233 X = check_array (X , copy = copy , accept_sparse = ["csr" , "csc" ], dtype = FLOAT_DTYPES )
233- elif copy :
234- if sp .issparse (X ):
235- X = X .copy ()
236- else :
237- X = X .copy (order = "K" )
238-
239- y = np .asarray (y , dtype = X .dtype )
234+ y = check_array (y , dtype = X .dtype , copy = copy_y , ensure_2d = False )
235+ else :
236+ y = y .astype (X .dtype , copy = copy_y )
237+ if copy :
238+ if sp .issparse (X ):
239+ X = X .copy ()
240+ else :
241+ X = X .copy (order = "K" )
240242
241243 if fit_intercept :
242244 if sp .issparse (X ):
@@ -276,7 +278,7 @@ def _preprocess_data(
276278 X_scale = np .ones (X .shape [1 ], dtype = X .dtype )
277279
278280 y_offset = np .average (y , axis = 0 , weights = sample_weight )
279- y = y - y_offset
281+ y -= y_offset
280282 else :
281283 X_offset = np .zeros (X .shape [1 ], dtype = X .dtype )
282284 X_scale = np .ones (X .shape [1 ], dtype = X .dtype )
@@ -293,7 +295,7 @@ def _preprocess_data(
293295# sample_weight makes the refactoring tricky.
294296
295297
296- def _rescale_data (X , y , sample_weight ):
298+ def _rescale_data (X , y , sample_weight , inplace = False ):
297299 """Rescale data sample-wise by square root of sample_weight.
298300
299301 For many linear models, this enables easy support for sample_weight because
@@ -315,14 +317,37 @@ def _rescale_data(X, y, sample_weight):
315317
316318 y_rescaled : {array-like, sparse matrix}
317319 """
320+ # Assume that _validate_data and _check_sample_weight have been called by
321+ # the caller.
318322 n_samples = X .shape [0 ]
319- sample_weight = np .asarray (sample_weight )
320- if sample_weight .ndim == 0 :
321- sample_weight = np .full (n_samples , sample_weight , dtype = sample_weight .dtype )
322323 sample_weight_sqrt = np .sqrt (sample_weight )
323- sw_matrix = sparse .dia_matrix ((sample_weight_sqrt , 0 ), shape = (n_samples , n_samples ))
324- X = safe_sparse_dot (sw_matrix , X )
325- y = safe_sparse_dot (sw_matrix , y )
324+
325+ if sp .issparse (X ) or sp .issparse (y ):
326+ sw_matrix = sparse .dia_matrix (
327+ (sample_weight_sqrt , 0 ), shape = (n_samples , n_samples )
328+ )
329+
330+ if sp .issparse (X ):
331+ X = safe_sparse_dot (sw_matrix , X )
332+ else :
333+ if inplace :
334+ X *= sample_weight_sqrt [:, np .newaxis ]
335+ else :
336+ X = X * sample_weight_sqrt [:, np .newaxis ]
337+
338+ if sp .issparse (y ):
339+ y = safe_sparse_dot (sw_matrix , y )
340+ else :
341+ if inplace :
342+ if y .ndim == 1 :
343+ y *= sample_weight_sqrt
344+ else :
345+ y *= sample_weight_sqrt [:, np .newaxis ]
346+ else :
347+ if y .ndim == 1 :
348+ y = y * sample_weight_sqrt
349+ else :
350+ y = y * sample_weight_sqrt [:, np .newaxis ]
326351 return X , y , sample_weight_sqrt
327352
328353
@@ -651,20 +676,32 @@ def fit(self, X, y, sample_weight=None):
651676 X , y , accept_sparse = accept_sparse , y_numeric = True , multi_output = True
652677 )
653678
654- sample_weight = _check_sample_weight (
655- sample_weight , X , dtype = X .dtype , only_non_negative = True
656- )
679+ has_sw = sample_weight is not None
680+ if has_sw :
681+ sample_weight = _check_sample_weight (
682+ sample_weight , X , dtype = X .dtype , only_non_negative = True
683+ )
684+
685+ # Note that neither _rescale_data nor the rest of the fit method of
686+ # LinearRegression can benefit from in-place operations when X is a
687+ # sparse matrix. Therefore, let's not copy X when it is sparse.
688+ copy_X_in_preprocess_data = self .copy_X and not sp .issparse (X )
657689
658690 X , y , X_offset , y_offset , X_scale = _preprocess_data (
659691 X ,
660692 y ,
661693 fit_intercept = self .fit_intercept ,
662- copy = self . copy_X ,
694+ copy = copy_X_in_preprocess_data ,
663695 sample_weight = sample_weight ,
664696 )
665697
666- # Sample weight can be implemented via a simple rescaling.
667- X , y , sample_weight_sqrt = _rescale_data (X , y , sample_weight )
698+ if has_sw :
699+ # Sample weight can be implemented via a simple rescaling. Note
700+ # that we safely do inplace rescaling when _preprocess_data has
701+ # already made a copy if requested.
702+ X , y , sample_weight_sqrt = _rescale_data (
703+ X , y , sample_weight , inplace = copy_X_in_preprocess_data
704+ )
668705
669706 if self .positive :
670707 if y .ndim < 2 :
@@ -678,11 +715,21 @@ def fit(self, X, y, sample_weight=None):
678715 elif sp .issparse (X ):
679716 X_offset_scale = X_offset / X_scale
680717
681- def matvec (b ):
682- return X .dot (b ) - sample_weight_sqrt * b .dot (X_offset_scale )
718+ if has_sw :
719+
720+ def matvec (b ):
721+ return X .dot (b ) - sample_weight_sqrt * b .dot (X_offset_scale )
722+
723+ def rmatvec (b ):
724+ return X .T .dot (b ) - X_offset_scale * b .dot (sample_weight_sqrt )
725+
726+ else :
727+
728+ def matvec (b ):
729+ return X .dot (b ) - b .dot (X_offset_scale )
683730
684- def rmatvec (b ):
685- return X .T .dot (b ) - X_offset_scale * b .dot ( sample_weight_sqrt )
731+ def rmatvec (b ):
732+ return X .T .dot (b ) - X_offset_scale * b .sum ( )
686733
687734 X_centered = sparse .linalg .LinearOperator (
688735 shape = X .shape , matvec = matvec , rmatvec = rmatvec
0 commit comments