Bokeh - Pandas 无法从 JS 读取 excel 文件的 bytesIO 对象
Bokeh - Pandas not able to read bytesIO object of excel file from JS
几天来我面临的一些挑战需要你的意见。
我的目标是有一个上传按钮,我从中共享 .xlsx
文件和 2 张。加载此数据并将其读入 pandas DataFrame
后,我执行一些 pythonic calculations/optimisation 代码等并得到很少的结果(表格总结)。现在根据唯一 'levels'/'groups' 的数量,我将创建那么多选项卡,然后在每个选项卡中显示此汇总结果。主页上也有一个通用的情节。
以下是我的努力(不是我的,而是社区的:)):
1。上传按钮代码:(来自 here)
## Load library
###########################################################################
import pandas as pd
import numpy as np
from xlrd import XLRDError
import io
import base64
import os
from bokeh.layouts import row, column, widgetbox, layout
from bokeh.models import ColumnDataSource, CustomJS, LabelSet
from bokeh.models.widgets import Button, Div, TextInput, DataTable, TableColumn, Panel, Tabs
from bokeh.io import curdoc
from bokeh.plotting import figure
###########################################################################
## Upload Button Widget
file_source = ColumnDataSource({'file_contents':[], 'file_name':[]})
cds_test = ColumnDataSource({'v1':[], 'v2':[]})
def file_callback(attr,old,new):
global tabs, t
print('filename:', file_source.data['file_name'])
raw_contents = file_source.data['file_contents'][0]
prefix, b64_contents = raw_contents.split(",", 1)
file_contents = base64.b64decode(b64_contents)
file_io = io.BytesIO(file_contents)
# Here it errors out when trying '.xlsx' file but work for .csv Any Idea ????
#df1 = pd.read_excel(file_io, sheet = 'Sheet1')
#df2 = pd.read_excel(file_io, sheet = 'Sheet2')
# call some python functions for analysis
# returns few results
# for now lets assume main_dt has all the results of analysis
df1 = pd.read_excel(file_path, sheet_name = 'Sheet1')
df2 = pd.read_excel(file_path, sheet_name = 'Sheet2')
main_dt = pd.DataFrame({'v1':df1['v1'], 'v2': df2['v2']})
level_names = main_dt['v2'].unique().tolist()
sum_v1_level = []
for i in level_names:
csd_temp = ColumnDataSource(main_dt[main_dt['v2'] == i])
columns = [TableColumn(field=j, title="First") for j in main_dt.columns]
dt = DataTable(source = csd_temp, columns = columns, width=400, height=280)
temp = Panel(child = dt, title = i)
t.append(temp)
sum_v1_level.append(sum(csd_temp.data['v1']))
tabs = Tabs(tabs = t)
cds_plot = ColumnDataSource({'x':level_names, 'y':sum_v1_level})
p_o = figure(x_range = level_names, plot_height=250, title="Plot")
p_o.vbar(x='x', top = 'y', width=0.9, source = cds_plot)
p_o.xgrid.grid_line_color = None
p_o.y_range.start = 0
p_o.y_range.end = max(sum_v1_level)*1.2
labels_o = LabelSet(x='x', y = 'y', text='y', level='glyph',
x_offset=-13.5, y_offset=0, render_mode='canvas', source = cds_plot)
p_o.add_layout(labels_o)
curdoc().add_root(p_o)
curdoc().add_root(tabs)
print('successful upload')
file_source.on_change('data', file_callback)
button = Button(label="Upload Data", button_type="success")
# when butotn is clicked, below code in CustomJS will be called
button.callback = CustomJS(args=dict(file_source=file_source), code = """
function read_file(filename) {
var reader = new FileReader();
reader.onload = load_handler;
reader.onerror = error_handler;
// readAsDataURL represents the file's data as a base64 encoded string
reader.readAsDataURL(filename);
}
function load_handler(event) {
var b64string = event.target.result;
file_source.data = {'file_contents' : [b64string], 'file_name':[input.files[0].name]};
file_source.trigger("change");
}
function error_handler(evt) {
if(evt.target.error.name == "NotReadableError") {
alert("Can't read file!");
}
}
var input = document.createElement('input');
input.setAttribute('type', 'file');
input.onchange = function(){
if (window.FileReader) {
read_file(input.files[0]);
} else {
alert('FileReader is not supported in this browser');
}
}
input.click();
""")
Bdw:有什么方法可以抑制此警告,还是我做错了?(将读取列插入 CDS 时)
BokehUserWarning: ColumnDataSource's columns must be of the same length. Current lengths: ('v1', 19), ('v2', 0)
2。添加到布局
curdoc().title = 'Test Joel'
curdoc().add_root(button)
下面是输出:
这是原始数据:
注意:这里分享的所有数据都是虚拟的,真实案例有更多的表和更多的维度。
总结一下:
无法通过上传按钮读取 .xlsx
文件
在按钮回调函数中执行所有步骤是否正确?
目前以这种方式使用 CDS 的总体思路是合理的。未来应该会有更好的机制,但具体什么时候实施我不敢说。关于 read_excel
的错误,那是 Pandas issue/question,而不是散景。
至于列长的警告,几乎可以肯定是使用问题。它告诉您 v2
列是空的,这似乎不是您想要的,并且还违反了所有 CDS 列的长度始终相同的基本假设。我不确定你为什么要为 v2
生成一个空列表,而不能 运行 代码。
编辑:例如,如果所有内容的长度都相同,则一个一个地添加就可以正常工作:
In [4]: s.add([1,2,3], 'foo')
Out[4]: 'foo'
In [5]: s.add([1,2,3], 'bar')
Out[5]: 'bar'
只有当您添加的内容长度不正确时才会出现问题,错误消息指出:
In [6]: s.add([], 'baz')
/Users/bryanv/work/bokeh/bokeh/models/sources.py:138: BokehUserWarning: ColumnDataSource's columns must be of the same length. Current lengths: ('bar', 3), ('baz', 0), ('foo', 3)
"Current lengths: %s" % ", ".join(sorted(str((k, len(v))) for k, v in data.items())), BokehUserWarning))
Out[6]: 'baz'
如果您前面没有列的数据,请不要放入 "empty list" 作为占位符,否则一旦放入实际列,长度就会不一致。这就是你的问题的原因。
对于那些您将来会参考此线程的人:这是一个使用上传按钮来处理 .xlsx
文件的解决方案。这是为了 python3
我只分享主要的数据处理代码。其他一切如上。
import pandas as pd
import io
import base64
def file_callback_dt1(attr,old,new):
print('filename:', file_source_dt1.data['file_name'])
raw_contents = file_source_dt1.data['file_contents'][0]
prefix, b64_contents = raw_contents.split(",", 1)
file_contents = base64.b64decode(b64_contents)
file_io = io.BytesIO(file_contents)
excel_object = pd.ExcelFile(file_io, engine='xlrd')
dt_1 = excel_object.parse(sheet_name = 'Sheet1', index_col = 0)
# rest is upto you :)
几天来我面临的一些挑战需要你的意见。
我的目标是有一个上传按钮,我从中共享 .xlsx
文件和 2 张。加载此数据并将其读入 pandas DataFrame
后,我执行一些 pythonic calculations/optimisation 代码等并得到很少的结果(表格总结)。现在根据唯一 'levels'/'groups' 的数量,我将创建那么多选项卡,然后在每个选项卡中显示此汇总结果。主页上也有一个通用的情节。
以下是我的努力(不是我的,而是社区的:)):
1。上传按钮代码:(来自 here)
## Load library
###########################################################################
import pandas as pd
import numpy as np
from xlrd import XLRDError
import io
import base64
import os
from bokeh.layouts import row, column, widgetbox, layout
from bokeh.models import ColumnDataSource, CustomJS, LabelSet
from bokeh.models.widgets import Button, Div, TextInput, DataTable, TableColumn, Panel, Tabs
from bokeh.io import curdoc
from bokeh.plotting import figure
###########################################################################
## Upload Button Widget
file_source = ColumnDataSource({'file_contents':[], 'file_name':[]})
cds_test = ColumnDataSource({'v1':[], 'v2':[]})
def file_callback(attr,old,new):
global tabs, t
print('filename:', file_source.data['file_name'])
raw_contents = file_source.data['file_contents'][0]
prefix, b64_contents = raw_contents.split(",", 1)
file_contents = base64.b64decode(b64_contents)
file_io = io.BytesIO(file_contents)
# Here it errors out when trying '.xlsx' file but work for .csv Any Idea ????
#df1 = pd.read_excel(file_io, sheet = 'Sheet1')
#df2 = pd.read_excel(file_io, sheet = 'Sheet2')
# call some python functions for analysis
# returns few results
# for now lets assume main_dt has all the results of analysis
df1 = pd.read_excel(file_path, sheet_name = 'Sheet1')
df2 = pd.read_excel(file_path, sheet_name = 'Sheet2')
main_dt = pd.DataFrame({'v1':df1['v1'], 'v2': df2['v2']})
level_names = main_dt['v2'].unique().tolist()
sum_v1_level = []
for i in level_names:
csd_temp = ColumnDataSource(main_dt[main_dt['v2'] == i])
columns = [TableColumn(field=j, title="First") for j in main_dt.columns]
dt = DataTable(source = csd_temp, columns = columns, width=400, height=280)
temp = Panel(child = dt, title = i)
t.append(temp)
sum_v1_level.append(sum(csd_temp.data['v1']))
tabs = Tabs(tabs = t)
cds_plot = ColumnDataSource({'x':level_names, 'y':sum_v1_level})
p_o = figure(x_range = level_names, plot_height=250, title="Plot")
p_o.vbar(x='x', top = 'y', width=0.9, source = cds_plot)
p_o.xgrid.grid_line_color = None
p_o.y_range.start = 0
p_o.y_range.end = max(sum_v1_level)*1.2
labels_o = LabelSet(x='x', y = 'y', text='y', level='glyph',
x_offset=-13.5, y_offset=0, render_mode='canvas', source = cds_plot)
p_o.add_layout(labels_o)
curdoc().add_root(p_o)
curdoc().add_root(tabs)
print('successful upload')
file_source.on_change('data', file_callback)
button = Button(label="Upload Data", button_type="success")
# when butotn is clicked, below code in CustomJS will be called
button.callback = CustomJS(args=dict(file_source=file_source), code = """
function read_file(filename) {
var reader = new FileReader();
reader.onload = load_handler;
reader.onerror = error_handler;
// readAsDataURL represents the file's data as a base64 encoded string
reader.readAsDataURL(filename);
}
function load_handler(event) {
var b64string = event.target.result;
file_source.data = {'file_contents' : [b64string], 'file_name':[input.files[0].name]};
file_source.trigger("change");
}
function error_handler(evt) {
if(evt.target.error.name == "NotReadableError") {
alert("Can't read file!");
}
}
var input = document.createElement('input');
input.setAttribute('type', 'file');
input.onchange = function(){
if (window.FileReader) {
read_file(input.files[0]);
} else {
alert('FileReader is not supported in this browser');
}
}
input.click();
""")
Bdw:有什么方法可以抑制此警告,还是我做错了?(将读取列插入 CDS 时)
BokehUserWarning: ColumnDataSource's columns must be of the same length. Current lengths: ('v1', 19), ('v2', 0)
2。添加到布局
curdoc().title = 'Test Joel'
curdoc().add_root(button)
下面是输出:
这是原始数据:
总结一下:
无法通过上传按钮读取
.xlsx
文件在按钮回调函数中执行所有步骤是否正确?
目前以这种方式使用 CDS 的总体思路是合理的。未来应该会有更好的机制,但具体什么时候实施我不敢说。关于 read_excel
的错误,那是 Pandas issue/question,而不是散景。
至于列长的警告,几乎可以肯定是使用问题。它告诉您 v2
列是空的,这似乎不是您想要的,并且还违反了所有 CDS 列的长度始终相同的基本假设。我不确定你为什么要为 v2
生成一个空列表,而不能 运行 代码。
编辑:例如,如果所有内容的长度都相同,则一个一个地添加就可以正常工作:
In [4]: s.add([1,2,3], 'foo')
Out[4]: 'foo'
In [5]: s.add([1,2,3], 'bar')
Out[5]: 'bar'
只有当您添加的内容长度不正确时才会出现问题,错误消息指出:
In [6]: s.add([], 'baz')
/Users/bryanv/work/bokeh/bokeh/models/sources.py:138: BokehUserWarning: ColumnDataSource's columns must be of the same length. Current lengths: ('bar', 3), ('baz', 0), ('foo', 3)
"Current lengths: %s" % ", ".join(sorted(str((k, len(v))) for k, v in data.items())), BokehUserWarning))
Out[6]: 'baz'
如果您前面没有列的数据,请不要放入 "empty list" 作为占位符,否则一旦放入实际列,长度就会不一致。这就是你的问题的原因。
对于那些您将来会参考此线程的人:这是一个使用上传按钮来处理 .xlsx
文件的解决方案。这是为了 python3
我只分享主要的数据处理代码。其他一切如上。
import pandas as pd
import io
import base64
def file_callback_dt1(attr,old,new):
print('filename:', file_source_dt1.data['file_name'])
raw_contents = file_source_dt1.data['file_contents'][0]
prefix, b64_contents = raw_contents.split(",", 1)
file_contents = base64.b64decode(b64_contents)
file_io = io.BytesIO(file_contents)
excel_object = pd.ExcelFile(file_io, engine='xlrd')
dt_1 = excel_object.parse(sheet_name = 'Sheet1', index_col = 0)
# rest is upto you :)