Dash DataTable + pandas: dash.exceptions.InvalidCallbackReturnValue when updating children

I just discovered Dash couple of days ago, it looks amazing!. I began learning by watching a few youtube videos and following the example code here:

specifically the last section: Connecting Backend Paging with a Graph
I apply this to my own data, keeping the framework exactly the same, and I get :

dash.exceptions.InvalidCallbackReturnValue: The callback for property children of component graphs returned a value which is not JSON serializable. In general, Dash properties can only be dash components, strings, dictionaries, numbers, None, or lists of those.

The part of the code which updates the graph upon user interaction with the data table, iterates through the columns of the dataframe and makes a plot for each of them. In my case, however, there is only one column to iterate over so the output should be only one graph (nonetheless I have kept the code format the same, I figured it shouldn’t matter). This is the only difference I can think of between the example code and my code.
I’ve been staring at it and researching for the third day now and I don’t see where my mistake is. I have never made a web app before, so not sure how to debug either. Obviously I am outputting a wrong graph, how do I find out what the graph looks like to being to see why it is wrong?

The structure of the dataframe I am plotting is:

Index | month | year | SumOrder
0 1 2019 2033
1 1 2020 1232
2 2 2019 221
3 2 2020 292

In principle, the graph should just represent a bar plot of the SumOrder for each month for both 2019 and 2020.

My code is below, I added a few comments for clarification. The error seems to be in the last callback.I have showed the preprocessing of the data as well, in case there is the source of the error.

date upload and preprocessing

data = pd.read_excel(‘Test.xls’)

make columns for month and year

data[‘Date Order’] = data[‘Date Order’].apply(dateutil.parser.parse, dayfirst = True)
data[‘month’] = data[‘Date Order’].dt.month.astype(‘category’)
data[‘year’] = data[‘Date Order’].dt.year.astype(‘category’)
data[‘date’] = data[‘Date Order’].dt.date.astype(‘category’)

group by month and year and find the mean for each month and year

groupedMonthYear = data.groupby([‘month’, ‘year’])
df = groupedMonthYear.agg({‘SumOrder’: ‘mean’}).fillna(0).reset_index() <---- this is the dataframe used

app = dash.Dash(name)

page_size = 5

from datetime import datetime as dt

app.layout = html.Div(
className = ‘row’,
children = [
html.H1(‘data visualised’),

    html.Br(),

    html.Div(       
        dash_table.DataTable(
            id = 'table',
            columns = [
                {'name': i, 'id': i} for i in sorted(df.columns)],
            page_current = 0,
            page_size = 20,
            page_action = 'custom',
            
            filter_action = 'custom',
            filter_query = '',
    
            sort_action = 'custom',
            sort_mode = 'multi',
            sort_by = []
        ),                          
        style = {'height': 750, 'overflowY': 'scroll'},
        className = 'three columns'
    ),    
    
    html.Br(),
    
    html.Div(          
        id = 'graphs',
        className = 'one column'
    ),
  ]

)

operators = [['ge ', ‘>=’],
['le ', ‘<=’],
['lt ', ‘<’],
['gt ', ‘>’],
['ne ', ‘!=’],
['eq ', ‘=’],
['contains '],
['datestartswith ']]

def split_filter_part(filter_part):
for operator_type in operators:
for operator in operator_type:
if operator in filter_part:
name_part, value_part = filter_part.split(operator, 1)
name = name_part[name_part.find(‘{’) + 1: name_part.rfind(‘}’)]

            value_part = value_part.strip()
            v0 = value_part[0]
            if (v0 == value_part[-1] and v0 in ("'", '"', '`')):
                value = value_part[1: -1].replace('\\' + v0, v0)
            else:
                try:
                    value = float(value_part)
                except ValueError:
                    value = value_part

            # word operators need spaces after them in the filter string,
            # but we don't want these later
            return name, operator_type[0].strip(), value
return [None]*3   

@app.callback(
Output(component_id=‘table’, component_property = ‘data’),
[Input(component_id=‘table’, component_property = ‘page_current’),
Input(component_id=‘table’, component_property = ‘page_size’),
Input(component_id=‘table’, component_property = ‘sort_by’),
Input(component_id=‘table’, component_property = ‘filter_query’)
])
def update_table(page_current, page_size, sort_by, filter):
filtering_expressions = filter.split(’ && ')
dff = df ## make a new variable with the data so we don’t change original data
for filter_part in filtering_expressions:
## extract all we need to know about the filter we just applied
col_name, operator, filter_value = split_filter_part(filter_part)

    if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
        # these operators match pandas series operator method names
        dff = dff.loc[dff[col_name].str.contains(filter_value)]
    elif operator == 'contains':
        dff = dff.loc[dff[col_name].str.contains(filter_value)]
    elif operator == 'datestartswith':
        # this is a simplification of the front-end filtering logic,
        # only works with complete fields in standard format
        dff = dff.loc[dff[col_name].str.startswith(filter_value)]
        
if len(sort_by):
    dff = dff.sort_values(
        [col['column_id'] for col in sort_by],
        ascending =[
            col['direction'] == 'asc'
            for col in sort_by
            ],
            inplace = True
        )

return dff.iloc[
    page_current*page_size: (page_current + 1)*page_size
    ].to_dict('records')

@app.callback(
Output(component_id=‘graphs’,component_property = ‘children’),
[Input(component_id=‘table’,component_property =‘data’)])
def update_graph(rows):
dff = pd.DataFrame(rows)
return html.Div(
[
dcc.Graph(
id=column,
figure={
‘data’: [
{
‘x’: dff[‘month’],
‘y’: dff[column] if column in dff else ,
‘type’: ‘bar’,
‘marker’: {‘colour’,‘#0074D9’},
}
],

                'layout': {
                    'xaxis': {'automargin': True},
                    'yaxis': {'automargin': True},
                    'height': 250,
                    'margin': {'t':10, 'l': 10, 'r': 10},
                },
            },
        )
        for column in ['SumOrder']           ## one graph for each column 
    ]
)

if name == ‘main’:
app.run_server(debug=True)