test_cbook.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940
  1. from __future__ import annotations
  2. import itertools
  3. import pickle
  4. from typing import Any
  5. from unittest.mock import patch, Mock
  6. from datetime import datetime, date, timedelta
  7. import numpy as np
  8. from numpy.testing import (assert_array_equal, assert_approx_equal,
  9. assert_array_almost_equal)
  10. import pytest
  11. from matplotlib import _api, cbook
  12. import matplotlib.colors as mcolors
  13. from matplotlib.cbook import delete_masked_points, strip_math
  14. class Test_delete_masked_points:
  15. def test_bad_first_arg(self):
  16. with pytest.raises(ValueError):
  17. delete_masked_points('a string', np.arange(1.0, 7.0))
  18. def test_string_seq(self):
  19. a1 = ['a', 'b', 'c', 'd', 'e', 'f']
  20. a2 = [1, 2, 3, np.nan, np.nan, 6]
  21. result1, result2 = delete_masked_points(a1, a2)
  22. ind = [0, 1, 2, 5]
  23. assert_array_equal(result1, np.array(a1)[ind])
  24. assert_array_equal(result2, np.array(a2)[ind])
  25. def test_datetime(self):
  26. dates = [datetime(2008, 1, 1), datetime(2008, 1, 2),
  27. datetime(2008, 1, 3), datetime(2008, 1, 4),
  28. datetime(2008, 1, 5), datetime(2008, 1, 6)]
  29. a_masked = np.ma.array([1, 2, 3, np.nan, np.nan, 6],
  30. mask=[False, False, True, True, False, False])
  31. actual = delete_masked_points(dates, a_masked)
  32. ind = [0, 1, 5]
  33. assert_array_equal(actual[0], np.array(dates)[ind])
  34. assert_array_equal(actual[1], a_masked[ind].compressed())
  35. def test_rgba(self):
  36. a_masked = np.ma.array([1, 2, 3, np.nan, np.nan, 6],
  37. mask=[False, False, True, True, False, False])
  38. a_rgba = mcolors.to_rgba_array(['r', 'g', 'b', 'c', 'm', 'y'])
  39. actual = delete_masked_points(a_masked, a_rgba)
  40. ind = [0, 1, 5]
  41. assert_array_equal(actual[0], a_masked[ind].compressed())
  42. assert_array_equal(actual[1], a_rgba[ind])
  43. class Test_boxplot_stats:
  44. def setup_method(self):
  45. np.random.seed(937)
  46. self.nrows = 37
  47. self.ncols = 4
  48. self.data = np.random.lognormal(size=(self.nrows, self.ncols),
  49. mean=1.5, sigma=1.75)
  50. self.known_keys = sorted([
  51. 'mean', 'med', 'q1', 'q3', 'iqr',
  52. 'cilo', 'cihi', 'whislo', 'whishi',
  53. 'fliers', 'label'
  54. ])
  55. self.std_results = cbook.boxplot_stats(self.data)
  56. self.known_nonbootstrapped_res = {
  57. 'cihi': 6.8161283264444847,
  58. 'cilo': -0.1489815330368689,
  59. 'iqr': 13.492709959447094,
  60. 'mean': 13.00447442387868,
  61. 'med': 3.3335733967038079,
  62. 'fliers': np.array([
  63. 92.55467075, 87.03819018, 42.23204914, 39.29390996
  64. ]),
  65. 'q1': 1.3597529879465153,
  66. 'q3': 14.85246294739361,
  67. 'whishi': 27.899688243699629,
  68. 'whislo': 0.042143774965502923
  69. }
  70. self.known_bootstrapped_ci = {
  71. 'cihi': 8.939577523357828,
  72. 'cilo': 1.8692703958676578,
  73. }
  74. self.known_whis3_res = {
  75. 'whishi': 42.232049135969874,
  76. 'whislo': 0.042143774965502923,
  77. 'fliers': np.array([92.55467075, 87.03819018]),
  78. }
  79. self.known_res_percentiles = {
  80. 'whislo': 0.1933685896907924,
  81. 'whishi': 42.232049135969874
  82. }
  83. self.known_res_range = {
  84. 'whislo': 0.042143774965502923,
  85. 'whishi': 92.554670752188699
  86. }
  87. def test_form_main_list(self):
  88. assert isinstance(self.std_results, list)
  89. def test_form_each_dict(self):
  90. for res in self.std_results:
  91. assert isinstance(res, dict)
  92. def test_form_dict_keys(self):
  93. for res in self.std_results:
  94. assert set(res) <= set(self.known_keys)
  95. def test_results_baseline(self):
  96. res = self.std_results[0]
  97. for key, value in self.known_nonbootstrapped_res.items():
  98. assert_array_almost_equal(res[key], value)
  99. def test_results_bootstrapped(self):
  100. results = cbook.boxplot_stats(self.data, bootstrap=10000)
  101. res = results[0]
  102. for key, value in self.known_bootstrapped_ci.items():
  103. assert_approx_equal(res[key], value)
  104. def test_results_whiskers_float(self):
  105. results = cbook.boxplot_stats(self.data, whis=3)
  106. res = results[0]
  107. for key, value in self.known_whis3_res.items():
  108. assert_array_almost_equal(res[key], value)
  109. def test_results_whiskers_range(self):
  110. results = cbook.boxplot_stats(self.data, whis=[0, 100])
  111. res = results[0]
  112. for key, value in self.known_res_range.items():
  113. assert_array_almost_equal(res[key], value)
  114. def test_results_whiskers_percentiles(self):
  115. results = cbook.boxplot_stats(self.data, whis=[5, 95])
  116. res = results[0]
  117. for key, value in self.known_res_percentiles.items():
  118. assert_array_almost_equal(res[key], value)
  119. def test_results_withlabels(self):
  120. labels = ['Test1', 2, 'Aardvark', 4]
  121. results = cbook.boxplot_stats(self.data, labels=labels)
  122. for lab, res in zip(labels, results):
  123. assert res['label'] == lab
  124. results = cbook.boxplot_stats(self.data)
  125. for res in results:
  126. assert 'label' not in res
  127. def test_label_error(self):
  128. labels = [1, 2]
  129. with pytest.raises(ValueError):
  130. cbook.boxplot_stats(self.data, labels=labels)
  131. def test_bad_dims(self):
  132. data = np.random.normal(size=(34, 34, 34))
  133. with pytest.raises(ValueError):
  134. cbook.boxplot_stats(data)
  135. def test_boxplot_stats_autorange_false(self):
  136. x = np.zeros(shape=140)
  137. x = np.hstack([-25, x, 25])
  138. bstats_false = cbook.boxplot_stats(x, autorange=False)
  139. bstats_true = cbook.boxplot_stats(x, autorange=True)
  140. assert bstats_false[0]['whislo'] == 0
  141. assert bstats_false[0]['whishi'] == 0
  142. assert_array_almost_equal(bstats_false[0]['fliers'], [-25, 25])
  143. assert bstats_true[0]['whislo'] == -25
  144. assert bstats_true[0]['whishi'] == 25
  145. assert_array_almost_equal(bstats_true[0]['fliers'], [])
  146. class Test_callback_registry:
  147. def setup_method(self):
  148. self.signal = 'test'
  149. self.callbacks = cbook.CallbackRegistry()
  150. def connect(self, s, func, pickle):
  151. if pickle:
  152. return self.callbacks.connect(s, func)
  153. else:
  154. return self.callbacks._connect_picklable(s, func)
  155. def disconnect(self, cid):
  156. return self.callbacks.disconnect(cid)
  157. def count(self):
  158. count1 = len(self.callbacks._func_cid_map.get(self.signal, []))
  159. count2 = len(self.callbacks.callbacks.get(self.signal))
  160. assert count1 == count2
  161. return count1
  162. def is_empty(self):
  163. np.testing.break_cycles()
  164. assert self.callbacks._func_cid_map == {}
  165. assert self.callbacks.callbacks == {}
  166. assert self.callbacks._pickled_cids == set()
  167. def is_not_empty(self):
  168. np.testing.break_cycles()
  169. assert self.callbacks._func_cid_map != {}
  170. assert self.callbacks.callbacks != {}
  171. def test_cid_restore(self):
  172. cb = cbook.CallbackRegistry()
  173. cb.connect('a', lambda: None)
  174. cb2 = pickle.loads(pickle.dumps(cb))
  175. cid = cb2.connect('c', lambda: None)
  176. assert cid == 1
  177. @pytest.mark.parametrize('pickle', [True, False])
  178. def test_callback_complete(self, pickle):
  179. # ensure we start with an empty registry
  180. self.is_empty()
  181. # create a class for testing
  182. mini_me = Test_callback_registry()
  183. # test that we can add a callback
  184. cid1 = self.connect(self.signal, mini_me.dummy, pickle)
  185. assert type(cid1) is int
  186. self.is_not_empty()
  187. # test that we don't add a second callback
  188. cid2 = self.connect(self.signal, mini_me.dummy, pickle)
  189. assert cid1 == cid2
  190. self.is_not_empty()
  191. assert len(self.callbacks._func_cid_map) == 1
  192. assert len(self.callbacks.callbacks) == 1
  193. del mini_me
  194. # check we now have no callbacks registered
  195. self.is_empty()
  196. @pytest.mark.parametrize('pickle', [True, False])
  197. def test_callback_disconnect(self, pickle):
  198. # ensure we start with an empty registry
  199. self.is_empty()
  200. # create a class for testing
  201. mini_me = Test_callback_registry()
  202. # test that we can add a callback
  203. cid1 = self.connect(self.signal, mini_me.dummy, pickle)
  204. assert type(cid1) is int
  205. self.is_not_empty()
  206. self.disconnect(cid1)
  207. # check we now have no callbacks registered
  208. self.is_empty()
  209. @pytest.mark.parametrize('pickle', [True, False])
  210. def test_callback_wrong_disconnect(self, pickle):
  211. # ensure we start with an empty registry
  212. self.is_empty()
  213. # create a class for testing
  214. mini_me = Test_callback_registry()
  215. # test that we can add a callback
  216. cid1 = self.connect(self.signal, mini_me.dummy, pickle)
  217. assert type(cid1) is int
  218. self.is_not_empty()
  219. self.disconnect("foo")
  220. # check we still have callbacks registered
  221. self.is_not_empty()
  222. @pytest.mark.parametrize('pickle', [True, False])
  223. def test_registration_on_non_empty_registry(self, pickle):
  224. # ensure we start with an empty registry
  225. self.is_empty()
  226. # setup the registry with a callback
  227. mini_me = Test_callback_registry()
  228. self.connect(self.signal, mini_me.dummy, pickle)
  229. # Add another callback
  230. mini_me2 = Test_callback_registry()
  231. self.connect(self.signal, mini_me2.dummy, pickle)
  232. # Remove and add the second callback
  233. mini_me2 = Test_callback_registry()
  234. self.connect(self.signal, mini_me2.dummy, pickle)
  235. # We still have 2 references
  236. self.is_not_empty()
  237. assert self.count() == 2
  238. # Removing the last 2 references
  239. mini_me = None
  240. mini_me2 = None
  241. self.is_empty()
  242. def dummy(self):
  243. pass
  244. def test_pickling(self):
  245. assert hasattr(pickle.loads(pickle.dumps(cbook.CallbackRegistry())),
  246. "callbacks")
  247. def test_callbackregistry_default_exception_handler(capsys, monkeypatch):
  248. cb = cbook.CallbackRegistry()
  249. cb.connect("foo", lambda: None)
  250. monkeypatch.setattr(
  251. cbook, "_get_running_interactive_framework", lambda: None)
  252. with pytest.raises(TypeError):
  253. cb.process("foo", "argument mismatch")
  254. outerr = capsys.readouterr()
  255. assert outerr.out == outerr.err == ""
  256. monkeypatch.setattr(
  257. cbook, "_get_running_interactive_framework", lambda: "not-none")
  258. cb.process("foo", "argument mismatch") # No error in that case.
  259. outerr = capsys.readouterr()
  260. assert outerr.out == ""
  261. assert "takes 0 positional arguments but 1 was given" in outerr.err
  262. def raising_cb_reg(func):
  263. class TestException(Exception):
  264. pass
  265. def raise_runtime_error():
  266. raise RuntimeError
  267. def raise_value_error():
  268. raise ValueError
  269. def transformer(excp):
  270. if isinstance(excp, RuntimeError):
  271. raise TestException
  272. raise excp
  273. # old default
  274. cb_old = cbook.CallbackRegistry(exception_handler=None)
  275. cb_old.connect('foo', raise_runtime_error)
  276. # filter
  277. cb_filt = cbook.CallbackRegistry(exception_handler=transformer)
  278. cb_filt.connect('foo', raise_runtime_error)
  279. # filter
  280. cb_filt_pass = cbook.CallbackRegistry(exception_handler=transformer)
  281. cb_filt_pass.connect('foo', raise_value_error)
  282. return pytest.mark.parametrize('cb, excp',
  283. [[cb_old, RuntimeError],
  284. [cb_filt, TestException],
  285. [cb_filt_pass, ValueError]])(func)
  286. @raising_cb_reg
  287. def test_callbackregistry_custom_exception_handler(monkeypatch, cb, excp):
  288. monkeypatch.setattr(
  289. cbook, "_get_running_interactive_framework", lambda: None)
  290. with pytest.raises(excp):
  291. cb.process('foo')
  292. def test_callbackregistry_signals():
  293. cr = cbook.CallbackRegistry(signals=["foo"])
  294. results = []
  295. def cb(x): results.append(x)
  296. cr.connect("foo", cb)
  297. with pytest.raises(ValueError):
  298. cr.connect("bar", cb)
  299. cr.process("foo", 1)
  300. with pytest.raises(ValueError):
  301. cr.process("bar", 1)
  302. assert results == [1]
  303. def test_callbackregistry_blocking():
  304. # Needs an exception handler for interactive testing environments
  305. # that would only print this out instead of raising the exception
  306. def raise_handler(excp):
  307. raise excp
  308. cb = cbook.CallbackRegistry(exception_handler=raise_handler)
  309. def test_func1():
  310. raise ValueError("1 should be blocked")
  311. def test_func2():
  312. raise ValueError("2 should be blocked")
  313. cb.connect("test1", test_func1)
  314. cb.connect("test2", test_func2)
  315. # block all of the callbacks to make sure they aren't processed
  316. with cb.blocked():
  317. cb.process("test1")
  318. cb.process("test2")
  319. # block individual callbacks to make sure the other is still processed
  320. with cb.blocked(signal="test1"):
  321. # Blocked
  322. cb.process("test1")
  323. # Should raise
  324. with pytest.raises(ValueError, match="2 should be blocked"):
  325. cb.process("test2")
  326. # Make sure the original callback functions are there after blocking
  327. with pytest.raises(ValueError, match="1 should be blocked"):
  328. cb.process("test1")
  329. with pytest.raises(ValueError, match="2 should be blocked"):
  330. cb.process("test2")
  331. @pytest.mark.parametrize('line, result', [
  332. ('a : no_comment', 'a : no_comment'),
  333. ('a : "quoted str"', 'a : "quoted str"'),
  334. ('a : "quoted str" # comment', 'a : "quoted str"'),
  335. ('a : "#000000"', 'a : "#000000"'),
  336. ('a : "#000000" # comment', 'a : "#000000"'),
  337. ('a : ["#000000", "#FFFFFF"]', 'a : ["#000000", "#FFFFFF"]'),
  338. ('a : ["#000000", "#FFFFFF"] # comment', 'a : ["#000000", "#FFFFFF"]'),
  339. ('a : val # a comment "with quotes"', 'a : val'),
  340. ('# only comment "with quotes" xx', ''),
  341. ])
  342. def test_strip_comment(line, result):
  343. """Strip everything from the first unquoted #."""
  344. assert cbook._strip_comment(line) == result
  345. def test_strip_comment_invalid():
  346. with pytest.raises(ValueError, match="Missing closing quote"):
  347. cbook._strip_comment('grid.color: "aa')
  348. def test_sanitize_sequence():
  349. d = {'a': 1, 'b': 2, 'c': 3}
  350. k = ['a', 'b', 'c']
  351. v = [1, 2, 3]
  352. i = [('a', 1), ('b', 2), ('c', 3)]
  353. assert k == sorted(cbook.sanitize_sequence(d.keys()))
  354. assert v == sorted(cbook.sanitize_sequence(d.values()))
  355. assert i == sorted(cbook.sanitize_sequence(d.items()))
  356. assert i == cbook.sanitize_sequence(i)
  357. assert k == cbook.sanitize_sequence(k)
  358. fail_mapping: tuple[tuple[dict, dict], ...] = (
  359. ({'a': 1, 'b': 2}, {'alias_mapping': {'a': ['b']}}),
  360. ({'a': 1, 'b': 2}, {'alias_mapping': {'a': ['a', 'b']}}),
  361. )
  362. pass_mapping: tuple[tuple[Any, dict, dict], ...] = (
  363. (None, {}, {}),
  364. ({'a': 1, 'b': 2}, {'a': 1, 'b': 2}, {}),
  365. ({'b': 2}, {'a': 2}, {'alias_mapping': {'a': ['a', 'b']}}),
  366. )
  367. @pytest.mark.parametrize('inp, kwargs_to_norm', fail_mapping)
  368. def test_normalize_kwargs_fail(inp, kwargs_to_norm):
  369. with pytest.raises(TypeError), \
  370. _api.suppress_matplotlib_deprecation_warning():
  371. cbook.normalize_kwargs(inp, **kwargs_to_norm)
  372. @pytest.mark.parametrize('inp, expected, kwargs_to_norm',
  373. pass_mapping)
  374. def test_normalize_kwargs_pass(inp, expected, kwargs_to_norm):
  375. with _api.suppress_matplotlib_deprecation_warning():
  376. # No other warning should be emitted.
  377. assert expected == cbook.normalize_kwargs(inp, **kwargs_to_norm)
  378. def test_warn_external_frame_embedded_python():
  379. with patch.object(cbook, "sys") as mock_sys:
  380. mock_sys._getframe = Mock(return_value=None)
  381. with pytest.warns(UserWarning, match=r"\Adummy\Z"):
  382. _api.warn_external("dummy")
  383. def test_to_prestep():
  384. x = np.arange(4)
  385. y1 = np.arange(4)
  386. y2 = np.arange(4)[::-1]
  387. xs, y1s, y2s = cbook.pts_to_prestep(x, y1, y2)
  388. x_target = np.asarray([0, 0, 1, 1, 2, 2, 3], dtype=float)
  389. y1_target = np.asarray([0, 1, 1, 2, 2, 3, 3], dtype=float)
  390. y2_target = np.asarray([3, 2, 2, 1, 1, 0, 0], dtype=float)
  391. assert_array_equal(x_target, xs)
  392. assert_array_equal(y1_target, y1s)
  393. assert_array_equal(y2_target, y2s)
  394. xs, y1s = cbook.pts_to_prestep(x, y1)
  395. assert_array_equal(x_target, xs)
  396. assert_array_equal(y1_target, y1s)
  397. def test_to_prestep_empty():
  398. steps = cbook.pts_to_prestep([], [])
  399. assert steps.shape == (2, 0)
  400. def test_to_poststep():
  401. x = np.arange(4)
  402. y1 = np.arange(4)
  403. y2 = np.arange(4)[::-1]
  404. xs, y1s, y2s = cbook.pts_to_poststep(x, y1, y2)
  405. x_target = np.asarray([0, 1, 1, 2, 2, 3, 3], dtype=float)
  406. y1_target = np.asarray([0, 0, 1, 1, 2, 2, 3], dtype=float)
  407. y2_target = np.asarray([3, 3, 2, 2, 1, 1, 0], dtype=float)
  408. assert_array_equal(x_target, xs)
  409. assert_array_equal(y1_target, y1s)
  410. assert_array_equal(y2_target, y2s)
  411. xs, y1s = cbook.pts_to_poststep(x, y1)
  412. assert_array_equal(x_target, xs)
  413. assert_array_equal(y1_target, y1s)
  414. def test_to_poststep_empty():
  415. steps = cbook.pts_to_poststep([], [])
  416. assert steps.shape == (2, 0)
  417. def test_to_midstep():
  418. x = np.arange(4)
  419. y1 = np.arange(4)
  420. y2 = np.arange(4)[::-1]
  421. xs, y1s, y2s = cbook.pts_to_midstep(x, y1, y2)
  422. x_target = np.asarray([0, .5, .5, 1.5, 1.5, 2.5, 2.5, 3], dtype=float)
  423. y1_target = np.asarray([0, 0, 1, 1, 2, 2, 3, 3], dtype=float)
  424. y2_target = np.asarray([3, 3, 2, 2, 1, 1, 0, 0], dtype=float)
  425. assert_array_equal(x_target, xs)
  426. assert_array_equal(y1_target, y1s)
  427. assert_array_equal(y2_target, y2s)
  428. xs, y1s = cbook.pts_to_midstep(x, y1)
  429. assert_array_equal(x_target, xs)
  430. assert_array_equal(y1_target, y1s)
  431. def test_to_midstep_empty():
  432. steps = cbook.pts_to_midstep([], [])
  433. assert steps.shape == (2, 0)
  434. @pytest.mark.parametrize(
  435. "args",
  436. [(np.arange(12).reshape(3, 4), 'a'),
  437. (np.arange(12), 'a'),
  438. (np.arange(12), np.arange(3))])
  439. def test_step_fails(args):
  440. with pytest.raises(ValueError):
  441. cbook.pts_to_prestep(*args)
  442. def test_grouper():
  443. class Dummy:
  444. pass
  445. a, b, c, d, e = objs = [Dummy() for _ in range(5)]
  446. g = cbook.Grouper()
  447. g.join(*objs)
  448. assert set(list(g)[0]) == set(objs)
  449. assert set(g.get_siblings(a)) == set(objs)
  450. for other in objs[1:]:
  451. assert g.joined(a, other)
  452. g.remove(a)
  453. for other in objs[1:]:
  454. assert not g.joined(a, other)
  455. for A, B in itertools.product(objs[1:], objs[1:]):
  456. assert g.joined(A, B)
  457. def test_grouper_private():
  458. class Dummy:
  459. pass
  460. objs = [Dummy() for _ in range(5)]
  461. g = cbook.Grouper()
  462. g.join(*objs)
  463. # reach in and touch the internals !
  464. mapping = g._mapping
  465. for o in objs:
  466. assert o in mapping
  467. base_set = mapping[objs[0]]
  468. for o in objs[1:]:
  469. assert mapping[o] is base_set
  470. def test_flatiter():
  471. x = np.arange(5)
  472. it = x.flat
  473. assert 0 == next(it)
  474. assert 1 == next(it)
  475. ret = cbook._safe_first_finite(it)
  476. assert ret == 0
  477. assert 0 == next(it)
  478. assert 1 == next(it)
  479. def test__safe_first_finite_all_nan():
  480. arr = np.full(2, np.nan)
  481. ret = cbook._safe_first_finite(arr)
  482. assert np.isnan(ret)
  483. def test__safe_first_finite_all_inf():
  484. arr = np.full(2, np.inf)
  485. ret = cbook._safe_first_finite(arr)
  486. assert np.isinf(ret)
  487. def test_reshape2d():
  488. class Dummy:
  489. pass
  490. xnew = cbook._reshape_2D([], 'x')
  491. assert np.shape(xnew) == (1, 0)
  492. x = [Dummy() for _ in range(5)]
  493. xnew = cbook._reshape_2D(x, 'x')
  494. assert np.shape(xnew) == (1, 5)
  495. x = np.arange(5)
  496. xnew = cbook._reshape_2D(x, 'x')
  497. assert np.shape(xnew) == (1, 5)
  498. x = [[Dummy() for _ in range(5)] for _ in range(3)]
  499. xnew = cbook._reshape_2D(x, 'x')
  500. assert np.shape(xnew) == (3, 5)
  501. # this is strange behaviour, but...
  502. x = np.random.rand(3, 5)
  503. xnew = cbook._reshape_2D(x, 'x')
  504. assert np.shape(xnew) == (5, 3)
  505. # Test a list of lists which are all of length 1
  506. x = [[1], [2], [3]]
  507. xnew = cbook._reshape_2D(x, 'x')
  508. assert isinstance(xnew, list)
  509. assert isinstance(xnew[0], np.ndarray) and xnew[0].shape == (1,)
  510. assert isinstance(xnew[1], np.ndarray) and xnew[1].shape == (1,)
  511. assert isinstance(xnew[2], np.ndarray) and xnew[2].shape == (1,)
  512. # Test a list of zero-dimensional arrays
  513. x = [np.array(0), np.array(1), np.array(2)]
  514. xnew = cbook._reshape_2D(x, 'x')
  515. assert isinstance(xnew, list)
  516. assert len(xnew) == 1
  517. assert isinstance(xnew[0], np.ndarray) and xnew[0].shape == (3,)
  518. # Now test with a list of lists with different lengths, which means the
  519. # array will internally be converted to a 1D object array of lists
  520. x = [[1, 2, 3], [3, 4], [2]]
  521. xnew = cbook._reshape_2D(x, 'x')
  522. assert isinstance(xnew, list)
  523. assert isinstance(xnew[0], np.ndarray) and xnew[0].shape == (3,)
  524. assert isinstance(xnew[1], np.ndarray) and xnew[1].shape == (2,)
  525. assert isinstance(xnew[2], np.ndarray) and xnew[2].shape == (1,)
  526. # We now need to make sure that this works correctly for Numpy subclasses
  527. # where iterating over items can return subclasses too, which may be
  528. # iterable even if they are scalars. To emulate this, we make a Numpy
  529. # array subclass that returns Numpy 'scalars' when iterating or accessing
  530. # values, and these are technically iterable if checking for example
  531. # isinstance(x, collections.abc.Iterable).
  532. class ArraySubclass(np.ndarray):
  533. def __iter__(self):
  534. for value in super().__iter__():
  535. yield np.array(value)
  536. def __getitem__(self, item):
  537. return np.array(super().__getitem__(item))
  538. v = np.arange(10, dtype=float)
  539. x = ArraySubclass((10,), dtype=float, buffer=v.data)
  540. xnew = cbook._reshape_2D(x, 'x')
  541. # We check here that the array wasn't split up into many individual
  542. # ArraySubclass, which is what used to happen due to a bug in _reshape_2D
  543. assert len(xnew) == 1
  544. assert isinstance(xnew[0], ArraySubclass)
  545. # check list of strings:
  546. x = ['a', 'b', 'c', 'c', 'dd', 'e', 'f', 'ff', 'f']
  547. xnew = cbook._reshape_2D(x, 'x')
  548. assert len(xnew[0]) == len(x)
  549. assert isinstance(xnew[0], np.ndarray)
  550. def test_reshape2d_pandas(pd):
  551. # separate to allow the rest of the tests to run if no pandas...
  552. X = np.arange(30).reshape(10, 3)
  553. x = pd.DataFrame(X, columns=["a", "b", "c"])
  554. Xnew = cbook._reshape_2D(x, 'x')
  555. # Need to check each row because _reshape_2D returns a list of arrays:
  556. for x, xnew in zip(X.T, Xnew):
  557. np.testing.assert_array_equal(x, xnew)
  558. def test_reshape2d_xarray(xr):
  559. # separate to allow the rest of the tests to run if no xarray...
  560. X = np.arange(30).reshape(10, 3)
  561. x = xr.DataArray(X, dims=["x", "y"])
  562. Xnew = cbook._reshape_2D(x, 'x')
  563. # Need to check each row because _reshape_2D returns a list of arrays:
  564. for x, xnew in zip(X.T, Xnew):
  565. np.testing.assert_array_equal(x, xnew)
  566. def test_index_of_pandas(pd):
  567. # separate to allow the rest of the tests to run if no pandas...
  568. X = np.arange(30).reshape(10, 3)
  569. x = pd.DataFrame(X, columns=["a", "b", "c"])
  570. Idx, Xnew = cbook.index_of(x)
  571. np.testing.assert_array_equal(X, Xnew)
  572. IdxRef = np.arange(10)
  573. np.testing.assert_array_equal(Idx, IdxRef)
  574. def test_index_of_xarray(xr):
  575. # separate to allow the rest of the tests to run if no xarray...
  576. X = np.arange(30).reshape(10, 3)
  577. x = xr.DataArray(X, dims=["x", "y"])
  578. Idx, Xnew = cbook.index_of(x)
  579. np.testing.assert_array_equal(X, Xnew)
  580. IdxRef = np.arange(10)
  581. np.testing.assert_array_equal(Idx, IdxRef)
  582. def test_contiguous_regions():
  583. a, b, c = 3, 4, 5
  584. # Starts and ends with True
  585. mask = [True]*a + [False]*b + [True]*c
  586. expected = [(0, a), (a+b, a+b+c)]
  587. assert cbook.contiguous_regions(mask) == expected
  588. d, e = 6, 7
  589. # Starts with True ends with False
  590. mask = mask + [False]*e
  591. assert cbook.contiguous_regions(mask) == expected
  592. # Starts with False ends with True
  593. mask = [False]*d + mask[:-e]
  594. expected = [(d, d+a), (d+a+b, d+a+b+c)]
  595. assert cbook.contiguous_regions(mask) == expected
  596. # Starts and ends with False
  597. mask = mask + [False]*e
  598. assert cbook.contiguous_regions(mask) == expected
  599. # No True in mask
  600. assert cbook.contiguous_regions([False]*5) == []
  601. # Empty mask
  602. assert cbook.contiguous_regions([]) == []
  603. def test_safe_first_element_pandas_series(pd):
  604. # deliberately create a pandas series with index not starting from 0
  605. s = pd.Series(range(5), index=range(10, 15))
  606. actual = cbook._safe_first_finite(s)
  607. assert actual == 0
  608. def test_warn_external(recwarn):
  609. _api.warn_external("oops")
  610. assert len(recwarn) == 1
  611. assert recwarn[0].filename == __file__
  612. def test_array_patch_perimeters():
  613. # This compares the old implementation as a reference for the
  614. # vectorized one.
  615. def check(x, rstride, cstride):
  616. rows, cols = x.shape
  617. row_inds = [*range(0, rows-1, rstride), rows-1]
  618. col_inds = [*range(0, cols-1, cstride), cols-1]
  619. polys = []
  620. for rs, rs_next in zip(row_inds[:-1], row_inds[1:]):
  621. for cs, cs_next in zip(col_inds[:-1], col_inds[1:]):
  622. # +1 ensures we share edges between polygons
  623. ps = cbook._array_perimeter(x[rs:rs_next+1, cs:cs_next+1]).T
  624. polys.append(ps)
  625. polys = np.asarray(polys)
  626. assert np.array_equal(polys,
  627. cbook._array_patch_perimeters(
  628. x, rstride=rstride, cstride=cstride))
  629. def divisors(n):
  630. return [i for i in range(1, n + 1) if n % i == 0]
  631. for rows, cols in [(5, 5), (7, 14), (13, 9)]:
  632. x = np.arange(rows * cols).reshape(rows, cols)
  633. for rstride, cstride in itertools.product(divisors(rows - 1),
  634. divisors(cols - 1)):
  635. check(x, rstride=rstride, cstride=cstride)
  636. def test_setattr_cm():
  637. class A:
  638. cls_level = object()
  639. override = object()
  640. def __init__(self):
  641. self.aardvark = 'aardvark'
  642. self.override = 'override'
  643. self._p = 'p'
  644. def meth(self):
  645. ...
  646. @classmethod
  647. def classy(cls):
  648. ...
  649. @staticmethod
  650. def static():
  651. ...
  652. @property
  653. def prop(self):
  654. return self._p
  655. @prop.setter
  656. def prop(self, val):
  657. self._p = val
  658. class B(A):
  659. ...
  660. other = A()
  661. def verify_pre_post_state(obj):
  662. # When you access a Python method the function is bound
  663. # to the object at access time so you get a new instance
  664. # of MethodType every time.
  665. #
  666. # https://docs.python.org/3/howto/descriptor.html#functions-and-methods
  667. assert obj.meth is not obj.meth
  668. # normal attribute should give you back the same instance every time
  669. assert obj.aardvark is obj.aardvark
  670. assert a.aardvark == 'aardvark'
  671. # and our property happens to give the same instance every time
  672. assert obj.prop is obj.prop
  673. assert obj.cls_level is A.cls_level
  674. assert obj.override == 'override'
  675. assert not hasattr(obj, 'extra')
  676. assert obj.prop == 'p'
  677. assert obj.monkey == other.meth
  678. assert obj.cls_level is A.cls_level
  679. assert 'cls_level' not in obj.__dict__
  680. assert 'classy' not in obj.__dict__
  681. assert 'static' not in obj.__dict__
  682. a = B()
  683. a.monkey = other.meth
  684. verify_pre_post_state(a)
  685. with cbook._setattr_cm(
  686. a, prop='squirrel',
  687. aardvark='moose', meth=lambda: None,
  688. override='boo', extra='extra',
  689. monkey=lambda: None, cls_level='bob',
  690. classy='classy', static='static'):
  691. # because we have set a lambda, it is normal attribute access
  692. # and the same every time
  693. assert a.meth is a.meth
  694. assert a.aardvark is a.aardvark
  695. assert a.aardvark == 'moose'
  696. assert a.override == 'boo'
  697. assert a.extra == 'extra'
  698. assert a.prop == 'squirrel'
  699. assert a.monkey != other.meth
  700. assert a.cls_level == 'bob'
  701. assert a.classy == 'classy'
  702. assert a.static == 'static'
  703. verify_pre_post_state(a)
  704. def test_format_approx():
  705. f = cbook._format_approx
  706. assert f(0, 1) == '0'
  707. assert f(0, 2) == '0'
  708. assert f(0, 3) == '0'
  709. assert f(-0.0123, 1) == '-0'
  710. assert f(1e-7, 5) == '0'
  711. assert f(0.0012345600001, 5) == '0.00123'
  712. assert f(-0.0012345600001, 5) == '-0.00123'
  713. assert f(0.0012345600001, 8) == f(0.0012345600001, 10) == '0.00123456'
  714. def test_safe_first_element_with_none():
  715. datetime_lst = [date.today() + timedelta(days=i) for i in range(10)]
  716. datetime_lst[0] = None
  717. actual = cbook._safe_first_finite(datetime_lst)
  718. assert actual is not None and actual == datetime_lst[1]
  719. def test_strip_math():
  720. assert strip_math(r'1 \times 2') == r'1 \times 2'
  721. assert strip_math(r'$1 \times 2$') == '1 x 2'
  722. assert strip_math(r'$\rm{hi}$') == 'hi'
  723. @pytest.mark.parametrize('fmt, value, result', [
  724. ('%.2f m', 0.2, '0.20 m'),
  725. ('{:.2f} m', 0.2, '0.20 m'),
  726. ('{} m', 0.2, '0.2 m'),
  727. ('const', 0.2, 'const'),
  728. ('%d or {}', 0.2, '0 or {}'),
  729. ('{{{:,.0f}}}', 2e5, '{200,000}'),
  730. ('{:.2%}', 2/3, '66.67%'),
  731. ('$%g', 2.54, '$2.54'),
  732. ])
  733. def test_auto_format_str(fmt, value, result):
  734. """Apply *value* to the format string *fmt*."""
  735. assert cbook._auto_format_str(fmt, value) == result
  736. assert cbook._auto_format_str(fmt, np.float64(value)) == result