Gibt es eine Möglichkeit, CSV-Spalten in hierarchische Beziehungen umzuwandeln?

27

Ich habe einen CSV von 7 Millionen Biodiversitätsdatensätzen, in denen Taxonomiestufen als Spalten angegeben sind. Zum Beispiel:

RecordID,kingdom,phylum,class,order,family,genus,species
1,Animalia,Chordata,Mammalia,Primates,Hominidae,Homo,Homo sapiens
2,Animalia,Chordata,Mammalia,Carnivora,Canidae,Canis,Canis
3,Plantae,nan,Magnoliopsida,Brassicales,Brassicaceae,Arabidopsis,Arabidopsis thaliana
4,Plantae,nan,Magnoliopsida,Fabales,Fabaceae,Phaseoulus,Phaseolus vulgaris

Ich möchte eine Visualisierung in D3 erstellen, aber das Datenformat muss ein Netzwerk sein, in dem jeder unterschiedliche Wert der Spalte für einen bestimmten Wert ein untergeordnetes Element der vorherigen Spalte ist. Ich muss von der CSV zu so etwas wechseln:

{
  name: 'Animalia',
  children: [{
    name: 'Chordata',
    children: [{
      name: 'Mammalia',
      children: [{
        name: 'Primates',
        children: 'Hominidae'
      }, {
        name: 'Carnivora',
        children: 'Canidae'
      }]
    }]
  }]
}

Ich habe keine Idee, wie man das macht, ohne tausend for-Schleifen zu verwenden. Hat jemand einen Vorschlag, wie man dieses Netzwerk entweder mit Python oder Javascript erstellt?

Andres Camilo Zuñiga Gonzalez
quelle
Nicht im Zusammenhang mit Ihrer Frage, aber kurz nachdem ich meine Antwort geschrieben hatte, bemerkte ich eine nanfür das Phylum enthaltende Magnoliopsida. Was ist das nan? Das Phylum ist Anthophyta oder alternativ Magnolie (es ist das alte Phylum Angiospermae).
Gerardo Furtado

Antworten:

16

Zum Erstellen des gewünschten genau verschachtelten Objekts verwenden wir eine Mischung aus reinem JavaScript und einer D3-Methode mit dem Namen d3.stratify. Beachten Sie jedoch, dass 7 Millionen Zeilen (siehe Post-Scriptum unten) viel zu berechnen sind.

Es ist sehr wichtig zu erwähnen, dass Sie für diese vorgeschlagene Lösung die Königreiche in verschiedene Datenfelder trennen müssen (z. B. mithilfe von Array.prototype.filter). Diese Einschränkung tritt auf, weil wir einen Wurzelknoten benötigen und in der linnäischen Taxonomie keine Beziehung zwischen Königreichen besteht (es sei denn, Sie erstellen "Domäne" als obersten Rang, der die Wurzel für alle Eukaryoten ist, aber dann haben Sie dieselbe Problem für Archaea und Bakterien).

Angenommen, Sie haben diese CSV (ich habe einige weitere Zeilen hinzugefügt) mit nur einem Königreich:

RecordID,kingdom,phylum,class,order,family,genus,species
1,Animalia,Chordata,Mammalia,Primates,Hominidae,Homo,Homo sapiens
2,Animalia,Chordata,Mammalia,Carnivora,Canidae,Canis,Canis latrans
3,Animalia,Chordata,Mammalia,Cetacea,Delphinidae,Tursiops,Tursiops truncatus
1,Animalia,Chordata,Mammalia,Primates,Hominidae,Pan,Pan paniscus

Basierend auf dieser CSV erstellen wir hier ein Array mit dem Namen, tableOfRelationshipsdas, wie der Name schon sagt, die Beziehungen zwischen den Rängen aufweist:

const data = d3.csvParse(csv);

const taxonomicRanks = data.columns.filter(d => d !== "RecordID");

const tableOfRelationships = [];

data.forEach(row => {
  taxonomicRanks.forEach((d, i) => {
    if (!tableOfRelationships.find(e => e.name === row[d])) tableOfRelationships.push({
      name: row[d],
      parent: row[taxonomicRanks[i - 1]] || null
    })
  })
});

Für die obigen Daten ist dies tableOfRelationships:

