Python Dash Datatable Input - python

I am new to Python Dash, so I am not sure what is possible. What I would like to accomplish is making a datatable where the user is able to manipulate data in the datatable which would result in changes for other graphs.
Here is an example of what I am trying to do:
I have information about people’s grocery lists and I would like to be able to change what is in their list
The data that I have is structured as such
Name
Item
Amount
Bob
Apple
1
Bob
Banana
2
Anna
Apple
2
Anna
Banana
1
I would like to be able to have a datatable where there is a drop down to select the person’s list so the datatable would just have their items and amounts. In the selected datatable, they should be able to add and delete rows, change the item with a dropdown in the cell, and change the amount by typing in the desired amount.
I have been able to create this somewhat where you can select a person’s list, and the item can be changed based on a list of foods given, but I cannot figure out how to do the rest. Here is what I have been able to put together for my example
from dash import dash_table as dt
from dash import dcc
from dash import html
from dash.dependencies import Input
from dash.dependencies import Output
import pandas as pd
df = pd.DataFrame({'Name' : ['Bob', 'Bob', 'Anna', 'Anna'],
'Item' : ['Apple', 'Banana', 'Apple', 'Banana'],
'Amount' : [1, 2, 2, 1]})
app = dash.Dash(__name__)
names = df['Name'].unique().tolist()
app.layout = html.Div(
children=[
dcc.Dropdown(
id="filter_dropdown",
options=[{"label": name, "value": name} for name in names],
placeholder="-Select a Person-",
multi=False,
value=df['Name'].values,
),
dt.DataTable(
id="table-container",
columns=[{'name': 'Item', 'id': 'Item', 'presentation':'dropdown'},
{'name': 'Amount', 'id': 'Amount'}],
data=df.to_dict("records"),
editable=True,
dropdown={
'Item' : {
'options': [
{'label': i, 'value': i}
for i in list(df['Item'].unique())
]
}
}
)
]
)
#app.callback(
Output("table-container", "data"),
Input("filter_dropdown", "value"))
def display_table(name):
dff = df[df['Name'].isin([name])]
return dff.to_dict("records")
if __name__ == "__main__":
app.run_server(debug=True)
Here is what it looks like in the browser
I can select an item from the dropdown in the cell and it will appear to change it, but when I switch between people it loses its change that I made. I also cannot change the amounts. The next step would be is if I can use all the data as well as the changes in the data in another graph (for example to show how many people have a certain item). If someone would be able to help, I would appreciate it.
Thanks

After doing some work, I do have it working. It is by means not the optimal or right way to do this probably, but it does what I want it to do. I ended up using a global variable for my data frame and whenever there was a change to the data table, I would filter out the data from the data frame and add the modified data from the data table.
To get a visual to work, I listen for changes to the data table. I know that there is probably a race between updating the global variable and returning the new visual, but I did not have to delay the visual being updated.
Below is the code that I came up with. I hope this helps others somehow if they wanted to make a simple "spreadsheet" that people can manipulate and result in a responsive visual related to the data.
import dash
import pandas as pd
from dash import dash_table as dt
from dash import dcc
from dash import html
from dash.dependencies import Input
from dash.dependencies import Output
import plotly.express as px
df = pd.DataFrame({'Name' : ['Bob', 'Bob', 'Anna', 'Anna'],
'Item' : ['Apple', 'Banana', 'Apple', 'Banana'],
'Amount' : [1, 2, 2, 1]})
app = dash.Dash(__name__)
names = df['Name'].unique().tolist()
app.layout = html.Div(
children=[
dcc.Dropdown(
id="filter_dropdown",
options=[{"label": name, "value": name} for name in names],
placeholder="-Select a Person-",
multi=False,
value=df['Name'].values,
),
dt.DataTable(
id="table-container",
columns=[{'name': 'Item', 'id': 'Item', 'presentation':'dropdown'},
{'name': 'Amount', 'id': 'Amount'}],
data=df.to_dict("records"),
editable=True,
row_deletable=True,
dropdown={
'Item' : {
'options': [
{'label': i, 'value': i}
for i in list(df['Item'].unique())
]
}
}
),
html.Button('Add', id='add_btn', n_clicks = 0),
dcc.Graph(id='visual')
]
)
#app.callback(
Output("table-container", "data"),
Input("filter_dropdown", "value"),
Input("table-container", "data"),
Input("table-container", "columns"),
Input("add_btn", 'n_clicks'))
def display_table(name, rows, columns, n_clicks):
e = dash.callback_context.triggered[0]['prop_id']
global df
if e in ["table-container.data", "table-container.columns"]:
temp = pd.DataFrame(rows, columns=[c['name'] for c in columns])
temp['Name'] = name
df = df[~df['Name'].isin([name])]
df = df.append(temp)
elif e == 'add_btn.n_clicks':
if n_clicks > 0:
df = df.append(pd.DataFrame({'Name':name, 'Item': '', 'Amount': 0}, index=[0]))
dff = df[df['Name'].isin([name])].to_dict("records")
return dff
#app.callback(Output('visual', 'figure'),
Input("table-container", "data"),
Input("table-container", "columns"))
def display_graph(rows, columns):
fig = px.bar(df, x='Item', y='Amount')
return fig
if __name__ == "__main__":
app.run_server(debug=True)

