test_validation.py 63 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901
  1. """Tests for input validation functions"""
  2. import numbers
  3. import re
  4. import warnings
  5. from itertools import product
  6. from operator import itemgetter
  7. from tempfile import NamedTemporaryFile
  8. import numpy as np
  9. import pytest
  10. import scipy.sparse as sp
  11. from pytest import importorskip
  12. import sklearn
  13. from sklearn._config import config_context
  14. from sklearn.base import BaseEstimator
  15. from sklearn.datasets import make_blobs
  16. from sklearn.ensemble import RandomForestRegressor
  17. from sklearn.exceptions import NotFittedError, PositiveSpectrumWarning
  18. from sklearn.linear_model import ARDRegression
  19. # TODO: add this estimator into the _mocking module in a further refactoring
  20. from sklearn.metrics.tests.test_score_objects import EstimatorWithFit
  21. from sklearn.neighbors import KNeighborsClassifier
  22. from sklearn.random_projection import _sparse_random_matrix
  23. from sklearn.svm import SVR
  24. from sklearn.utils import (
  25. _safe_indexing,
  26. as_float_array,
  27. check_array,
  28. check_symmetric,
  29. check_X_y,
  30. deprecated,
  31. )
  32. from sklearn.utils._mocking import (
  33. MockDataFrame,
  34. _MockEstimatorOnOffPrediction,
  35. )
  36. from sklearn.utils._testing import (
  37. SkipTest,
  38. TempMemmap,
  39. _convert_container,
  40. assert_allclose,
  41. assert_allclose_dense_sparse,
  42. assert_array_equal,
  43. assert_no_warnings,
  44. ignore_warnings,
  45. skip_if_array_api_compat_not_configured,
  46. )
  47. from sklearn.utils.estimator_checks import _NotAnArray
  48. from sklearn.utils.fixes import parse_version
  49. from sklearn.utils.validation import (
  50. FLOAT_DTYPES,
  51. _allclose_dense_sparse,
  52. _check_feature_names_in,
  53. _check_fit_params,
  54. _check_psd_eigenvalues,
  55. _check_response_method,
  56. _check_sample_weight,
  57. _check_y,
  58. _deprecate_positional_args,
  59. _get_feature_names,
  60. _is_fitted,
  61. _num_features,
  62. _num_samples,
  63. assert_all_finite,
  64. check_consistent_length,
  65. check_is_fitted,
  66. check_memory,
  67. check_non_negative,
  68. check_scalar,
  69. has_fit_parameter,
  70. )
  71. def test_as_float_array():
  72. # Test function for as_float_array
  73. X = np.ones((3, 10), dtype=np.int32)
  74. X = X + np.arange(10, dtype=np.int32)
  75. X2 = as_float_array(X, copy=False)
  76. assert X2.dtype == np.float32
  77. # Another test
  78. X = X.astype(np.int64)
  79. X2 = as_float_array(X, copy=True)
  80. # Checking that the array wasn't overwritten
  81. assert as_float_array(X, copy=False) is not X
  82. assert X2.dtype == np.float64
  83. # Test int dtypes <= 32bit
  84. tested_dtypes = [bool, np.int8, np.int16, np.int32, np.uint8, np.uint16, np.uint32]
  85. for dtype in tested_dtypes:
  86. X = X.astype(dtype)
  87. X2 = as_float_array(X)
  88. assert X2.dtype == np.float32
  89. # Test object dtype
  90. X = X.astype(object)
  91. X2 = as_float_array(X, copy=True)
  92. assert X2.dtype == np.float64
  93. # Here, X is of the right type, it shouldn't be modified
  94. X = np.ones((3, 2), dtype=np.float32)
  95. assert as_float_array(X, copy=False) is X
  96. # Test that if X is fortran ordered it stays
  97. X = np.asfortranarray(X)
  98. assert np.isfortran(as_float_array(X, copy=True))
  99. # Test the copy parameter with some matrices
  100. matrices = [
  101. sp.csc_matrix(np.arange(5)).toarray(),
  102. _sparse_random_matrix(10, 10, density=0.10).toarray(),
  103. ]
  104. for M in matrices:
  105. N = as_float_array(M, copy=True)
  106. N[0, 0] = np.nan
  107. assert not np.isnan(M).any()
  108. @pytest.mark.parametrize("X", [(np.random.random((10, 2))), (sp.rand(10, 2).tocsr())])
  109. def test_as_float_array_nan(X):
  110. X[5, 0] = np.nan
  111. X[6, 1] = np.nan
  112. X_converted = as_float_array(X, force_all_finite="allow-nan")
  113. assert_allclose_dense_sparse(X_converted, X)
  114. def test_np_matrix():
  115. # Confirm that input validation code does not return np.matrix
  116. X = np.arange(12).reshape(3, 4)
  117. assert not isinstance(as_float_array(X), np.matrix)
  118. assert not isinstance(as_float_array(sp.csc_matrix(X)), np.matrix)
  119. def test_memmap():
  120. # Confirm that input validation code doesn't copy memory mapped arrays
  121. asflt = lambda x: as_float_array(x, copy=False)
  122. with NamedTemporaryFile(prefix="sklearn-test") as tmp:
  123. M = np.memmap(tmp, shape=(10, 10), dtype=np.float32)
  124. M[:] = 0
  125. for f in (check_array, np.asarray, asflt):
  126. X = f(M)
  127. X[:] = 1
  128. assert_array_equal(X.ravel(), M.ravel())
  129. X[:] = 0
  130. def test_ordering():
  131. # Check that ordering is enforced correctly by validation utilities.
  132. # We need to check each validation utility, because a 'copy' without
  133. # 'order=K' will kill the ordering.
  134. X = np.ones((10, 5))
  135. for A in X, X.T:
  136. for copy in (True, False):
  137. B = check_array(A, order="C", copy=copy)
  138. assert B.flags["C_CONTIGUOUS"]
  139. B = check_array(A, order="F", copy=copy)
  140. assert B.flags["F_CONTIGUOUS"]
  141. if copy:
  142. assert A is not B
  143. X = sp.csr_matrix(X)
  144. X.data = X.data[::-1]
  145. assert not X.data.flags["C_CONTIGUOUS"]
  146. @pytest.mark.parametrize(
  147. "value, force_all_finite", [(np.inf, False), (np.nan, "allow-nan"), (np.nan, False)]
  148. )
  149. @pytest.mark.parametrize("retype", [np.asarray, sp.csr_matrix])
  150. def test_check_array_force_all_finite_valid(value, force_all_finite, retype):
  151. X = retype(np.arange(4).reshape(2, 2).astype(float))
  152. X[0, 0] = value
  153. X_checked = check_array(X, force_all_finite=force_all_finite, accept_sparse=True)
  154. assert_allclose_dense_sparse(X, X_checked)
  155. @pytest.mark.parametrize(
  156. "value, input_name, force_all_finite, match_msg",
  157. [
  158. (np.inf, "", True, "Input contains infinity"),
  159. (np.inf, "X", True, "Input X contains infinity"),
  160. (np.inf, "sample_weight", True, "Input sample_weight contains infinity"),
  161. (np.inf, "X", "allow-nan", "Input X contains infinity"),
  162. (np.nan, "", True, "Input contains NaN"),
  163. (np.nan, "X", True, "Input X contains NaN"),
  164. (np.nan, "y", True, "Input y contains NaN"),
  165. (
  166. np.nan,
  167. "",
  168. "allow-inf",
  169. 'force_all_finite should be a bool or "allow-nan"',
  170. ),
  171. (np.nan, "", 1, "Input contains NaN"),
  172. ],
  173. )
  174. @pytest.mark.parametrize("retype", [np.asarray, sp.csr_matrix])
  175. def test_check_array_force_all_finiteinvalid(
  176. value, input_name, force_all_finite, match_msg, retype
  177. ):
  178. X = retype(np.arange(4).reshape(2, 2).astype(np.float64))
  179. X[0, 0] = value
  180. with pytest.raises(ValueError, match=match_msg):
  181. check_array(
  182. X,
  183. input_name=input_name,
  184. force_all_finite=force_all_finite,
  185. accept_sparse=True,
  186. )
  187. @pytest.mark.parametrize("input_name", ["X", "y", "sample_weight"])
  188. @pytest.mark.parametrize("retype", [np.asarray, sp.csr_matrix])
  189. def test_check_array_links_to_imputer_doc_only_for_X(input_name, retype):
  190. data = retype(np.arange(4).reshape(2, 2).astype(np.float64))
  191. data[0, 0] = np.nan
  192. estimator = SVR()
  193. extended_msg = (
  194. f"\n{estimator.__class__.__name__} does not accept missing values"
  195. " encoded as NaN natively. For supervised learning, you might want"
  196. " to consider sklearn.ensemble.HistGradientBoostingClassifier and Regressor"
  197. " which accept missing values encoded as NaNs natively."
  198. " Alternatively, it is possible to preprocess the"
  199. " data, for instance by using an imputer transformer in a pipeline"
  200. " or drop samples with missing values. See"
  201. " https://scikit-learn.org/stable/modules/impute.html"
  202. " You can find a list of all estimators that handle NaN values"
  203. " at the following page:"
  204. " https://scikit-learn.org/stable/modules/impute.html"
  205. "#estimators-that-handle-nan-values"
  206. )
  207. with pytest.raises(ValueError, match=f"Input {input_name} contains NaN") as ctx:
  208. check_array(
  209. data,
  210. estimator=estimator,
  211. input_name=input_name,
  212. accept_sparse=True,
  213. )
  214. if input_name == "X":
  215. assert extended_msg in ctx.value.args[0]
  216. else:
  217. assert extended_msg not in ctx.value.args[0]
  218. if input_name == "X":
  219. # Veriy that _validate_data is automatically called with the right argument
  220. # to generate the same exception:
  221. with pytest.raises(ValueError, match=f"Input {input_name} contains NaN") as ctx:
  222. SVR().fit(data, np.ones(data.shape[0]))
  223. assert extended_msg in ctx.value.args[0]
  224. def test_check_array_force_all_finite_object():
  225. X = np.array([["a", "b", np.nan]], dtype=object).T
  226. X_checked = check_array(X, dtype=None, force_all_finite="allow-nan")
  227. assert X is X_checked
  228. X_checked = check_array(X, dtype=None, force_all_finite=False)
  229. assert X is X_checked
  230. with pytest.raises(ValueError, match="Input contains NaN"):
  231. check_array(X, dtype=None, force_all_finite=True)
  232. @pytest.mark.parametrize(
  233. "X, err_msg",
  234. [
  235. (
  236. np.array([[1, np.nan]]),
  237. "Input contains NaN.",
  238. ),
  239. (
  240. np.array([[1, np.nan]]),
  241. "Input contains NaN.",
  242. ),
  243. (
  244. np.array([[1, np.inf]]),
  245. "Input contains infinity or a value too large for.*int",
  246. ),
  247. (np.array([[1, np.nan]], dtype=object), "cannot convert float NaN to integer"),
  248. ],
  249. )
  250. @pytest.mark.parametrize("force_all_finite", [True, False])
  251. def test_check_array_force_all_finite_object_unsafe_casting(
  252. X, err_msg, force_all_finite
  253. ):
  254. # casting a float array containing NaN or inf to int dtype should
  255. # raise an error irrespective of the force_all_finite parameter.
  256. with pytest.raises(ValueError, match=err_msg):
  257. check_array(X, dtype=int, force_all_finite=force_all_finite)
  258. @ignore_warnings
  259. def test_check_array():
  260. # accept_sparse == False
  261. # raise error on sparse inputs
  262. X = [[1, 2], [3, 4]]
  263. X_csr = sp.csr_matrix(X)
  264. with pytest.raises(TypeError):
  265. check_array(X_csr)
  266. # ensure_2d=False
  267. X_array = check_array([0, 1, 2], ensure_2d=False)
  268. assert X_array.ndim == 1
  269. # ensure_2d=True with 1d array
  270. with pytest.raises(ValueError, match="Expected 2D array, got 1D array instead"):
  271. check_array([0, 1, 2], ensure_2d=True)
  272. # ensure_2d=True with scalar array
  273. with pytest.raises(ValueError, match="Expected 2D array, got scalar array instead"):
  274. check_array(10, ensure_2d=True)
  275. # don't allow ndim > 3
  276. X_ndim = np.arange(8).reshape(2, 2, 2)
  277. with pytest.raises(ValueError):
  278. check_array(X_ndim)
  279. check_array(X_ndim, allow_nd=True) # doesn't raise
  280. # dtype and order enforcement.
  281. X_C = np.arange(4).reshape(2, 2).copy("C")
  282. X_F = X_C.copy("F")
  283. X_int = X_C.astype(int)
  284. X_float = X_C.astype(float)
  285. Xs = [X_C, X_F, X_int, X_float]
  286. dtypes = [np.int32, int, float, np.float32, None, bool, object]
  287. orders = ["C", "F", None]
  288. copys = [True, False]
  289. for X, dtype, order, copy in product(Xs, dtypes, orders, copys):
  290. X_checked = check_array(X, dtype=dtype, order=order, copy=copy)
  291. if dtype is not None:
  292. assert X_checked.dtype == dtype
  293. else:
  294. assert X_checked.dtype == X.dtype
  295. if order == "C":
  296. assert X_checked.flags["C_CONTIGUOUS"]
  297. assert not X_checked.flags["F_CONTIGUOUS"]
  298. elif order == "F":
  299. assert X_checked.flags["F_CONTIGUOUS"]
  300. assert not X_checked.flags["C_CONTIGUOUS"]
  301. if copy:
  302. assert X is not X_checked
  303. else:
  304. # doesn't copy if it was already good
  305. if (
  306. X.dtype == X_checked.dtype
  307. and X_checked.flags["C_CONTIGUOUS"] == X.flags["C_CONTIGUOUS"]
  308. and X_checked.flags["F_CONTIGUOUS"] == X.flags["F_CONTIGUOUS"]
  309. ):
  310. assert X is X_checked
  311. # allowed sparse != None
  312. X_csc = sp.csc_matrix(X_C)
  313. X_coo = X_csc.tocoo()
  314. X_dok = X_csc.todok()
  315. X_int = X_csc.astype(int)
  316. X_float = X_csc.astype(float)
  317. Xs = [X_csc, X_coo, X_dok, X_int, X_float]
  318. accept_sparses = [["csr", "coo"], ["coo", "dok"]]
  319. # scipy sparse matrices do not support the object dtype so
  320. # this dtype is skipped in this loop
  321. non_object_dtypes = [dt for dt in dtypes if dt is not object]
  322. for X, dtype, accept_sparse, copy in product(
  323. Xs, non_object_dtypes, accept_sparses, copys
  324. ):
  325. X_checked = check_array(X, dtype=dtype, accept_sparse=accept_sparse, copy=copy)
  326. if dtype is not None:
  327. assert X_checked.dtype == dtype
  328. else:
  329. assert X_checked.dtype == X.dtype
  330. if X.format in accept_sparse:
  331. # no change if allowed
  332. assert X.format == X_checked.format
  333. else:
  334. # got converted
  335. assert X_checked.format == accept_sparse[0]
  336. if copy:
  337. assert X is not X_checked
  338. else:
  339. # doesn't copy if it was already good
  340. if X.dtype == X_checked.dtype and X.format == X_checked.format:
  341. assert X is X_checked
  342. # other input formats
  343. # convert lists to arrays
  344. X_dense = check_array([[1, 2], [3, 4]])
  345. assert isinstance(X_dense, np.ndarray)
  346. # raise on too deep lists
  347. with pytest.raises(ValueError):
  348. check_array(X_ndim.tolist())
  349. check_array(X_ndim.tolist(), allow_nd=True) # doesn't raise
  350. # convert weird stuff to arrays
  351. X_no_array = _NotAnArray(X_dense)
  352. result = check_array(X_no_array)
  353. assert isinstance(result, np.ndarray)
  354. @pytest.mark.parametrize(
  355. "X",
  356. [
  357. [["1", "2"], ["3", "4"]],
  358. np.array([["1", "2"], ["3", "4"]], dtype="U"),
  359. np.array([["1", "2"], ["3", "4"]], dtype="S"),
  360. [[b"1", b"2"], [b"3", b"4"]],
  361. np.array([[b"1", b"2"], [b"3", b"4"]], dtype="V1"),
  362. ],
  363. )
  364. def test_check_array_numeric_error(X):
  365. """Test that check_array errors when it receives an array of bytes/string
  366. while a numeric dtype is required."""
  367. expected_msg = r"dtype='numeric' is not compatible with arrays of bytes/strings"
  368. with pytest.raises(ValueError, match=expected_msg):
  369. check_array(X, dtype="numeric")
  370. @pytest.mark.parametrize(
  371. "pd_dtype", ["Int8", "Int16", "UInt8", "UInt16", "Float32", "Float64"]
  372. )
  373. @pytest.mark.parametrize(
  374. "dtype, expected_dtype",
  375. [
  376. ([np.float32, np.float64], np.float32),
  377. (np.float64, np.float64),
  378. ("numeric", np.float64),
  379. ],
  380. )
  381. def test_check_array_pandas_na_support(pd_dtype, dtype, expected_dtype):
  382. # Test pandas numerical extension arrays with pd.NA
  383. pd = pytest.importorskip("pandas")
  384. if pd_dtype in {"Float32", "Float64"}:
  385. # Extension dtypes with Floats was added in 1.2
  386. pd = pytest.importorskip("pandas", minversion="1.2")
  387. X_np = np.array(
  388. [[1, 2, 3, np.nan, np.nan], [np.nan, np.nan, 8, 4, 6], [1, 2, 3, 4, 5]]
  389. ).T
  390. # Creates dataframe with numerical extension arrays with pd.NA
  391. X = pd.DataFrame(X_np, dtype=pd_dtype, columns=["a", "b", "c"])
  392. # column c has no nans
  393. X["c"] = X["c"].astype("float")
  394. X_checked = check_array(X, force_all_finite="allow-nan", dtype=dtype)
  395. assert_allclose(X_checked, X_np)
  396. assert X_checked.dtype == expected_dtype
  397. X_checked = check_array(X, force_all_finite=False, dtype=dtype)
  398. assert_allclose(X_checked, X_np)
  399. assert X_checked.dtype == expected_dtype
  400. msg = "Input contains NaN"
  401. with pytest.raises(ValueError, match=msg):
  402. check_array(X, force_all_finite=True)
  403. def test_check_array_panadas_na_support_series():
  404. """Check check_array is correct with pd.NA in a series."""
  405. pd = pytest.importorskip("pandas")
  406. X_int64 = pd.Series([1, 2, pd.NA], dtype="Int64")
  407. msg = "Input contains NaN"
  408. with pytest.raises(ValueError, match=msg):
  409. check_array(X_int64, force_all_finite=True, ensure_2d=False)
  410. X_out = check_array(X_int64, force_all_finite=False, ensure_2d=False)
  411. assert_allclose(X_out, [1, 2, np.nan])
  412. assert X_out.dtype == np.float64
  413. X_out = check_array(
  414. X_int64, force_all_finite=False, ensure_2d=False, dtype=np.float32
  415. )
  416. assert_allclose(X_out, [1, 2, np.nan])
  417. assert X_out.dtype == np.float32
  418. def test_check_array_pandas_dtype_casting():
  419. # test that data-frames with homogeneous dtype are not upcast
  420. pd = pytest.importorskip("pandas")
  421. X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.float32)
  422. X_df = pd.DataFrame(X)
  423. assert check_array(X_df).dtype == np.float32
  424. assert check_array(X_df, dtype=FLOAT_DTYPES).dtype == np.float32
  425. X_df = X_df.astype({0: np.float16})
  426. assert_array_equal(X_df.dtypes, (np.float16, np.float32, np.float32))
  427. assert check_array(X_df).dtype == np.float32
  428. assert check_array(X_df, dtype=FLOAT_DTYPES).dtype == np.float32
  429. X_df = X_df.astype({0: np.int16})
  430. # float16, int16, float32 casts to float32
  431. assert check_array(X_df).dtype == np.float32
  432. assert check_array(X_df, dtype=FLOAT_DTYPES).dtype == np.float32
  433. X_df = X_df.astype({2: np.float16})
  434. # float16, int16, float16 casts to float32
  435. assert check_array(X_df).dtype == np.float32
  436. assert check_array(X_df, dtype=FLOAT_DTYPES).dtype == np.float32
  437. X_df = X_df.astype(np.int16)
  438. assert check_array(X_df).dtype == np.int16
  439. # we're not using upcasting rules for determining
  440. # the target type yet, so we cast to the default of float64
  441. assert check_array(X_df, dtype=FLOAT_DTYPES).dtype == np.float64
  442. # check that we handle pandas dtypes in a semi-reasonable way
  443. # this is actually tricky because we can't really know that this
  444. # should be integer ahead of converting it.
  445. cat_df = pd.DataFrame({"cat_col": pd.Categorical([1, 2, 3])})
  446. assert check_array(cat_df).dtype == np.int64
  447. assert check_array(cat_df, dtype=FLOAT_DTYPES).dtype == np.float64
  448. def test_check_array_on_mock_dataframe():
  449. arr = np.array([[0.2, 0.7], [0.6, 0.5], [0.4, 0.1], [0.7, 0.2]])
  450. mock_df = MockDataFrame(arr)
  451. checked_arr = check_array(mock_df)
  452. assert checked_arr.dtype == arr.dtype
  453. checked_arr = check_array(mock_df, dtype=np.float32)
  454. assert checked_arr.dtype == np.dtype(np.float32)
  455. def test_check_array_dtype_stability():
  456. # test that lists with ints don't get converted to floats
  457. X = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
  458. assert check_array(X).dtype.kind == "i"
  459. assert check_array(X, ensure_2d=False).dtype.kind == "i"
  460. def test_check_array_dtype_warning():
  461. X_int_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
  462. X_float32 = np.asarray(X_int_list, dtype=np.float32)
  463. X_int64 = np.asarray(X_int_list, dtype=np.int64)
  464. X_csr_float32 = sp.csr_matrix(X_float32)
  465. X_csc_float32 = sp.csc_matrix(X_float32)
  466. X_csc_int32 = sp.csc_matrix(X_int64, dtype=np.int32)
  467. integer_data = [X_int64, X_csc_int32]
  468. float32_data = [X_float32, X_csr_float32, X_csc_float32]
  469. for X in integer_data:
  470. X_checked = assert_no_warnings(
  471. check_array, X, dtype=np.float64, accept_sparse=True
  472. )
  473. assert X_checked.dtype == np.float64
  474. for X in float32_data:
  475. X_checked = assert_no_warnings(
  476. check_array, X, dtype=[np.float64, np.float32], accept_sparse=True
  477. )
  478. assert X_checked.dtype == np.float32
  479. assert X_checked is X
  480. X_checked = assert_no_warnings(
  481. check_array,
  482. X,
  483. dtype=[np.float64, np.float32],
  484. accept_sparse=["csr", "dok"],
  485. copy=True,
  486. )
  487. assert X_checked.dtype == np.float32
  488. assert X_checked is not X
  489. X_checked = assert_no_warnings(
  490. check_array,
  491. X_csc_float32,
  492. dtype=[np.float64, np.float32],
  493. accept_sparse=["csr", "dok"],
  494. copy=False,
  495. )
  496. assert X_checked.dtype == np.float32
  497. assert X_checked is not X_csc_float32
  498. assert X_checked.format == "csr"
  499. def test_check_array_accept_sparse_type_exception():
  500. X = [[1, 2], [3, 4]]
  501. X_csr = sp.csr_matrix(X)
  502. invalid_type = SVR()
  503. msg = (
  504. "A sparse matrix was passed, but dense data is required. "
  505. r"Use X.toarray\(\) to convert to a dense numpy array."
  506. )
  507. with pytest.raises(TypeError, match=msg):
  508. check_array(X_csr, accept_sparse=False)
  509. msg = (
  510. "Parameter 'accept_sparse' should be a string, "
  511. "boolean or list of strings. You provided 'accept_sparse=.*'."
  512. )
  513. with pytest.raises(ValueError, match=msg):
  514. check_array(X_csr, accept_sparse=invalid_type)
  515. msg = (
  516. "When providing 'accept_sparse' as a tuple or list, "
  517. "it must contain at least one string value."
  518. )
  519. with pytest.raises(ValueError, match=msg):
  520. check_array(X_csr, accept_sparse=[])
  521. with pytest.raises(ValueError, match=msg):
  522. check_array(X_csr, accept_sparse=())
  523. with pytest.raises(TypeError, match="SVR"):
  524. check_array(X_csr, accept_sparse=[invalid_type])
  525. def test_check_array_accept_sparse_no_exception():
  526. X = [[1, 2], [3, 4]]
  527. X_csr = sp.csr_matrix(X)
  528. check_array(X_csr, accept_sparse=True)
  529. check_array(X_csr, accept_sparse="csr")
  530. check_array(X_csr, accept_sparse=["csr"])
  531. check_array(X_csr, accept_sparse=("csr",))
  532. @pytest.fixture(params=["csr", "csc", "coo", "bsr"])
  533. def X_64bit(request):
  534. X = sp.rand(20, 10, format=request.param)
  535. for attr in ["indices", "indptr", "row", "col"]:
  536. if hasattr(X, attr):
  537. setattr(X, attr, getattr(X, attr).astype("int64"))
  538. yield X
  539. def test_check_array_accept_large_sparse_no_exception(X_64bit):
  540. # When large sparse are allowed
  541. check_array(X_64bit, accept_large_sparse=True, accept_sparse=True)
  542. def test_check_array_accept_large_sparse_raise_exception(X_64bit):
  543. # When large sparse are not allowed
  544. msg = (
  545. "Only sparse matrices with 32-bit integer indices "
  546. "are accepted. Got int64 indices. Please do report"
  547. )
  548. with pytest.raises(ValueError, match=msg):
  549. check_array(X_64bit, accept_sparse=True, accept_large_sparse=False)
  550. def test_check_array_min_samples_and_features_messages():
  551. # empty list is considered 2D by default:
  552. msg = r"0 feature\(s\) \(shape=\(1, 0\)\) while a minimum of 1 is" " required."
  553. with pytest.raises(ValueError, match=msg):
  554. check_array([[]])
  555. # If considered a 1D collection when ensure_2d=False, then the minimum
  556. # number of samples will break:
  557. msg = r"0 sample\(s\) \(shape=\(0,\)\) while a minimum of 1 is required."
  558. with pytest.raises(ValueError, match=msg):
  559. check_array([], ensure_2d=False)
  560. # Invalid edge case when checking the default minimum sample of a scalar
  561. msg = r"Singleton array array\(42\) cannot be considered a valid" " collection."
  562. with pytest.raises(TypeError, match=msg):
  563. check_array(42, ensure_2d=False)
  564. # Simulate a model that would need at least 2 samples to be well defined
  565. X = np.ones((1, 10))
  566. y = np.ones(1)
  567. msg = r"1 sample\(s\) \(shape=\(1, 10\)\) while a minimum of 2 is" " required."
  568. with pytest.raises(ValueError, match=msg):
  569. check_X_y(X, y, ensure_min_samples=2)
  570. # The same message is raised if the data has 2 dimensions even if this is
  571. # not mandatory
  572. with pytest.raises(ValueError, match=msg):
  573. check_X_y(X, y, ensure_min_samples=2, ensure_2d=False)
  574. # Simulate a model that would require at least 3 features (e.g. SelectKBest
  575. # with k=3)
  576. X = np.ones((10, 2))
  577. y = np.ones(2)
  578. msg = r"2 feature\(s\) \(shape=\(10, 2\)\) while a minimum of 3 is" " required."
  579. with pytest.raises(ValueError, match=msg):
  580. check_X_y(X, y, ensure_min_features=3)
  581. # Only the feature check is enabled whenever the number of dimensions is 2
  582. # even if allow_nd is enabled:
  583. with pytest.raises(ValueError, match=msg):
  584. check_X_y(X, y, ensure_min_features=3, allow_nd=True)
  585. # Simulate a case where a pipeline stage as trimmed all the features of a
  586. # 2D dataset.
  587. X = np.empty(0).reshape(10, 0)
  588. y = np.ones(10)
  589. msg = r"0 feature\(s\) \(shape=\(10, 0\)\) while a minimum of 1 is" " required."
  590. with pytest.raises(ValueError, match=msg):
  591. check_X_y(X, y)
  592. # nd-data is not checked for any minimum number of features by default:
  593. X = np.ones((10, 0, 28, 28))
  594. y = np.ones(10)
  595. X_checked, y_checked = check_X_y(X, y, allow_nd=True)
  596. assert_array_equal(X, X_checked)
  597. assert_array_equal(y, y_checked)
  598. def test_check_array_complex_data_error():
  599. X = np.array([[1 + 2j, 3 + 4j, 5 + 7j], [2 + 3j, 4 + 5j, 6 + 7j]])
  600. with pytest.raises(ValueError, match="Complex data not supported"):
  601. check_array(X)
  602. # list of lists
  603. X = [[1 + 2j, 3 + 4j, 5 + 7j], [2 + 3j, 4 + 5j, 6 + 7j]]
  604. with pytest.raises(ValueError, match="Complex data not supported"):
  605. check_array(X)
  606. # tuple of tuples
  607. X = ((1 + 2j, 3 + 4j, 5 + 7j), (2 + 3j, 4 + 5j, 6 + 7j))
  608. with pytest.raises(ValueError, match="Complex data not supported"):
  609. check_array(X)
  610. # list of np arrays
  611. X = [np.array([1 + 2j, 3 + 4j, 5 + 7j]), np.array([2 + 3j, 4 + 5j, 6 + 7j])]
  612. with pytest.raises(ValueError, match="Complex data not supported"):
  613. check_array(X)
  614. # tuple of np arrays
  615. X = (np.array([1 + 2j, 3 + 4j, 5 + 7j]), np.array([2 + 3j, 4 + 5j, 6 + 7j]))
  616. with pytest.raises(ValueError, match="Complex data not supported"):
  617. check_array(X)
  618. # dataframe
  619. X = MockDataFrame(np.array([[1 + 2j, 3 + 4j, 5 + 7j], [2 + 3j, 4 + 5j, 6 + 7j]]))
  620. with pytest.raises(ValueError, match="Complex data not supported"):
  621. check_array(X)
  622. # sparse matrix
  623. X = sp.coo_matrix([[0, 1 + 2j], [0, 0]])
  624. with pytest.raises(ValueError, match="Complex data not supported"):
  625. check_array(X)
  626. # target variable does not always go through check_array but should
  627. # never accept complex data either.
  628. y = np.array([1 + 2j, 3 + 4j, 5 + 7j, 2 + 3j, 4 + 5j, 6 + 7j])
  629. with pytest.raises(ValueError, match="Complex data not supported"):
  630. _check_y(y)
  631. def test_has_fit_parameter():
  632. assert not has_fit_parameter(KNeighborsClassifier, "sample_weight")
  633. assert has_fit_parameter(RandomForestRegressor, "sample_weight")
  634. assert has_fit_parameter(SVR, "sample_weight")
  635. assert has_fit_parameter(SVR(), "sample_weight")
  636. class TestClassWithDeprecatedFitMethod:
  637. @deprecated("Deprecated for the purpose of testing has_fit_parameter")
  638. def fit(self, X, y, sample_weight=None):
  639. pass
  640. assert has_fit_parameter(
  641. TestClassWithDeprecatedFitMethod, "sample_weight"
  642. ), "has_fit_parameter fails for class with deprecated fit method."
  643. def test_check_symmetric():
  644. arr_sym = np.array([[0, 1], [1, 2]])
  645. arr_bad = np.ones(2)
  646. arr_asym = np.array([[0, 2], [0, 2]])
  647. test_arrays = {
  648. "dense": arr_asym,
  649. "dok": sp.dok_matrix(arr_asym),
  650. "csr": sp.csr_matrix(arr_asym),
  651. "csc": sp.csc_matrix(arr_asym),
  652. "coo": sp.coo_matrix(arr_asym),
  653. "lil": sp.lil_matrix(arr_asym),
  654. "bsr": sp.bsr_matrix(arr_asym),
  655. }
  656. # check error for bad inputs
  657. with pytest.raises(ValueError):
  658. check_symmetric(arr_bad)
  659. # check that asymmetric arrays are properly symmetrized
  660. for arr_format, arr in test_arrays.items():
  661. # Check for warnings and errors
  662. with pytest.warns(UserWarning):
  663. check_symmetric(arr)
  664. with pytest.raises(ValueError):
  665. check_symmetric(arr, raise_exception=True)
  666. output = check_symmetric(arr, raise_warning=False)
  667. if sp.issparse(output):
  668. assert output.format == arr_format
  669. assert_array_equal(output.toarray(), arr_sym)
  670. else:
  671. assert_array_equal(output, arr_sym)
  672. def test_check_is_fitted_with_is_fitted():
  673. class Estimator(BaseEstimator):
  674. def fit(self, **kwargs):
  675. self._is_fitted = True
  676. return self
  677. def __sklearn_is_fitted__(self):
  678. return hasattr(self, "_is_fitted") and self._is_fitted
  679. with pytest.raises(NotFittedError):
  680. check_is_fitted(Estimator())
  681. check_is_fitted(Estimator().fit())
  682. def test_check_is_fitted():
  683. # Check is TypeError raised when non estimator instance passed
  684. with pytest.raises(TypeError):
  685. check_is_fitted(ARDRegression)
  686. with pytest.raises(TypeError):
  687. check_is_fitted("SVR")
  688. ard = ARDRegression()
  689. svr = SVR()
  690. try:
  691. with pytest.raises(NotFittedError):
  692. check_is_fitted(ard)
  693. with pytest.raises(NotFittedError):
  694. check_is_fitted(svr)
  695. except ValueError:
  696. assert False, "check_is_fitted failed with ValueError"
  697. # NotFittedError is a subclass of both ValueError and AttributeError
  698. msg = "Random message %(name)s, %(name)s"
  699. match = "Random message ARDRegression, ARDRegression"
  700. with pytest.raises(ValueError, match=match):
  701. check_is_fitted(ard, msg=msg)
  702. msg = "Another message %(name)s, %(name)s"
  703. match = "Another message SVR, SVR"
  704. with pytest.raises(AttributeError, match=match):
  705. check_is_fitted(svr, msg=msg)
  706. ard.fit(*make_blobs())
  707. svr.fit(*make_blobs())
  708. assert check_is_fitted(ard) is None
  709. assert check_is_fitted(svr) is None
  710. def test_check_is_fitted_attributes():
  711. class MyEstimator:
  712. def fit(self, X, y):
  713. return self
  714. msg = "not fitted"
  715. est = MyEstimator()
  716. assert not _is_fitted(est, attributes=["a_", "b_"])
  717. with pytest.raises(NotFittedError, match=msg):
  718. check_is_fitted(est, attributes=["a_", "b_"])
  719. assert not _is_fitted(est, attributes=["a_", "b_"], all_or_any=all)
  720. with pytest.raises(NotFittedError, match=msg):
  721. check_is_fitted(est, attributes=["a_", "b_"], all_or_any=all)
  722. assert not _is_fitted(est, attributes=["a_", "b_"], all_or_any=any)
  723. with pytest.raises(NotFittedError, match=msg):
  724. check_is_fitted(est, attributes=["a_", "b_"], all_or_any=any)
  725. est.a_ = "a"
  726. assert not _is_fitted(est, attributes=["a_", "b_"])
  727. with pytest.raises(NotFittedError, match=msg):
  728. check_is_fitted(est, attributes=["a_", "b_"])
  729. assert not _is_fitted(est, attributes=["a_", "b_"], all_or_any=all)
  730. with pytest.raises(NotFittedError, match=msg):
  731. check_is_fitted(est, attributes=["a_", "b_"], all_or_any=all)
  732. assert _is_fitted(est, attributes=["a_", "b_"], all_or_any=any)
  733. check_is_fitted(est, attributes=["a_", "b_"], all_or_any=any)
  734. est.b_ = "b"
  735. assert _is_fitted(est, attributes=["a_", "b_"])
  736. check_is_fitted(est, attributes=["a_", "b_"])
  737. assert _is_fitted(est, attributes=["a_", "b_"], all_or_any=all)
  738. check_is_fitted(est, attributes=["a_", "b_"], all_or_any=all)
  739. assert _is_fitted(est, attributes=["a_", "b_"], all_or_any=any)
  740. check_is_fitted(est, attributes=["a_", "b_"], all_or_any=any)
  741. @pytest.mark.parametrize(
  742. "wrap", [itemgetter(0), list, tuple], ids=["single", "list", "tuple"]
  743. )
  744. def test_check_is_fitted_with_attributes(wrap):
  745. ard = ARDRegression()
  746. with pytest.raises(NotFittedError, match="is not fitted yet"):
  747. check_is_fitted(ard, wrap(["coef_"]))
  748. ard.fit(*make_blobs())
  749. # Does not raise
  750. check_is_fitted(ard, wrap(["coef_"]))
  751. # Raises when using attribute that is not defined
  752. with pytest.raises(NotFittedError, match="is not fitted yet"):
  753. check_is_fitted(ard, wrap(["coef_bad_"]))
  754. def test_check_consistent_length():
  755. check_consistent_length([1], [2], [3], [4], [5])
  756. check_consistent_length([[1, 2], [[1, 2]]], [1, 2], ["a", "b"])
  757. check_consistent_length([1], (2,), np.array([3]), sp.csr_matrix((1, 2)))
  758. with pytest.raises(ValueError, match="inconsistent numbers of samples"):
  759. check_consistent_length([1, 2], [1])
  760. with pytest.raises(TypeError, match=r"got <\w+ 'int'>"):
  761. check_consistent_length([1, 2], 1)
  762. with pytest.raises(TypeError, match=r"got <\w+ 'object'>"):
  763. check_consistent_length([1, 2], object())
  764. with pytest.raises(TypeError):
  765. check_consistent_length([1, 2], np.array(1))
  766. # Despite ensembles having __len__ they must raise TypeError
  767. with pytest.raises(TypeError, match="Expected sequence or array-like"):
  768. check_consistent_length([1, 2], RandomForestRegressor())
  769. # XXX: We should have a test with a string, but what is correct behaviour?
  770. def test_check_dataframe_fit_attribute():
  771. # check pandas dataframe with 'fit' column does not raise error
  772. # https://github.com/scikit-learn/scikit-learn/issues/8415
  773. try:
  774. import pandas as pd
  775. X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
  776. X_df = pd.DataFrame(X, columns=["a", "b", "fit"])
  777. check_consistent_length(X_df)
  778. except ImportError:
  779. raise SkipTest("Pandas not found")
  780. def test_suppress_validation():
  781. X = np.array([0, np.inf])
  782. with pytest.raises(ValueError):
  783. assert_all_finite(X)
  784. sklearn.set_config(assume_finite=True)
  785. assert_all_finite(X)
  786. sklearn.set_config(assume_finite=False)
  787. with pytest.raises(ValueError):
  788. assert_all_finite(X)
  789. def test_check_array_series():
  790. # regression test that check_array works on pandas Series
  791. pd = importorskip("pandas")
  792. res = check_array(pd.Series([1, 2, 3]), ensure_2d=False)
  793. assert_array_equal(res, np.array([1, 2, 3]))
  794. # with categorical dtype (not a numpy dtype) (GH12699)
  795. s = pd.Series(["a", "b", "c"]).astype("category")
  796. res = check_array(s, dtype=None, ensure_2d=False)
  797. assert_array_equal(res, np.array(["a", "b", "c"], dtype=object))
  798. @pytest.mark.parametrize(
  799. "dtype", ((np.float64, np.float32), np.float64, None, "numeric")
  800. )
  801. @pytest.mark.parametrize("bool_dtype", ("bool", "boolean"))
  802. def test_check_dataframe_mixed_float_dtypes(dtype, bool_dtype):
  803. # pandas dataframe will coerce a boolean into a object, this is a mismatch
  804. # with np.result_type which will return a float
  805. # check_array needs to explicitly check for bool dtype in a dataframe for
  806. # this situation
  807. # https://github.com/scikit-learn/scikit-learn/issues/15787
  808. if bool_dtype == "boolean":
  809. # boolean extension arrays was introduced in 1.0
  810. pd = importorskip("pandas", minversion="1.0")
  811. else:
  812. pd = importorskip("pandas")
  813. df = pd.DataFrame(
  814. {
  815. "int": [1, 2, 3],
  816. "float": [0, 0.1, 2.1],
  817. "bool": pd.Series([True, False, True], dtype=bool_dtype),
  818. },
  819. columns=["int", "float", "bool"],
  820. )
  821. array = check_array(df, dtype=dtype)
  822. assert array.dtype == np.float64
  823. expected_array = np.array(
  824. [[1.0, 0.0, 1.0], [2.0, 0.1, 0.0], [3.0, 2.1, 1.0]], dtype=float
  825. )
  826. assert_allclose_dense_sparse(array, expected_array)
  827. def test_check_dataframe_with_only_bool():
  828. """Check that dataframe with bool return a boolean arrays."""
  829. pd = importorskip("pandas")
  830. df = pd.DataFrame({"bool": [True, False, True]})
  831. array = check_array(df, dtype=None)
  832. assert array.dtype == np.bool_
  833. assert_array_equal(array, [[True], [False], [True]])
  834. # common dtype is int for bool + int
  835. df = pd.DataFrame(
  836. {"bool": [True, False, True], "int": [1, 2, 3]},
  837. columns=["bool", "int"],
  838. )
  839. array = check_array(df, dtype="numeric")
  840. assert array.dtype == np.int64
  841. assert_array_equal(array, [[1, 1], [0, 2], [1, 3]])
  842. def test_check_dataframe_with_only_boolean():
  843. """Check that dataframe with boolean return a float array with dtype=None"""
  844. pd = importorskip("pandas", minversion="1.0")
  845. df = pd.DataFrame({"bool": pd.Series([True, False, True], dtype="boolean")})
  846. array = check_array(df, dtype=None)
  847. assert array.dtype == np.float64
  848. assert_array_equal(array, [[True], [False], [True]])
  849. class DummyMemory:
  850. def cache(self, func):
  851. return func
  852. class WrongDummyMemory:
  853. pass
  854. def test_check_memory():
  855. memory = check_memory("cache_directory")
  856. assert memory.location == "cache_directory"
  857. memory = check_memory(None)
  858. assert memory.location is None
  859. dummy = DummyMemory()
  860. memory = check_memory(dummy)
  861. assert memory is dummy
  862. msg = (
  863. "'memory' should be None, a string or have the same interface as"
  864. " joblib.Memory. Got memory='1' instead."
  865. )
  866. with pytest.raises(ValueError, match=msg):
  867. check_memory(1)
  868. dummy = WrongDummyMemory()
  869. msg = (
  870. "'memory' should be None, a string or have the same interface as"
  871. " joblib.Memory. Got memory='{}' instead.".format(dummy)
  872. )
  873. with pytest.raises(ValueError, match=msg):
  874. check_memory(dummy)
  875. @pytest.mark.parametrize("copy", [True, False])
  876. def test_check_array_memmap(copy):
  877. X = np.ones((4, 4))
  878. with TempMemmap(X, mmap_mode="r") as X_memmap:
  879. X_checked = check_array(X_memmap, copy=copy)
  880. assert np.may_share_memory(X_memmap, X_checked) == (not copy)
  881. assert X_checked.flags["WRITEABLE"] == copy
  882. @pytest.mark.parametrize(
  883. "retype",
  884. [
  885. np.asarray,
  886. sp.csr_matrix,
  887. sp.csc_matrix,
  888. sp.coo_matrix,
  889. sp.lil_matrix,
  890. sp.bsr_matrix,
  891. sp.dok_matrix,
  892. sp.dia_matrix,
  893. ],
  894. )
  895. def test_check_non_negative(retype):
  896. A = np.array([[1, 1, 0, 0], [1, 1, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])
  897. X = retype(A)
  898. check_non_negative(X, "")
  899. X = retype([[0, 0], [0, 0]])
  900. check_non_negative(X, "")
  901. A[0, 0] = -1
  902. X = retype(A)
  903. with pytest.raises(ValueError, match="Negative "):
  904. check_non_negative(X, "")
  905. def test_check_X_y_informative_error():
  906. X = np.ones((2, 2))
  907. y = None
  908. msg = "estimator requires y to be passed, but the target y is None"
  909. with pytest.raises(ValueError, match=msg):
  910. check_X_y(X, y)
  911. msg = "RandomForestRegressor requires y to be passed, but the target y is None"
  912. with pytest.raises(ValueError, match=msg):
  913. check_X_y(X, y, estimator=RandomForestRegressor())
  914. def test_retrieve_samples_from_non_standard_shape():
  915. class TestNonNumericShape:
  916. def __init__(self):
  917. self.shape = ("not numeric",)
  918. def __len__(self):
  919. return len([1, 2, 3])
  920. X = TestNonNumericShape()
  921. assert _num_samples(X) == len(X)
  922. # check that it gives a good error if there's no __len__
  923. class TestNoLenWeirdShape:
  924. def __init__(self):
  925. self.shape = ("not numeric",)
  926. with pytest.raises(TypeError, match="Expected sequence or array-like"):
  927. _num_samples(TestNoLenWeirdShape())
  928. @pytest.mark.parametrize("x", [2, 3, 2.5, 5])
  929. def test_check_scalar_valid(x):
  930. """Test that check_scalar returns no error/warning if valid inputs are
  931. provided"""
  932. with warnings.catch_warnings():
  933. warnings.simplefilter("error")
  934. scalar = check_scalar(
  935. x,
  936. "test_name",
  937. target_type=numbers.Real,
  938. min_val=2,
  939. max_val=5,
  940. include_boundaries="both",
  941. )
  942. assert scalar == x
  943. @pytest.mark.parametrize(
  944. "x, target_name, target_type, min_val, max_val, include_boundaries, err_msg",
  945. [
  946. (
  947. 1,
  948. "test_name1",
  949. float,
  950. 2,
  951. 4,
  952. "neither",
  953. TypeError("test_name1 must be an instance of float, not int."),
  954. ),
  955. (
  956. None,
  957. "test_name1",
  958. numbers.Real,
  959. 2,
  960. 4,
  961. "neither",
  962. TypeError("test_name1 must be an instance of float, not NoneType."),
  963. ),
  964. (
  965. None,
  966. "test_name1",
  967. numbers.Integral,
  968. 2,
  969. 4,
  970. "neither",
  971. TypeError("test_name1 must be an instance of int, not NoneType."),
  972. ),
  973. (
  974. 1,
  975. "test_name1",
  976. (float, bool),
  977. 2,
  978. 4,
  979. "neither",
  980. TypeError("test_name1 must be an instance of {float, bool}, not int."),
  981. ),
  982. (
  983. 1,
  984. "test_name2",
  985. int,
  986. 2,
  987. 4,
  988. "neither",
  989. ValueError("test_name2 == 1, must be > 2."),
  990. ),
  991. (
  992. 5,
  993. "test_name3",
  994. int,
  995. 2,
  996. 4,
  997. "neither",
  998. ValueError("test_name3 == 5, must be < 4."),
  999. ),
  1000. (
  1001. 2,
  1002. "test_name4",
  1003. int,
  1004. 2,
  1005. 4,
  1006. "right",
  1007. ValueError("test_name4 == 2, must be > 2."),
  1008. ),
  1009. (
  1010. 4,
  1011. "test_name5",
  1012. int,
  1013. 2,
  1014. 4,
  1015. "left",
  1016. ValueError("test_name5 == 4, must be < 4."),
  1017. ),
  1018. (
  1019. 4,
  1020. "test_name6",
  1021. int,
  1022. 2,
  1023. 4,
  1024. "bad parameter value",
  1025. ValueError(
  1026. "Unknown value for `include_boundaries`: 'bad parameter value'. "
  1027. "Possible values are: ('left', 'right', 'both', 'neither')."
  1028. ),
  1029. ),
  1030. (
  1031. 4,
  1032. "test_name7",
  1033. int,
  1034. None,
  1035. 4,
  1036. "left",
  1037. ValueError(
  1038. "`include_boundaries`='left' without specifying explicitly `min_val` "
  1039. "is inconsistent."
  1040. ),
  1041. ),
  1042. (
  1043. 4,
  1044. "test_name8",
  1045. int,
  1046. 2,
  1047. None,
  1048. "right",
  1049. ValueError(
  1050. "`include_boundaries`='right' without specifying explicitly `max_val` "
  1051. "is inconsistent."
  1052. ),
  1053. ),
  1054. ],
  1055. )
  1056. def test_check_scalar_invalid(
  1057. x, target_name, target_type, min_val, max_val, include_boundaries, err_msg
  1058. ):
  1059. """Test that check_scalar returns the right error if a wrong input is
  1060. given"""
  1061. with pytest.raises(Exception) as raised_error:
  1062. check_scalar(
  1063. x,
  1064. target_name,
  1065. target_type=target_type,
  1066. min_val=min_val,
  1067. max_val=max_val,
  1068. include_boundaries=include_boundaries,
  1069. )
  1070. assert str(raised_error.value) == str(err_msg)
  1071. assert type(raised_error.value) == type(err_msg)
  1072. _psd_cases_valid = {
  1073. "nominal": ((1, 2), np.array([1, 2]), None, ""),
  1074. "nominal_np_array": (np.array([1, 2]), np.array([1, 2]), None, ""),
  1075. "insignificant_imag": (
  1076. (5, 5e-5j),
  1077. np.array([5, 0]),
  1078. PositiveSpectrumWarning,
  1079. "There are imaginary parts in eigenvalues \\(1e\\-05 of the maximum real part",
  1080. ),
  1081. "insignificant neg": ((5, -5e-5), np.array([5, 0]), PositiveSpectrumWarning, ""),
  1082. "insignificant neg float32": (
  1083. np.array([1, -1e-6], dtype=np.float32),
  1084. np.array([1, 0], dtype=np.float32),
  1085. PositiveSpectrumWarning,
  1086. "There are negative eigenvalues \\(1e\\-06 of the maximum positive",
  1087. ),
  1088. "insignificant neg float64": (
  1089. np.array([1, -1e-10], dtype=np.float64),
  1090. np.array([1, 0], dtype=np.float64),
  1091. PositiveSpectrumWarning,
  1092. "There are negative eigenvalues \\(1e\\-10 of the maximum positive",
  1093. ),
  1094. "insignificant pos": (
  1095. (5, 4e-12),
  1096. np.array([5, 0]),
  1097. PositiveSpectrumWarning,
  1098. "the largest eigenvalue is more than 1e\\+12 times the smallest",
  1099. ),
  1100. }
  1101. @pytest.mark.parametrize(
  1102. "lambdas, expected_lambdas, w_type, w_msg",
  1103. list(_psd_cases_valid.values()),
  1104. ids=list(_psd_cases_valid.keys()),
  1105. )
  1106. @pytest.mark.parametrize("enable_warnings", [True, False])
  1107. def test_check_psd_eigenvalues_valid(
  1108. lambdas, expected_lambdas, w_type, w_msg, enable_warnings
  1109. ):
  1110. # Test that ``_check_psd_eigenvalues`` returns the right output for valid
  1111. # input, possibly raising the right warning
  1112. if not enable_warnings:
  1113. w_type = None
  1114. if w_type is None:
  1115. with warnings.catch_warnings():
  1116. warnings.simplefilter("error", PositiveSpectrumWarning)
  1117. lambdas_fixed = _check_psd_eigenvalues(
  1118. lambdas, enable_warnings=enable_warnings
  1119. )
  1120. else:
  1121. with pytest.warns(w_type, match=w_msg):
  1122. lambdas_fixed = _check_psd_eigenvalues(
  1123. lambdas, enable_warnings=enable_warnings
  1124. )
  1125. assert_allclose(expected_lambdas, lambdas_fixed)
  1126. _psd_cases_invalid = {
  1127. "significant_imag": (
  1128. (5, 5j),
  1129. ValueError,
  1130. "There are significant imaginary parts in eigenv",
  1131. ),
  1132. "all negative": (
  1133. (-5, -1),
  1134. ValueError,
  1135. "All eigenvalues are negative \\(maximum is -1",
  1136. ),
  1137. "significant neg": (
  1138. (5, -1),
  1139. ValueError,
  1140. "There are significant negative eigenvalues",
  1141. ),
  1142. "significant neg float32": (
  1143. np.array([3e-4, -2e-6], dtype=np.float32),
  1144. ValueError,
  1145. "There are significant negative eigenvalues",
  1146. ),
  1147. "significant neg float64": (
  1148. np.array([1e-5, -2e-10], dtype=np.float64),
  1149. ValueError,
  1150. "There are significant negative eigenvalues",
  1151. ),
  1152. }
  1153. @pytest.mark.parametrize(
  1154. "lambdas, err_type, err_msg",
  1155. list(_psd_cases_invalid.values()),
  1156. ids=list(_psd_cases_invalid.keys()),
  1157. )
  1158. def test_check_psd_eigenvalues_invalid(lambdas, err_type, err_msg):
  1159. # Test that ``_check_psd_eigenvalues`` raises the right error for invalid
  1160. # input
  1161. with pytest.raises(err_type, match=err_msg):
  1162. _check_psd_eigenvalues(lambdas)
  1163. def test_check_sample_weight():
  1164. # check array order
  1165. sample_weight = np.ones(10)[::2]
  1166. assert not sample_weight.flags["C_CONTIGUOUS"]
  1167. sample_weight = _check_sample_weight(sample_weight, X=np.ones((5, 1)))
  1168. assert sample_weight.flags["C_CONTIGUOUS"]
  1169. # check None input
  1170. sample_weight = _check_sample_weight(None, X=np.ones((5, 2)))
  1171. assert_allclose(sample_weight, np.ones(5))
  1172. # check numbers input
  1173. sample_weight = _check_sample_weight(2.0, X=np.ones((5, 2)))
  1174. assert_allclose(sample_weight, 2 * np.ones(5))
  1175. # check wrong number of dimensions
  1176. with pytest.raises(ValueError, match="Sample weights must be 1D array or scalar"):
  1177. _check_sample_weight(np.ones((2, 4)), X=np.ones((2, 2)))
  1178. # check incorrect n_samples
  1179. msg = r"sample_weight.shape == \(4,\), expected \(2,\)!"
  1180. with pytest.raises(ValueError, match=msg):
  1181. _check_sample_weight(np.ones(4), X=np.ones((2, 2)))
  1182. # float32 dtype is preserved
  1183. X = np.ones((5, 2))
  1184. sample_weight = np.ones(5, dtype=np.float32)
  1185. sample_weight = _check_sample_weight(sample_weight, X)
  1186. assert sample_weight.dtype == np.float32
  1187. # int dtype will be converted to float64 instead
  1188. X = np.ones((5, 2), dtype=int)
  1189. sample_weight = _check_sample_weight(None, X, dtype=X.dtype)
  1190. assert sample_weight.dtype == np.float64
  1191. # check negative weight when only_non_negative=True
  1192. X = np.ones((5, 2))
  1193. sample_weight = np.ones(_num_samples(X))
  1194. sample_weight[-1] = -10
  1195. err_msg = "Negative values in data passed to `sample_weight`"
  1196. with pytest.raises(ValueError, match=err_msg):
  1197. _check_sample_weight(sample_weight, X, only_non_negative=True)
  1198. @pytest.mark.parametrize("toarray", [np.array, sp.csr_matrix, sp.csc_matrix])
  1199. def test_allclose_dense_sparse_equals(toarray):
  1200. base = np.arange(9).reshape(3, 3)
  1201. x, y = toarray(base), toarray(base)
  1202. assert _allclose_dense_sparse(x, y)
  1203. @pytest.mark.parametrize("toarray", [np.array, sp.csr_matrix, sp.csc_matrix])
  1204. def test_allclose_dense_sparse_not_equals(toarray):
  1205. base = np.arange(9).reshape(3, 3)
  1206. x, y = toarray(base), toarray(base + 1)
  1207. assert not _allclose_dense_sparse(x, y)
  1208. @pytest.mark.parametrize("toarray", [sp.csr_matrix, sp.csc_matrix])
  1209. def test_allclose_dense_sparse_raise(toarray):
  1210. x = np.arange(9).reshape(3, 3)
  1211. y = toarray(x + 1)
  1212. msg = "Can only compare two sparse matrices, not a sparse matrix and an array"
  1213. with pytest.raises(ValueError, match=msg):
  1214. _allclose_dense_sparse(x, y)
  1215. def test_deprecate_positional_args_warns_for_function():
  1216. @_deprecate_positional_args
  1217. def f1(a, b, *, c=1, d=1):
  1218. pass
  1219. with pytest.warns(FutureWarning, match=r"Pass c=3 as keyword args"):
  1220. f1(1, 2, 3)
  1221. with pytest.warns(FutureWarning, match=r"Pass c=3, d=4 as keyword args"):
  1222. f1(1, 2, 3, 4)
  1223. @_deprecate_positional_args
  1224. def f2(a=1, *, b=1, c=1, d=1):
  1225. pass
  1226. with pytest.warns(FutureWarning, match=r"Pass b=2 as keyword args"):
  1227. f2(1, 2)
  1228. # The * is place before a keyword only argument without a default value
  1229. @_deprecate_positional_args
  1230. def f3(a, *, b, c=1, d=1):
  1231. pass
  1232. with pytest.warns(FutureWarning, match=r"Pass b=2 as keyword args"):
  1233. f3(1, 2)
  1234. def test_deprecate_positional_args_warns_for_function_version():
  1235. @_deprecate_positional_args(version="1.1")
  1236. def f1(a, *, b):
  1237. pass
  1238. with pytest.warns(
  1239. FutureWarning, match=r"From version 1.1 passing these as positional"
  1240. ):
  1241. f1(1, 2)
  1242. def test_deprecate_positional_args_warns_for_class():
  1243. class A1:
  1244. @_deprecate_positional_args
  1245. def __init__(self, a, b, *, c=1, d=1):
  1246. pass
  1247. with pytest.warns(FutureWarning, match=r"Pass c=3 as keyword args"):
  1248. A1(1, 2, 3)
  1249. with pytest.warns(FutureWarning, match=r"Pass c=3, d=4 as keyword args"):
  1250. A1(1, 2, 3, 4)
  1251. class A2:
  1252. @_deprecate_positional_args
  1253. def __init__(self, a=1, b=1, *, c=1, d=1):
  1254. pass
  1255. with pytest.warns(FutureWarning, match=r"Pass c=3 as keyword args"):
  1256. A2(1, 2, 3)
  1257. with pytest.warns(FutureWarning, match=r"Pass c=3, d=4 as keyword args"):
  1258. A2(1, 2, 3, 4)
  1259. @pytest.mark.parametrize("indices", [None, [1, 3]])
  1260. def test_check_fit_params(indices):
  1261. X = np.random.randn(4, 2)
  1262. fit_params = {
  1263. "list": [1, 2, 3, 4],
  1264. "array": np.array([1, 2, 3, 4]),
  1265. "sparse-col": sp.csc_matrix([1, 2, 3, 4]).T,
  1266. "sparse-row": sp.csc_matrix([1, 2, 3, 4]),
  1267. "scalar-int": 1,
  1268. "scalar-str": "xxx",
  1269. "None": None,
  1270. }
  1271. result = _check_fit_params(X, fit_params, indices)
  1272. indices_ = indices if indices is not None else list(range(X.shape[0]))
  1273. for key in ["sparse-row", "scalar-int", "scalar-str", "None"]:
  1274. assert result[key] is fit_params[key]
  1275. assert result["list"] == _safe_indexing(fit_params["list"], indices_)
  1276. assert_array_equal(result["array"], _safe_indexing(fit_params["array"], indices_))
  1277. assert_allclose_dense_sparse(
  1278. result["sparse-col"], _safe_indexing(fit_params["sparse-col"], indices_)
  1279. )
  1280. @pytest.mark.parametrize("sp_format", [True, "csr", "csc", "coo", "bsr"])
  1281. def test_check_sparse_pandas_sp_format(sp_format):
  1282. # check_array converts pandas dataframe with only sparse arrays into
  1283. # sparse matrix
  1284. pd = pytest.importorskip("pandas")
  1285. sp_mat = _sparse_random_matrix(10, 3)
  1286. sdf = pd.DataFrame.sparse.from_spmatrix(sp_mat)
  1287. result = check_array(sdf, accept_sparse=sp_format)
  1288. if sp_format is True:
  1289. # by default pandas converts to coo when accept_sparse is True
  1290. sp_format = "coo"
  1291. assert sp.issparse(result)
  1292. assert result.format == sp_format
  1293. assert_allclose_dense_sparse(sp_mat, result)
  1294. @pytest.mark.parametrize(
  1295. "ntype1, ntype2",
  1296. [
  1297. ("longdouble", "float16"),
  1298. ("float16", "float32"),
  1299. ("float32", "double"),
  1300. ("int16", "int32"),
  1301. ("int32", "long"),
  1302. ("byte", "uint16"),
  1303. ("ushort", "uint32"),
  1304. ("uint32", "uint64"),
  1305. ("uint8", "int8"),
  1306. ],
  1307. )
  1308. def test_check_pandas_sparse_invalid(ntype1, ntype2):
  1309. """check that we raise an error with dataframe having
  1310. sparse extension arrays with unsupported mixed dtype
  1311. and pandas version below 1.1. pandas versions 1.1 and
  1312. above fixed this issue so no error will be raised."""
  1313. pd = pytest.importorskip("pandas")
  1314. df = pd.DataFrame(
  1315. {
  1316. "col1": pd.arrays.SparseArray([0, 1, 0], dtype=ntype1, fill_value=0),
  1317. "col2": pd.arrays.SparseArray([1, 0, 1], dtype=ntype2, fill_value=0),
  1318. }
  1319. )
  1320. if parse_version(pd.__version__) < parse_version("1.1"):
  1321. err_msg = "Pandas DataFrame with mixed sparse extension arrays"
  1322. with pytest.raises(ValueError, match=err_msg):
  1323. check_array(df, accept_sparse=["csr", "csc"])
  1324. else:
  1325. # pandas fixed this issue at 1.1 so from here on,
  1326. # no error will be raised.
  1327. check_array(df, accept_sparse=["csr", "csc"])
  1328. @pytest.mark.parametrize(
  1329. "ntype1, ntype2, expected_subtype",
  1330. [
  1331. ("double", "longdouble", np.floating),
  1332. ("float16", "half", np.floating),
  1333. ("single", "float32", np.floating),
  1334. ("double", "float64", np.floating),
  1335. ("int8", "byte", np.integer),
  1336. ("short", "int16", np.integer),
  1337. ("intc", "int32", np.integer),
  1338. ("intp", "long", np.integer),
  1339. ("int", "long", np.integer),
  1340. ("int64", "longlong", np.integer),
  1341. ("int_", "intp", np.integer),
  1342. ("ubyte", "uint8", np.unsignedinteger),
  1343. ("uint16", "ushort", np.unsignedinteger),
  1344. ("uintc", "uint32", np.unsignedinteger),
  1345. ("uint", "uint64", np.unsignedinteger),
  1346. ("uintp", "ulonglong", np.unsignedinteger),
  1347. ],
  1348. )
  1349. def test_check_pandas_sparse_valid(ntype1, ntype2, expected_subtype):
  1350. # check that we support the conversion of sparse dataframe with mixed
  1351. # type which can be converted safely.
  1352. pd = pytest.importorskip("pandas")
  1353. df = pd.DataFrame(
  1354. {
  1355. "col1": pd.arrays.SparseArray([0, 1, 0], dtype=ntype1, fill_value=0),
  1356. "col2": pd.arrays.SparseArray([1, 0, 1], dtype=ntype2, fill_value=0),
  1357. }
  1358. )
  1359. arr = check_array(df, accept_sparse=["csr", "csc"])
  1360. assert np.issubdtype(arr.dtype, expected_subtype)
  1361. @pytest.mark.parametrize(
  1362. "constructor_name",
  1363. ["list", "tuple", "array", "dataframe", "sparse_csr", "sparse_csc"],
  1364. )
  1365. def test_num_features(constructor_name):
  1366. """Check _num_features for array-likes."""
  1367. X = [[1, 2, 3], [4, 5, 6]]
  1368. X = _convert_container(X, constructor_name)
  1369. assert _num_features(X) == 3
  1370. @pytest.mark.parametrize(
  1371. "X",
  1372. [
  1373. [1, 2, 3],
  1374. ["a", "b", "c"],
  1375. [False, True, False],
  1376. [1.0, 3.4, 4.0],
  1377. [{"a": 1}, {"b": 2}, {"c": 3}],
  1378. ],
  1379. ids=["int", "str", "bool", "float", "dict"],
  1380. )
  1381. @pytest.mark.parametrize("constructor_name", ["list", "tuple", "array", "series"])
  1382. def test_num_features_errors_1d_containers(X, constructor_name):
  1383. X = _convert_container(X, constructor_name)
  1384. if constructor_name == "array":
  1385. expected_type_name = "numpy.ndarray"
  1386. elif constructor_name == "series":
  1387. expected_type_name = "pandas.core.series.Series"
  1388. else:
  1389. expected_type_name = constructor_name
  1390. message = (
  1391. f"Unable to find the number of features from X of type {expected_type_name}"
  1392. )
  1393. if hasattr(X, "shape"):
  1394. message += " with shape (3,)"
  1395. elif isinstance(X[0], str):
  1396. message += " where the samples are of type str"
  1397. elif isinstance(X[0], dict):
  1398. message += " where the samples are of type dict"
  1399. with pytest.raises(TypeError, match=re.escape(message)):
  1400. _num_features(X)
  1401. @pytest.mark.parametrize("X", [1, "b", False, 3.0], ids=["int", "str", "bool", "float"])
  1402. def test_num_features_errors_scalars(X):
  1403. msg = f"Unable to find the number of features from X of type {type(X).__qualname__}"
  1404. with pytest.raises(TypeError, match=msg):
  1405. _num_features(X)
  1406. @pytest.mark.parametrize(
  1407. "names",
  1408. [list(range(2)), range(2), None, [["a", "b"], ["c", "d"]]],
  1409. ids=["list-int", "range", "default", "MultiIndex"],
  1410. )
  1411. def test_get_feature_names_pandas_with_ints_no_warning(names):
  1412. """Get feature names with pandas dataframes without warning.
  1413. Column names with consistent dtypes will not warn, such as int or MultiIndex.
  1414. """
  1415. pd = pytest.importorskip("pandas")
  1416. X = pd.DataFrame([[1, 2], [4, 5], [5, 6]], columns=names)
  1417. with warnings.catch_warnings():
  1418. warnings.simplefilter("error", FutureWarning)
  1419. names = _get_feature_names(X)
  1420. assert names is None
  1421. def test_get_feature_names_pandas():
  1422. """Get feature names with pandas dataframes."""
  1423. pd = pytest.importorskip("pandas")
  1424. columns = [f"col_{i}" for i in range(3)]
  1425. X = pd.DataFrame([[1, 2, 3], [4, 5, 6]], columns=columns)
  1426. feature_names = _get_feature_names(X)
  1427. assert_array_equal(feature_names, columns)
  1428. def test_get_feature_names_numpy():
  1429. """Get feature names return None for numpy arrays."""
  1430. X = np.array([[1, 2, 3], [4, 5, 6]])
  1431. names = _get_feature_names(X)
  1432. assert names is None
  1433. @pytest.mark.parametrize(
  1434. "names, dtypes",
  1435. [
  1436. (["a", 1], "['int', 'str']"),
  1437. (["pizza", ["a", "b"]], "['list', 'str']"),
  1438. ],
  1439. ids=["int-str", "list-str"],
  1440. )
  1441. def test_get_feature_names_invalid_dtypes(names, dtypes):
  1442. """Get feature names errors when the feature names have mixed dtypes"""
  1443. pd = pytest.importorskip("pandas")
  1444. X = pd.DataFrame([[1, 2], [4, 5], [5, 6]], columns=names)
  1445. msg = re.escape(
  1446. "Feature names are only supported if all input features have string names, "
  1447. f"but your input has {dtypes} as feature name / column name types. "
  1448. "If you want feature names to be stored and validated, you must convert "
  1449. "them all to strings, by using X.columns = X.columns.astype(str) for "
  1450. "example. Otherwise you can remove feature / column names from your input "
  1451. "data, or convert them all to a non-string data type."
  1452. )
  1453. with pytest.raises(TypeError, match=msg):
  1454. names = _get_feature_names(X)
  1455. class PassthroughTransformer(BaseEstimator):
  1456. def fit(self, X, y=None):
  1457. self._validate_data(X, reset=True)
  1458. return self
  1459. def transform(self, X):
  1460. return X
  1461. def get_feature_names_out(self, input_features=None):
  1462. return _check_feature_names_in(self, input_features)
  1463. def test_check_feature_names_in():
  1464. """Check behavior of check_feature_names_in for arrays."""
  1465. X = np.array([[0.0, 1.0, 2.0]])
  1466. est = PassthroughTransformer().fit(X)
  1467. names = est.get_feature_names_out()
  1468. assert_array_equal(names, ["x0", "x1", "x2"])
  1469. incorrect_len_names = ["x10", "x1"]
  1470. with pytest.raises(ValueError, match="input_features should have length equal to"):
  1471. est.get_feature_names_out(incorrect_len_names)
  1472. # remove n_feature_in_
  1473. del est.n_features_in_
  1474. with pytest.raises(ValueError, match="Unable to generate feature names"):
  1475. est.get_feature_names_out()
  1476. def test_check_feature_names_in_pandas():
  1477. """Check behavior of check_feature_names_in for pandas dataframes."""
  1478. pd = pytest.importorskip("pandas")
  1479. names = ["a", "b", "c"]
  1480. df = pd.DataFrame([[0.0, 1.0, 2.0]], columns=names)
  1481. est = PassthroughTransformer().fit(df)
  1482. names = est.get_feature_names_out()
  1483. assert_array_equal(names, ["a", "b", "c"])
  1484. with pytest.raises(ValueError, match="input_features is not equal to"):
  1485. est.get_feature_names_out(["x1", "x2", "x3"])
  1486. def test_check_response_method_unknown_method():
  1487. """Check the error message when passing an unknown response method."""
  1488. err_msg = (
  1489. "RandomForestRegressor has none of the following attributes: unknown_method."
  1490. )
  1491. with pytest.raises(AttributeError, match=err_msg):
  1492. _check_response_method(RandomForestRegressor(), "unknown_method")
  1493. @pytest.mark.parametrize(
  1494. "response_method", ["decision_function", "predict_proba", "predict"]
  1495. )
  1496. def test_check_response_method_not_supported_response_method(response_method):
  1497. """Check the error message when a response method is not supported by the
  1498. estimator."""
  1499. err_msg = (
  1500. f"EstimatorWithFit has none of the following attributes: {response_method}."
  1501. )
  1502. with pytest.raises(AttributeError, match=err_msg):
  1503. _check_response_method(EstimatorWithFit(), response_method)
  1504. def test_check_response_method_list_str():
  1505. """Check that we can pass a list of ordered method."""
  1506. method_implemented = ["predict_proba"]
  1507. my_estimator = _MockEstimatorOnOffPrediction(method_implemented)
  1508. X = "mocking_data"
  1509. # raise an error when no methods are defined
  1510. response_method = ["decision_function", "predict"]
  1511. err_msg = (
  1512. "_MockEstimatorOnOffPrediction has none of the following attributes: "
  1513. f"{', '.join(response_method)}."
  1514. )
  1515. with pytest.raises(AttributeError, match=err_msg):
  1516. _check_response_method(my_estimator, response_method)(X)
  1517. # check that we don't get issue when one of the method is defined
  1518. response_method = ["decision_function", "predict_proba"]
  1519. method_name_predicting = _check_response_method(my_estimator, response_method)(X)
  1520. assert method_name_predicting == "predict_proba"
  1521. # check the order of the methods returned
  1522. method_implemented = ["predict_proba", "predict"]
  1523. my_estimator = _MockEstimatorOnOffPrediction(method_implemented)
  1524. response_method = ["decision_function", "predict", "predict_proba"]
  1525. method_name_predicting = _check_response_method(my_estimator, response_method)(X)
  1526. assert method_name_predicting == "predict"
  1527. def test_boolean_series_remains_boolean():
  1528. """Regression test for gh-25145"""
  1529. pd = importorskip("pandas")
  1530. res = check_array(pd.Series([True, False]), ensure_2d=False)
  1531. expected = np.array([True, False])
  1532. assert res.dtype == expected.dtype
  1533. assert_array_equal(res, expected)
  1534. @pytest.mark.parametrize("input_values", [[0, 1, 0, 1, 0, np.nan], [0, 1, 0, 1, 0, 1]])
  1535. def test_pandas_array_returns_ndarray(input_values):
  1536. """Check pandas array with extensions dtypes returns a numeric ndarray.
  1537. Non-regression test for gh-25637.
  1538. """
  1539. pd = importorskip("pandas")
  1540. input_series = pd.array(input_values, dtype="Int32")
  1541. result = check_array(
  1542. input_series,
  1543. dtype=None,
  1544. ensure_2d=False,
  1545. allow_nd=False,
  1546. force_all_finite=False,
  1547. )
  1548. assert np.issubdtype(result.dtype.kind, np.floating)
  1549. assert_allclose(result, input_values)
  1550. @skip_if_array_api_compat_not_configured
  1551. @pytest.mark.parametrize("array_namespace", ["numpy.array_api", "cupy.array_api"])
  1552. def test_check_array_array_api_has_non_finite(array_namespace):
  1553. """Checks that Array API arrays checks non-finite correctly."""
  1554. xp = pytest.importorskip(array_namespace)
  1555. X_nan = xp.asarray([[xp.nan, 1, 0], [0, xp.nan, 3]], dtype=xp.float32)
  1556. with config_context(array_api_dispatch=True):
  1557. with pytest.raises(ValueError, match="Input contains NaN."):
  1558. check_array(X_nan)
  1559. X_inf = xp.asarray([[xp.inf, 1, 0], [0, xp.inf, 3]], dtype=xp.float32)
  1560. with config_context(array_api_dispatch=True):
  1561. with pytest.raises(ValueError, match="infinity or a value too large"):
  1562. check_array(X_inf)
  1563. @pytest.mark.parametrize(
  1564. "extension_dtype, regular_dtype",
  1565. [
  1566. ("boolean", "bool"),
  1567. ("Int64", "int64"),
  1568. ("Float64", "float64"),
  1569. ("category", "object"),
  1570. ],
  1571. )
  1572. @pytest.mark.parametrize("include_object", [True, False])
  1573. def test_check_array_multiple_extensions(
  1574. extension_dtype, regular_dtype, include_object
  1575. ):
  1576. """Check pandas extension arrays give the same result as non-extension arrays."""
  1577. pd = pytest.importorskip("pandas")
  1578. X_regular = pd.DataFrame(
  1579. {
  1580. "a": pd.Series([1, 0, 1, 0], dtype=regular_dtype),
  1581. "c": pd.Series([9, 8, 7, 6], dtype="int64"),
  1582. }
  1583. )
  1584. if include_object:
  1585. X_regular["b"] = pd.Series(["a", "b", "c", "d"], dtype="object")
  1586. X_extension = X_regular.assign(a=X_regular["a"].astype(extension_dtype))
  1587. X_regular_checked = check_array(X_regular, dtype=None)
  1588. X_extension_checked = check_array(X_extension, dtype=None)
  1589. assert_array_equal(X_regular_checked, X_extension_checked)