_iterative.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  1. import warnings
  2. from collections import namedtuple
  3. from numbers import Integral, Real
  4. from time import time
  5. import numpy as np
  6. from scipy import stats
  7. from ..base import _fit_context, clone
  8. from ..exceptions import ConvergenceWarning
  9. from ..preprocessing import normalize
  10. from ..utils import (
  11. _safe_assign,
  12. _safe_indexing,
  13. check_array,
  14. check_random_state,
  15. is_scalar_nan,
  16. )
  17. from ..utils._mask import _get_mask
  18. from ..utils._param_validation import HasMethods, Interval, StrOptions
  19. from ..utils.validation import FLOAT_DTYPES, _check_feature_names_in, check_is_fitted
  20. from ._base import SimpleImputer, _BaseImputer, _check_inputs_dtype
  21. _ImputerTriplet = namedtuple(
  22. "_ImputerTriplet", ["feat_idx", "neighbor_feat_idx", "estimator"]
  23. )
  24. def _assign_where(X1, X2, cond):
  25. """Assign X2 to X1 where cond is True.
  26. Parameters
  27. ----------
  28. X1 : ndarray or dataframe of shape (n_samples, n_features)
  29. Data.
  30. X2 : ndarray of shape (n_samples, n_features)
  31. Data to be assigned.
  32. cond : ndarray of shape (n_samples, n_features)
  33. Boolean mask to assign data.
  34. """
  35. if hasattr(X1, "mask"): # pandas dataframes
  36. X1.mask(cond=cond, other=X2, inplace=True)
  37. else: # ndarrays
  38. X1[cond] = X2[cond]
  39. class IterativeImputer(_BaseImputer):
  40. """Multivariate imputer that estimates each feature from all the others.
  41. A strategy for imputing missing values by modeling each feature with
  42. missing values as a function of other features in a round-robin fashion.
  43. Read more in the :ref:`User Guide <iterative_imputer>`.
  44. .. versionadded:: 0.21
  45. .. note::
  46. This estimator is still **experimental** for now: the predictions
  47. and the API might change without any deprecation cycle. To use it,
  48. you need to explicitly import `enable_iterative_imputer`::
  49. >>> # explicitly require this experimental feature
  50. >>> from sklearn.experimental import enable_iterative_imputer # noqa
  51. >>> # now you can import normally from sklearn.impute
  52. >>> from sklearn.impute import IterativeImputer
  53. Parameters
  54. ----------
  55. estimator : estimator object, default=BayesianRidge()
  56. The estimator to use at each step of the round-robin imputation.
  57. If `sample_posterior=True`, the estimator must support
  58. `return_std` in its `predict` method.
  59. missing_values : int or np.nan, default=np.nan
  60. The placeholder for the missing values. All occurrences of
  61. `missing_values` will be imputed. For pandas' dataframes with
  62. nullable integer dtypes with missing values, `missing_values`
  63. should be set to `np.nan`, since `pd.NA` will be converted to `np.nan`.
  64. sample_posterior : bool, default=False
  65. Whether to sample from the (Gaussian) predictive posterior of the
  66. fitted estimator for each imputation. Estimator must support
  67. `return_std` in its `predict` method if set to `True`. Set to
  68. `True` if using `IterativeImputer` for multiple imputations.
  69. max_iter : int, default=10
  70. Maximum number of imputation rounds to perform before returning the
  71. imputations computed during the final round. A round is a single
  72. imputation of each feature with missing values. The stopping criterion
  73. is met once `max(abs(X_t - X_{t-1}))/max(abs(X[known_vals])) < tol`,
  74. where `X_t` is `X` at iteration `t`. Note that early stopping is only
  75. applied if `sample_posterior=False`.
  76. tol : float, default=1e-3
  77. Tolerance of the stopping condition.
  78. n_nearest_features : int, default=None
  79. Number of other features to use to estimate the missing values of
  80. each feature column. Nearness between features is measured using
  81. the absolute correlation coefficient between each feature pair (after
  82. initial imputation). To ensure coverage of features throughout the
  83. imputation process, the neighbor features are not necessarily nearest,
  84. but are drawn with probability proportional to correlation for each
  85. imputed target feature. Can provide significant speed-up when the
  86. number of features is huge. If `None`, all features will be used.
  87. initial_strategy : {'mean', 'median', 'most_frequent', 'constant'}, \
  88. default='mean'
  89. Which strategy to use to initialize the missing values. Same as the
  90. `strategy` parameter in :class:`~sklearn.impute.SimpleImputer`.
  91. fill_value : str or numerical value, default=None
  92. When `strategy="constant"`, `fill_value` is used to replace all
  93. occurrences of missing_values. For string or object data types,
  94. `fill_value` must be a string.
  95. If `None`, `fill_value` will be 0 when imputing numerical
  96. data and "missing_value" for strings or object data types.
  97. .. versionadded:: 1.3
  98. imputation_order : {'ascending', 'descending', 'roman', 'arabic', \
  99. 'random'}, default='ascending'
  100. The order in which the features will be imputed. Possible values:
  101. - `'ascending'`: From features with fewest missing values to most.
  102. - `'descending'`: From features with most missing values to fewest.
  103. - `'roman'`: Left to right.
  104. - `'arabic'`: Right to left.
  105. - `'random'`: A random order for each round.
  106. skip_complete : bool, default=False
  107. If `True` then features with missing values during :meth:`transform`
  108. which did not have any missing values during :meth:`fit` will be
  109. imputed with the initial imputation method only. Set to `True` if you
  110. have many features with no missing values at both :meth:`fit` and
  111. :meth:`transform` time to save compute.
  112. min_value : float or array-like of shape (n_features,), default=-np.inf
  113. Minimum possible imputed value. Broadcast to shape `(n_features,)` if
  114. scalar. If array-like, expects shape `(n_features,)`, one min value for
  115. each feature. The default is `-np.inf`.
  116. .. versionchanged:: 0.23
  117. Added support for array-like.
  118. max_value : float or array-like of shape (n_features,), default=np.inf
  119. Maximum possible imputed value. Broadcast to shape `(n_features,)` if
  120. scalar. If array-like, expects shape `(n_features,)`, one max value for
  121. each feature. The default is `np.inf`.
  122. .. versionchanged:: 0.23
  123. Added support for array-like.
  124. verbose : int, default=0
  125. Verbosity flag, controls the debug messages that are issued
  126. as functions are evaluated. The higher, the more verbose. Can be 0, 1,
  127. or 2.
  128. random_state : int, RandomState instance or None, default=None
  129. The seed of the pseudo random number generator to use. Randomizes
  130. selection of estimator features if `n_nearest_features` is not `None`,
  131. the `imputation_order` if `random`, and the sampling from posterior if
  132. `sample_posterior=True`. Use an integer for determinism.
  133. See :term:`the Glossary <random_state>`.
  134. add_indicator : bool, default=False
  135. If `True`, a :class:`MissingIndicator` transform will stack onto output
  136. of the imputer's transform. This allows a predictive estimator
  137. to account for missingness despite imputation. If a feature has no
  138. missing values at fit/train time, the feature won't appear on
  139. the missing indicator even if there are missing values at
  140. transform/test time.
  141. keep_empty_features : bool, default=False
  142. If True, features that consist exclusively of missing values when
  143. `fit` is called are returned in results when `transform` is called.
  144. The imputed value is always `0` except when
  145. `initial_strategy="constant"` in which case `fill_value` will be
  146. used instead.
  147. .. versionadded:: 1.2
  148. Attributes
  149. ----------
  150. initial_imputer_ : object of type :class:`~sklearn.impute.SimpleImputer`
  151. Imputer used to initialize the missing values.
  152. imputation_sequence_ : list of tuples
  153. Each tuple has `(feat_idx, neighbor_feat_idx, estimator)`, where
  154. `feat_idx` is the current feature to be imputed,
  155. `neighbor_feat_idx` is the array of other features used to impute the
  156. current feature, and `estimator` is the trained estimator used for
  157. the imputation. Length is `self.n_features_with_missing_ *
  158. self.n_iter_`.
  159. n_iter_ : int
  160. Number of iteration rounds that occurred. Will be less than
  161. `self.max_iter` if early stopping criterion was reached.
  162. n_features_in_ : int
  163. Number of features seen during :term:`fit`.
  164. .. versionadded:: 0.24
  165. feature_names_in_ : ndarray of shape (`n_features_in_`,)
  166. Names of features seen during :term:`fit`. Defined only when `X`
  167. has feature names that are all strings.
  168. .. versionadded:: 1.0
  169. n_features_with_missing_ : int
  170. Number of features with missing values.
  171. indicator_ : :class:`~sklearn.impute.MissingIndicator`
  172. Indicator used to add binary indicators for missing values.
  173. `None` if `add_indicator=False`.
  174. random_state_ : RandomState instance
  175. RandomState instance that is generated either from a seed, the random
  176. number generator or by `np.random`.
  177. See Also
  178. --------
  179. SimpleImputer : Univariate imputer for completing missing values
  180. with simple strategies.
  181. KNNImputer : Multivariate imputer that estimates missing features using
  182. nearest samples.
  183. Notes
  184. -----
  185. To support imputation in inductive mode we store each feature's estimator
  186. during the :meth:`fit` phase, and predict without refitting (in order)
  187. during the :meth:`transform` phase.
  188. Features which contain all missing values at :meth:`fit` are discarded upon
  189. :meth:`transform`.
  190. Using defaults, the imputer scales in :math:`\\mathcal{O}(knp^3\\min(n,p))`
  191. where :math:`k` = `max_iter`, :math:`n` the number of samples and
  192. :math:`p` the number of features. It thus becomes prohibitively costly when
  193. the number of features increases. Setting
  194. `n_nearest_features << n_features`, `skip_complete=True` or increasing `tol`
  195. can help to reduce its computational cost.
  196. Depending on the nature of missing values, simple imputers can be
  197. preferable in a prediction context.
  198. References
  199. ----------
  200. .. [1] `Stef van Buuren, Karin Groothuis-Oudshoorn (2011). "mice:
  201. Multivariate Imputation by Chained Equations in R". Journal of
  202. Statistical Software 45: 1-67.
  203. <https://www.jstatsoft.org/article/view/v045i03>`_
  204. .. [2] `S. F. Buck, (1960). "A Method of Estimation of Missing Values in
  205. Multivariate Data Suitable for use with an Electronic Computer".
  206. Journal of the Royal Statistical Society 22(2): 302-306.
  207. <https://www.jstor.org/stable/2984099>`_
  208. Examples
  209. --------
  210. >>> import numpy as np
  211. >>> from sklearn.experimental import enable_iterative_imputer
  212. >>> from sklearn.impute import IterativeImputer
  213. >>> imp_mean = IterativeImputer(random_state=0)
  214. >>> imp_mean.fit([[7, 2, 3], [4, np.nan, 6], [10, 5, 9]])
  215. IterativeImputer(random_state=0)
  216. >>> X = [[np.nan, 2, 3], [4, np.nan, 6], [10, np.nan, 9]]
  217. >>> imp_mean.transform(X)
  218. array([[ 6.9584..., 2. , 3. ],
  219. [ 4. , 2.6000..., 6. ],
  220. [10. , 4.9999..., 9. ]])
  221. For a more detailed example see
  222. :ref:`sphx_glr_auto_examples_impute_plot_missing_values.py` or
  223. :ref:`sphx_glr_auto_examples_impute_plot_iterative_imputer_variants_comparison.py`.
  224. """
  225. _parameter_constraints: dict = {
  226. **_BaseImputer._parameter_constraints,
  227. "estimator": [None, HasMethods(["fit", "predict"])],
  228. "sample_posterior": ["boolean"],
  229. "max_iter": [Interval(Integral, 0, None, closed="left")],
  230. "tol": [Interval(Real, 0, None, closed="left")],
  231. "n_nearest_features": [None, Interval(Integral, 1, None, closed="left")],
  232. "initial_strategy": [
  233. StrOptions({"mean", "median", "most_frequent", "constant"})
  234. ],
  235. "fill_value": "no_validation", # any object is valid
  236. "imputation_order": [
  237. StrOptions({"ascending", "descending", "roman", "arabic", "random"})
  238. ],
  239. "skip_complete": ["boolean"],
  240. "min_value": [None, Interval(Real, None, None, closed="both"), "array-like"],
  241. "max_value": [None, Interval(Real, None, None, closed="both"), "array-like"],
  242. "verbose": ["verbose"],
  243. "random_state": ["random_state"],
  244. }
  245. def __init__(
  246. self,
  247. estimator=None,
  248. *,
  249. missing_values=np.nan,
  250. sample_posterior=False,
  251. max_iter=10,
  252. tol=1e-3,
  253. n_nearest_features=None,
  254. initial_strategy="mean",
  255. fill_value=None,
  256. imputation_order="ascending",
  257. skip_complete=False,
  258. min_value=-np.inf,
  259. max_value=np.inf,
  260. verbose=0,
  261. random_state=None,
  262. add_indicator=False,
  263. keep_empty_features=False,
  264. ):
  265. super().__init__(
  266. missing_values=missing_values,
  267. add_indicator=add_indicator,
  268. keep_empty_features=keep_empty_features,
  269. )
  270. self.estimator = estimator
  271. self.sample_posterior = sample_posterior
  272. self.max_iter = max_iter
  273. self.tol = tol
  274. self.n_nearest_features = n_nearest_features
  275. self.initial_strategy = initial_strategy
  276. self.fill_value = fill_value
  277. self.imputation_order = imputation_order
  278. self.skip_complete = skip_complete
  279. self.min_value = min_value
  280. self.max_value = max_value
  281. self.verbose = verbose
  282. self.random_state = random_state
  283. def _impute_one_feature(
  284. self,
  285. X_filled,
  286. mask_missing_values,
  287. feat_idx,
  288. neighbor_feat_idx,
  289. estimator=None,
  290. fit_mode=True,
  291. ):
  292. """Impute a single feature from the others provided.
  293. This function predicts the missing values of one of the features using
  294. the current estimates of all the other features. The `estimator` must
  295. support `return_std=True` in its `predict` method for this function
  296. to work.
  297. Parameters
  298. ----------
  299. X_filled : ndarray
  300. Input data with the most recent imputations.
  301. mask_missing_values : ndarray
  302. Input data's missing indicator matrix.
  303. feat_idx : int
  304. Index of the feature currently being imputed.
  305. neighbor_feat_idx : ndarray
  306. Indices of the features to be used in imputing `feat_idx`.
  307. estimator : object
  308. The estimator to use at this step of the round-robin imputation.
  309. If `sample_posterior=True`, the estimator must support
  310. `return_std` in its `predict` method.
  311. If None, it will be cloned from self._estimator.
  312. fit_mode : boolean, default=True
  313. Whether to fit and predict with the estimator or just predict.
  314. Returns
  315. -------
  316. X_filled : ndarray
  317. Input data with `X_filled[missing_row_mask, feat_idx]` updated.
  318. estimator : estimator with sklearn API
  319. The fitted estimator used to impute
  320. `X_filled[missing_row_mask, feat_idx]`.
  321. """
  322. if estimator is None and fit_mode is False:
  323. raise ValueError(
  324. "If fit_mode is False, then an already-fitted "
  325. "estimator should be passed in."
  326. )
  327. if estimator is None:
  328. estimator = clone(self._estimator)
  329. missing_row_mask = mask_missing_values[:, feat_idx]
  330. if fit_mode:
  331. X_train = _safe_indexing(
  332. _safe_indexing(X_filled, neighbor_feat_idx, axis=1),
  333. ~missing_row_mask,
  334. axis=0,
  335. )
  336. y_train = _safe_indexing(
  337. _safe_indexing(X_filled, feat_idx, axis=1),
  338. ~missing_row_mask,
  339. axis=0,
  340. )
  341. estimator.fit(X_train, y_train)
  342. # if no missing values, don't predict
  343. if np.sum(missing_row_mask) == 0:
  344. return X_filled, estimator
  345. # get posterior samples if there is at least one missing value
  346. X_test = _safe_indexing(
  347. _safe_indexing(X_filled, neighbor_feat_idx, axis=1),
  348. missing_row_mask,
  349. axis=0,
  350. )
  351. if self.sample_posterior:
  352. mus, sigmas = estimator.predict(X_test, return_std=True)
  353. imputed_values = np.zeros(mus.shape, dtype=X_filled.dtype)
  354. # two types of problems: (1) non-positive sigmas
  355. # (2) mus outside legal range of min_value and max_value
  356. # (results in inf sample)
  357. positive_sigmas = sigmas > 0
  358. imputed_values[~positive_sigmas] = mus[~positive_sigmas]
  359. mus_too_low = mus < self._min_value[feat_idx]
  360. imputed_values[mus_too_low] = self._min_value[feat_idx]
  361. mus_too_high = mus > self._max_value[feat_idx]
  362. imputed_values[mus_too_high] = self._max_value[feat_idx]
  363. # the rest can be sampled without statistical issues
  364. inrange_mask = positive_sigmas & ~mus_too_low & ~mus_too_high
  365. mus = mus[inrange_mask]
  366. sigmas = sigmas[inrange_mask]
  367. a = (self._min_value[feat_idx] - mus) / sigmas
  368. b = (self._max_value[feat_idx] - mus) / sigmas
  369. truncated_normal = stats.truncnorm(a=a, b=b, loc=mus, scale=sigmas)
  370. imputed_values[inrange_mask] = truncated_normal.rvs(
  371. random_state=self.random_state_
  372. )
  373. else:
  374. imputed_values = estimator.predict(X_test)
  375. imputed_values = np.clip(
  376. imputed_values, self._min_value[feat_idx], self._max_value[feat_idx]
  377. )
  378. # update the feature
  379. _safe_assign(
  380. X_filled,
  381. imputed_values,
  382. row_indexer=missing_row_mask,
  383. column_indexer=feat_idx,
  384. )
  385. return X_filled, estimator
  386. def _get_neighbor_feat_idx(self, n_features, feat_idx, abs_corr_mat):
  387. """Get a list of other features to predict `feat_idx`.
  388. If `self.n_nearest_features` is less than or equal to the total
  389. number of features, then use a probability proportional to the absolute
  390. correlation between `feat_idx` and each other feature to randomly
  391. choose a subsample of the other features (without replacement).
  392. Parameters
  393. ----------
  394. n_features : int
  395. Number of features in `X`.
  396. feat_idx : int
  397. Index of the feature currently being imputed.
  398. abs_corr_mat : ndarray, shape (n_features, n_features)
  399. Absolute correlation matrix of `X`. The diagonal has been zeroed
  400. out and each feature has been normalized to sum to 1. Can be None.
  401. Returns
  402. -------
  403. neighbor_feat_idx : array-like
  404. The features to use to impute `feat_idx`.
  405. """
  406. if self.n_nearest_features is not None and self.n_nearest_features < n_features:
  407. p = abs_corr_mat[:, feat_idx]
  408. neighbor_feat_idx = self.random_state_.choice(
  409. np.arange(n_features), self.n_nearest_features, replace=False, p=p
  410. )
  411. else:
  412. inds_left = np.arange(feat_idx)
  413. inds_right = np.arange(feat_idx + 1, n_features)
  414. neighbor_feat_idx = np.concatenate((inds_left, inds_right))
  415. return neighbor_feat_idx
  416. def _get_ordered_idx(self, mask_missing_values):
  417. """Decide in what order we will update the features.
  418. As a homage to the MICE R package, we will have 4 main options of
  419. how to order the updates, and use a random order if anything else
  420. is specified.
  421. Also, this function skips features which have no missing values.
  422. Parameters
  423. ----------
  424. mask_missing_values : array-like, shape (n_samples, n_features)
  425. Input data's missing indicator matrix, where `n_samples` is the
  426. number of samples and `n_features` is the number of features.
  427. Returns
  428. -------
  429. ordered_idx : ndarray, shape (n_features,)
  430. The order in which to impute the features.
  431. """
  432. frac_of_missing_values = mask_missing_values.mean(axis=0)
  433. if self.skip_complete:
  434. missing_values_idx = np.flatnonzero(frac_of_missing_values)
  435. else:
  436. missing_values_idx = np.arange(np.shape(frac_of_missing_values)[0])
  437. if self.imputation_order == "roman":
  438. ordered_idx = missing_values_idx
  439. elif self.imputation_order == "arabic":
  440. ordered_idx = missing_values_idx[::-1]
  441. elif self.imputation_order == "ascending":
  442. n = len(frac_of_missing_values) - len(missing_values_idx)
  443. ordered_idx = np.argsort(frac_of_missing_values, kind="mergesort")[n:]
  444. elif self.imputation_order == "descending":
  445. n = len(frac_of_missing_values) - len(missing_values_idx)
  446. ordered_idx = np.argsort(frac_of_missing_values, kind="mergesort")[n:][::-1]
  447. elif self.imputation_order == "random":
  448. ordered_idx = missing_values_idx
  449. self.random_state_.shuffle(ordered_idx)
  450. return ordered_idx
  451. def _get_abs_corr_mat(self, X_filled, tolerance=1e-6):
  452. """Get absolute correlation matrix between features.
  453. Parameters
  454. ----------
  455. X_filled : ndarray, shape (n_samples, n_features)
  456. Input data with the most recent imputations.
  457. tolerance : float, default=1e-6
  458. `abs_corr_mat` can have nans, which will be replaced
  459. with `tolerance`.
  460. Returns
  461. -------
  462. abs_corr_mat : ndarray, shape (n_features, n_features)
  463. Absolute correlation matrix of `X` at the beginning of the
  464. current round. The diagonal has been zeroed out and each feature's
  465. absolute correlations with all others have been normalized to sum
  466. to 1.
  467. """
  468. n_features = X_filled.shape[1]
  469. if self.n_nearest_features is None or self.n_nearest_features >= n_features:
  470. return None
  471. with np.errstate(invalid="ignore"):
  472. # if a feature in the neighborhood has only a single value
  473. # (e.g., categorical feature), the std. dev. will be null and
  474. # np.corrcoef will raise a warning due to a division by zero
  475. abs_corr_mat = np.abs(np.corrcoef(X_filled.T))
  476. # np.corrcoef is not defined for features with zero std
  477. abs_corr_mat[np.isnan(abs_corr_mat)] = tolerance
  478. # ensures exploration, i.e. at least some probability of sampling
  479. np.clip(abs_corr_mat, tolerance, None, out=abs_corr_mat)
  480. # features are not their own neighbors
  481. np.fill_diagonal(abs_corr_mat, 0)
  482. # needs to sum to 1 for np.random.choice sampling
  483. abs_corr_mat = normalize(abs_corr_mat, norm="l1", axis=0, copy=False)
  484. return abs_corr_mat
  485. def _initial_imputation(self, X, in_fit=False):
  486. """Perform initial imputation for input `X`.
  487. Parameters
  488. ----------
  489. X : ndarray of shape (n_samples, n_features)
  490. Input data, where `n_samples` is the number of samples and
  491. `n_features` is the number of features.
  492. in_fit : bool, default=False
  493. Whether function is called in :meth:`fit`.
  494. Returns
  495. -------
  496. Xt : ndarray of shape (n_samples, n_features)
  497. Input data, where `n_samples` is the number of samples and
  498. `n_features` is the number of features.
  499. X_filled : ndarray of shape (n_samples, n_features)
  500. Input data with the most recent imputations.
  501. mask_missing_values : ndarray of shape (n_samples, n_features)
  502. Input data's missing indicator matrix, where `n_samples` is the
  503. number of samples and `n_features` is the number of features,
  504. masked by non-missing features.
  505. X_missing_mask : ndarray, shape (n_samples, n_features)
  506. Input data's mask matrix indicating missing datapoints, where
  507. `n_samples` is the number of samples and `n_features` is the
  508. number of features.
  509. """
  510. if is_scalar_nan(self.missing_values):
  511. force_all_finite = "allow-nan"
  512. else:
  513. force_all_finite = True
  514. X = self._validate_data(
  515. X,
  516. dtype=FLOAT_DTYPES,
  517. order="F",
  518. reset=in_fit,
  519. force_all_finite=force_all_finite,
  520. )
  521. _check_inputs_dtype(X, self.missing_values)
  522. X_missing_mask = _get_mask(X, self.missing_values)
  523. mask_missing_values = X_missing_mask.copy()
  524. if self.initial_imputer_ is None:
  525. self.initial_imputer_ = SimpleImputer(
  526. missing_values=self.missing_values,
  527. strategy=self.initial_strategy,
  528. fill_value=self.fill_value,
  529. keep_empty_features=self.keep_empty_features,
  530. ).set_output(transform="default")
  531. X_filled = self.initial_imputer_.fit_transform(X)
  532. else:
  533. X_filled = self.initial_imputer_.transform(X)
  534. valid_mask = np.flatnonzero(
  535. np.logical_not(np.isnan(self.initial_imputer_.statistics_))
  536. )
  537. if not self.keep_empty_features:
  538. # drop empty features
  539. Xt = X[:, valid_mask]
  540. mask_missing_values = mask_missing_values[:, valid_mask]
  541. else:
  542. # mark empty features as not missing and keep the original
  543. # imputation
  544. mask_missing_values[:, valid_mask] = True
  545. Xt = X
  546. return Xt, X_filled, mask_missing_values, X_missing_mask
  547. @staticmethod
  548. def _validate_limit(limit, limit_type, n_features):
  549. """Validate the limits (min/max) of the feature values.
  550. Converts scalar min/max limits to vectors of shape `(n_features,)`.
  551. Parameters
  552. ----------
  553. limit: scalar or array-like
  554. The user-specified limit (i.e, min_value or max_value).
  555. limit_type: {'max', 'min'}
  556. Type of limit to validate.
  557. n_features: int
  558. Number of features in the dataset.
  559. Returns
  560. -------
  561. limit: ndarray, shape(n_features,)
  562. Array of limits, one for each feature.
  563. """
  564. limit_bound = np.inf if limit_type == "max" else -np.inf
  565. limit = limit_bound if limit is None else limit
  566. if np.isscalar(limit):
  567. limit = np.full(n_features, limit)
  568. limit = check_array(limit, force_all_finite=False, copy=False, ensure_2d=False)
  569. if not limit.shape[0] == n_features:
  570. raise ValueError(
  571. f"'{limit_type}_value' should be of "
  572. f"shape ({n_features},) when an array-like "
  573. f"is provided. Got {limit.shape}, instead."
  574. )
  575. return limit
  576. @_fit_context(
  577. # IterativeImputer.estimator is not validated yet
  578. prefer_skip_nested_validation=False
  579. )
  580. def fit_transform(self, X, y=None):
  581. """Fit the imputer on `X` and return the transformed `X`.
  582. Parameters
  583. ----------
  584. X : array-like, shape (n_samples, n_features)
  585. Input data, where `n_samples` is the number of samples and
  586. `n_features` is the number of features.
  587. y : Ignored
  588. Not used, present for API consistency by convention.
  589. Returns
  590. -------
  591. Xt : array-like, shape (n_samples, n_features)
  592. The imputed input data.
  593. """
  594. self.random_state_ = getattr(
  595. self, "random_state_", check_random_state(self.random_state)
  596. )
  597. if self.estimator is None:
  598. from ..linear_model import BayesianRidge
  599. self._estimator = BayesianRidge()
  600. else:
  601. self._estimator = clone(self.estimator)
  602. self.imputation_sequence_ = []
  603. self.initial_imputer_ = None
  604. X, Xt, mask_missing_values, complete_mask = self._initial_imputation(
  605. X, in_fit=True
  606. )
  607. super()._fit_indicator(complete_mask)
  608. X_indicator = super()._transform_indicator(complete_mask)
  609. if self.max_iter == 0 or np.all(mask_missing_values):
  610. self.n_iter_ = 0
  611. return super()._concatenate_indicator(Xt, X_indicator)
  612. # Edge case: a single feature. We return the initial ...
  613. if Xt.shape[1] == 1:
  614. self.n_iter_ = 0
  615. return super()._concatenate_indicator(Xt, X_indicator)
  616. self._min_value = self._validate_limit(self.min_value, "min", X.shape[1])
  617. self._max_value = self._validate_limit(self.max_value, "max", X.shape[1])
  618. if not np.all(np.greater(self._max_value, self._min_value)):
  619. raise ValueError("One (or more) features have min_value >= max_value.")
  620. # order in which to impute
  621. # note this is probably too slow for large feature data (d > 100000)
  622. # and a better way would be good.
  623. # see: https://goo.gl/KyCNwj and subsequent comments
  624. ordered_idx = self._get_ordered_idx(mask_missing_values)
  625. self.n_features_with_missing_ = len(ordered_idx)
  626. abs_corr_mat = self._get_abs_corr_mat(Xt)
  627. n_samples, n_features = Xt.shape
  628. if self.verbose > 0:
  629. print("[IterativeImputer] Completing matrix with shape %s" % (X.shape,))
  630. start_t = time()
  631. if not self.sample_posterior:
  632. Xt_previous = Xt.copy()
  633. normalized_tol = self.tol * np.max(np.abs(X[~mask_missing_values]))
  634. for self.n_iter_ in range(1, self.max_iter + 1):
  635. if self.imputation_order == "random":
  636. ordered_idx = self._get_ordered_idx(mask_missing_values)
  637. for feat_idx in ordered_idx:
  638. neighbor_feat_idx = self._get_neighbor_feat_idx(
  639. n_features, feat_idx, abs_corr_mat
  640. )
  641. Xt, estimator = self._impute_one_feature(
  642. Xt,
  643. mask_missing_values,
  644. feat_idx,
  645. neighbor_feat_idx,
  646. estimator=None,
  647. fit_mode=True,
  648. )
  649. estimator_triplet = _ImputerTriplet(
  650. feat_idx, neighbor_feat_idx, estimator
  651. )
  652. self.imputation_sequence_.append(estimator_triplet)
  653. if self.verbose > 1:
  654. print(
  655. "[IterativeImputer] Ending imputation round "
  656. "%d/%d, elapsed time %0.2f"
  657. % (self.n_iter_, self.max_iter, time() - start_t)
  658. )
  659. if not self.sample_posterior:
  660. inf_norm = np.linalg.norm(Xt - Xt_previous, ord=np.inf, axis=None)
  661. if self.verbose > 0:
  662. print(
  663. "[IterativeImputer] Change: {}, scaled tolerance: {} ".format(
  664. inf_norm, normalized_tol
  665. )
  666. )
  667. if inf_norm < normalized_tol:
  668. if self.verbose > 0:
  669. print("[IterativeImputer] Early stopping criterion reached.")
  670. break
  671. Xt_previous = Xt.copy()
  672. else:
  673. if not self.sample_posterior:
  674. warnings.warn(
  675. "[IterativeImputer] Early stopping criterion not reached.",
  676. ConvergenceWarning,
  677. )
  678. _assign_where(Xt, X, cond=~mask_missing_values)
  679. return super()._concatenate_indicator(Xt, X_indicator)
  680. def transform(self, X):
  681. """Impute all missing values in `X`.
  682. Note that this is stochastic, and that if `random_state` is not fixed,
  683. repeated calls, or permuted input, results will differ.
  684. Parameters
  685. ----------
  686. X : array-like of shape (n_samples, n_features)
  687. The input data to complete.
  688. Returns
  689. -------
  690. Xt : array-like, shape (n_samples, n_features)
  691. The imputed input data.
  692. """
  693. check_is_fitted(self)
  694. X, Xt, mask_missing_values, complete_mask = self._initial_imputation(
  695. X, in_fit=False
  696. )
  697. X_indicator = super()._transform_indicator(complete_mask)
  698. if self.n_iter_ == 0 or np.all(mask_missing_values):
  699. return super()._concatenate_indicator(Xt, X_indicator)
  700. imputations_per_round = len(self.imputation_sequence_) // self.n_iter_
  701. i_rnd = 0
  702. if self.verbose > 0:
  703. print("[IterativeImputer] Completing matrix with shape %s" % (X.shape,))
  704. start_t = time()
  705. for it, estimator_triplet in enumerate(self.imputation_sequence_):
  706. Xt, _ = self._impute_one_feature(
  707. Xt,
  708. mask_missing_values,
  709. estimator_triplet.feat_idx,
  710. estimator_triplet.neighbor_feat_idx,
  711. estimator=estimator_triplet.estimator,
  712. fit_mode=False,
  713. )
  714. if not (it + 1) % imputations_per_round:
  715. if self.verbose > 1:
  716. print(
  717. "[IterativeImputer] Ending imputation round "
  718. "%d/%d, elapsed time %0.2f"
  719. % (i_rnd + 1, self.n_iter_, time() - start_t)
  720. )
  721. i_rnd += 1
  722. _assign_where(Xt, X, cond=~mask_missing_values)
  723. return super()._concatenate_indicator(Xt, X_indicator)
  724. def fit(self, X, y=None):
  725. """Fit the imputer on `X` and return self.
  726. Parameters
  727. ----------
  728. X : array-like, shape (n_samples, n_features)
  729. Input data, where `n_samples` is the number of samples and
  730. `n_features` is the number of features.
  731. y : Ignored
  732. Not used, present for API consistency by convention.
  733. Returns
  734. -------
  735. self : object
  736. Fitted estimator.
  737. """
  738. self.fit_transform(X)
  739. return self
  740. def get_feature_names_out(self, input_features=None):
  741. """Get output feature names for transformation.
  742. Parameters
  743. ----------
  744. input_features : array-like of str or None, default=None
  745. Input features.
  746. - If `input_features` is `None`, then `feature_names_in_` is
  747. used as feature names in. If `feature_names_in_` is not defined,
  748. then the following input feature names are generated:
  749. `["x0", "x1", ..., "x(n_features_in_ - 1)"]`.
  750. - If `input_features` is an array-like, then `input_features` must
  751. match `feature_names_in_` if `feature_names_in_` is defined.
  752. Returns
  753. -------
  754. feature_names_out : ndarray of str objects
  755. Transformed feature names.
  756. """
  757. check_is_fitted(self, "n_features_in_")
  758. input_features = _check_feature_names_in(self, input_features)
  759. names = self.initial_imputer_.get_feature_names_out(input_features)
  760. return self._concatenate_indicator_feature_names_out(names, input_features)