Related

Filtering a column on Dash dataTable based on a list

I am very new to Dash. I have made a dataTable that includes several columns. These columns can be filtered and sorted. However, one problem with the filtering is that I cannot filter based on a list (like pandas .loc) e.g. if I want to filter the countries based on a list (say, ['India', 'United States']), the filter does not work. I have previously checked the advanced filtering here and found that I can use || operators; however,this would not be a good choice if the list is more than 4 or 5.
Here's the code:
import dash
from dash.dependencies import Input, Output
import dash_table
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import json
df = pd.read_csv(
'https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv')
app = dash.Dash(__name__)
app.layout = html.Div([
html.Div(id='heading-users', children='Users\' Country details', style={
'textAlign': 'center', 'font-family': 'Helvetica'}),
dash_table.DataTable( # users
id='datatable-users',
columns=[
{"name": i, "id": i, "deletable": True, "selectable": True} for i in df.columns
],
data=df.to_dict('records'),
editable=True,
filter_action="native",
sort_action="native",
sort_mode="multi",
column_selectable="single",
row_selectable="multi",
row_deletable=True,
selected_columns=[],
selected_rows=[],
page_action="native",
page_current=0,
page_size=20,
export_format='csv'
),
html.Div(id='datatable-users-container')
])
#app.callback(
Output('datatable-users-container', "children"),
Input('datatable-users', "derived_virtual_data"),
Input('datatable-users', "derived_virtual_selected_rows"))
def update_graphs(rows, derived_virtual_selected_rows):
if derived_virtual_selected_rows is None:
derived_virtual_selected_rows = []
dff = df if rows is None else pd.DataFrame(rows)
colors = ['#7FDBFF' if i in derived_virtual_selected_rows else '#0074D9'
for i in range(len(dff))]
return [
dcc.Graph(
id=column,
figure={
"data": [
{
"x": dff["country"],
"y": dff[column],
"type": "bar",
"marker": {"color": colors},
}
],
"layout": {
"xaxis": {"automargin": True},
"yaxis": {
"automargin": True,
"title": {"text": column}
},
"height": 250,
"margin": {"t": 10, "l": 10, "r": 10},
},
},
)
# check if column exists - user may have deleted it
# If `column.deletable=False`, then you don't
# need to do this check.
for column in ["pop", "lifeExp", "gdpPercap"] if column in dff
]
if __name__ == '__main__':
app.run_server(debug=True)
From that link - The 'native' filter function doesn't support 'OR' operations within a single column. Assign filter_action="custom" then create a callback to update the table children. See the 'Back-End Filtering section of that link.
You'll need to grab the filter query string in a callback and decompose to extract the column name and query string. With that you can query the pandas dataframe and return the results in a callback. I don't have the code for "OR" functionality but found some I used that can query pandas once you have the input values
def filter_df(df, filter_column, value_list):
conditions = []
for val in value_list:
conditions.append(f'{filter_column} == "{val}"')
query = ' or '.join(conditions)
print(f'querying with: {query}')
return df.query(query_expr)
filter_df(df, 'country', ['Albania', 'Algeria'])