+---------+----------------------+---------------+
| (Index) |         name         |    parent     |
+---------+----------------------+---------------+
|       0 | "Animalia"           | null          |
|       1 | "Chordata"           | "Animalia"    |
|       2 | "Mammalia"           | "Chordata"    |
|       3 | "Primates"           | "Mammalia"    |
|       4 | "Hominidae"          | "Primates"    |
|       5 | "Homo"               | "Hominidae"   |
|       6 | "Homo sapiens"       | "Homo"        |
|       7 | "Carnivora"          | "Mammalia"    |
|       8 | "Canidae"            | "Carnivora"   |
|       9 | "Canis"              | "Canidae"     |
|      10 | "Canis latrans"      | "Canis"       |
|      11 | "Cetacea"            | "Mammalia"    |
|      12 | "Delphinidae"        | "Cetacea"     |
|      13 | "Tursiops"           | "Delphinidae" |
|      14 | "Tursiops truncatus" | "Tursiops"    |
|      15 | "Pan"                | "Hominidae"   |
|      16 | "Pan paniscus"       | "Pan"         |
+---------+----------------------+---------------+

Schauen Sie sich nullals übergeordnetes Element Folgendes an Animalia: Deshalb habe ich Ihnen gesagt, dass Sie Ihren Datensatz nach Königreichen trennen müssen. Es kann nur einen nullWert in der gesamten Tabelle geben.

Basierend auf dieser Tabelle erstellen wir schließlich die Hierarchie mit d3.stratify():

const stratify = d3.stratify()
    .id(function(d) { return d.name; })
    .parentId(function(d) { return d.parent; });

const hierarchicalData = stratify(tableOfRelationships);

Und hier ist die Demo. Öffnen Sie die Konsole Ihres Browsers (die des Snippets ist für diese Aufgabe nicht sehr gut geeignet) und überprüfen Sie die verschiedenen Ebenen ( children) des Objekts:


PS : Ich weiß nicht, welche Art von Daten Sie erstellen werden, aber Sie sollten taxonomische Ränge wirklich vermeiden. Die gesamte linnäische Taxonomie ist veraltet, wir verwenden keine Ränge mehr: Da die phylogenetische Systematik Mitte der 60er Jahre entwickelt wurde, verwenden wir nur Taxa ohne taxonomischen Rang (Lehrer für Evolutionsbiologie hier). Ich bin auch ziemlich neugierig auf diese 7 Millionen Reihen, da wir etwas mehr als 1 Million Arten beschrieben haben!

Gerardo Furtado
quelle
3
. @ gerardo Danke für deine Antwort, ich werde sehen, ob es in einem Beispiel der 7M-Zeilen funktioniert. Die Datenbank enthält wiederholte Zeilen für viele Arten. Die Idee ist also zu zeigen, wie viele Datensätze es für einen bestimmten taxonomischen Rang gibt. Die Idee ist, etwas Ähnliches wie Mike Bostocks Zoomable Icicle Tree zu erstellen .
Andres Camilo Zuñiga Gonzalez
9

