Repository: holoviz/holoviews

Issue:
CUDF-related test failures

#### ALL software version info
```
cudf                      21.10.01        cuda_11.2_py38_ga1d2d13a14_0    rapidsai
holoviews                 1.14.6                     py_0    pyviz/label/dev
libcudf                   21.10.01        cuda11.2_ga1d2d13a14_0    rapidsai
```

#### Description of expected behavior and the observed behavior

Expected: Tests pass

Observed:

```
FAILED holoviews/tests/core/data/test_cudfinterface.py::cuDFInterfaceTests::test_dataset_expanded_dimvals_ht - TypeError: The DType <class 'numpy._FloatAbstractDType'> could not be promoted by <class 'numpy.dtype[str_]'>. T...
FAILED holoviews/tests/core/data/test_cudfinterface.py::cuDFInterfaceTests::test_dataset_sample_hm - holoviews.core.data.interface.DataError: Following columns specified as dimensions but not found in data: ['y']
FAILED holoviews/tests/core/data/test_cudfinterface.py::cuDFInterfaceTests::test_dataset_sample_hm_alias - holoviews.core.data.interface.DataError: Following columns specified as dimensions but not found in data: ['y']
FAILED holoviews/tests/core/data/test_cudfinterface.py::cuDFInterfaceTests::test_dataset_sample_ht - holoviews.core.data.interface.DataError: Following columns specified as dimensions but not found in data: ['y']

```

#### Complete, minimal, self-contained example code that reproduces the issue

```
 pytest holoviews/tests/core/data/test_cudfinterface.py 
```

#### Stack traceback and/or browser JavaScript console output

<details>

