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

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

У меня есть объект 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
4b9b3361

Ответ 1

data.frame для Newick

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

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

    ## 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, который сообщает функции писать метки для внутренних узлов (bulean)

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

    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() от ape

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

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

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

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


Дополнительная заметка для формата данных:

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

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

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

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

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

Ответ 2

Я не знаю много о внутренней структуре дендрограмм в 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
}

он работает с вектором строк. поэтому в случае вашего фрейма данных вам нужно будет позвонить Stree (as.character(DF [3]))

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