Update/manipulate dataframe on button click

I am attempting to build an app that will get a specific range of data from an input dataset, store that subset of data in a different dataset by a button click, then rinse and repeat until the user has gone through their data. Note that the intermittently updated dataframe will need to be downloaded. I am having some issues getting the data to be properly called and stored. I have tried implementing this with param and panel, and I have also referenced some of the topics here (e.g., with panel, with param, etc.). Code as follows:

# import dependencies and data
import pandas as pd
import panel as pn
import param

data = pd.DataFrame({'first_subset': ['sample1', 'sample1', 'sample1', 'sample2', 'sample2', 'sample2'],
                           'slider_col': [1,2,3,4,5,6],
                           'other': [1,2,3,4,5,6],
                           'other1': [1,2,3,4,5,6]})
options = list(data.first_subset.unique())

# define widgets
subset_data_by_selection = pn.widgets.Select(name='my selector',options=options)
slider_to_filter_subset = pn.widgets.IntRangeSlider(name='My Slider', start=0, end=100, value = (33,50), step=1)
input_table = pn.widgets.DataFrame(data)
output_table = pn.widgets.DataFrame()
update_output_button = pn.widgets.Button(name='Approve Subset',button_type='primary')


# set up function - note watch = true. Doesn't work if not watching either.
@pn.depends(input_table,slider_to_filter_subset,subset_data_by_selection,output_table)
def update_output(input_table,slider_to_filter_subset,subset_data_by_selection,output_table):
    data_tosend = input_table[input_table['first_subset'] == subset_data_by_selection]
    data_tosend = data_tosend[(data_tosend.slider_col >= slider_to_filter_subset[0]) & (data_tosend.slider_col <= slider_to_filter_subset[1])]
    if output_table is None:
        output_table_pass = data_tosend
    else:
        output_table_pass = pd.concat([output_table,data_tosend])
    output_table = output_table_pass

# call function by button click
update_output_button.on_click(update_output)

# watch table
@pn.depends(output_table)
def view_output(output_table):
    if output_table is None:
        print('why')
    else:
        print(output_table.head())

pn.extension()

app = pn.Row(
    pn.Column(pn.WidgetBox(subset_data_by_selection,slider_to_filter_subset,update_output_button,width=500)
             ),
    pn.Column(view_output
             )
)

app

Following error is returned:

TypeError: update_output() missing 3 required positional arguments: 'slider_to_filter_subset', 'subset_data_by_selection', and 'output_table'

Similarly, in param:

import pandas as pd
import panel as pn
import param

data = pd.DataFrame({'first_subset': ['sample1', 'sample1', 'sample1', 'sample2', 'sample2', 'sample2'],
                           'slider_col': [1,2,3,4,5,6],
                           'other': [1,2,3,4,5,6],
                           'other1': [1,2,3,4,5,6]})
options = list(data.first_subset.unique())

class do_the_things(param.Parameterized):

    update_output_button = pn.widgets.Button(name='Approve Subset',button_type='primary')

    # set up parameters. those with the suffix _pass are set up in order to be updated in the first callback below.
    input_data = param.DataFrame(data,precedence=-1)
    output_data = param.DataFrame(precedence=-1)
    
    
    slider_to_filter_subset1 = param.Integer(default=33,bounds=(0,100))
    slider_to_filter_subset2 = param.Integer(default=55,bounds=(0,100))
    
    subset_data_by_selection = param.Selector(objects=options)
    
    update_output_button = param.Action(lambda x: x.param.trigger('update_output_button'), label='Output Data')
    

    @param.depends('update_output_button','input_data','output_data','slider_to_filter_subset1','slider_to_filter_subset2',
                   'subset_data_by_selection')
    def update_output(self):
        data_tosend = self.input_data[self.input_data['first_subset'] == self.subset_data_by_selection]
        data_tosend = data_tosend[(data_tosend.slider_col >= self.slider_to_filter_subset1) & (data_tosend.slider_col < self.slider_to_filter_subset2)]
        if self.output_data is None:
            output_data = pd.DataFrame(data_tosend)
        else:
            output_data = pd.concat([self.output_data,data_tosend])
        self.output_data = output_data

    
    # @param.depends('output_data')
    # def view_output(self):
    #     df = self.output_data[0]
    #     return df

    
do_things = do_the_things(name='name')

pn.extension()

app = pn.Row(
    pn.Column(do_things.update_output),
    pn.Column(do_things.param),
    # pn.Column(ablation_.view_output)
)

app

This returns:

RecursionError: maximum recursion depth exceeded while calling a Python object

Any help would be sincerely appreciated.

The error thrown in the first code block above is due to the update_output function note having inputs in the update_output_button.on_click(). It is strange that the error claims three arguments are missing when the function actually requires four. Nonetheless, inputting .value for all required arguments (see below) results in a typerror of NoneType being called. Can’t find where this is.

update_output_button.on_click(update_output(input_table.value,slider_to_filter_subset.value,subset_data_by_selection.value,output_table.value))
.......
TypeError: 'NoneType' object is not callable

For those interested, solved using the response to this post. :partying_face:

Initializing the state is definitely required for messing around with dfs, it would seem.

import pandas as pd
import panel as pn
import param
from panel.viewable import Viewer

data = pd.DataFrame({'first_subset': ['sample1', 'sample1', 'sample1', 'sample2', 'sample2', 'sample2'],
                           'slider_col': [1,2,3,4,5,6],
                           'other': [1,2,3,4,5,6],
                           'other1': [1,2,3,4,5,6]})
options = list(data.first_subset.unique())

# class do_the_things(param.Parameterized):
class do_the_things(Viewer):

    update_output_button = pn.widgets.Button(name='Approve Subset',button_type='primary')
    # set up parameters. those with the suffix _pass are set up in order to be updated in the first callback below.
    input_data = param.DataFrame(default=data,precedence=-1)
    output_data = param.DataFrame(default=data)#,precedence=-1)
    slider_to_filter_subset1 = param.Integer(default=33,bounds=(0,100))
    slider_to_filter_subset2 = param.Integer(default=55,bounds=(0,100))
    subset_data_by_selection = param.Selector(objects=options)
    update_output_button = param.Action(lambda x: x.add_output_data(),label='Approve Interval')
    # add = param.Action(lambda x:x.add_datapoint())
    
    def __init__(self,**params):
        super().__init__(**params)
        self.output_data_widget = pn.Param(self.param.output_data)
        self.input_data_widget = pn.Param(self.param.input_data)
        self._layout = pn.Column(
            pn.Param(self,parameters=['slider_to_filter_subset1','slider_to_filter_subset2','subset_data_by_selection',
                                     'update_output_button']),
            self.output_data_widget,
            self.input_data_widget
            #self.update_params_with_selected_row_values
        )
    
    @pn.depends('output_data',watch=True)
    def _update_output_widget(self):
        self.output_data_widget.value = self.output_data
        self.output_data_widget.height = 5# int(min(len(self.output_data)/6,1)*6*self.output_data_widget.row_height) + self.output_data_widget.row_height

    def __panel__(self):
        return self._layout
    
    def add_output_data(self, event=None):
        data_tosend = self.input_data[self.input_data['first_subset'] == self.subset_data_by_selection]
        data_tosend = data_tosend[(data_tosend.slider_col >= self.slider_to_filter_subset1) & (data_tosend.slider_col < self.slider_to_filter_subset2)]
        if self.output_data is None:
            self.output_data = data_tosend
        else:
            self.output_data = self.output_data.append(data_tosend,ignore_index=True)
            

    
do_things = do_the_things()

pn.extension()

app = pn.Row(do_things.param)
app
1 Like