提取每个终端节点的路径

Extract path for each terminal node

我有一个 python 嵌套字典结构,如下所示。 这是一个小例子,但我有更大的例子,可以有不同层次的嵌套。

由此,我需要提取一个列表:

  1. 每个终端一条记录'leaf'节点
  2. 表示通向该节点的逻辑路径的字符串、列表或对象
    • (例如'nodeid_3: X < 0.500007 and X < 0.279907')

这个周末的大部分时间我都在尝试让一些东西工作,并且意识到我在递归方面有多么糟糕。

# Extract json string
json_string = booster.get_dump(with_stats=True, dump_format='json')[0]

# Convert to python dictionary
json.loads(json_string)

{u'children': [{u'children': [
    {u'cover': 2291, u'leaf': -0.0611795, u'nodeid': 3},
    {u'cover': 1779, u'leaf': -0.00965727, u'nodeid': 4}],
   u'cover': 4070,
   u'depth': 1,
   u'gain': 265.811,
   u'missing': 3,
   u'no': 4,
   u'nodeid': 1,
   u'split': u'X',
   u'split_condition': 0.279907,
   u'yes': 3},
  {u'cover': 3930, u'leaf': -0.0611946, u'nodeid': 2}],
 u'cover': 8000,
 u'depth': 0,
 u'gain': 101.245,
 u'missing': 1,
 u'no': 2,
 u'nodeid': 0,
 u'split': u'X',
 u'split_condition': 0.500007,
 u'yes': 1}

你的数据结构是递归的。如果一个节点有children键,那么我们可以认为它不是终端

要分析您的数据,您需要一个递归函数来跟踪祖先(路径)。

我会这样实现:

def find_path(obj, path=None):
    path = path or []
    if 'children' in obj:
        child_obj = {k: v for k, v in obj.items()
                     if k in ['nodeid', 'split_condition']}
        child_path = path + [child_obj]
        children = obj['children']
        for child in children:
            find_path(child, child_path)
    else:
        pprint.pprint((obj, path))

如果您致电:

find_path(data)

您得到 3 个结果:

({'cover': 2291, 'leaf': -0.0611795, 'nodeid': 3},
 [{'nodeid': 0, 'split_condition': 0.500007},
  {'nodeid': 1, 'split_condition': 0.279907}])
({'cover': 1779, 'leaf': -0.00965727, 'nodeid': 4},
 [{'nodeid': 0, 'split_condition': 0.500007},
  {'nodeid': 1, 'split_condition': 0.279907}])
({'cover': 3930, 'leaf': -0.0611946, 'nodeid': 2},
 [{'nodeid': 0, 'split_condition': 0.500007}])

当然,您可以将对 pprint.pprint() 的调用替换为 yield 以将此函数转换为生成器:

def iter_path(obj, path=None):
    path = path or []
    if 'children' in obj:
        child_obj = {k: v for k, v in obj.items()
                     if k in ['nodeid', 'split_condition']}
        child_path = path + [child_obj]
        children = obj['children']
        for child in children:
            # for o, p in iteration_path(child, child_path):
            #     yield o, p
            yield from iter_path(child, child_path)
    else:
        yield obj, path

注意递归调用的yield from用法。您可以像下面这样使用此生成器:

for obj, path in iter_path(data):
    pprint.pprint((obj, path))

您还可以更改 child_obj 对象的构建方式以满足您的需要。

要保持​​对象的顺序:反转 if 条件:if 'children' not in obj: …