test_converter.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. from datetime import date, datetime
  2. import subprocess
  3. import sys
  4. import numpy as np
  5. import pytest
  6. from pandas.compat import u
  7. from pandas.compat.numpy import np_datetime64_compat
  8. from pandas import Index, Period, Series, Timestamp, date_range
  9. import pandas.core.config as cf
  10. import pandas.util.testing as tm
  11. from pandas.tseries.offsets import Day, Micro, Milli, Second
  12. converter = pytest.importorskip('pandas.plotting._converter')
  13. from pandas.plotting import (deregister_matplotlib_converters, # isort:skip
  14. register_matplotlib_converters)
  15. def test_timtetonum_accepts_unicode():
  16. assert (converter.time2num("00:01") == converter.time2num(u("00:01")))
  17. class TestRegistration(object):
  18. def test_register_by_default(self):
  19. # Run in subprocess to ensure a clean state
  20. code = ("'import matplotlib.units; "
  21. "import pandas as pd; "
  22. "units = dict(matplotlib.units.registry); "
  23. "assert pd.Timestamp in units)'")
  24. call = [sys.executable, '-c', code]
  25. assert subprocess.check_call(call) == 0
  26. def test_warns(self):
  27. plt = pytest.importorskip("matplotlib.pyplot")
  28. s = Series(range(12), index=date_range('2017', periods=12))
  29. _, ax = plt.subplots()
  30. # Set to the "warning" state, in case this isn't the first test run
  31. converter._WARN = True
  32. with tm.assert_produces_warning(FutureWarning,
  33. check_stacklevel=False) as w:
  34. ax.plot(s.index, s.values)
  35. plt.close()
  36. assert len(w) == 1
  37. assert "Using an implicitly registered datetime converter" in str(w[0])
  38. def test_registering_no_warning(self):
  39. plt = pytest.importorskip("matplotlib.pyplot")
  40. s = Series(range(12), index=date_range('2017', periods=12))
  41. _, ax = plt.subplots()
  42. # Set to the "warn" state, in case this isn't the first test run
  43. converter._WARN = True
  44. register_matplotlib_converters()
  45. with tm.assert_produces_warning(None) as w:
  46. ax.plot(s.index, s.values)
  47. assert len(w) == 0
  48. def test_pandas_plots_register(self):
  49. pytest.importorskip("matplotlib.pyplot")
  50. s = Series(range(12), index=date_range('2017', periods=12))
  51. # Set to the "warn" state, in case this isn't the first test run
  52. converter._WARN = True
  53. with tm.assert_produces_warning(None) as w:
  54. s.plot()
  55. assert len(w) == 0
  56. def test_matplotlib_formatters(self):
  57. units = pytest.importorskip("matplotlib.units")
  58. assert Timestamp in units.registry
  59. ctx = cf.option_context("plotting.matplotlib.register_converters",
  60. False)
  61. with ctx:
  62. assert Timestamp not in units.registry
  63. assert Timestamp in units.registry
  64. def test_option_no_warning(self):
  65. pytest.importorskip("matplotlib.pyplot")
  66. ctx = cf.option_context("plotting.matplotlib.register_converters",
  67. False)
  68. plt = pytest.importorskip("matplotlib.pyplot")
  69. s = Series(range(12), index=date_range('2017', periods=12))
  70. _, ax = plt.subplots()
  71. converter._WARN = True
  72. # Test without registering first, no warning
  73. with ctx:
  74. with tm.assert_produces_warning(None) as w:
  75. ax.plot(s.index, s.values)
  76. assert len(w) == 0
  77. # Now test with registering
  78. converter._WARN = True
  79. register_matplotlib_converters()
  80. with ctx:
  81. with tm.assert_produces_warning(None) as w:
  82. ax.plot(s.index, s.values)
  83. assert len(w) == 0
  84. def test_registry_resets(self):
  85. units = pytest.importorskip("matplotlib.units")
  86. dates = pytest.importorskip("matplotlib.dates")
  87. # make a copy, to reset to
  88. original = dict(units.registry)
  89. try:
  90. # get to a known state
  91. units.registry.clear()
  92. date_converter = dates.DateConverter()
  93. units.registry[datetime] = date_converter
  94. units.registry[date] = date_converter
  95. register_matplotlib_converters()
  96. assert units.registry[date] is not date_converter
  97. deregister_matplotlib_converters()
  98. assert units.registry[date] is date_converter
  99. finally:
  100. # restore original stater
  101. units.registry.clear()
  102. for k, v in original.items():
  103. units.registry[k] = v
  104. def test_old_import_warns(self):
  105. with tm.assert_produces_warning(FutureWarning) as w:
  106. from pandas.tseries import converter
  107. converter.register()
  108. assert len(w)
  109. assert ('pandas.plotting.register_matplotlib_converters' in
  110. str(w[0].message))
  111. class TestDateTimeConverter(object):
  112. def setup_method(self, method):
  113. self.dtc = converter.DatetimeConverter()
  114. self.tc = converter.TimeFormatter(None)
  115. def test_convert_accepts_unicode(self):
  116. r1 = self.dtc.convert("12:22", None, None)
  117. r2 = self.dtc.convert(u("12:22"), None, None)
  118. assert (r1 == r2), "DatetimeConverter.convert should accept unicode"
  119. def test_conversion(self):
  120. rs = self.dtc.convert(['2012-1-1'], None, None)[0]
  121. xp = datetime(2012, 1, 1).toordinal()
  122. assert rs == xp
  123. rs = self.dtc.convert('2012-1-1', None, None)
  124. assert rs == xp
  125. rs = self.dtc.convert(date(2012, 1, 1), None, None)
  126. assert rs == xp
  127. rs = self.dtc.convert(datetime(2012, 1, 1).toordinal(), None, None)
  128. assert rs == xp
  129. rs = self.dtc.convert('2012-1-1', None, None)
  130. assert rs == xp
  131. rs = self.dtc.convert(Timestamp('2012-1-1'), None, None)
  132. assert rs == xp
  133. # also testing datetime64 dtype (GH8614)
  134. rs = self.dtc.convert(np_datetime64_compat('2012-01-01'), None, None)
  135. assert rs == xp
  136. rs = self.dtc.convert(np_datetime64_compat(
  137. '2012-01-01 00:00:00+0000'), None, None)
  138. assert rs == xp
  139. rs = self.dtc.convert(np.array([
  140. np_datetime64_compat('2012-01-01 00:00:00+0000'),
  141. np_datetime64_compat('2012-01-02 00:00:00+0000')]), None, None)
  142. assert rs[0] == xp
  143. # we have a tz-aware date (constructed to that when we turn to utc it
  144. # is the same as our sample)
  145. ts = (Timestamp('2012-01-01')
  146. .tz_localize('UTC')
  147. .tz_convert('US/Eastern')
  148. )
  149. rs = self.dtc.convert(ts, None, None)
  150. assert rs == xp
  151. rs = self.dtc.convert(ts.to_pydatetime(), None, None)
  152. assert rs == xp
  153. rs = self.dtc.convert(Index([ts - Day(1), ts]), None, None)
  154. assert rs[1] == xp
  155. rs = self.dtc.convert(Index([ts - Day(1), ts]).to_pydatetime(),
  156. None, None)
  157. assert rs[1] == xp
  158. def test_conversion_float(self):
  159. decimals = 9
  160. rs = self.dtc.convert(
  161. Timestamp('2012-1-1 01:02:03', tz='UTC'), None, None)
  162. xp = converter.dates.date2num(Timestamp('2012-1-1 01:02:03', tz='UTC'))
  163. tm.assert_almost_equal(rs, xp, decimals)
  164. rs = self.dtc.convert(
  165. Timestamp('2012-1-1 09:02:03', tz='Asia/Hong_Kong'), None, None)
  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('time,format_expected', [
  186. (0, '00:00'), # time2num(datetime.time.min)
  187. (86399.999999, '23:59:59.999999'), # time2num(datetime.time.max)
  188. (90000, '01:00'),
  189. (3723, '01:02:03'),
  190. (39723.2, '11:02:03.200')
  191. ])
  192. def test_time_formatter(self, time, format_expected):
  193. # issue 18478
  194. result = self.tc(time)
  195. assert result == format_expected
  196. def test_dateindex_conversion(self):
  197. decimals = 9
  198. for freq in ('B', 'L', 'S'):
  199. dateindex = tm.makeDateIndex(k=10, freq=freq)
  200. rs = self.dtc.convert(dateindex, None, None)
  201. xp = converter.dates.date2num(dateindex._mpl_repr())
  202. tm.assert_almost_equal(rs, xp, decimals)
  203. def test_resolution(self):
  204. def _assert_less(ts1, ts2):
  205. val1 = self.dtc.convert(ts1, None, None)
  206. val2 = self.dtc.convert(ts2, None, None)
  207. if not val1 < val2:
  208. raise AssertionError('{0} is not less than {1}.'.format(val1,
  209. val2))
  210. # Matplotlib's time representation using floats cannot distinguish
  211. # intervals smaller than ~10 microsecond in the common range of years.
  212. ts = Timestamp('2012-1-1')
  213. _assert_less(ts, ts + Second())
  214. _assert_less(ts, ts + Milli())
  215. _assert_less(ts, ts + Micro(50))
  216. def test_convert_nested(self):
  217. inner = [Timestamp('2017-01-01'), Timestamp('2017-01-02')]
  218. data = [inner, inner]
  219. result = self.dtc.convert(data, None, None)
  220. expected = [self.dtc.convert(x, None, None) for x in data]
  221. assert (np.array(result) == expected).all()
  222. class TestPeriodConverter(object):
  223. def setup_method(self, method):
  224. self.pc = converter.PeriodConverter()
  225. class Axis(object):
  226. pass
  227. self.axis = Axis()
  228. self.axis.freq = 'D'
  229. def test_convert_accepts_unicode(self):
  230. r1 = self.pc.convert("2012-1-1", None, self.axis)
  231. r2 = self.pc.convert(u("2012-1-1"), None, self.axis)
  232. assert r1 == r2
  233. def test_conversion(self):
  234. rs = self.pc.convert(['2012-1-1'], None, self.axis)[0]
  235. xp = Period('2012-1-1').ordinal
  236. assert rs == xp
  237. rs = self.pc.convert('2012-1-1', None, self.axis)
  238. assert rs == xp
  239. rs = self.pc.convert([date(2012, 1, 1)], None, self.axis)[0]
  240. assert rs == xp
  241. rs = self.pc.convert(date(2012, 1, 1), None, self.axis)
  242. assert rs == xp
  243. rs = self.pc.convert([Timestamp('2012-1-1')], None, self.axis)[0]
  244. assert rs == xp
  245. rs = self.pc.convert(Timestamp('2012-1-1'), None, self.axis)
  246. assert rs == xp
  247. rs = self.pc.convert(
  248. np_datetime64_compat('2012-01-01'), None, self.axis)
  249. assert rs == xp
  250. rs = self.pc.convert(
  251. np_datetime64_compat('2012-01-01 00:00:00+0000'), None, self.axis)
  252. assert rs == xp
  253. rs = self.pc.convert(np.array([
  254. np_datetime64_compat('2012-01-01 00:00:00+0000'),
  255. np_datetime64_compat('2012-01-02 00:00:00+0000')]),
  256. None, self.axis)
  257. assert rs[0] == xp
  258. def test_integer_passthrough(self):
  259. # GH9012
  260. rs = self.pc.convert([0, 1], None, self.axis)
  261. xp = [0, 1]
  262. assert rs == xp
  263. def test_convert_nested(self):
  264. data = ['2012-1-1', '2012-1-2']
  265. r1 = self.pc.convert([data, data], None, self.axis)
  266. r2 = [self.pc.convert(data, None, self.axis) for _ in range(2)]
  267. assert r1 == r2