```
rapids-21.10-test ❯ pytest holoviews/tests/core/data/test_cudfinterface.py 
===================================================== test session starts =====================================================
platform linux -- Python 3.8.12, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /home/bryan/work/holoviews, configfile: pytest.ini
plugins: anyio-3.3.4
collected 113 items                                                                                                           

holoviews/tests/core/data/test_cudfinterface.py .....s...........s...................F................................. [ 62%]
............FFF...........................                                                                              [100%]

========================================================== FAILURES ===========================================================
_____________________________________ cuDFInterfaceTests.test_dataset_expanded_dimvals_ht _____________________________________

cls = <class 'holoviews.element.comparison.Comparison'>, arr1 = array(['F', 'M'], dtype=object)
arr2 = array(['M', 'F'], dtype='<U1'), msg = 'Arrays'

    @classmethod
    def compare_arrays(cls, arr1, arr2, msg='Arrays'):
        try:
            if arr1.dtype.kind == 'M':
                arr1 = cast_array_to_int64(arr1.astype('datetime64[ns]'))
            if arr2.dtype.kind == 'M':
                arr2 = cast_array_to_int64(arr2.astype('datetime64[ns]'))
>           assert_array_equal(arr1, arr2)

holoviews/element/comparison.py:258: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

x = array(['F', 'M'], dtype=object), y = array(['M', 'F'], dtype='<U1'), err_msg = '', verbose = True

    def assert_array_equal(x, y, err_msg='', verbose=True):
        """
        Raises an AssertionError if two array_like objects are not equal.
    
        Given two array_like objects, check that the shape is equal and all
        elements of these objects are equal (but see the Notes for the special
        handling of a scalar). An exception is raised at shape mismatch or
        conflicting values. In contrast to the standard usage in numpy, NaNs
        are compared like numbers, no assertion is raised if both objects have
        NaNs in the same positions.
    
        The usual caution for verifying equality with floating point numbers is
        advised.
    
        Parameters
        ----------
        x : array_like
            The actual object to check.
        y : array_like
            The desired, expected object.
        err_msg : str, optional
            The error message to be printed in case of failure.
        verbose : bool, optional
            If True, the conflicting values are appended to the error message.
    
        Raises
        ------
        AssertionError
            If actual and desired objects are not equal.
    
        See Also
        --------
        assert_allclose: Compare two array_like objects for equality with desired
                         relative and/or absolute precision.
        assert_array_almost_equal_nulp, assert_array_max_ulp, assert_equal
    
        Notes
        -----
        When one of `x` and `y` is a scalar and the other is array_like, the
        function checks that each element of the array_like object is equal to
        the scalar.
    
        Examples
        --------
        The first assert does not raise an exception:
    
        >>> np.testing.assert_array_equal([1.0,2.33333,np.nan],
        ...                               [np.exp(0),2.33333, np.nan])
    
        Assert fails with numerical imprecision with floats:
    
        >>> np.testing.assert_array_equal([1.0,np.pi,np.nan],
        ...                               [1, np.sqrt(np.pi)**2, np.nan])
        Traceback (most recent call last):
            ...
        AssertionError:
        Arrays are not equal
        <BLANKLINE>
        Mismatched elements: 1 / 3 (33.3%)
        Max absolute difference: 4.4408921e-16
        Max relative difference: 1.41357986e-16
         x: array([1.      , 3.141593,      nan])
         y: array([1.      , 3.141593,      nan])
    
        Use `assert_allclose` or one of the nulp (number of floating point values)
        functions for these cases instead:
    
        >>> np.testing.assert_allclose([1.0,np.pi,np.nan],
        ...                            [1, np.sqrt(np.pi)**2, np.nan],
        ...                            rtol=1e-10, atol=0)
    
        As mentioned in the Notes section, `assert_array_equal` has special
        handling for scalars. Here the test checks that each value in `x` is 3:
    
        >>> x = np.full((2, 5), fill_value=3)
        >>> np.testing.assert_array_equal(x, 3)
    
        """
        __tracebackhide__ = True  # Hide traceback for py.test
>       assert_array_compare(operator.__eq__, x, y, err_msg=err_msg,
                             verbose=verbose, header='Arrays are not equal')

../../anaconda3/envs/rapids-21.10-test/lib/python3.8/site-packages/numpy/testing/_private/utils.py:934: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

comparison = <built-in function eq>, x = array(['F', 'M'], dtype=object), y = array(['M', 'F'], dtype='<U1')
err_msg = '\nMismatched elements: 2 / 2 (100%)', verbose = True, header = 'Arrays are not equal', precision = 6
equal_nan = True, equal_inf = True

    def assert_array_compare(comparison, x, y, err_msg='', verbose=True, header='',
                             precision=6, equal_nan=True, equal_inf=True):
        __tracebackhide__ = True  # Hide traceback for py.test
        from numpy.core import array, array2string, isnan, inf, bool_, errstate, all, max, object_
    
        x = np.asanyarray(x)
        y = np.asanyarray(y)
    
        # original array for output formatting
        ox, oy = x, y
    
        def isnumber(x):
            return x.dtype.char in '?bhilqpBHILQPefdgFDG'
    
        def istime(x):
            return x.dtype.char in "Mm"
    
        def func_assert_same_pos(x, y, func=isnan, hasval='nan'):
            """Handling nan/inf.
    
            Combine results of running func on x and y, checking that they are True
            at the same locations.
    
            """
            __tracebackhide__ = True  # Hide traceback for py.test
    
            x_id = func(x)
            y_id = func(y)
            # We include work-arounds here to handle three types of slightly
            # pathological ndarray subclasses:
            # (1) all() on `masked` array scalars can return masked arrays, so we
            #     use != True
            # (2) __eq__ on some ndarray subclasses returns Python booleans
            #     instead of element-wise comparisons, so we cast to bool_() and
            #     use isinstance(..., bool) checks
            # (3) subclasses with bare-bones __array_function__ implementations may
            #     not implement np.all(), so favor using the .all() method
            # We are not committed to supporting such subclasses, but it's nice to
            # support them if possible.
            if bool_(x_id == y_id).all() != True:
                msg = build_err_msg([x, y],
                                    err_msg + '\nx and y %s location mismatch:'
                                    % (hasval), verbose=verbose, header=header,
                                    names=('x', 'y'), precision=precision)
                raise AssertionError(msg)
            # If there is a scalar, then here we know the array has the same
            # flag as it everywhere, so we should return the scalar flag.
            if isinstance(x_id, bool) or x_id.ndim == 0:
                return bool_(x_id)
            elif isinstance(y_id, bool) or y_id.ndim == 0:
                return bool_(y_id)
            else:
                return y_id
    
        try:
            cond = (x.shape == () or y.shape == ()) or x.shape == y.shape
            if not cond:
                msg = build_err_msg([x, y],
                                    err_msg
                                    + f'\n(shapes {x.shape}, {y.shape} mismatch)',
                                    verbose=verbose, header=header,
                                    names=('x', 'y'), precision=precision)
                raise AssertionError(msg)
    
            flagged = bool_(False)
            if isnumber(x) and isnumber(y):
                if equal_nan:
                    flagged = func_assert_same_pos(x, y, func=isnan, hasval='nan')
    
                if equal_inf:
                    flagged |= func_assert_same_pos(x, y,
                                                    func=lambda xy: xy == +inf,
                                                    hasval='+inf')
                    flagged |= func_assert_same_pos(x, y,
                                                    func=lambda xy: xy == -inf,
                                                    hasval='-inf')
    
            elif istime(x) and istime(y):
                # If one is datetime64 and the other timedelta64 there is no point
                if equal_nan and x.dtype.type == y.dtype.type:
                    flagged = func_assert_same_pos(x, y, func=isnat, hasval="NaT")
    
            if flagged.ndim > 0:
                x, y = x[~flagged], y[~flagged]
                # Only do the comparison if actual values are left
                if x.size == 0:
                    return
            elif flagged:
                # no sense doing comparison if everything is flagged.
                return
    
            val = comparison(x, y)
    
            if isinstance(val, bool):
                cond = val
                reduced = array([val])
            else:
                reduced = val.ravel()
                cond = reduced.all()
    
            # The below comparison is a hack to ensure that fully masked
            # results, for which val.ravel().all() returns np.ma.masked,
            # do not trigger a failure (np.ma.masked != True evaluates as
            # np.ma.masked, which is falsy).
            if cond != True:
                n_mismatch = reduced.size - reduced.sum(dtype=intp)
                n_elements = flagged.size if flagged.ndim != 0 else reduced.size
                percent_mismatch = 100 * n_mismatch / n_elements
                remarks = [
                    'Mismatched elements: {} / {} ({:.3g}%)'.format(
                        n_mismatch, n_elements, percent_mismatch)]
    
                with errstate(invalid='ignore', divide='ignore'):
                    # ignore errors for non-numeric types
                    with contextlib.suppress(TypeError):
                        error = abs(x - y)
                        max_abs_error = max(error)
                        if getattr(error, 'dtype', object_) == object_:
                            remarks.append('Max absolute difference: '
                                            + str(max_abs_error))
                        else:
                            remarks.append('Max absolute difference: '
                                            + array2string(max_abs_error))
    
                        # note: this definition of relative error matches that one
                        # used by assert_allclose (found in np.isclose)
                        # Filter values where the divisor would be zero
                        nonzero = bool_(y != 0)
                        if all(~nonzero):
                            max_rel_error = array(inf)
                        else:
                            max_rel_error = max(error[nonzero] / abs(y[nonzero]))
                        if getattr(error, 'dtype', object_) == object_:
                            remarks.append('Max relative difference: '
                                            + str(max_rel_error))
                        else:
                            remarks.append('Max relative difference: '
                                            + array2string(max_rel_error))
    
                err_msg += '\n' + '\n'.join(remarks)
                msg = build_err_msg([ox, oy], err_msg,
                                    verbose=verbose, header=header,
                                    names=('x', 'y'), precision=precision)
>               raise AssertionError(msg)
E               AssertionError: 
E               Arrays are not equal
E               
E               Mismatched elements: 2 / 2 (100%)
E                x: array(['F', 'M'], dtype=object)
E                y: array(['M', 'F'], dtype='<U1')

../../anaconda3/envs/rapids-21.10-test/lib/python3.8/site-packages/numpy/testing/_private/utils.py:844: AssertionError

During handling of the above exception, another exception occurred:

self = <holoviews.tests.core.data.test_cudfinterface.cuDFInterfaceTests testMethod=test_dataset_expanded_dimvals_ht>

    def test_dataset_expanded_dimvals_ht(self):
>       self.assertEqual(self.table.dimension_values('Gender', expanded=False),
                         np.array(['M', 'F']))

holoviews/tests/core/data/base.py:466: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
holoviews/element/comparison.py:76: in assertEqual
    asserter(first, second)
holoviews/element/comparison.py:261: in compare_arrays
    cls.assert_array_almost_equal_fn(arr1, arr2)
../../anaconda3/envs/rapids-21.10-test/lib/python3.8/site-packages/numpy/testing/_private/utils.py:1037: in compare
    dtype = result_type(y, 1.)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

args = (array(['M', 'F'], dtype='<U1'), 1.0), kwargs = {}, relevant_args = (array(['M', 'F'], dtype='<U1'), 1.0)

>   ???
E   TypeError: The DType <class 'numpy._FloatAbstractDType'> could not be promoted by <class 'numpy.dtype[str_]'>. This means that no common DType exists for the given inputs. For example they cannot be stored in a single array unless the dtype is `object`. The full list of DTypes is: (<class 'numpy.dtype[str_]'>, <class 'numpy._FloatAbstractDType'>)

<__array_function__ internals>:5: TypeError
__________________________________________ cuDFInterfaceTests.test_dataset_sample_hm __________________________________________

self = <holoviews.tests.core.data.test_cudfinterface.cuDFInterfaceTests testMethod=test_dataset_sample_hm>

    def test_dataset_sample_hm(self):
>       samples = self.dataset_hm.sample([0, 5, 10]).dimension_values('y')

holoviews/tests/core/data/base.py:229: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
holoviews/core/data/__init__.py:204: in pipelined_fn
    result = method_fn(*args, **kwargs)
holoviews/core/data/__init__.py:835: in sample
    return self.clone(sampled, new_type=Table, datatype=datatype)
holoviews/core/data/__init__.py:1206: in clone
    return super(Dataset, self).clone(data, shared_data, new_type, *args, **overrides)
holoviews/core/dimension.py:566: in clone
    return clone_type(data, *args, **{k:v for k,v in settings.items()
holoviews/element/selection.py:23: in __init__
    super().__init__(*args, **kwargs)
holoviews/core/data/__init__.py:342: in __init__
    self.interface.validate(self, validate_vdims)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

cls = <class 'holoviews.core.data.dictionary.DictInterface'>, dataset = :Table   [x]   (y), vdims = True

    @classmethod
    def validate(cls, dataset, vdims=True):
        dim_types = 'all' if vdims else 'key'
        dimensions = dataset.dimensions(dim_types, label='name')
        not_found = [d for d in dimensions if d not in dataset.data]
        if not_found:
>           raise DataError('Following columns specified as dimensions '
                            'but not found in data: %s' % not_found, cls)
E           holoviews.core.data.interface.DataError: Following columns specified as dimensions but not found in data: ['y']
E           
E           DictInterface expects tabular data, for more information on supported datatypes see http://holoviews.org/user_guide/Tabular_Datasets.html

holoviews/core/data/dictionary.py:132: DataError
_______________________________________ cuDFInterfaceTests.test_dataset_sample_hm_alias _______________________________________

self = <holoviews.tests.core.data.test_cudfinterface.cuDFInterfaceTests testMethod=test_dataset_sample_hm_alias>

    def test_dataset_sample_hm_alias(self):
>       samples = self.dataset_hm_alias.sample([0, 5, 10]).dimension_values('y')

holoviews/tests/core/data/base.py:233: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
holoviews/core/data/__init__.py:204: in pipelined_fn
    result = method_fn(*args, **kwargs)
holoviews/core/data/__init__.py:835: in sample
    return self.clone(sampled, new_type=Table, datatype=datatype)
holoviews/core/data/__init__.py:1206: in clone
    return super(Dataset, self).clone(data, shared_data, new_type, *args, **overrides)
holoviews/core/dimension.py:566: in clone
    return clone_type(data, *args, **{k:v for k,v in settings.items()
holoviews/element/selection.py:23: in __init__
    super().__init__(*args, **kwargs)
holoviews/core/data/__init__.py:342: in __init__
    self.interface.validate(self, validate_vdims)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

cls = <class 'holoviews.core.data.dictionary.DictInterface'>, dataset = :Table   [x]   (y), vdims = True

    @classmethod
    def validate(cls, dataset, vdims=True):
        dim_types = 'all' if vdims else 'key'
        dimensions = dataset.dimensions(dim_types, label='name')
        not_found = [d for d in dimensions if d not in dataset.data]
        if not_found:
>           raise DataError('Following columns specified as dimensions '
                            'but not found in data: %s' % not_found, cls)
E           holoviews.core.data.interface.DataError: Following columns specified as dimensions but not found in data: ['y']
E           
E           DictInterface expects tabular data, for more information on supported datatypes see http://holoviews.org/user_guide/Tabular_Datasets.html

holoviews/core/data/dictionary.py:132: DataError
__________________________________________ cuDFInterfaceTests.test_dataset_sample_ht __________________________________________

self = <holoviews.tests.core.data.test_cudfinterface.cuDFInterfaceTests testMethod=test_dataset_sample_ht>

    def test_dataset_sample_ht(self):
>       samples = self.dataset_ht.sample([0, 5, 10]).dimension_values('y')

holoviews/tests/core/data/base.py:551: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
holoviews/core/data/__init__.py:204: in pipelined_fn
    result = method_fn(*args, **kwargs)
holoviews/core/data/__init__.py:835: in sample
    return self.clone(sampled, new_type=Table, datatype=datatype)
holoviews/core/data/__init__.py:1206: in clone
    return super(Dataset, self).clone(data, shared_data, new_type, *args, **overrides)
holoviews/core/dimension.py:566: in clone
    return clone_type(data, *args, **{k:v for k,v in settings.items()
holoviews/element/selection.py:23: in __init__
    super().__init__(*args, **kwargs)
holoviews/core/data/__init__.py:342: in __init__
    self.interface.validate(self, validate_vdims)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

cls = <class 'holoviews.core.data.dictionary.DictInterface'>, dataset = :Table   [x]   (y), vdims = True

    @classmethod
    def validate(cls, dataset, vdims=True):
        dim_types = 'all' if vdims else 'key'
        dimensions = dataset.dimensions(dim_types, label='name')
        not_found = [d for d in dimensions if d not in dataset.data]
        if not_found:
>           raise DataError('Following columns specified as dimensions '
                            'but not found in data: %s' % not_found, cls)
E           holoviews.core.data.interface.DataError: Following columns specified as dimensions but not found in data: ['y']
E           
E           DictInterface expects tabular data, for more information on supported datatypes see http://holoviews.org/user_guide/Tabular_Datasets.html
```
</details>

