2017-06-18 5 views
1

J'ai une structure de dictionnaire imbriquée python qui ressemble à celle ci-dessous. Ceci est un petit exemple mais j'ai des exemples plus grands qui peuvent avoir différents niveaux d'imbrication.Chemin d'extraction pour chaque noeud terminal

De là, j'ai besoin d'extraire une liste avec:

  1. Un enregistrement pour chaque noeud feuillettent borne
  2. Une chaîne, une liste ou un objet représentant le chemin logique menant à ce noeud
    • (par exemple, 'nodeid_3: X et X < 0,500007 0,279907 <')

J'ai passé la plus grande partie de ce week-end en essayant d'obtenir quelque chose de travail et je réalise à quel point je suis avec récursivité.

# 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} 

Répondre

1

Votre structure de données est récursive. Si un noeud a une clé , alors nous pouvons considérer qu'il n'est pas terminal.

Pour analyser vos données, vous avez besoin d'une fonction récursive qui garde la trace des ancêtres (le chemin ).

Je mettre en œuvre ce genre:

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)) 

Si vous appelez:

find_path(data) 

Vous obtenez 3 résultats:

({'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}]) 

Bien sûr, vous pouvez remplacer l'appel à pprint.pprint() par un yield pour transformer cette fonction en générateur:

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 

Notez l'utilisation de yield from pour l'appel récursif. Vous utilisez ce générateur comme ci-dessous:

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

Vous pouvez également modifier l'objet ainsi child_obj est construit pour répondre à vos besoins.

Pour conserver l'ordre des objets: inverser la condition if: if 'children' not in obj: ….

+0

C'est vraiment sympa! Je suis capable d'imprimer mais le passage à "yield" ne retourne rien si je le répète sur le générateur résultant. Pourriez-vous ajouter la version rendement avec l'appel associé (dans le cas où j'ai réussi à gâcher ça)? – Chris

+0

@Chris J'ai ajouté un générateur 'iter_path'. –

+0

Cela fonctionne :). 'find_path' doit être changé en' iter_path' dans les lignes commentées, mais c'est sympa de votre part de fournir les deux versions en premier lieu. Merci, cela m'a économisé une tonne de temps! – Chris