Боке - Панды не могут прочитать объект bytesIO файла Excel из JS

1

Мне нужны ваши материалы по некоторым проблемам, с которыми я сталкиваюсь с нескольких дней.

Моя цель - загрузить кнопку загрузки, из которой я делюсь файлом .xlsx на 2 листа. Как только я загружаю эти данные и читаю их в pandas DataFrame, я выполняю некоторые pythonic расчеты/код оптимизации и т.д. И получаю несколько результатов (суммарно суммируется). Теперь, основываясь на количестве уникальных "уровней" / "групп", я создам так много вкладок, а затем покажу эти итоговые результаты на каждой вкладке. Также на общей странице есть общий график.

Ниже мое усилие (не мое, а сообщество:)):

1. Код кнопки загрузки: (из здесь)

## 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 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 должны иметь одинаковую длину. Текущая длина: ('v1', 19), ('v2', 0)

2. Добавление в макет

curdoc().title = 'Test Joel'
curdoc().add_root(button)

Ниже представлен результат: Изображение 174551

Это были исходные данные: Изображение 174551 Примечание. Все данные, общие здесь, являются фиктивными, а в реальном случае больше листов и больше.

Итак, суммируем:

  • Не удалось прочитать файл .xlsx с помощью кнопки загрузки

  • Правильно ли делать все шаги в самой функции обратного вызова кнопки?

Теги:
pandas
web-applications
bokeh

2 ответа

1
Лучший ответ

Для тех, с кем вы бы связались с этим потоком: вот решение иметь кнопку загрузки для обработки файла .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 :)
1

Общая идея использования CDS таким образом является разумной на данный момент. В будущем должны быть лучшие механизмы, но я не могу спекулировать, когда они могут быть реализованы. Что касается ошибки с read_excel, то это проблема/вопрос Pandas, а не Bokeh.

Что касается предупреждения о длинах столбцов, это почти наверняка указывает на проблему использования. Он говорит вам, что ваш столбец 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 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'

Если у вас нет данных для столбца вверх, не помещайте "пустой список" в качестве заполнителя или как только вы помещаете в реальный столбец, у вас есть несогласованные длины. Это и есть причина вашей проблемы.

  • 0
    Что касается последнего пункта, у меня есть сомнения: допустим, вы читаете объект dataFrame pandas с 2 столбцами v1,v2 а затем суммируете эти столбцы, чтобы получить sum_v1, sum_v2 Затем вы вставляете это в CDS один за другим. Поэтому в первый раз, когда вы вставляете sum_v1 в CDS, он выдает это предупреждение, что длина не равна (1,0)
  • 1
    Нет, это не так. Эта ошибка означает, что вы не добавили их по одному. Это означает, что оба столбца существуют одновременно, но один из них пуст .
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню