2424from sklearn .utils ._bitset import set_bitset_memoryview
2525from sklearn .utils ._openmp_helpers import _openmp_effective_n_threads
2626from sklearn .utils .parallel import Parallel , delayed
27- from sklearn .utils .stats import _weighted_percentile
27+ from sklearn .utils .stats import _weighted_percentile_1d_sorted
2828from sklearn .utils .validation import check_is_fitted
2929
3030
@@ -50,35 +50,42 @@ def _find_binning_thresholds(col_data, max_bins, sample_weight=None):
5050 A given value x will be mapped into bin value i iff
5151 bining_thresholds[i - 1] < x <= binning_thresholds[i]
5252 """
53- # ignore missing values when computing bin thresholds
54- missing_mask = np .isnan (col_data )
55- any_missing = missing_mask .any ()
56- if any_missing :
57- col_data = col_data [~ missing_mask ]
58-
59- # If sample_weight is not None and 0-weighted values exist, we need to
60- # remove those before calculating the distinct points.
61- if sample_weight is not None :
62- if any_missing :
53+ # The data will be sorted anyway to find distinct values and again in percentile,
54+ # so we do it here, like once and for all. Sorting also returns a contiguous array.
55+ if sample_weight is None :
56+ col_data = np .sort (col_data )
57+ # ignore missing values when computing bin thresholds
58+ idx_nan = np .searchsorted (col_data , np .nan )
59+ col_data = col_data [:idx_nan ]
60+ else :
61+ # First, remove missing values because argsort is much slower when missing
62+ # values are present (which is not the case for sort).
63+ missing_mask = np .isnan (col_data )
64+ if missing_mask .any ():
65+ col_data = col_data [~ missing_mask ]
6366 sample_weight = sample_weight [~ missing_mask ]
67+
68+ # If 0-weighted values exist, we need to remove those
69+ # before calculating the distinct points.
6470 nnz_sw = sample_weight != 0
6571 col_data = col_data [nnz_sw ]
6672 sample_weight = sample_weight [nnz_sw ]
6773
68- # The data will be sorted anyway in np.unique and again in percentile, so we do it
69- # here. Sorting also returns a contiguous array.
70- sort_idx = np .argsort (col_data )
71- col_data = col_data [sort_idx ]
72- if sample_weight is not None :
74+ sort_idx = np .argsort (col_data )
75+ col_data = col_data [sort_idx ]
7376 sample_weight = sample_weight [sort_idx ]
7477
75- distinct_values = np .unique (col_data ).astype (X_DTYPE )
78+ # fast way for n_distinct = len(np.unique(col_data))
79+ distinct_mask = np .empty (len (col_data ), dtype = bool )
80+ distinct_mask [0 ] = True
81+ distinct_mask [1 :] = col_data [1 :] != col_data [:- 1 ]
82+ n_distincts = distinct_mask .sum ()
7683
77- if len ( distinct_values ) == 1 :
84+ if n_distincts == 1 :
7885 return np .asarray ([])
79-
80- if len (distinct_values ) <= max_bins :
86+ elif n_distincts <= max_bins :
8187 # Calculate midpoints if distinct values <= max_bins
88+ distinct_values = col_data [distinct_mask ]
8289 bin_thresholds = sliding_window_view (distinct_values , 2 ).mean (axis = 1 )
8390 elif sample_weight is None :
8491 # We compute bin edges using the output of np.percentile with
@@ -93,17 +100,12 @@ def _find_binning_thresholds(col_data, max_bins, sample_weight=None):
93100 else :
94101 percentiles = np .linspace (0 , 100 , num = max_bins + 1 )
95102 percentiles = percentiles [1 :- 1 ]
96- bin_thresholds = np .array (
97- [
98- _weighted_percentile (col_data , sample_weight , percentile , average = True )
99- for percentile in percentiles
100- ]
103+ bin_thresholds = _weighted_percentile_1d_sorted (
104+ col_data , sample_weight , percentiles
101105 )
102106 assert bin_thresholds .shape [0 ] == max_bins - 1
103107 # Remove duplicated thresholds if they exist.
104- unique_bin_values = np .unique (bin_thresholds )
105- if unique_bin_values .shape [0 ] != bin_thresholds .shape [0 ]:
106- bin_thresholds = unique_bin_values
108+ bin_thresholds = np .unique (bin_thresholds )
107109
108110 # We avoid having +inf thresholds: +inf thresholds are only allowed in
109111 # a "split on nan" situation.
0 commit comments