cc @philippjfr (also @ajaythorve for FYI)


Discussion:

Resolving patch:
diff --git a/holoviews/core/data/__init__.py b/holoviews/core/data/__init__.py
--- a/holoviews/core/data/__init__.py
+++ b/holoviews/core/data/__init__.py
@@ -797,7 +797,7 @@ def sample(self, samples=[], bounds=None, closest=True, **kwargs):
         # may be replaced with more general handling
         # see https://github.com/holoviz/holoviews/issues/1173
         from ...element import Table, Curve
-        datatype = ['dataframe', 'dictionary', 'dask', 'ibis']
+        datatype = ['dataframe', 'dictionary', 'dask', 'ibis', 'cuDF']
         if len(samples) == 1:
             sel = {kd.name: s for kd, s in zip(self.kdims, samples[0])}
             dims = [kd for kd, v in sel.items() if not np.isscalar(v)]
diff --git a/holoviews/core/data/cudf.py b/holoviews/core/data/cudf.py
--- a/holoviews/core/data/cudf.py
+++ b/holoviews/core/data/cudf.py
@@ -245,7 +245,7 @@ def select(cls, dataset, selection_mask=None, **selection):
 
         indexed = cls.indexed(dataset, selection)
         if selection_mask is not None:
-            df = df.loc[selection_mask]
+            df = df.iloc[selection_mask]
         if indexed and len(df) == 1 and len(dataset.vdims) == 1:
             return df[dataset.vdims[0].name].iloc[0]
         return df
