test_constructors.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. from datetime import timedelta
  2. import numpy as np
  3. import pytest
  4. import pandas as pd
  5. from pandas import (
  6. Timedelta,
  7. TimedeltaIndex,
  8. timedelta_range,
  9. to_timedelta,
  10. )
  11. import pandas._testing as tm
  12. from pandas.core.arrays.timedeltas import (
  13. TimedeltaArray,
  14. sequence_to_td64ns,
  15. )
  16. class TestTimedeltaIndex:
  17. def test_array_of_dt64_nat_raises(self):
  18. # GH#39462
  19. nat = np.datetime64("NaT", "ns")
  20. arr = np.array([nat], dtype=object)
  21. msg = "Invalid type for timedelta scalar"
  22. with pytest.raises(TypeError, match=msg):
  23. TimedeltaIndex(arr)
  24. with pytest.raises(TypeError, match=msg):
  25. TimedeltaArray._from_sequence(arr)
  26. with pytest.raises(TypeError, match=msg):
  27. sequence_to_td64ns(arr)
  28. with pytest.raises(TypeError, match=msg):
  29. to_timedelta(arr)
  30. @pytest.mark.parametrize("unit", ["Y", "y", "M"])
  31. def test_unit_m_y_raises(self, unit):
  32. msg = "Units 'M', 'Y', and 'y' are no longer supported"
  33. with pytest.raises(ValueError, match=msg):
  34. TimedeltaIndex([1, 3, 7], unit)
  35. def test_int64_nocopy(self):
  36. # GH#23539 check that a copy isn't made when we pass int64 data
  37. # and copy=False
  38. arr = np.arange(10, dtype=np.int64)
  39. tdi = TimedeltaIndex(arr, copy=False)
  40. assert tdi._data._ndarray.base is arr
  41. def test_infer_from_tdi(self):
  42. # GH#23539
  43. # fast-path for inferring a frequency if the passed data already
  44. # has one
  45. tdi = timedelta_range("1 second", periods=10**7, freq="1s")
  46. result = TimedeltaIndex(tdi, freq="infer")
  47. assert result.freq == tdi.freq
  48. # check that inferred_freq was not called by checking that the
  49. # value has not been cached
  50. assert "inferred_freq" not in getattr(result, "_cache", {})
  51. def test_infer_from_tdi_mismatch(self):
  52. # GH#23539
  53. # fast-path for invalidating a frequency if the passed data already
  54. # has one and it does not match the `freq` input
  55. tdi = timedelta_range("1 second", periods=100, freq="1s")
  56. msg = (
  57. "Inferred frequency .* from passed values does "
  58. "not conform to passed frequency"
  59. )
  60. with pytest.raises(ValueError, match=msg):
  61. TimedeltaIndex(tdi, freq="D")
  62. with pytest.raises(ValueError, match=msg):
  63. # GH#23789
  64. TimedeltaArray(tdi, freq="D")
  65. with pytest.raises(ValueError, match=msg):
  66. TimedeltaIndex(tdi._data, freq="D")
  67. with pytest.raises(ValueError, match=msg):
  68. TimedeltaArray(tdi._data, freq="D")
  69. def test_dt64_data_invalid(self):
  70. # GH#23539
  71. # passing tz-aware DatetimeIndex raises, naive or ndarray[datetime64]
  72. # raise as of GH#29794
  73. dti = pd.date_range("2016-01-01", periods=3)
  74. msg = "cannot be converted to timedelta64"
  75. with pytest.raises(TypeError, match=msg):
  76. TimedeltaIndex(dti.tz_localize("Europe/Brussels"))
  77. with pytest.raises(TypeError, match=msg):
  78. TimedeltaIndex(dti)
  79. with pytest.raises(TypeError, match=msg):
  80. TimedeltaIndex(np.asarray(dti))
  81. def test_float64_ns_rounded(self):
  82. # GH#23539 without specifying a unit, floats are regarded as nanos,
  83. # and fractional portions are truncated
  84. tdi = TimedeltaIndex([2.3, 9.7])
  85. expected = TimedeltaIndex([2, 9])
  86. tm.assert_index_equal(tdi, expected)
  87. # integral floats are non-lossy
  88. tdi = TimedeltaIndex([2.0, 9.0])
  89. expected = TimedeltaIndex([2, 9])
  90. tm.assert_index_equal(tdi, expected)
  91. # NaNs get converted to NaT
  92. tdi = TimedeltaIndex([2.0, np.nan])
  93. expected = TimedeltaIndex([Timedelta(nanoseconds=2), pd.NaT])
  94. tm.assert_index_equal(tdi, expected)
  95. def test_float64_unit_conversion(self):
  96. # GH#23539
  97. tdi = TimedeltaIndex([1.5, 2.25], unit="D")
  98. expected = TimedeltaIndex([Timedelta(days=1.5), Timedelta(days=2.25)])
  99. tm.assert_index_equal(tdi, expected)
  100. def test_construction_base_constructor(self):
  101. arr = [Timedelta("1 days"), pd.NaT, Timedelta("3 days")]
  102. tm.assert_index_equal(pd.Index(arr), TimedeltaIndex(arr))
  103. tm.assert_index_equal(pd.Index(np.array(arr)), TimedeltaIndex(np.array(arr)))
  104. arr = [np.nan, pd.NaT, Timedelta("1 days")]
  105. tm.assert_index_equal(pd.Index(arr), TimedeltaIndex(arr))
  106. tm.assert_index_equal(pd.Index(np.array(arr)), TimedeltaIndex(np.array(arr)))
  107. def test_constructor(self):
  108. expected = TimedeltaIndex(
  109. [
  110. "1 days",
  111. "1 days 00:00:05",
  112. "2 days",
  113. "2 days 00:00:02",
  114. "0 days 00:00:03",
  115. ]
  116. )
  117. result = TimedeltaIndex(
  118. [
  119. "1 days",
  120. "1 days, 00:00:05",
  121. np.timedelta64(2, "D"),
  122. timedelta(days=2, seconds=2),
  123. pd.offsets.Second(3),
  124. ]
  125. )
  126. tm.assert_index_equal(result, expected)
  127. expected = TimedeltaIndex(
  128. ["0 days 00:00:00", "0 days 00:00:01", "0 days 00:00:02"]
  129. )
  130. tm.assert_index_equal(TimedeltaIndex(range(3), unit="s"), expected)
  131. expected = TimedeltaIndex(
  132. ["0 days 00:00:00", "0 days 00:00:05", "0 days 00:00:09"]
  133. )
  134. tm.assert_index_equal(TimedeltaIndex([0, 5, 9], unit="s"), expected)
  135. expected = TimedeltaIndex(
  136. ["0 days 00:00:00.400", "0 days 00:00:00.450", "0 days 00:00:01.200"]
  137. )
  138. tm.assert_index_equal(TimedeltaIndex([400, 450, 1200], unit="ms"), expected)
  139. def test_constructor_iso(self):
  140. # GH #21877
  141. expected = timedelta_range("1s", periods=9, freq="s")
  142. durations = [f"P0DT0H0M{i}S" for i in range(1, 10)]
  143. result = to_timedelta(durations)
  144. tm.assert_index_equal(result, expected)
  145. def test_constructor_coverage(self):
  146. rng = timedelta_range("1 days", periods=10.5)
  147. exp = timedelta_range("1 days", periods=10)
  148. tm.assert_index_equal(rng, exp)
  149. msg = "periods must be a number, got foo"
  150. with pytest.raises(TypeError, match=msg):
  151. timedelta_range(start="1 days", periods="foo", freq="D")
  152. msg = (
  153. r"TimedeltaIndex\(\.\.\.\) must be called with a collection of some kind, "
  154. "'1 days' was passed"
  155. )
  156. with pytest.raises(TypeError, match=msg):
  157. TimedeltaIndex("1 days")
  158. # generator expression
  159. gen = (timedelta(i) for i in range(10))
  160. result = TimedeltaIndex(gen)
  161. expected = TimedeltaIndex([timedelta(i) for i in range(10)])
  162. tm.assert_index_equal(result, expected)
  163. # NumPy string array
  164. strings = np.array(["1 days", "2 days", "3 days"])
  165. result = TimedeltaIndex(strings)
  166. expected = to_timedelta([1, 2, 3], unit="d")
  167. tm.assert_index_equal(result, expected)
  168. from_ints = TimedeltaIndex(expected.asi8)
  169. tm.assert_index_equal(from_ints, expected)
  170. # non-conforming freq
  171. msg = (
  172. "Inferred frequency None from passed values does not conform to "
  173. "passed frequency D"
  174. )
  175. with pytest.raises(ValueError, match=msg):
  176. TimedeltaIndex(["1 days", "2 days", "4 days"], freq="D")
  177. msg = (
  178. "Of the four parameters: start, end, periods, and freq, exactly "
  179. "three must be specified"
  180. )
  181. with pytest.raises(ValueError, match=msg):
  182. timedelta_range(periods=10, freq="D")
  183. def test_constructor_name(self):
  184. idx = timedelta_range(start="1 days", periods=1, freq="D", name="TEST")
  185. assert idx.name == "TEST"
  186. # GH10025
  187. idx2 = TimedeltaIndex(idx, name="something else")
  188. assert idx2.name == "something else"
  189. def test_constructor_no_precision_raises(self):
  190. # GH-24753, GH-24739
  191. msg = "with no precision is not allowed"
  192. with pytest.raises(ValueError, match=msg):
  193. TimedeltaIndex(["2000"], dtype="timedelta64")
  194. msg = "The 'timedelta64' dtype has no unit. Please pass in"
  195. with pytest.raises(ValueError, match=msg):
  196. pd.Index(["2000"], dtype="timedelta64")
  197. def test_constructor_wrong_precision_raises(self):
  198. msg = r"dtype timedelta64\[D\] cannot be converted to timedelta64\[ns\]"
  199. with pytest.raises(ValueError, match=msg):
  200. TimedeltaIndex(["2000"], dtype="timedelta64[D]")
  201. # "timedelta64[us]" was unsupported pre-2.0, but now this works.
  202. tdi = TimedeltaIndex(["2000"], dtype="timedelta64[us]")
  203. assert tdi.dtype == "m8[us]"
  204. def test_explicit_none_freq(self):
  205. # Explicitly passing freq=None is respected
  206. tdi = timedelta_range(1, periods=5)
  207. assert tdi.freq is not None
  208. result = TimedeltaIndex(tdi, freq=None)
  209. assert result.freq is None
  210. result = TimedeltaIndex(tdi._data, freq=None)
  211. assert result.freq is None
  212. tda = TimedeltaArray(tdi, freq=None)
  213. assert tda.freq is None
  214. def test_from_categorical(self):
  215. tdi = timedelta_range(1, periods=5)
  216. cat = pd.Categorical(tdi)
  217. result = TimedeltaIndex(cat)
  218. tm.assert_index_equal(result, tdi)
  219. ci = pd.CategoricalIndex(tdi)
  220. result = TimedeltaIndex(ci)
  221. tm.assert_index_equal(result, tdi)