Domanda come convertire un data.frame in oggetto struttura ad albero come dendrogramma


Ho un oggetto data.frame. Per un semplice esempio:

> data.frame(x=c('A','A','B','B','B'), y=c('Ab','Ac','Ba', 'Ba','Bd'), z=c('Abb','Acc','Bad', 'Bae','Bdd'))
  x  y   z
1 A Ab Abb
2 A Ac Acc
3 B Ba Bad
4 B Ba Bae
5 B Bd Bdd

ci sono molte più righe e colonne nei dati reali. come posso creare un oggetto ad albero nidificato di dendrogramma come questo:

         |---Ab---Abb
     A---|
     |   |---Ac---Acc
   --|                 /--Bad 
     |   |---Ba-------|
     B---|             \--Bae
         |---Bb---Bdd

14
2018-03-11 16:11


origine


risposte:


data.frame a Newick

Ho fatto il mio dottorato in filogenetica computazionale e da qualche parte nel modo in cui ho prodotto questo codice, che ho usato una o due volte quando ho ottenuto alcuni dati in questo formato non standard (in senso filogenetico). Lo script attraversa il dataframe come se fosse un albero ... e incolla il materiale lungo la strada in una stringa Newick, che è un formato standard e può quindi essere trasformato in qualsiasi tipo di oggetto ad albero.

Immagino che la sceneggiatura potrebbe essere ottimizzata (l'ho usata così raramente che più lavoro su di essa ridurrebbe l'efficienza complessiva), ma almeno è meglio condividere che lasciare che raccolga polvere sul mio hard disk.

    ## recursion function
    traverse <- function(a,i,innerl){
        if(i < (ncol(df))){
            alevelinner <- as.character(unique(df[which(as.character(df[,i])==a),i+1]))
            desc <- NULL
            if(length(alevelinner) == 1) (newickout <- traverse(alevelinner,i+1,innerl))
            else {
                for(b in alevelinner) desc <- c(desc,traverse(b,i+1,innerl))
                il <- NULL; if(innerl==TRUE) il <- a
                (newickout <- paste("(",paste(desc,collapse=","),")",il,sep=""))
            }
        }
        else { (newickout <- a) }
    }

    ## data.frame to newick function
    df2newick <- function(df, innerlabel=FALSE){
        alevel <- as.character(unique(df[,1]))
        newick <- NULL
        for(x in alevel) newick <- c(newick,traverse(x,1,innerlabel))
        (newick <- paste("(",paste(newick,collapse=","),");",sep=""))
    }

La funzione principale df2newick() prende due argomenti:

  • df che è il dataframe da trasformare (oggetto della classe data.frame)
  • innerlabel che dice alla funzione di scrivere etichette per i nodi interni (bulean)

Per dimostrarlo sul tuo esempio:

    df <- data.frame(x=c('A','A','B','B','B'), y=c('Ab','Ac','Ba', 'Ba','Bd'), z=c('Abb','Acc','Bad', 'Bae','Bdd'))
    myNewick <- df2newick(df)
    #[1] "((Abb,Acc),((Bad,Bae),Bdd));"

Ora puoi leggerlo in un oggetto di classe phylo con read.tree() dalla scimmia

    library(ape)
    mytree <- read.tree(text=myNewick)
    plot(mytree)

Se si desidera aggiungere etichette nodo interno alla stringa Newick, è possibile utilizzare questo:

    myNewick <- df2newick(df, TRUE)
    #[1] "((Abb,Acc)A,((Bad,Bae)Ba,Bdd)B);"

Spero che questo sia utile (e forse il mio dottorato non è stato un periodo di tempo completo ;-)


Nota aggiuntiva per il tuo formato dataframe:

Come si può osservare la funzione df2newick ignora i modi interni con un figlio (che è comunque il modo migliore per essere utilizzato con la maggior parte dei metodi filogenetici ... era rilevante solo per me). Il df gli oggetti che ho originariamente ricevuto e usato con questo script erano di questo formato:

    df <- data.frame(x=c('A','A','B','B','B'), y=c('Abb','Acc','Ba', 'Ba','Bdd'), z=c('Abb','Acc','Bad', 'Bae','Bdd'))

Molto simile al tuo ... ma i "nodi interiori" hanno avuto lo stesso nome dei loro figli, ma hai anche nomi interni diversi per questi nodi, e i nomi vengono ignorati ... potrebbe non essere rilevante ma puoi ignora solo una parte della funzione di ricorsione, in questo modo:

    traverse <- function(a,i,innerl){
        if(i < (ncol(df))){
            alevelinner <- as.character(unique(df[which(as.character(df[,i])==a),i+1]))
            desc <- NULL
            ##if(length(alevelinner) == 1) (newickout <- traverse(alevelinner,i+1,innerl))
            ##else {
                for(b in alevelinner) desc <- c(desc,traverse(b,i+1,innerl))
                il <- NULL; if(innerl==TRUE) il <- a
                (newickout <- paste("(",paste(desc,collapse=","),")",il,sep=""))
            ##}
        }
        else { (newickout <- a) }
    }

e otterresti qualcosa del genere:

    [1] "(((Abb)Ab,(Acc)Ac)A,((Bad,Bae)Ba,(Bdd)Bd)B);"

Questo mi sembra davvero strano, ma lo aggiungo per ogni evenienza, perché in realtà include tutte le informazioni dal tuo dataframe originale.


14
2018-03-12 19:44



Non so molto sulla struttura interna dei dendrogrammi in R, ma il codice seguente creerà una struttura ad elenco annidata che ha la gerarchia che penso tu cerchi:

stree = function(x,level=0) {
#x is a string vector
#resultis a hierarchical structure of lists (that contains lists, etc.)
#the names of the lists are the node values.

level = level+1
if (length(x)==1) {
    result = list()
    result[[substring(x[1],level)]]=list()
    return(result)
}
result=list()
this.level = substring(x,level,level)
next.levels = unique(this.level)
for (p in next.levels) {
    if (p=="") {
        result$p = list()
    } else {
        ids = which(this.level==p)
        result[[p]] = stree(x[ids],level)
    }
}
result
}

funziona su un vettore di stringhe. quindi nel caso del tuo dataframe dovrai chiamare stree (as.character (df [3]))

Spero che questo ti aiuti.


1
2018-03-12 07:41