@@ -187,6 +187,7 @@ def _preprocess_data(
187
187
fit_intercept ,
188
188
normalize = False ,
189
189
copy = True ,
190
+ copy_y = True ,
190
191
sample_weight = None ,
191
192
check_input = True ,
192
193
):
@@ -230,13 +231,14 @@ def _preprocess_data(
230
231
231
232
if check_input :
232
233
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" )
240
242
241
243
if fit_intercept :
242
244
if sp .issparse (X ):
@@ -276,7 +278,7 @@ def _preprocess_data(
276
278
X_scale = np .ones (X .shape [1 ], dtype = X .dtype )
277
279
278
280
y_offset = np .average (y , axis<
F438
/span>= 0 , weights = sample_weight )
279
- y = y - y_offset
281
+ y -= y_offset
280
282
else :
281
283
X_offset = np .zeros (X .shape [1 ], dtype = X .dtype )
282
284
X_scale = np .ones (X .shape [1 ], dtype = X .dtype )
@@ -293,7 +295,7 @@ def _preprocess_data(
293
295
# sample_weight makes the refactoring tricky.
294
296
295
297
296
- def _rescale_data (X , y , sample_weight ):
298
+ def _rescale_data (X , y , sample_weight , inplace = False ):
297
299
"""Rescale data sample-wise by square root of sample_weight.
298
300
299
301
For many linear models, this enables easy support for sample_weight because
@@ -315,14 +317,37 @@ def _rescale_data(X, y, sample_weight):
315
317
316
318
y_rescaled : {array-like, sparse matrix}
317
319
"""
320
+ # Assume that _validate_data and _check_sample_weight have been called by
321
+ # the caller.
318
322
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 )
322
323
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 ]
326
351
return X , y , sample_weight_sqrt
327
352
328
353
@@ -651,20 +676,32 @@ def fit(self, X, y, sample_weight=None):
651
676
X , y , accept_sparse = accept_sparse , y_numeric = True , multi_output = True
652
677
)
653
678
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 )
657
689
658
690
X , y , X_offset , y_offset , X_scale = _preprocess_data (
659
691
X ,
660
692
y ,
661
693
fit_intercept = self .fit_intercept ,
662
- copy = self . copy_X ,
694
+ copy = copy_X_in_preprocess_data ,
663
695
sample_weight = sample_weight ,
664
696
)
665
697
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
+ )
668
705
669
706
if self .positive :
670
707
if y .ndim < 2 :
@@ -678,11 +715,21 @@ def fit(self, X, y, sample_weight=None):
678
715
elif sp .issparse (X ):
679
716
X_offset_scale = X_offset / X_scale
680
717
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 )
683
730
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 ( )
686
733
687
734
X_centered = sparse .linalg .LinearOperator (
688
735
shape = X .shape , matvec = matvec , rmatvec = rmatvec
0 commit comments