首页 > 解决方案 > 如何在熊猫数据框输出中定义索引或点击行数?

问题描述

我想在 ipywidgets 上制作一些表格,它在其输入中显示特定行的熊猫数据框的值。

为了使行选择和以更具交互性的形式显示,我需要以某种方式处理在显示的数据框中单击。实际上,我希望能够识别最后点击的数据框行的索引或数量。


PS
我见过像 ipysheet 和其他工具这样的工具,但我认为它们仍然太新,尽管 ipysheet 将来可能会非常强大。所以这些工具不适用于这项任务。

标签: javascriptpandasjupyter-notebookipythonipywidgets

解决方案


我有类似的问题,qgrid对我来说太过分了。

jupyter 中的工作原理(灵感来自在 html table 中的每一行添加点击功能):

  1. 注入的脚本将处理程序添加到数据框表的每个单元格(由id='T_table'通过Styler.set_uuid函数标记)。

  2. 单击单元格时的典型处理程序:

    1. 查找指定的输入元素(隐藏并由占位符“未定义”标记),

    2. 为单击的单元格的类名设置找到的输入的 value 属性,

    3. 触发“更改”事件。
  3. 之后,单击单元格的类名(pandas 使它们包含必要的行/列信息)可用于 ipywidgets 框架,并且可以在 python 中处理。

评论:

  1. 在调用脚本之前需要一定的超时时间。对于更大的表,所需的超时可能会更大。

  2. 逻辑有点复杂,因为我使用的将 js-values 带入 python 世界的方法是通过ipywidgets.Text.

  3. 添加 2.1 项以防 DOM 在启动后重新渲染。

import pandas as pd
import ipywidgets as wgt
from IPython.display import display, HTML
import re

# javascript-part
script = """
<script>
var input
var xpath = "//input[contains(@placeholder,'undefined')]";

function addHandlers() {
    input = document.evaluate(xpath, document, null, 
        XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    input.setAttribute("hidden","");

    var table = document.querySelector("#T_table");
    var headcells = [].slice.call(table.getElementsByTagName("th"));
    var datacells = [].slice.call(table.getElementsByTagName("td"));
    var cells = headcells.concat(datacells);
    for (var i=0; i < cells.length; i++) {
       var createClickHandler = function(cell) {
         return function() { 
            input = document.evaluate(xpath, document, null,
                XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
            input.value = cell.className; 
            var event = new Event('change', { bubbles: true });
            input.dispatchEvent(event);
      }}
      cells[i].onclick = createClickHandler(cells[i]);
    };
}

window.onload = setTimeout(addHandlers, 500);
</script>
"""
display(HTML(script))

# ipywidgets-part
newdf = pd.DataFrame(data={
    '1': [11,21,31,41], '2': [12,22,32,42], '3': [13,23,33,43],
    '4': [14,24,34,44], '5': [15,25,35,45], '6': [16,26,36,46],
    '7': [17,27,37,47], '8': [18,28,38,48], '9': [19,29,39,49],
    '10': [110,210,310,410],'11': [111,211,311,411],
})

html = newdf.style.\
    set_uuid('table') 

def on_change(change):
    cls = change['new'].split(' ')
    if len(cls) == 2: 
        place.value, row.value = cls
        col.value = '0'
    elif len(cls) == 3: 
        place.value, txtrow, txtcol = cls
        res = re.search(r'\d+',txtrow).group(0)
        row.value = str(int(res)+1)
        res = re.search(r'\d+',txtcol).group(0)
        col.value = str(int(res)+1)
    else:
        place.value, row.value, col.value = ['unknown']*3

status = wgt.Text(placeholder='undefined',layout={'font-size':'6px'}) 
status.observe(on_change,names=['value'])

table = wgt.Output()
with table: display(html)

layout = {'width':'192px'}
row = wgt.Text(layout=layout,description='row')
col = wgt.Text(layout=layout,description='col')
place = wgt.Text(layout=layout,description='place')
body = wgt.HBox([table,wgt.VBox([place,row,col])])

wgt.VBox([body,status])

推荐阅读