Repository: holoviz/holoviews

Issue:
'Pre' and 'post' step interpolations loose precision
When using `.step()` with datetime, it looses precisions.

This is especially noticeable when comparing the hover information of `.hvlplot()` and `.hvplot.step()`:

```python
import pandas as pd
import hvplot.pandas

index = pd.to_datetime([
                    "2019-07-01 12:00:00+00:00",
                    "2019-07-01 16:30:47+00:00",
                    "2019-07-01 17:03:48+00:00",
                    "2019-07-01 18:27:06+00:00",
                    "2019-07-01 18:41:31+00:00",
                    "2019-07-01 19:23:19+00:00",
                    "2019-07-01 19:23:45+00:00",
                    "2019-07-01 21:11:57+00:00",
                    "2019-07-01 21:12:53+00:00",
                    "2019-07-01 23:12:42+00:00",
                  ])
Z = pd.Series([1, 0,1,0,1,0,1,0,1,0], index=index)
Z.hvplot() + Z.hvplot.step(where="post")
```

As fas as I understand, this caused by the float conversion here:

```python
    def _process_layer(self, element, key=None):
        INTERPOLATE_FUNCS = {'steps-pre': self.pts_to_prestep,
                             'steps-mid': self.pts_to_midstep,
                             'steps-post': self.pts_to_poststep}
        if self.p.interpolation not in INTERPOLATE_FUNCS:
            return element
        x = element.dimension_values(0)
        is_datetime = isdatetime(x)
        if is_datetime:
            dt_type = 'datetime64[ns]'
            x = x.astype(dt_type).astype('int64')
        dvals = tuple(element.dimension_values(d) for d in element.dimensions()[1:])
        xs, dvals = INTERPOLATE_FUNCS[self.p.interpolation](x.astype('f'), dvals)
        if is_datetime:
            xs = xs.astype(dt_type)
        return element.clone((xs,)+dvals)

    def _process(self, element, key=None):
        return element.map(self._process_layer, Element)
```

The float conversion might be useful for the `mid` interpolation but is not necessary for the pre and post interpolations.


Discussion:
I would love to see this fixed, displaying curves as a step function instead of linearly interpolated makes a huge difference visually.
We'd be very happy to see a PR, for now we could simply fix the pre/post interpolations using the simple suggested fix:

```python
x = x.astype('f') if self.p.interpolation != 'mid' else x
xs, dvals = INTERPOLATE_FUNCS[self.p.interpolation](x, dvals)
```

but a proper fix for all interpolation methods would of course be preferred.

Resolving patch:
diff --git a/holoviews/operation/element.py b/holoviews/operation/element.py
--- a/holoviews/operation/element.py
+++ b/holoviews/operation/element.py
@@ -797,9 +797,9 @@ def _process_layer(self, element, key=None):
         is_datetime = isdatetime(x)
         if is_datetime:
             dt_type = 'datetime64[ns]'
-            x = x.astype(dt_type).astype('int64')
+            x = x.astype(dt_type)
         dvals = tuple(element.dimension_values(d) for d in element.dimensions()[1:])
-        xs, dvals = INTERPOLATE_FUNCS[self.p.interpolation](x.astype('f'), dvals)
+        xs, dvals = INTERPOLATE_FUNCS[self.p.interpolation](x, dvals)
         if is_datetime:
             xs = xs.astype(dt_type)
         return element.clone((xs,)+dvals)


Test patch:
diff --git a/holoviews/tests/operation/testoperation.py b/holoviews/tests/operation/testoperation.py
--- a/holoviews/tests/operation/testoperation.py
+++ b/holoviews/tests/operation/testoperation.py
@@ -331,10 +331,10 @@ def test_interpolate_datetime_curve_pre(self):
         values = [0, 1, 2, 3]
         interpolated = interpolate_curve(Curve((dates, values)), interpolation='steps-pre')
         dates_interp = np.array([
-            '2017-01-01T00:00:16.364011520', '2017-01-01T00:00:16.364011520',
-            '2017-01-02T00:01:05.465745408', '2017-01-02T00:01:05.465745408',
-            '2017-01-02T23:59:37.128525824', '2017-01-02T23:59:37.128525824',
-            '2017-01-04T00:00:26.230259712'
+            '2017-01-01T00:00:00', '2017-01-01T00:00:00',
+            '2017-01-02T00:00:00', '2017-01-02T00:00:00',
+            '2017-01-03T00:00:00', '2017-01-03T00:00:00',
+            '2017-01-04T00:00:00'
         ], dtype='datetime64[ns]')
         curve = Curve((dates_interp, [0, 1, 1, 2, 2, 3, 3]))
         self.assertEqual(interpolated, curve)
@@ -357,10 +357,10 @@ def test_interpolate_datetime_curve_mid(self):
         values = [0, 1, 2, 3]
         interpolated = interpolate_curve(Curve((dates, values)), interpolation='steps-mid')
         dates_interp = np.array([
-            '2017-01-01T00:00:16.364011520', '2017-01-01T11:59:32.195401728',
-            '2017-01-01T11:59:32.195401728', '2017-01-02T12:00:21.297135616',
-            '2017-01-02T12:00:21.297135616', '2017-01-03T12:01:10.398869504',
-            '2017-01-03T12:01:10.398869504', '2017-01-04T00:00:26.230259712'
+            '2017-01-01T00:00:00', '2017-01-01T12:00:00',
+            '2017-01-01T12:00:00', '2017-01-02T12:00:00',
+            '2017-01-02T12:00:00', '2017-01-03T12:00:00',
+            '2017-01-03T12:00:00', '2017-01-04T00:00:00'
         ], dtype='datetime64[ns]')
         curve = Curve((dates_interp, [0, 0, 1, 1, 2, 2, 3, 3]))
         self.assertEqual(interpolated, curve)
@@ -383,10 +383,10 @@ def test_interpolate_datetime_curve_post(self):
         values = [0, 1, 2, 3]
         interpolated = interpolate_curve(Curve((dates, values)), interpolation='steps-post')
         dates_interp = np.array([
-            '2017-01-01T00:00:16.364011520', '2017-01-02T00:01:05.465745408',
-            '2017-01-02T00:01:05.465745408', '2017-01-02T23:59:37.128525824',
-            '2017-01-02T23:59:37.128525824', '2017-01-04T00:00:26.230259712',
-            '2017-01-04T00:00:26.230259712'
+            '2017-01-01T00:00:00', '2017-01-02T00:00:00',
+            '2017-01-02T00:00:00', '2017-01-03T00:00:00',
+            '2017-01-03T00:00:00', '2017-01-04T00:00:00',
+            '2017-01-04T00:00:00'
         ], dtype='datetime64[ns]')
         curve = Curve((dates_interp, [0, 0, 1, 1, 2, 2, 3]))
         self.assertEqual(interpolated, curve)