@@ -282,7 +282,13 @@ def aggregate(cls, dataset, dimensions, function, **kwargs):
             if not hasattr(reindexed, agg):
                 raise ValueError('%s aggregation is not supported on cudf DataFrame.' % agg)
             agg = getattr(reindexed, agg)()
-            data = dict(((col, [v]) for col, v in zip(agg.index.values_host, agg.to_array())))
+            try:
+                data = dict(((col, [v]) for col, v in zip(agg.index.values_host, agg.to_numpy())))
+            except Exception:
+                # Give FutureWarning: 'The to_array method will be removed in a future cuDF release.
+                # Consider using `to_numpy` instead.'
+                # Seen in cudf=21.12.01
+                data = dict(((col, [v]) for col, v in zip(agg.index.values_host, agg.to_array())))
             df = util.pd.DataFrame(data, columns=list(agg.index.values_host))
 
         dropped = []


Test patch:
diff --git a/holoviews/tests/core/data/base.py b/holoviews/tests/core/data/base.py
--- a/holoviews/tests/core/data/base.py
+++ b/holoviews/tests/core/data/base.py
@@ -463,8 +463,13 @@ def test_dataset_dataset_ht_dtypes(self):
     # Test literal formats
 
     def test_dataset_expanded_dimvals_ht(self):
-        self.assertEqual(self.table.dimension_values('Gender', expanded=False),
-                         np.array(['M', 'F']))
+        # This will run unique(), which for pandas return
+        # in order of appearance, but can be sorted for other
+        # interfaces like cudf.
+        #   pd.Series(["M", "M", "F"]).unique()   -> ["M", "F"]
+        #   cudf.Series(["M", "M", "F"]).unique() -> ["F", "M"]
+        data = self.table.dimension_values('Gender', expanded=False)
+        self.assertEqual(np.sort(data), np.array(['F', 'M']))
 
     def test_dataset_implicit_indexing_init(self):
         dataset = Scatter(self.ys, kdims=['x'], vdims=['y'])