Czy istnieje sposób na konwersję kolumn CSV w relacje hierarchiczne?

27

Mam csv 7 milionów rekordów różnorodności biologicznej, w których poziomy taksonomii są w kolumnach. Na przykład:

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

Chcę utworzyć wizualizację w D3, ale formatem danych musi być sieć, w której każda inna wartość kolumny jest potomkiem poprzedniej kolumny dla określonej wartości. Muszę przejść z csv do czegoś takiego:

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

Nie wpadłem na pomysł, jak to zrobić bez użycia tysiąca pętli. Czy ktoś ma sugestie, jak utworzyć tę sieć w Pythonie lub javascript?

Andres Camilo Zuñiga Gonzalez
źródło
Nie związane z twoim pytaniem, ale zaraz po napisaniu odpowiedzi zauważyłem a nandla Phylum zawierającego Magnoliopsida. Co to nanjest Phylum to Anthophyta lub alternatywnie Magnolia (to stara Phylum Angiospermae).
Gerardo Furtado,

Odpowiedzi:

16

Do utworzenia dokładnie zagnieżdżonego obiektu, którego chcesz, użyjemy kombinacji czystego JavaScript i metody D3 o nazwie d3.stratify. Należy jednak pamiętać, że 7 milionów wierszy (patrz poniżej post scriptum ) to dużo do obliczenia.

Bardzo ważne jest, aby wspomnieć, że w tym proponowanym rozwiązaniu będziesz musiał rozdzielić Królestwa na różne tablice danych (na przykład przy użyciu Array.prototype.filter). To ograniczenie występuje, ponieważ potrzebujemy węzła głównego, a w taksonomii linnańskiej nie ma związku między Królestwami (chyba że stworzysz „domenę” jako najwyższą rangę, która będzie pierwiastkiem dla wszystkich eukariontów, ale wtedy będziesz mieć to samo problem dla Archaea i Bakterii).

Załóżmy, że masz ten plik CSV (dodałem kilka wierszy) tylko z jednym Królestwem:

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

Na podstawie tego pliku CSV utworzymy tutaj tablicę o nazwie, tableOfRelationshipsktóra, jak sama nazwa wskazuje, ma relacje między szeregami:

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

Dla powyższych danych jest to 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"         |
+---------+----------------------+---------------+

Spójrz na nullrodzica Animalia: dlatego powiedziałem ci, że musisz oddzielić zestaw danych od Kingdoms, nullw całej tabeli może być tylko jedna wartość.

Wreszcie, na podstawie tej tabeli, tworzymy hierarchię przy użyciu d3.stratify():

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

const hierarchicalData = stratify(tableOfRelationships);

A oto wersja demo. Otwórz konsolę przeglądarki (fragment kodu nie jest zbyt dobry do tego zadania) i sprawdź kilka poziomów ( children) obiektu:


PS : Nie wiem, jaki rodzaj danych stworzysz, ale naprawdę powinieneś unikać rang taksonomicznych. Cała taksonomia linnańska jest przestarzała, nie używamy już szeregów: ponieważ systematyka filogenetyczna została opracowana w połowie lat 60-tych, używamy tylko taksonów, bez żadnej rangi taksonomicznej (tutaj nauczyciel biologii ewolucyjnej). Jestem też bardzo ciekawy tych 7 milionów wierszy, ponieważ opisaliśmy nieco ponad 1 milion gatunków!

Gerardo Furtado
źródło
3
. @ gerardo Dziękuję za odpowiedź, zobaczę, czy zadziała w próbce 7M wierszy. Baza danych zawiera powtarzające się wiersze dla wielu gatunków. więc chodzi o to, aby pokazać, ile jest rekordów dla określonej rangi taksonomicznej. Pomysł polega na stworzeniu czegoś podobnego do Zoomable Icicle Tree Mike'a Bostocka .
Andres Camilo Zuñiga Gonzalez
9

Łatwo jest robić dokładnie to, czego potrzebujesz, używając Pythona i python-benedictbiblioteki (jest to oprogramowanie typu open source w Github :

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

Pierwszym wyjściem będzie:

{
    "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": {}
                        }
                    }
                }
            }
        }
    }
}

Drugim wydrukiem będą:

{
    "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
źródło
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);

Mistrz Kuli
źródło
5

Wydaje się to proste, więc może nie rozumiem twojego problemu.

Potrzebna struktura danych to zagnieżdżony zestaw słowników, pary klucz / wartość. Twój słownik królestwa najwyższego poziomu ma klucz do każdego z twoich królestw, których wartościami są słowniki typów. Słownik typu (dla jednego królestwa) ma klucz dla każdej nazwy typu, a każdy klucz ma wartość, która jest słownikiem klas i tak dalej.

Aby ułatwić kodowanie, słowniki rodzajów będą miały klucz dla każdego gatunku, ale wartości dla gatunku będą pustymi słownikami.

