Как преобразовать data.frame в объект древовидной структуры, такой как дендрограмма

У меня есть объект data.frame. Для простого примера:

> 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

в реальных данных намного больше строк и столбцов. Как я могу создать вложенный объект древовидной структуры дендрограммы, как это:

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

2 ответа

Решение

data.frame в Ньюик

Я защитил докторскую диссертацию по вычислительной филогенетике и где-то по пути, когда я создал этот код, который я использовал один или два раза, когда получил некоторые данные в этом нестандартном формате (в филогенетическом смысле). Скрипт пересекает фрейм данных, как если бы это было дерево... и по пути вставляет материал в строку Newick, которая является стандартным форматом и затем может быть преобразована в любой тип дерева.

Я думаю, сценарий можно оптимизировать (я использовал его так редко, что большая работа над ним снизила бы общую эффективность), но, по крайней мере, лучше поделиться, чем позволить ему собирать пыль, лежащую на моем жестком диске.

    ## 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=""))
    }

Основная функция df2newick() принимает два аргумента:

  • df который является преобразуемым фреймом данных (объект класса data.frame)
  • innerlabel который говорит функции писать метки для внутренних узлов (булево)

Чтобы продемонстрировать это на вашем примере:

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

Теперь вы можете прочитать его в объект класса phylo с read.tree() от обезьяны

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

Если вы хотите добавить метки внутренних узлов в строку Newick, вы можете использовать это:

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

Надеюсь, что это полезно (и, возможно, моя докторская степень не была полной талией времени;-)


Дополнительное примечание для вашего формата данных:

Как вы можете заметить, функция df2newick игнорирует внутренние моды с одним ребенком (что в любом случае лучше всего использовать с большинством филогенетических методов... было актуально только для меня). df объекты, которые я изначально получил и использовал с этим сценарием, имели следующий формат:

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

Очень похоже на ваш... но "внутренние отдельные дочерние узлы" просто имели то же имя, что и их дочерние элементы, но у вас есть и другие внутренние имена для этих узлов, и имена игнорируются... может быть неактуально, но вы можете просто проигнорируйте часть функции рекурсии, например:

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

и вы получите что-то вроде этого:

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

Это действительно выглядит странно для меня, но я добавляю это на всякий случай, потому что теперь оно действительно включает всю информацию из вашего исходного кадра данных.

Я не знаю много о внутренней структуре дендрограмм в R, но следующий код создаст структуру вложенного списка с иерархией, которую, я думаю, вы ищете:

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
}

он работает с вектором строк. так что в случае вашего dataframe вам нужно вызвать stree(as.character(df[,3]))

Надеюсь это поможет.

Другие вопросы по тегам