How to stream to initially empty Tabulator

Hello,

im using the new Tabulator widget and trying the streaming tutorially from this page https://panel.holoviz.org/reference/widgets/Tabulator.html#streaming and the example works.
But if i start from an initially empty dataframe for the tabulator, like this

different_sized_stream_df = pd.DataFrame(np.random.randn(0, 5), columns=list('ABCDE'))
different_sizedstream_table = pn.widgets.Tabulator(different_sized_stream_df, layout='fit_columns', width=450, height=400)

def different_sized_stream_data(follow=True):
    different_sized_stream_df = pd.DataFrame(np.random.randn(10, 5), columns=list('ABCDE'))
    different_sizedstream_table.stream(different_sized_stream_df, follow=follow)

pn.state.add_periodic_callback(different_sized_stream_data, period=1000)

pn.Column(
pn.Row(different_sizedstream_table)
).servable()

then no values are shown and i get this error:

2021-04-19 20:31:01,858 Exception in callback <bound method PeriodicCallback._periodic_callback of PeriodicCallback(callback=<function different_sized_stream_data at 0x0000018F0516F8B8>, count=None, name='PeriodicCallback00101', per
iod=1000, running=True, timeout=None)>
Traceback (most recent call last):
  File "PATH\.venv\lib\site-packages\tornado\ioloop.py", line 905, in _run
    return self.callback()
  File "PATH\.venv\lib\site-packages\panel\io\callbacks.py", line 70, in _periodic_callback
    self.callback()
  File "PATH\panel_dev.py", line 21, in different_sized_stream_data
    different_sizedstream_table.stream(different_sized_stream_df, follow=follow)
  File "PATH\.venv\lib\site-packages\panel\widgets\tables.py", line 851, in stream
    super().stream(stream_value, rollover, reset_index)
  File "PATH\.venv\lib\site-packages\panel\widgets\tables.py", line 394, in stream
    self.param.trigger('value')
  File "PATH\.venv\lib\site-packages\param\parameterized.py", line 1541, in trigger
    self_.set_param(**dict(params, **triggers))
  File "PATH\.venv\lib\site-packages\param\parameterized.py", line 1472, in set_param
    self_._batch_call_watchers()
  File "PATH\.venv\lib\site-packages\param\parameterized.py", line 1611, in _batch_call_watchers
    self_._execute_watcher(watcher, events)
  File "PATH\.venv\lib\site-packages\param\parameterized.py", line 1573, in _execute_watcher
    watcher.fn(*args, **kwargs)
  File "PATH\.venv\lib\site-packages\panel\widgets\tables.py", line 748, in _validate
    self.style = self.value.style
  File "PATH\.venv\lib\site-packages\pandas\core\frame.py", line 901, in style
    return Styler(self)
  File "PATH\.venv\lib\site-packages\pandas\io\formats\style.py", line 156, in __init__
    raise ValueError("style is not supported for non-unique indices.")
ValueError: style is not supported for non-unique indices.

Not sure what that means though, no styling is used and the index is unique too for the updating dataframe.

EDIT 1:
The culprit seems to be this line at https://github.com/holoviz/panel/blob/44be3cfbc069ee2579b780aadc1f4076a70473f7/panel/widgets/tables.py#L382

        value_index_start = self.value.index.max() + 1

self.value.index.max() gives np.nan for an empty index, therefore all resulting index values will be np.nan.
and consequently non-unique when the styling is set here https://github.com/holoviz/panel/blob/44be3cfbc069ee2579b780aadc1f4076a70473f7/panel/widgets/tables.py#L742.

    def _validate(self, event):
        super()._validate(event)
        if self.value is not None:
            todo = []
            if self.style is not None:
                todo = self.style._todo
            self.style = self.value.style
            self.style._todo = todo

A kinda hacky workaround is settin an index on the empty initial dataframe like this

pd.DataFrame([], columns=list('ABCDE'), index=[0,1,2])

then the updating works, but it shows the fake index rows as all nan-cells which looks ugly.

Best option would be to probably safeguard the starting index with something like this i guess:

        if self.value.index.max() is np.nan: # or self.value.size
            value_index_start = 1
        else:
            value_index_start = self.value.index.max() + 1

EDIT 2:
i have submitted an issue here https://github.com/holoviz/panel/issues/2217

1 Like

Thanks for the issue, does seem like a bug.