To powinno być to, czego chcesz; nie są wymagane dziwne biblioteki.

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

Aby to przetestować, wykorzystałem twoje dane i pprintze standardowej biblioteki.

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

coraz

{'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': {}}}}}}}}

Czytając ponownie twoje pytanie, możesz chcieć mieć duży zestaw par („link z bardziej ogólnej grupy”, „link do bardziej szczegółowej grupy”). Oznacza to, że „Animalia” prowadzi do „Animalia: Chordata” i „Animalia: Chordata” prowadzi do „Animalia: Chordata: Mammalia” itd. Niestety, „nan” w Twoich danych oznacza, że ​​potrzebujesz pełnych nazwisk przy każdym linku. pary rodzic, dziecko) są tym, czego chcesz, idź po drzewie w ten sposób:

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}"')

dający:

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
źródło
To nie zwraca zagnieżdżony dict z namei childrenna żądanie w pytaniu.
Fabio Caccamo,
Nie, nie ma. Poproszono o „coś takiego”; Traktuję to jako próbę znalezienia struktury danych pomysłu. Można po prostu zbudować niestandardową strukturę, chodząc po drzewie - ćwiczenie składające się z czterech linii.
Charles Merriam
3

W Pythonie jednym ze sposobów kodowania drzewa jest użycie znaku dict, w którym klucze reprezentują węzły, a powiązaną wartością jest element nadrzędny węzła:

{'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}

Zaletą tego jest upewnienie się, że węzły są unikalne, ponieważ dictsnie mogą mieć zduplikowanych kluczy.

Jeśli zamiast tego chcesz zakodować bardziej ogólny wykres (tzn. Węzły mogą mieć więcej niż jednego rodzica), możesz użyć list do wartości i mieć reprezentujące dzieci (lub rodziców, jak sądzę):

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

Możesz zrobić coś podobnego z Obiektami w JS, zastępując tablice listami, jeśli to konieczne.

Oto kod Pythona, którego użyłem do stworzenia pierwszego powyższego dykta:

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
źródło
2

Prawdopodobnie najprostszym sposobem przekształcenia danych w hierarchię jest użycie wbudowanego operatora zagnieżdżania D3 d3.nest():

Zagnieżdżanie pozwala na grupowanie elementów w tablicy w hierarchiczną strukturę drzewa;

Rejestrując kluczowe funkcje za pośrednictwem nest.key(), możesz łatwo określić strukturę swojej hierarchii. Podobnie jak Gerardo w swojej odpowiedzi , możesz użyć .columnswłaściwości ujawnionej w tablicy danych po przeanalizowaniu pliku CSV, aby zautomatyzować generowanie tych kluczowych funkcji. Cały kod sprowadza się do następujących wierszy:

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

Zauważ jednak, że wynikowa hierarchia nie do końca przypomina strukturę wymaganą w pytaniu, ponieważ obiekty są { key, values }zamiast { name, children }; tak przy okazji, to samo dotyczy odpowiedzi Gerardo. Nie ma to jednak negatywnego wpływu na obie odpowiedzi, ponieważ wyniki mogą być przeciążone d3.hierarchy()przez określenie funkcji akcesora podrzędnego:

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

Poniższe demo łączy wszystkie części razem:

Możesz także rzucić okiem na klucz d3.nest () i konwersję wartości na imię i dzieci, na wypadek, gdybyś poczuł potrzebę posiadania dokładnie takiej struktury.

altocumulus
źródło
Ciesz się, d3.nestdopóki trwa: wkrótce zostanie wycofany.
Gerardo Furtado
@GerardoFurtado To była moja pierwsza myśl. Nie mogłem jednak znaleźć żadnego odniesienia popierającego to założenie. Myślałem, że przeczytałem o jego usunięciu i nawet byłem zaskoczony, że nadal znajduję się w pakiecie. Kolekcja d3 jest zarchiwizowana, ale nie ma na niej notki o wycofaniu. Czy masz jakieś wiarygodne informacje na ten temat?
altocumulus
To dotyczy v6, spójrz tutaj . Spójrz na „kolekcja d3 [Usunięto!]” .
Gerardo Furtado
@GerardoFurtado Nie, nie to miałem na myśli. Niestety odpowiada to na moje pytanie.
altocumulus
1

Zabawne wyzwanie. Wypróbuj ten kod javascript. Używam zestawu Lodasha dla uproszczenia.

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

To daje wynik końcowy (podobny) do tego, czego chcesz.

[
  {
    "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
źródło
1

W rzeczywistości @Charles Merriam jego rozwiązanie jest bardzo eleganckie.

Jeśli chcesz uzyskać wynik identyczny z pytaniem, spróbuj wykonać następujące czynności.

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

wprowadź opis zdjęcia tutaj

Carson Arucard
źródło