Mit Python und python-benedictLibrary ist es einfach, genau das zu tun, was Sie brauchen (es ist Open Source auf Github :

Installation pip install python-benedict

from benedict import benedict as bdict

# data source can be a filepath or an url
data_source = """
RecordID,kingdom,phylum,class,order,family,genus,species
1,Animalia,Chordata,Mammalia,Primates,Hominidae,Homo,Homo sapiens
2,Animalia,Chordata,Mammalia,Carnivora,Canidae,Canis,Canis
3,Plantae,nan,Magnoliopsida,Brassicales,Brassicaceae,Arabidopsis,Arabidopsis thaliana
4,Plantae,nan,Magnoliopsida,Fabales,Fabaceae,Phaseoulus,Phaseolus vulgaris
"""
data_input = bdict.from_csv(data_source)
data_output = bdict()

ancestors_hierarchy = ['kingdom', 'phylum', 'class', 'order', 'family', 'genus', 'species']
for value in data_input['values']:
    data_output['.'.join([value[ancestor] for ancestor in ancestors_hierarchy])] = bdict()

print(data_output.dump())
# if this output is ok for your needs, you don't need the following code

keypaths = sorted(data_output.keypaths(), key=lambda item: len(item.split('.')), reverse=True)

data_output['children'] = []
def transform_data(d, key, value):
    if isinstance(value, dict):
        value.update({ 'name':key, 'children':[] })
data_output.traverse(transform_data)

for keypath in keypaths:
    target_keypath = '.'.join(keypath.split('.')[:-1] + ['children'])
    data_output[target_keypath].append(data_output.pop(keypath))

print(data_output.dump())

Die erste Druckausgabe ist:

{
    "Animalia": {
        "Chordata": {
            "Mammalia": {
                "Carnivora": {
                    "Canidae": {
                        "Canis": {
                            "Canis": {}
                        }
                    }
                },
                "Primates": {
                    "Hominidae": {
                        "Homo": {
                            "Homo sapiens": {}
                        }
                    }
                }
            }
        }
    },
    "Plantae": {
        "nan": {
            "Magnoliopsida": {
                "Brassicales": {
                    "Brassicaceae": {
                        "Arabidopsis": {
                            "Arabidopsis thaliana": {}
                        }
                    }
                },
                "Fabales": {
                    "Fabaceae": {
                        "Phaseoulus": {
                            "Phaseolus vulgaris": {}
                        }
                    }
                }
            }
        }
    }
}

Die zweite gedruckte Ausgabe ist:

{
    "children": [
        {
            "name": "Animalia",
            "children": [
                {
                    "name": "Chordata",
                    "children": [
                        {
                            "name": "Mammalia",
                            "children": [
                                {
                                    "name": "Carnivora",
                                    "children": [
                                        {
                                            "name": "Canidae",
                                            "children": [
                                                {
                                                    "name": "Canis",
                                                    "children": [
                                                        {
                                                            "name": "Canis",
                                                            "children": []
                                                        }
                                                    ]
                                                }
                                            ]
                                        }
                                    ]
                                },
                                {
                                    "name": "Primates",
                                    "children": [
                                        {
                                            "name": "Hominidae",
                                            "children": [
                                                {
                                                    "name": "Homo",
                                                    "children": [
                                                        {
                                                            "name": "Homo sapiens",
                                                            "children": []
                                                        }
                                                    ]
                                                }
                                            ]
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ]
        },
        {
            "name": "Plantae",
            "children": [
                {
                    "name": "nan",
                    "children": [
                        {
                            "name": "Magnoliopsida",
                            "children": [
                                {
                                    "name": "Brassicales",
                                    "children": [
                                        {
                                            "name": "Brassicaceae",
                                            "children": [
                                                {
                                                    "name": "Arabidopsis",
                                                    "children": [
                                                        {
                                                            "name": "Arabidopsis thaliana",
                                                            "children": []
                                                        }
                                                    ]
                                                }
                                            ]
                                        }
                                    ]
                                },
                                {
                                    "name": "Fabales",
                                    "children": [
                                        {
                                            "name": "Fabaceae",
                                            "children": [
                                                {
                                                    "name": "Phaseoulus",
                                                    "children": [
                                                        {
                                                            "name": "Phaseolus vulgaris",
                                                            "children": []
                                                        }
                                                    ]
                                                }
                                            ]
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    ]
}
Fabio Caccamo
quelle
5

var log = console.log;
var data = `
1,Animalia,Chordata,Mammalia,Primates,Hominidae,Homo,Homo sapiens
2,Animalia,Chordata,Mammalia,Carnivora,Canidae,Canis,Canis
3,Plantae,nan,Magnoliopsida,Brassicales,Brassicaceae,Arabidopsis,Arabidopsis thaliana
4,Plantae,nan,Magnoliopsida,Fabales,Fabaceae,Phaseoulus,Phaseolus vulgaris`;
//make array of rows with array of values
data = data.split("\n").map(v=>v.split(","));
//init tree
var tree = {};
data.forEach(row=>{
    //set current = root of tree for every row
    var cur = tree; 
    var id = false;
    row.forEach((value,i)=>{
        if (i == 0) {
            //set id and skip value
            id = value;
            return;
        }
        //If branch not exists create. 
        //If last value - write id
        if (!cur[value]) cur[value] = (i == row.length - 1) ? id : {};
        //Move link down on hierarhy
        cur = cur[value];
    });
}); 
log("Tree:");
log(JSON.stringify(tree, null, "  "));

//Now you have hierarhy in tree and can do anything with it.
var toStruct = function(obj) {
    let ret = [];
    for (let key in obj) {
        let child = obj[key];
        let rec = {};
        rec.name = key;
        if (typeof child == "object") rec.children = toStruct(child);
        ret.push(rec);
    }
    return ret;
}
var struct = toStruct(tree);
console.log("Struct:");
console.log(struct);

Krückenmeister
quelle
5

Dies scheint unkompliziert zu sein. Vielleicht verstehe ich Ihr Problem nicht.

Die gewünschte Datenstruktur besteht aus einem verschachtelten Satz von Wörterbüchern, Schlüssel / Wert-Paaren. Ihr Königreichswörterbuch der obersten Ebene enthält einen Schlüssel für jedes Ihrer Königreiche, dessen Werte Phylum-Wörterbücher sind. Ein Phylum-Wörterbuch (für ein Königreich) hat einen Schlüssel für jeden Phylum-Namen und jeder Schlüssel hat einen Wert, der ein Klassenwörterbuch ist, und so weiter.

Um das Codieren zu vereinfachen, haben Ihre Gattungswörterbücher einen Schlüssel für jede Art, aber die Werte für die Art sind leere Wörterbücher.

Dies sollte das sein, was Sie wollen; Keine seltsamen Bibliotheken erforderlich.

import csv

def read_data(filename):
    tree = {}
    with open(filename) as f:
        f.readline()  # skip the column headers line of the file
        for animal_cols in csv.reader(f):
            spot = tree
            for name in animal_cols[1:]:  # each name, skipping the record number
                if name in spot:  # The parent is already in the tree
                    spot = spot[name]  
                else:
                    spot[name] = {}  # creates a new entry in the tree
                    spot = spot[name]
    return tree

Zum Testen habe ich Ihre Daten und pprintaus der Standardbibliothek verwendet.

from pprint import pprint
pprint(read_data('data.txt'))

bekommen

{'Animalia': {'Chordata': {'Mammalia': {'Carnivora': {'Canidae': {'Canis': {'Canis': {}}}},
                                        'Primates': {'Hominidae': {'Homo': {'Homo sapiens': {}}}}}}},
 'Plantae': {'nan': {'Magnoliopsida': {'Brassicales': {'Brassicaceae': {'Arabidopsis': {'Arabidopsis thaliana': {}}}},
                                       'Fabales': {'Fabaceae': {'Phaseoulus': {'Phaseolus vulgaris': {}}}}}}}}

Wenn Sie Ihre Frage noch einmal lesen, möchten Sie möglicherweise eine große Tabelle mit Paaren ('Link von einer allgemeineren Gruppe', 'Link zu einer spezifischeren Gruppe'). Das heißt, "Animalia" -Links zu "Animalia: Chordata" und "Animalia: Chordata" -Links zu "Animalia: Chordata: Mammalia" usw. Leider bedeutet das "Nan" in Ihren Daten, dass Sie bei jedem Link vollständige Namen benötigen. If ( Eltern, Kind) Paare sind das, was Sie wollen. Gehen Sie den Baum folgendermaßen:

def walk_children(tree, parent=''):
    for child in tree.keys():
        full_name = parent + ':' + child
        yield (parent, full_name)
        yield from walk_children(tree[child], full_name)

tree = read_data('data.txt')
for (parent, child) in walk_children(tree):
    print(f'parent="{parent}" child="{child}"')

Geben:

parent="" child=":Animalia"
parent=":Animalia" child=":Animalia:Chordata"
parent=":Animalia:Chordata" child=":Animalia:Chordata:Mammalia"
parent=":Animalia:Chordata:Mammalia" child=":Animalia:Chordata:Mammalia:Primates"
parent=":Animalia:Chordata:Mammalia:Primates" child=":Animalia:Chordata:Mammalia:Primates:Hominidae"
parent=":Animalia:Chordata:Mammalia:Primates:Hominidae" child=":Animalia:Chordata:Mammalia:Primates:Hominidae:Homo"
parent=":Animalia:Chordata:Mammalia:Primates:Hominidae:Homo" child=":Animalia:Chordata:Mammalia:Primates:Hominidae:Homo:Homo sapiens"
parent=":Animalia:Chordata:Mammalia" child=":Animalia:Chordata:Mammalia:Carnivora"
parent=":Animalia:Chordata:Mammalia:Carnivora" child=":Animalia:Chordata:Mammalia:Carnivora:Canidae"
parent=":Animalia:Chordata:Mammalia:Carnivora:Canidae" child=":Animalia:Chordata:Mammalia:Carnivora:Canidae:Canis"
parent=":Animalia:Chordata:Mammalia:Carnivora:Canidae:Canis" child=":Animalia:Chordata:Mammalia:Carnivora:Canidae:Canis:Canis"
parent="" child=":Plantae"
parent=":Plantae" child=":Plantae:nan"
parent=":Plantae:nan" child=":Plantae:nan:Magnoliopsida"
parent=":Plantae:nan:Magnoliopsida" child=":Plantae:nan:Magnoliopsida:Brassicales"
parent=":Plantae:nan:Magnoliopsida:Brassicales" child=":Plantae:nan:Magnoliopsida:Brassicales:Brassicaceae"
parent=":Plantae:nan:Magnoliopsida:Brassicales:Brassicaceae" child=":Plantae:nan:Magnoliopsida:Brassicales:Brassicaceae:Arabidopsis"
parent=":Plantae:nan:Magnoliopsida:Brassicales:Brassicaceae:Arabidopsis" child=":Plantae:nan:Magnoliopsida:Brassicales:Brassicaceae:Arabidopsis:Arabidopsis thaliana"
parent=":Plantae:nan:Magnoliopsida" child=":Plantae:nan:Magnoliopsida:Fabales"
parent=":Plantae:nan:Magnoliopsida:Fabales" child=":Plantae:nan:Magnoliopsida:Fabales:Fabaceae"
parent=":Plantae:nan:Magnoliopsida:Fabales:Fabaceae" child=":Plantae:nan:Magnoliopsida:Fabales:Fabaceae:Phaseoulus"
parent=":Plantae:nan:Magnoliopsida:Fabales:Fabaceae:Phaseoulus" child=":Plantae:nan:Magnoliopsida:Fabales:Fabaceae:Phaseoulus:Phaseolus vulgaris"
Charles Merriam
quelle
Dies gibt kein verschachteltes Diktat mit nameund childrenwie in der Frage angefordert zurück.
Fabio Caccamo
Nein, tut es nicht. Was angefordert wurde, war "so etwas"; Ich nehme das als Versuch, die Ideendatenstruktur zu finden. Man könnte einfach eine benutzerdefinierte Struktur erstellen, indem man über den Baum geht, eine vierzeilige Übung.
Charles Merriam
3

In Python besteht eine Möglichkeit zum Codieren eines Baums darin, a zu verwenden dict, wobei die Schlüssel Knoten darstellen und der zugehörige Wert das übergeordnete Element des Knotens ist:

{'Homo sapiens': 'Homo',
 'Canis': 'Canidae',
 'Arabidopsis thaliana': 'Arabidopsis',
 'Phaseolus vulgaris': 'Phaseoulus',
 'Homo': 'Hominidae',
 'Arabidopsis': 'Brassicaceae',
 'Phaseoulus': 'Fabaceae',
 'Hominidae': 'Primates',
 'Canidae': 'Carnivora',
 'Brassicaceae': 'Brassicales',
 'Fabaceae': 'Fabales',
 'Primates': 'Mammalia',
 'Carnivora': 'Mammalia',
 'Brassicales': 'Magnoliopsida',
 'Fabales': 'Magnoliopsida',
 'Mammalia': 'Chordata',
 'Magnoliopsida': 'nan',
 'Chordata': 'Animalia',
 'nan': 'Plantae',
 'Animalia': None,
 'Plantae': None}

Dies hat den Vorteil, dass Sie sicherstellen, dass die Knoten eindeutig sind, da dictskeine doppelten Schlüssel vorhanden sein können.

Wenn Sie stattdessen einen allgemeineren gerichteten Graphen codieren möchten (dh Knoten können mehr als ein übergeordnetes Element haben), können Sie Listen für Werte verwenden und die Kinder (oder Eltern, nehme ich an) darstellen:

{'Homo': ['Homo sapiens', 'ManBearPig'],
'Ursus': ['Ursus arctos', 'ManBearPig'],
'Sus': ['ManBearPig']}

Sie können mit Objekten in JS etwas Ähnliches tun und bei Bedarf Listen durch Arrays ersetzen.

Hier ist der Python-Code, mit dem ich das erste Diktat oben erstellt habe:

import csv

ROWS = []
# Load file: tbl.csv
with open('tbl.csv', 'r') as in_file:
    csvreader = csv.reader(in_file)

    # Ignore leading row numbers
    ROWS = [row[1:] for row in csvreader]
    # Drop header row
    del ROWS[0]

# Build dict
mytree = {row[i]: row[i-1] for row in ROWS for i in range(len(row)-1, 0, -1)}
# Add top-level nodes
mytree = {**mytree, **{row[0]: None for row in ROWS}}
dizzy77
quelle
2

Wahrscheinlich der einfachste Weg , um Ihre Daten in eine Hierarchie dreht die Nutzung von D3-interner Verschachtelung Betreiber d3.nest():

Durch das Verschachteln können Elemente in einem Array in einer hierarchischen Baumstruktur gruppiert werden.

Durch die Registrierung von Schlüsselfunktionen über können nest.key()Sie einfach die Struktur Ihrer Hierarchie festlegen. Ähnlich wie Gerardo in seiner Antwort dargelegt hat, können Sie die .columnsim Datenarray nach dem Parsen Ihrer CSV bereitgestellte Eigenschaft verwenden, um die Generierung dieser Schlüsselfunktionen zu automatisieren. Der gesamte Code besteht aus folgenden Zeilen:

const nester = d3.nest();                             // Create a nest operator
const [, ...taxonomicRanks] = data.columns;           // Get rid of the RecordID property
taxonomicRanks.forEach(r => nester.key(d => d[r]));   // Register key functions
const nest = nester.entries(data);                    // Calculate hierarchy

Beachten Sie jedoch, dass die resultierende Hierarchie nicht genau der in Ihrer Frage angeforderten Struktur ähnelt, da die Objekte { key, values }stattdessen sind { name, children }. Dies gilt übrigens auch für Gerardos Antwort. Dies gilt nicht für beide Antworten verletzt, obwohl, wie die Ergebnisse können durch lastet werden d3.hierarchy()durch eine Angabe Kinder Accessor - Funktion:

d3.hierarchy(nest, d => d.values)   // Second argument is the children accessor

Die folgende Demo fügt alle Teile zusammen:

Vielleicht möchten Sie auch einen Blick auf die Konvertierung von Schlüsseln und Werten von d3.nest () in Namen und untergeordnete Elemente werfen, falls Sie das Gefühl haben, genau Ihre veröffentlichte Struktur zu haben.

Altocumulus
quelle
Genießen Sie, d3.nestsolange es dauert: Es wird bald veraltet sein.
Gerardo Furtado
@ GerardoFurtado Das war mein erster Gedanke. Ich konnte jedoch keine Referenz finden, die diese Annahme stützt. Ich dachte, ich hätte über die Entfernung gelesen und war sogar überrascht, dass sie immer noch im Paket enthalten war. Die d3-Sammlung ist archiviert, es befindet sich jedoch kein Verfallshinweis darauf. Haben Sie verlässliche Informationen zu diesem Thema?
Altocumulus
Das ist für v6, schau hier . Schauen Sie sich "d3-collection [Entfernt!]" An .
Gerardo Furtado
@ GerardoFurtado Nein, das war nicht die Referenz, an die ich gedacht hatte. Trotzdem beantwortet es meine Frage leider.
Altocumulus
1

Eine lustige Herausforderung. Versuchen Sie diesen Javascript-Code. Der Einfachheit halber benutze ich Lodashs Set.

import { set } from 'lodash'

const csvString = `RecordID,kingdom,phylum,class,order,family,genus,species
    1,Animalia,Chordata,Mammalia,Primates,Hominidae,Homo,Homo sapiens
    2,Animalia,Chordata,Mammalia,Carnivora,Canidae,Canis,Canis
    3,Plantae,nan,Magnoliopsida,Brassicales,Brassicaceae,Arabidopsis,Arabidopsis thaliana
    4,Plantae,nan,Magnoliopsida,Fabales,Fabaceae,Phaseoulus,Phaseolus vulgaris`

// First create a quick lookup map
const result = csvString
  .split('\n') // Split for Rows
  .slice(1) // Remove headers
  .reduce((acc, row) => {
    const path = row
      .split(',') // Split for columns
      .filter(item => item !== 'nan') // OPTIONAL: Filter 'nan'
      .slice(1) // Remove record id
    const species = path.pop() // Pull out species (last entry)
    set(acc, path, species)
    return acc
  }, {})

console.log(JSON.stringify(result, null, 2))

// Then convert to the name-children structure by recursively calling this function
const convert = (obj) => {
  // If we're at the end of our chain, end the chain (children is empty)
  if (typeof obj === 'string') {
    return [{
      name: obj,
      children: [],
    }]
  }
  // Else loop through each entry and add them as children
  return Object.entries(obj)
    .reduce((acc, [key, value]) => acc.concat({
      name: key,
      children: convert(value), // Recursive call
    }), [])
}

const result2 = convert(result)

console.log(JSON.stringify(result2, null, 2))

Dies ergibt das Endergebnis (ähnlich) wie gewünscht.

[
  {
    "name": "Animalia",
    "children": [
      {
        "name": "Chordata",
        "children": [
          {
            "name": "Mammalia",
            "children": [
              {
                "name": "Primates",
                "children": [
                  {
                    "name": "Hominidae",
                    "children": [
                      {
                        "name": "Homo",
                        "children": [
                          {
                            "name": "Homo sapiens",
                            "children": []
                          }
                        ]
                      }
                    ]
                  }
                ]
              },
              {
                "name": "Carnivora",
                "children": [
                  {
                    "name": "Canidae",
                    "children": [
                      {
                        "name": "Canis",
                        "children": [
                          {
                            "name": "Canis",
                            "children": []
                          }
                        ]
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  },
  {
    "name": "Plantae",
    "children": [
      {
        "name": "Magnoliopsida",
        "children": [
          {
            "name": "Brassicales",
            "children": [
              {
                "name": "Brassicaceae",
                "children": [
                  {
                    "name": "Arabidopsis",
                    "children": [
                      {
                        "name": "Arabidopsis thaliana",
                        "children": []
                      }
                    ]
                  }
                ]
              }
            ]
          },
          {
            "name": "Fabales",
            "children": [
              {
                "name": "Fabaceae",
                "children": [
                  {
                    "name": "Phaseoulus",
                    "children": [
                      {
                        "name": "Phaseolus vulgaris",
                        "children": []
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
]
ZephDavies
quelle
1

In der Tat ist @Charles Merriam seine Lösung sehr elegant.

Wenn Sie ein Ergebnis erzielen möchten, das mit der Frage übereinstimmt, versuchen Sie Folgendes.

from io import StringIO
import csv


CSV_CONTENTS = """RecordID,kingdom,phylum,class,order,family,genus,species
1,Animalia,Chordata,Mammalia,Primates,Hominidae,Homo,Homo sapiens
2,Animalia,Chordata,Mammalia,Carnivora,Canidae,Canis,Canis
3,Plantae,nan,Magnoliopsida,Brassicales,Brassicaceae,Arabidopsis,Arabidopsis thaliana
4,Plantae,nan,Magnoliopsida,Fabales,Fabaceae,Phaseoulus,Phaseolus vulgaris
"""


def recursive(dict_data):
    lst = []
    for key, val in dict_data.items():
        children = recursive(val)
        lst.append(dict(name=key, children=children))
    return lst


def main():
    with StringIO() as io_f:
        io_f.write(CSV_CONTENTS)
        io_f.seek(0)
        io_f.readline()  # skip the column headers line of the file
        result_tree = {}
        for row_data in csv.reader(io_f):
            cur_dict = result_tree  # cursor, back to root
            for item in row_data[1:]:  # each item, skip the record number
                if item not in cur_dict:
                    cur_dict[item] = {}  # create new dict
                    cur_dict = cur_dict[item]
                else:
                    cur_dict = cur_dict[item]

    # change answer format
    result_list = []
    for cur_kingdom_name in result_tree:
        result_list.append(dict(name=cur_kingdom_name, children=recursive(result_tree[cur_kingdom_name])))

    # Optional
    import json
    from os import startfile
    output_file = 'result.json'
    with open(output_file, 'w') as f:
        json.dump(result_list, f)
    startfile(output_file)


if __name__ == '__main__':
    main()

Geben Sie hier die Bildbeschreibung ein

Carson Arucard
quelle