问题
Uploading Excel or CSV results in an error. I followed the Dash demo, but as soon as I try to extend it to do something like plotting, it doesn't work. I don't want to just show a table. The Dash_Table function was updated, so previous examples that used Dash_Table_Experiments no longer work
I've spent the whole night on stack exchange, tinkering with my code and reading other solutions. the full working code is provided below. I'd like to also add a drop down call back function to "filter" the data by a categorical variable.
import base64
import datetime
import io
import plotly.graph_objs as go
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import dash_table
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([
dcc.Upload(
id='upload-data',
children=html.Div([
'Drag and Drop or ',
html.A('Select Files')
]),
style={
'width': '100%',
'height': '60px',
'lineHeight': '60px',
'borderWidth': '1px',
'borderStyle': 'dashed',
'borderRadius': '5px',
'textAlign': 'center',
'margin': '10px'
},
# Allow multiple files to be uploaded
multiple=False
),
html.Div(id='output-data-upload'),
dcc.Graph(id='graph1')
])
def parse_contents(contents, filename):
content_type, content_string = contents.split(',')
decoded = base64.b64decode(content_string)
try:
if 'csv' in filename:
# Assume that the user uploaded a CSV file
df = pd.read_csv(
io.StringIO(decoded.decode('utf-8')))
elif 'xls' in filename:
# Assume that the user uploaded an excel file
df = pd.read_excel(io.BytesIO(decoded))
except Exception as e:
print(e)
return html.Div([
'There was an error processing this file.'
])
return html.Div([
html.H5(filename),
# html.H6(datetime.datetime.fromtimestamp(date)),
dash_table.DataTable(
data=df.to_dict('records'),
columns=[{'name': i, 'id': i} for i in df.columns]
),
html.Hr(), # horizontal line
# For debugging, display the raw contents provided by the web browser
html.Div('Raw Content'),
html.Pre(contents[0:200] + '...', style={
'whiteSpace': 'pre-wrap',
'wordBreak': 'break-all'
})
])
@app.callback(Output('output-data-upload', 'children'),
[Input('upload-data', 'contents')],
[State('upload-data', 'filename')])
def update_output(list_of_contents, list_of_names):
if list_of_contents is not None:
children = [
parse_contents(c, n) for c, n in
zip(list_of_contents, list_of_names)]
return children
@app.callback(
Output('graph1', 'figure'),
[Input('upload-data', 'contents'),
Input('upload-data', 'filename')])
def plot_graph(contents, filename):
df = parse_contents(contents, filename)
trace1 = go.Bar(
x=df['Quarter'],
y=df['Score'],
)
layout = go.Layout(
title='graph1'
)
fig = go.Figure(data = [trace1], layout=layout)
return fig
if __name__ == '__main__':
app.run_server(debug=True)
The error I get is: Callback error updating output-data-upload.children: ValueError: not enough values to unpack (expected 2, got 1)
and
AttributeError: 'NoneType' object has no attribute 'split'
The problem appears to be with how python is handling the parser:
def parse_contents(contents, filename):
content_type, content_string = contents.split(',')
decoded = base64.b64decode(content_string)
But none of the solutions seem to resolve the problem.
please help. Since so many people are struggling with this (it would seem), it would be great if we could resolve it and post a functioning code (Github?) that accomplishes what Shiny can so easily do already.
回答1:
Your code:
children = [
parse_contents(c, n) for c, n in
zip(list_of_contents, list_of_names)]
And then
def parse_contents(contents, filename):
content_type, content_string = contents.split(',')
decoded = base64.b64decode(content_string)
...
Here the error happens when c
in None, then the contents
parameter of parse_contents
is None, and the "None has no .split" error happens.
If c
is not None but only has one word, then contents.split()
returns just one element, and the "not enough values to unpack" error happens.
I would filter it:
pairs = zip(list_of_contents, list_of_names)
children = [parse_contents(c, n) for (c, n) in pairs if c and (len(c.split(',')) == 2)]
You could also consider doing the split outside parse_contents
and change the code around it.
I would also try to log the filenames for which contents wrong, e.g. [n for (c, n) in pairs if not c or len(c.split(',')) != 2])
.
回答2:
Solved it. Posting here for others to make use of:
def parse_contents(contents, filename):
if contents is not None:
content_type, content_string = contents.split(',')
decoded = base64.b64decode(content_string)
try:
if 'csv' in filename:
# Assume that the user uploaded a CSV file
df = pd.read_csv(
io.StringIO(decoded.decode('utf-8')))
elif 'xlsx' in filename:
# Assume that the user uploaded an excel file
df = pd.read_excel(io.BytesIO(decoded))
except Exception as e:
print(e)
return html.Div([
'There was an error processing this file.'
])
return df
else:
return [{}]
@app.callback(Output('table', 'data'),
[Input('upload-data', 'contents'),
Input('upload-data', 'filename')])
def update_output(contents, filename):
if contents is not None:
df = parse_contents(contents, filename)
if df is not None:
return df.to_dict('records')
else:
return [{}]
else:
return [{}]
@app.callback(
Output('graph1', 'figure'),
[Input('upload-data', 'contents'),
Input('upload-data', 'filename')])
def plot_graph(contents, filename):
df = parse_contents(contents, filename)
trace1 = go.Bar(
x=df['Quarter'],
y=df['Score'],
)
layout = go.Layout(
title='graph1'
)
fig = go.Figure(data = [trace1], layout=layout)
return fig
来源:https://stackoverflow.com/questions/57677709/why-is-dash-giving-a-parse-error-when-uploading-files