Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ibis duckdb no rowid #5527

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
13 changes: 11 additions & 2 deletions holoviews/core/data/ibis.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,14 @@ def nonzero(cls, dataset):
@cached
def range(cls, dataset, dimension):
dimension = dataset.get_dimension(dimension, strict=True)
if cls.dtype(dataset, dimension).kind in 'SUO':
dtype_kind = cls.dtype(dataset, dimension).kind
if dtype_kind == 'O':
# Can this be done more efficiently?
column = dataset.data[dimension.name].execute()
first = column.iloc[0]
last = column.iloc[-1]
return first, last
if dtype_kind in 'SU':
return None, None
if dimension.nodata is not None:
return Interface.range(dataset, dimension)
Expand Down Expand Up @@ -172,7 +179,9 @@ def dtype(cls, dataset, dimension):
dimension = dataset.get_dimension(dimension)
return dataset.data.head(0).execute().dtypes[dimension.name]

dimension_type = dtype
@classmethod
def dimension_type(cls, dataset, dim):
return cls.dtype(dataset, dim).type

@classmethod
def sort(cls, dataset, by=[], reverse=False):
Expand Down
1 change: 0 additions & 1 deletion holoviews/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,6 @@ def isfinite(val):
return finite & (~pd.isna(val))
return finite


def isdatetime(value):
"""
Whether the array or scalar is recognized datetime type.
Expand Down
94 changes: 89 additions & 5 deletions holoviews/tests/core/data/test_ibisinterface.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
import sqlite3
from unittest import SkipTest

from tempfile import NamedTemporaryFile
from unittest import SkipTest

try:
import ibis
from ibis import sqlite
except:
raise SkipTest("Could not import ibis, skipping IbisInterface tests.")

try:
import duckdb
except:
raise SkipTest("Could not import duckdb, skipping IbisInterface tests.")

from pathlib import Path

import numpy as np
import pandas as pd

import pytest
from bokeh.models import axes as bokeh_axes
from holoviews import render
from holoviews.core.data import Dataset
from holoviews.core.spaces import HoloMap
from holoviews.core.data.ibis import IbisInterface
from holoviews.core.spaces import HoloMap
from holoviews.element.chart import Curve

from .base import HeterogeneousColumnTests, ScalarColumnTests, InterfaceTests
from .base import HeterogeneousColumnTests, InterfaceTests, ScalarColumnTests


def create_temp_db(df, name, index=False):
Expand Down Expand Up @@ -303,3 +312,78 @@ def test_dataset_iloc_ellipsis_list_cols(self):

def test_dataset_boolean_index(self):
raise SkipTest("Not supported")

def create_pandas_connection(df: pd.DataFrame, *args, **kwargs):
return ibis.pandas.connect({"df": df})

def create_duckdb_connection(df: pd.DataFrame, *args, **kwargs):
tmpdir = kwargs["tmpdir"]
filename = str(Path(tmpdir)/"db.db")
duckdb_con = duckdb.connect(filename)
duckdb_con.execute("CREATE TABLE df AS SELECT * FROM df")

return ibis.duckdb.connect(filename)

def create_sqlite_connection(df: pd.DataFrame, *args, **kwargs):
return create_temp_db(df, "df")

@pytest.fixture
def reference_df():
return pd.DataFrame(
{
"actual": [100, 150, 125, 140, 145, 135, 123],
"forecast": [90, 160, 125, 150, 141, 141, 120],
"numerical": [1.1, 1.9, 3.2, 3.8, 4.3, 5.0, 5.5],
"date": pd.date_range("2022-01-03", "2022-01-09"),
"string": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
},
)

@pytest.fixture(params=[create_pandas_connection, create_duckdb_connection, create_sqlite_connection])
def connection(request, reference_df, tmpdir):
return request.param(reference_df, tmpdir=tmpdir)

@pytest.fixture
def data(connection):
return connection.table("df")

@pytest.fixture
def dataset(data):
return Dataset(data, kdims=["numerical", "date", "string"], vdims=["actual", "forecast"])

def test_index_ibis_table(data):
table = IbisInterface._index_ibis_table(data)
table.execute()

@pytest.mark.parametrize(["dimension", "expected"], [
("date", (np.datetime64('2022-01-03'), np.datetime64('2022-01-09'))),
("string", ('Mon', 'Sun')),
("numerical",(np.float64(1.1), np.float64(5.5))),
])
def test_range(dimension, expected, dataset):
assert IbisInterface.range(dataset, dimension) == expected

@pytest.mark.parametrize(["dimension", "expected"], [
("date", np.datetime64),
("string", np.object_),
("numerical", np.float64),
])
def test_dimension_type(dimension, expected, dataset):
assert IbisInterface.dimension_type(dataset, dimension) is expected

@pytest.mark.parametrize(["kdims", "vdims", "xaxis_type", "yaxis_type"], [
("date", "actual", bokeh_axes.DatetimeAxis, bokeh_axes.LinearAxis),
("string", "actual", bokeh_axes.CategoricalAxis, bokeh_axes.LinearAxis),
("numerical", "actual", bokeh_axes.LinearAxis, bokeh_axes.LinearAxis),
("numerical", "date", bokeh_axes.LinearAxis, bokeh_axes.DatetimeAxis),
("numerical", "string", bokeh_axes.LinearAxis, bokeh_axes.CategoricalAxis),
])
def test_bokeh_axis(data, kdims, vdims, xaxis_type, yaxis_type):
"""Test to make sure the right axis can be identified for the bokeh backend"""
plot_ibis = Curve(data, kdims=kdims, vdims=vdims)
# When
plot_bokeh = render(plot_ibis, "bokeh")
xaxis, yaxis = plot_bokeh.axis
# Then
assert isinstance(xaxis, xaxis_type)
assert isinstance(yaxis, yaxis_type)