test_converter.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. from datetime import date, datetime
  2. import subprocess
  3. import sys
  4. import numpy as np
  5. import pytest
  6. import pandas._config.config as cf
  7. from pandas.compat.numpy import np_datetime64_compat
  8. import pandas.util._test_decorators as td
  9. from pandas import Index, Period, Series, Timestamp, date_range
  10. import pandas._testing as tm
  11. from pandas.plotting import (
  12. deregister_matplotlib_converters,
  13. register_matplotlib_converters,
  14. )
  15. from pandas.tseries.offsets import Day, Micro, Milli, Second
  16. try:
  17. from pandas.plotting._matplotlib import converter
  18. except ImportError:
  19. # try / except, rather than skip, to avoid internal refactoring
  20. # causing an improper skip
  21. pass
  22. pytest.importorskip("matplotlib.pyplot")
  23. def test_registry_mpl_resets():
  24. # Check that Matplotlib converters are properly reset (see issue #27481)
  25. code = (
  26. "import matplotlib.units as units; "
  27. "import matplotlib.dates as mdates; "
  28. "n_conv = len(units.registry); "
  29. "import pandas as pd; "
  30. "pd.plotting.register_matplotlib_converters(); "
  31. "pd.plotting.deregister_matplotlib_converters(); "
  32. "assert len(units.registry) == n_conv"
  33. )
  34. call = [sys.executable, "-c", code]
  35. subprocess.check_output(call)
  36. def test_timtetonum_accepts_unicode():
  37. assert converter.time2num("00:01") == converter.time2num("00:01")
  38. class TestRegistration:
  39. def test_register_by_default(self):
  40. # Run in subprocess to ensure a clean state
  41. code = (
  42. "'import matplotlib.units; "
  43. "import pandas as pd; "
  44. "units = dict(matplotlib.units.registry); "
  45. "assert pd.Timestamp in units)'"
  46. )
  47. call = [sys.executable, "-c", code]
  48. assert subprocess.check_call(call) == 0
  49. @td.skip_if_no("matplotlib", min_version="3.1.3")
  50. def test_registering_no_warning(self):
  51. plt = pytest.importorskip("matplotlib.pyplot")
  52. s = Series(range(12), index=date_range("2017", periods=12))
  53. _, ax = plt.subplots()
  54. # Set to the "warn" state, in case this isn't the first test run
  55. register_matplotlib_converters()
  56. ax.plot(s.index, s.values)
  57. def test_pandas_plots_register(self):
  58. pytest.importorskip("matplotlib.pyplot")
  59. s = Series(range(12), index=date_range("2017", periods=12))
  60. # Set to the "warn" state, in case this isn't the first test run
  61. with tm.assert_produces_warning(None) as w:
  62. s.plot()
  63. assert len(w) == 0
  64. def test_matplotlib_formatters(self):
  65. units = pytest.importorskip("matplotlib.units")
  66. # Can't make any assertion about the start state.
  67. # We we check that toggling converters off removes it, and toggling it
  68. # on restores it.
  69. with cf.option_context("plotting.matplotlib.register_converters", True):
  70. with cf.option_context("plotting.matplotlib.register_converters", False):
  71. assert Timestamp not in units.registry
  72. assert Timestamp in units.registry
  73. @td.skip_if_no("matplotlib", min_version="3.1.3")
  74. def test_option_no_warning(self):
  75. pytest.importorskip("matplotlib.pyplot")
  76. ctx = cf.option_context("plotting.matplotlib.register_converters", False)
  77. plt = pytest.importorskip("matplotlib.pyplot")
  78. s = Series(range(12), index=date_range("2017", periods=12))
  79. _, ax = plt.subplots()
  80. # Test without registering first, no warning
  81. with ctx:
  82. ax.plot(s.index, s.values)
  83. # Now test with registering
  84. register_matplotlib_converters()
  85. with ctx:
  86. ax.plot(s.index, s.values)
  87. def test_registry_resets(self):
  88. units = pytest.importorskip("matplotlib.units")
  89. dates = pytest.importorskip("matplotlib.dates")
  90. # make a copy, to reset to
  91. original = dict(units.registry)
  92. try:
  93. # get to a known state
  94. units.registry.clear()
  95. date_converter = dates.DateConverter()
  96. units.registry[datetime] = date_converter
  97. units.registry[date] = date_converter
  98. register_matplotlib_converters()
  99. assert units.registry[date] is not date_converter
  100. deregister_matplotlib_converters()
  101. assert units.registry[date] is date_converter
  102. finally:
  103. # restore original stater
  104. units.registry.clear()
  105. for k, v in original.items():
  106. units.registry[k] = v
  107. class TestDateTimeConverter:
  108. def setup_method(self, method):
  109. self.dtc = converter.DatetimeConverter()
  110. self.tc = converter.TimeFormatter(None)
  111. def test_convert_accepts_unicode(self):
  112. r1 = self.dtc.convert("12:22", None, None)
  113. r2 = self.dtc.convert("12:22", None, None)
  114. assert r1 == r2, "DatetimeConverter.convert should accept unicode"
  115. def test_conversion(self):
  116. rs = self.dtc.convert(["2012-1-1"], None, None)[0]
  117. xp = datetime(2012, 1, 1).toordinal()
  118. assert rs == xp
  119. rs = self.dtc.convert("2012-1-1", None, None)
  120. assert rs == xp
  121. rs = self.dtc.convert(date(2012, 1, 1), None, None)
  122. assert rs == xp
  123. rs = self.dtc.convert(datetime(2012, 1, 1).toordinal(), None, None)
  124. assert rs == xp
  125. rs = self.dtc.convert("2012-1-1", None, None)
  126. assert rs == xp
  127. rs = self.dtc.convert(Timestamp("2012-1-1"), None, None)
  128. assert rs == xp
  129. # also testing datetime64 dtype (GH8614)
  130. rs = self.dtc.convert(np_datetime64_compat("2012-01-01"), None, None)
  131. assert rs == xp
  132. rs = self.dtc.convert(
  133. np_datetime64_compat("2012-01-01 00:00:00+0000"), None, None
  134. )
  135. assert rs == xp
  136. rs = self.dtc.convert(
  137. np.array(
  138. [
  139. np_datetime64_compat("2012-01-01 00:00:00+0000"),
  140. np_datetime64_compat("2012-01-02 00:00:00+0000"),
  141. ]
  142. ),
  143. None,
  144. None,
  145. )
  146. assert rs[0] == xp
  147. # we have a tz-aware date (constructed to that when we turn to utc it
  148. # is the same as our sample)
  149. ts = Timestamp("2012-01-01").tz_localize("UTC").tz_convert("US/Eastern")
  150. rs = self.dtc.convert(ts, None, None)
  151. assert rs == xp
  152. rs = self.dtc.convert(ts.to_pydatetime(), None, None)
  153. assert rs == xp
  154. rs = self.dtc.convert(Index([ts - Day(1), ts]), None, None)
  155. assert rs[1] == xp
  156. rs = self.dtc.convert(Index([ts - Day(1), ts]).to_pydatetime(), None, None)
  157. assert rs[1] == xp
  158. def test_conversion_float(self):
  159. decimals = 9
  160. rs = self.dtc.convert(Timestamp("2012-1-1 01:02:03", tz="UTC"), None, None)
  161. xp = converter.dates.date2num(Timestamp("2012-1-1 01:02:03", tz="UTC"))
  162. tm.assert_almost_equal(rs, xp, decimals)
  163. rs = self.dtc.convert(
  164. Timestamp("2012-1-1 09:02:03", tz="Asia/Hong_Kong"), None, None
  165. )
  166. tm.assert_almost_equal(rs, xp, decimals)
  167. rs = self.dtc.convert(datetime(2012, 1, 1, 1, 2, 3), None, None)
  168. tm.assert_almost_equal(rs, xp, decimals)
  169. def test_conversion_outofbounds_datetime(self):
  170. # 2579
  171. values = [date(1677, 1, 1), date(1677, 1, 2)]
  172. rs = self.dtc.convert(values, None, None)
  173. xp = converter.dates.date2num(values)
  174. tm.assert_numpy_array_equal(rs, xp)
  175. rs = self.dtc.convert(values[0], None, None)
  176. xp = converter.dates.date2num(values[0])
  177. assert rs == xp
  178. values = [datetime(1677, 1, 1, 12), datetime(1677, 1, 2, 12)]
  179. rs = self.dtc.convert(values, None, None)
  180. xp = converter.dates.date2num(values)
  181. tm.assert_numpy_array_equal(rs, xp)
  182. rs = self.dtc.convert(values[0], None, None)
  183. xp = converter.dates.date2num(values[0])
  184. assert rs == xp
  185. @pytest.mark.parametrize(
  186. "time,format_expected",
  187. [
  188. (0, "00:00"), # time2num(datetime.time.min)
  189. (86399.999999, "23:59:59.999999"), # time2num(datetime.time.max)
  190. (90000, "01:00"),
  191. (3723, "01:02:03"),
  192. (39723.2, "11:02:03.200"),
  193. ],
  194. )
  195. def test_time_formatter(self, time, format_expected):
  196. # issue 18478
  197. result = self.tc(time)
  198. assert result == format_expected
  199. def test_dateindex_conversion(self):
  200. decimals = 9
  201. for freq in ("B", "L", "S"):
  202. dateindex = tm.makeDateIndex(k=10, freq=freq)
  203. rs = self.dtc.convert(dateindex, None, None)
  204. xp = converter.dates.date2num(dateindex._mpl_repr())
  205. tm.assert_almost_equal(rs, xp, decimals)
  206. def test_resolution(self):
  207. def _assert_less(ts1, ts2):
  208. val1 = self.dtc.convert(ts1, None, None)
  209. val2 = self.dtc.convert(ts2, None, None)
  210. if not val1 < val2:
  211. raise AssertionError(f"{val1} is not less than {val2}.")
  212. # Matplotlib's time representation using floats cannot distinguish
  213. # intervals smaller than ~10 microsecond in the common range of years.
  214. ts = Timestamp("2012-1-1")
  215. _assert_less(ts, ts + Second())
  216. _assert_less(ts, ts + Milli())
  217. _assert_less(ts, ts + Micro(50))
  218. def test_convert_nested(self):
  219. inner = [Timestamp("2017-01-01"), Timestamp("2017-01-02")]
  220. data = [inner, inner]
  221. result = self.dtc.convert(data, None, None)
  222. expected = [self.dtc.convert(x, None, None) for x in data]
  223. assert (np.array(result) == expected).all()
  224. class TestPeriodConverter:
  225. def setup_method(self, method):
  226. self.pc = converter.PeriodConverter()
  227. class Axis:
  228. pass
  229. self.axis = Axis()
  230. self.axis.freq = "D"
  231. def test_convert_accepts_unicode(self):
  232. r1 = self.pc.convert("2012-1-1", None, self.axis)
  233. r2 = self.pc.convert("2012-1-1", None, self.axis)
  234. assert r1 == r2
  235. def test_conversion(self):
  236. rs = self.pc.convert(["2012-1-1"], None, self.axis)[0]
  237. xp = Period("2012-1-1").ordinal
  238. assert rs == xp
  239. rs = self.pc.convert("2012-1-1", None, self.axis)
  240. assert rs == xp
  241. rs = self.pc.convert([date(2012, 1, 1)], None, self.axis)[0]
  242. assert rs == xp
  243. rs = self.pc.convert(date(2012, 1, 1), None, self.axis)
  244. assert rs == xp
  245. rs = self.pc.convert([Timestamp("2012-1-1")], None, self.axis)[0]
  246. assert rs == xp
  247. rs = self.pc.convert(Timestamp("2012-1-1"), None, self.axis)
  248. assert rs == xp
  249. rs = self.pc.convert(np_datetime64_compat("2012-01-01"), None, self.axis)
  250. assert rs == xp
  251. rs = self.pc.convert(
  252. np_datetime64_compat("2012-01-01 00:00:00+0000"), None, self.axis
  253. )
  254. assert rs == xp
  255. rs = self.pc.convert(
  256. np.array(
  257. [
  258. np_datetime64_compat("2012-01-01 00:00:00+0000"),
  259. np_datetime64_compat("2012-01-02 00:00:00+0000"),
  260. ]
  261. ),
  262. None,
  263. self.axis,
  264. )
  265. assert rs[0] == xp
  266. def test_integer_passthrough(self):
  267. # GH9012
  268. rs = self.pc.convert([0, 1], None, self.axis)
  269. xp = [0, 1]
  270. assert rs == xp
  271. def test_convert_nested(self):
  272. data = ["2012-1-1", "2012-1-2"]
  273. r1 = self.pc.convert([data, data], None, self.axis)
  274. r2 = [self.pc.convert(data, None, self.axis) for _ in range(2)]
  275. assert r1 == r2
  276. class TestTimeDeltaConverter:
  277. """Test timedelta converter"""
  278. @pytest.mark.parametrize(
  279. "x, decimal, format_expected",
  280. [
  281. (0.0, 0, "00:00:00"),
  282. (3972320000000, 1, "01:06:12.3"),
  283. (713233432000000, 2, "8 days 06:07:13.43"),
  284. (32423432000000, 4, "09:00:23.4320"),
  285. ],
  286. )
  287. def test_format_timedelta_ticks(self, x, decimal, format_expected):
  288. tdc = converter.TimeSeries_TimedeltaFormatter
  289. result = tdc.format_timedelta_ticks(x, pos=None, n_decimals=decimal)
  290. assert result == format_expected