Error updating graph invalid component prop figure

Just creating a simple Graph, from an excel file, it loads correctly but at the moment of updating the graph dash gives back this error:
Failed component prop type: Invalid component prop figure key props supplied to Graph.
Bad object: {
“props”: {
“id”: “average_country”,
“figure”: {
“data”: [
{
“x”: [
“DE”,
“ES”,
“FR”,
“IT”,
“UK”
],
“y”: [
[
2365.56,
4528.33875,
4851.085,
4325.14,
2107.921428571429
]
],
“type”: “bar”
}
],
“layout”: {
“title”: “Hyperzone”
}
}
},
“type”: “Graph”,
“namespace”: “dash_core_components”
}
Valid keys: [
“data”,
“layout”,
“frames”
]
I’m just using a pivot table to create the mean of data of some countries, here’s my code, the pivot table is correct and outputs a valid result with the values I want, just when I try to update the graph with the callback function it returns an Error message.
import pandas as pd
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
from dash.dependencies import Input, Output
HP= pd.read_excel(r’C:\Users\salinejs\HP.xlsx’)
Columns = [‘Station’, ‘Week’, ‘Owner’, ‘Cost’,‘Country’]
HP_filtered = HP[Columns]
avaliable_weeks= HP_filtered[‘Week’].unique()
external_stylesheets = [‘https://codepen.io/chriddyp/pen/bWLwgP.css’]
app = dash.Dash(name, external_stylesheets=external_stylesheets)
app.layout = html.Div([
html.Div([
html.Div([
html.Div([
dcc.Dropdown(
id= 'Week_filter',
options = [{'label':j, 'value':j}for j in avaliable_weeks],
value= 'Week 42'
),
],
style= {'float':'left', 'display':'inline-block'}
),
html.Div([
dcc.Graph(
id='average_country',
)
],
)
]
),
],
)
#app.callback(
Output(‘average_country’, ‘figure’),
[Input(‘Week_filter’,‘value’)]
)
def update_value(week):
HP_filtered_week = HP_filtered[ HP_filtered['Week'] == week ]
pv = pd.pivot_table(HP_filtered_week, values='Cost', columns='Country', index='Week')
print(pv)
print(pv.columns)
return dcc.Graph(
id='average_country',
figure={
'data': [{'x': pv.columns , 'y' : pv.values,'type': 'bar'
}],
'layout':{
'title': 'Cost',
}
})
if name == ‘main’:
app.run_server(debug=True)
A bit late - you probably sorted this by now, but I just had the same error and an answer here would have saved me time.
It's because you are sending the callback output to figure but the function is returning the entire dcc.Graph() instead of just the figure.
change
return dcc.Graph(
id='average_country',
figure={
'data': [{'x': pv.columns , 'y' : pv.values,'type': 'bar'
}],
'layout':{
'title': 'Cost',
}
})
to
return {'data': [{'x': pv.columns , 'y' : pv.values,'type': 'bar'
}],
'layout':{
'title': 'Cost',},}

Changing from scattermapbox to densitymapbox causes error

I was looking for a solution to this issue I encountered. There is a question almost similar to mine in plotly community (https://community.plot.ly/t/problem-with-densitymapbox-chart-type/28517), but still haven’t found a resolution. My dropdown menu consists of scattermapbox and densitymapbox as i wanted to juggle between these. However, when changing from scattermapbox to densitymapbox, it results to the image below:
densitymapbox after scattermapbox format
import dash
import copy
import pathlib
import dash
import numpy as np
import math
import datetime as dt
import pandas as pd
from dash.dependencies import Input, Output, State, ClientsideFunction
import dash_core_components as dcc
import dash_html_components as html
import dash_table
import plotly.graph_objs as go
# get relative data folder
PATH = pathlib.Path(__file__).parent
DATA_PATH = PATH.joinpath("data").resolve()
external_scripts = [
‘https://cdn.plot.ly/plotly-1.39.1.min.js’
]
external_stylesheets = [
‘https://codepen.io/etpinard/pen/wxwPXJ’
]
app = dash.Dash(
__name__,
external_scripts=external_scripts,
external_stylesheets=external_stylesheets
)
server = app.server
# Load data
df = pd.read_excel("Clean_TR(6.8.19).xlsx")
group_name = df['gname'].unique()
mapbox_access_token = <your token>
app.layout = html.Div(
[
dcc.Store(id = 'aggregate_data'),
dcc.Dropdown(
id = 'map_plot',
options = [{'label':i, "value":i} for i in ['Scatter', 'Density']],
value = ['Scatter']
),
dcc.Graph(id = 'mindanao-map')
]
)
#app.callback(
Output('mindanao-map', 'figure'),
[Input('map_plot', 'value')]
)
def update_map(map_plot):
if map_plot == "Density":
maptype = 'densitymapbox'
else:
maptype = 'scattermapbox'
return {
'data' : [{
'lat':df['latitude'],
'lon':df['longitude'],
'marker':{
'color': df['freq'],
'size': 8,
'opacity': 0.6
},
'customdata': df['idno'],
'type': maptype
}],
'layout': {
'mapbox': {
'accesstoken': mapbox_access_token,
'style':"light",
'center': dict(lon=123.30, lat= 7.50),
'zoom':'6',
},
'hovermode': 'closest',
'margin': {'l': 0, 'r': 0, 'b': 0, 't': 0}
}
}
if __name__ == "__main__":
app.run_server(debug=True)
But whenever I swap out the if-else ordering, i.e,
if map_plot == "Scatter":
maptype = 'scattermapbox'
else:
maptype = 'densitymapbox'
it results to density map showing, but scatter will not.
Do I need to separate these two instead of if-else? Any inputs will do. Thank you for your time!
This is a Plotly.js bug, and I've filed a report here: https://github.com/plotly/plotly.js/issues/4285
Edit: this bug is fixed in recent versions of Plotly.js and Plotly.py and Dash.

How do I return a dataframe from an app.callback with my as output as dbc.Table.from_dataframe

I am trying to have a table load upon clicking a search button, and provide ID/password. My output is a dbc.Table.from_dataframe which allows an df argument; however when I use this as my output property, I am getting an error.
Here are the available properties in "my_table":
['children', 'id', 'style', 'className', 'key', 'tag', 'size', 'bordered', 'borderless', 'striped', 'dark', 'hover', 'responsive', 'loading_state']
I read the docs here
https://dash-bootstrap-components.opensource.faculty.ai/l/components/table
I tried such using 'children' and that didn't work either. I know with dcc table I need to return a dictionary, however I thought with dbc.Table.from_dataframe I can return a dataframe.
#app.callback(Output('my_table', 'df' ),
[Input('search-button','n_clicks')],
[State('input-id', 'value'),
State('input-password', 'value')]
)
def search_fi(n_clicks, iuser, ipasw):
if n_clicks > 0:
df = pd.DataFrame(
{
"First Name": ["Arthur", "Ford", "Zaphod", "Trillian"],
"Last Name": ["Dent", "Prefect", "Beeblebrox", "Astra"],
}
return df
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
import pandas as pd
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
html.Button('Table', id='table-but', n_clicks=0),
html.Div(id='container-button-basic')
])
#app.callback(
dash.dependencies.Output('container-button-basic', 'children'),
[dash.dependencies.Input('table-but', 'n_clicks')])
def search_fi(n_clicks):
if n_clicks > 0:
df = pd.DataFrame(
{
"First Name": ["Arthur", "Ford", "Zaphod", "Trillian"],
"Last Name": ["Dent", "Prefect", "Beeblebrox", "Astra"]
})
print(df)
return dbc.Table.from_dataframe(df)
if __name__ == '__main__':
app.run_server(debug=True)
or
https://plotly.com/python/table/ figure
Html dash table html-table
Example

Dash: Creating a dropdown per column, instead of a dropdown per table

I have a data set that looks like this:
cat_id author year publisher country value (dollars)
name1 kunga 1998 D and D Australia 10
name2 siba 2001 D and D UK 20
name3 siba 2001 D and D US 20
name3 shevara 2001 D and D UK 10
name3 dougherty 1992 D and D Australia 20
name4 ken 2011 S and K Australia 10
The aim, to produce a Multi-Select Dropdown menu PER COLUMN, not of all columns (because the per-column filter that i currently do is not sufficient, I need to be able to filter by multiple items per column in one go).
Using the above example, this would add a cell just under cat_id with name1,2,3,4; author would have a dropdown with kunga, siba, shevara and dougherty; year would have a dropdown with 1992,1998,2001,2011 etc.
I wrote the below code:
import dash
from dash.dependencies import Input, Output, State
import dash_table
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
app = dash.Dash(__name__)
df = pd.read_excel('dash_test_doc.xlsx')
app.layout = html.Div([
html.Div([
dcc.Input(
id='adding-rows-name',
placeholder='Enter a column name...',
value='',
style={'padding': 10},),html.Button('Add Column', id='adding-rows-button', n_clicks=0)], style={'height': 50}),
#Need to move this to being within each column?
html.Label('Multi-Select Dropdown'),
dcc.Dropdown(options=[{'label':i,'value':i} for i in df.columns],
value = [i for i in df.columns],
multi=True),
dash_table.DataTable(
id='adding-rows-table',
columns=[{"name": i, "id": i,"deletable":True} for i in df.columns],
# column_conditional_dropdowns=[{'id':i,'dropdowns':df[i]} for i in df.columns],
data = df.to_dict('rows'),
editable=True,
filtering=True,
sorting=True,
sorting_type="multi",
row_selectable="multi",
row_deletable=True,
selected_rows=[],
pagination_mode="fe",
style_cell_conditional=[
{
'if': {'row_index': 'odd'},
'backgroundColor': 'rgb(230, 255, 230)'
}
] + [
{
'if': {'column_id': c},
'textAlign': 'left'
} for c in ['Date', 'Region']
],
style_header={
'backgroundColor': 'white',
'fontWeight': 'bold'
}
),
html.Button('Add Row', id='editing-rows-button', n_clicks=0),
dcc.Graph(id='adding-rows-graph')
])
#app.callback(
Output('adding-rows-table', 'data'),
[Input('editing-rows-button', 'n_clicks')],
[State('adding-rows-table', 'data'),
State('adding-rows-table', 'columns')])
def add_row(n_clicks, rows, columns):
if n_clicks > 0:
rows.append({c['id']: '' for c in columns})
return rows
#app.callback(
Output('adding-rows-table', 'columns'),
[Input('adding-rows-button', 'n_clicks')],
[State('adding-rows-name', 'value'),
State('adding-rows-table', 'columns')])
def update_columns(n_clicks, value, existing_columns):
if n_clicks > 0:
existing_columns.append({
'id': value, 'name': value,
'editable_name': True, 'deletable': True
})
return existing_columns
#app.callback(
Output('adding-rows-graph', 'figure'),
[Input('adding-rows-table', 'data'),
Input('adding-rows-table', 'columns')])
def display_output(rows, columns):
return {
'data': [{
'type': 'heatmap',
'z': [[row.get(c['id'], None) for c in columns] for row in rows],
'x': [c['name'] for c in columns]
}]
}
if __name__ == '__main__':
app.run_server(debug=True)
There are two specific issues with this method:
The dropdown is being done at the table level, not the per-column level.
I want to then filter the table to only display what is in the dropdown bar, which is currently not being done.
I know that the parts of the code that are the issue are:
#Need to move this to being within each column?
html.Label('Multi-Select Dropdown'),
dcc.Dropdown(options=[{'label':i,'value':i} for i in df.columns],
value = [i for i in df.columns],
multi=True),
and that there is no callback, but I'm new to dash, and I'm struggling to understand what specifically I should do. If someone could show me how to fix these two issues in this code, I would appreciate it.
As a side note/disclaimer, I had originally asked this question as part of a much bigger question here, but I have realised that I need to make my questions more code-specific, and more specific in general, so I'm going to work through each issue individually.
Thanks, Kela, for making this question a little more specific. This still a good size bite, so I'll see if I can help with all of it.
First thing is you need to change the column defintion in the table to have 'presentation': 'dropdown' in the dictionary for each column you want to show up as a dropdown. The current dropdown isn't working inside the table because you've used the dcc.Dropdown component, which is a separate component from the table. You'll need to use column_static_dropdown or column_conditional_dropdowns (which I see you've commented out) if you want them to work inside the table.
I'm not sure that the dropdowns inside the table can be used as part of a callback, though. I tried to hook one up, and the ID of the dropdown was not recognized as part of the layout. To do what you want would - I think - require setting up multiple dropdowns using dcc.Dropdown and hooking those values up as input to the callback that updates the table's data prop.
Here is a little example that I've confirmed works, with some limitations:
app = dash.Dash(__name__)
df = pd.DataFrame(np.arange(30).reshape(5, 6))
app.layout = html.Div([
html.Div([
dcc.Input(
id='adding-rows-name',
placeholder='Enter a column name...',
value='',
style={'padding': 10}, ),
html.Button('Add Column', id='adding-rows-button', n_clicks=0)], style={'height': 50}),
# Need to move this to being within each column?
html.Label('Multi-Select Dropdown'),
dcc.Dropdown(
id='test-dropdown',
options=[{'label': i, 'value': i} for i in df[0]],
value=[i for i in df[0]],
multi=True),
dash_table.DataTable(
id='adding-rows-table',
columns=[{"name": i, "id": i, "deletable": True} for i in df.columns],
data=df.to_dict('rows'),
editable=True,
filtering=True,
sorting=True,
sorting_type="multi",
row_selectable="multi",
row_deletable=True,
selected_rows=[],
pagination_mode="fe",
style_cell_conditional=[
{
'if': {'row_index': 'odd'},
'backgroundColor': 'rgb(230, 255, 230)'
}
] + [
{
'if': {'column_id': c},
'textAlign': 'left'
} for c in ['Date', 'Region']
],
style_header={
'backgroundColor': 'white',
'fontWeight': 'bold'
}
),
html.Button('Add Row', id='editing-rows-button', n_clicks=0),
dcc.Graph(id='adding-rows-graph'),
])
#app.callback(
Output('adding-rows-table', 'data'),
[Input('editing-rows-button', 'n_clicks'),
Input('test-dropdown', 'value')],
[State('adding-rows-table', 'data'),
State('adding-rows-table', 'columns')])
def add_row(n_clicks, dropdown_value, rows, columns):
if n_clicks > 0:
rows.append({c['id']: '' for c in columns})
df = pd.DataFrame.from_dict(rows)
try:
df_filtered = df[df['0'].isin(dropdown_value)]
except TypeError:
df_filtered = df[df['0'].eq(dropdown_value)]
return df_filtered.to_dict(orient='records')
There is only one dropdown right now, and it works with the values in the first column. You would need to add more, and it would get complicated quickly. You could probably style the dropdowns so they're more clearly associated with the related columns .
There is also the problem of what to do with rows once you've filtered them out. The callback pulls in the State data from the table but, if a row was filtered out, then it's not in the state and can't be added back in by making that selection in the dropdown again. You would need a different way to handle storing the data for the table so that rows aren't erased this way.

Categories