Я знаю, что могу создать дерево выражений в R с помощью функции substitute. Допустим, я генерирую следующее дерево выражений:

expT <- substitute(a+(2*b+c))

Можно ли визуализировать дерево выражений в R, создавая что-то вроде:

Expression Tree

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

11
Gumeo 29 Окт 2015 в 19:09

4 ответа

Лучший ответ

Вот подход, использующий преимущества функции utils::getParseData и заимствованный из функции, написанной для parser package и использование igraph для визуальных эффектов. Связанная функция почти выполняет то, что вы хотели, но данные, возвращаемые функцией getParseData, имеют пустые узлы с числовыми значениями / символами / операторами и т. Д. На листьях. Это имеет смысл, если вы пытаетесь анализировать функции, тернарные выражения или более сложные вещи.

Эта функция просто создает список edgelist из данных синтаксического анализа.

## https://github.com/halpo/parser/blob/master/R/plot.parser.R
## Modified slightly to return graph instead of print/add attr
parser2graph <- function(y, ...){
    y$new.id <- seq_along(y$id)
    h <- graph.tree(0) + vertices(id = y$id, label= y$text)
    for(i in 1:nrow(y)){
        if(y[i, 'parent'])
            h <- h + edge(c(y[y$id == y[i, 'parent'], 'new.id'], y[i, 'new.id']))
    }
    h <- set_edge_attr(h, 'color', value='black')
    return(h)
}

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

## Function to collapse the parse tree (removing () and {})
parseTree <- function(string, ignore=c('(',')','{','}'), ...) {
    dat <- utils::getParseData(parse(text=string))
    g <- parser2graph(dat[!(dat$text %in% ignore), ])
    leaves <- V(g)[!degree(g, mode='out')]                             # tree leaves
    preds <- sapply(leaves, neighbors, g=g, mode="in")                 # their predecessors
    vertex_attr(g, 'label', preds) <- vertex_attr(g, 'label', leaves)  # bump labels up a level
    g <- g - leaves                                                    # remove the leaves
    gaps <- V(g)[!nchar(vertex_attr(g, 'label'))]                      # gaps where ()/{} were
    nebs <- c(sapply(gaps, neighbors, graph=g, mode='all'))            # neighbors of gaps
    g <- add_edges(g, nebs, color='blue')                              # edges around the gaps
    g <- g - V(g)[!nchar(vertex_attr(g, 'label'))]                     # remove leaves/gaps
    plot(g, layout=layout.reingold.tilford, ...)
    title(string, cex.main=2.5)
}

Пример, чуть более вложенное выражение. Анимация показывает, как сворачивается исходное дерево.

## Example string
library(igraph)
string <- "(a/{5})+(2*b+c)"

parseTree(string,  # plus some graphing stuff
          vertex.color="#FCFDBFFF", vertex.frame.color=NA,
          vertex.label.font=2, vertex.label.cex=2.5,
          vertex.label.color="darkred", vertex.size=25,
          asp=.7, edge.width=3, margin=-.05)

enter image description here

11
Rorschach 3 Ноя 2015 в 12:26

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

Исходя из этого, остальное «относительно» просто:

  • Для каждого узла определите символ, который нужно напечатать.
  • Сохраните (относительную) координату для текущего узла. При рекурсии выражения эта координата обновляется в зависимости от того, что вы делаете; например, вы знаете, что аргументы вызова функции должны быть центрированы под ее вызовом, поэтому вы можете соответствующим образом обновить координату y, а затем вычислить x в зависимости от количества аргументов. Операторы - лишь частный случай этого.

Наконец, вы можете использовать символы вместе с их координатами, рассчитанными таким образом, чтобы построить их относительно друг друга.

2
Konrad Rudolph 29 Окт 2015 в 16:16

Вот некоторый код и результаты, которые могут быть полезны и наименее важны для возможности «пройтись» по «дереву синтаксического анализа»:

> parse( text="a+(2*b+c)")
expression(a+(2*b+c))
> parse( text="a+(2*b+c)")[[1]]
a + (2 * b + c)
> parse( text="a+(2*b+c)")[[1]][[1]]
`+`
> parse( text="a+(2*b+c)")[[1]][[2]]
a
> parse( text="a+(2*b+c)")[[1]][[3]]
(2 * b + c)
> parse( text="a+(2*b+c)")[[1]][[4]]
Error in parse(text = "a+(2*b+c)")[[1]][[4]] : subscript out of bounds
> parse( text="a+(2*b+c)")[[1]][[3]][[1]]
`(`
> parse( text="a+(2*b+c)")[[1]][[3]][[2]]
2 * b + c
> parse( text="a+(2*b+c)")[[1]][[3]][[2]][[1]]
`+`
> parse( text="a+(2*b+c)")[[1]][[3]][[2]][[2]]
2 * b
> parse( text="a+(2*b+c)")[[1]][[3]][[2]][[3]]
c
> parse( text="a+(2*b+c)")[[1]][[3]][[2]][[2]][[1]]
`*`
> parse( text="a+(2*b+c)")[[1]][[3]][[2]][[2]][[2]]
[1] 2
> parse( text="a+(2*b+c)")[[1]][[3]][[2]][[2]][[3]]
b

Я подумал, что видел сообщение в R-help или r-devel Томаса Ламли или Люка Тирни, в котором это было сделано, но пока не смог найти его. Я нашел сообщение @ G.Grothendieck, которое программно разбирает дерево синтаксического анализа, на котором вы можете строить:

 e <- parse(text = "a+(2*b+c)") 
my.print <- function(e) { 
  L <- as.list(e) 
  if (length(L) == 0) return(invisible()) 
  if (length(L) == 1) 
     print(L[[1]]) 
     else sapply(L, my.print) 
return(invisible()) } 
my.print(e[[1]])
#----- output-----
`+`
a
`(`
`+`
`*`
[1] 2
b
c
3
IRTFM 29 Окт 2015 в 17:41

Следующее дает большую часть пути. Он имитирует pryr:::tree для рекурсивного исследования дерева вызовов, а затем назначает data.tree узлов. Я бы предпочел igraph, но он нетерпим к повторяющимся именам узлов (например, + появляется дважды). Я также не могу заставить dendrogram пометить любую из ветвей, кроме корня.

#install.packages("data.tree")
library(data.tree)

make_tree <- function(x) {
  if (is.atomic(x) && length(x) == 1) {
    as.character(deparse(x)[1])
  } else if (is.name(x)) {
    x <- as.character(x)
    if (x %in% c("(", ")")) {
      NULL
    } else {
      x
    }
  } else if (is.call(x)) {
    call_items <- as.list(x)
    node <- call_items[[1]]
    call_items <- call_items[-1]
    while (as.character(node) == "(" && length(call_items) > 0) {
      node <- call_items[[1]]
      call_items <- call_items[-1]
    }
    if (length(call_items) == 0) 
      return(make_tree(node))
    call_node <- Node$new(as.character(node))
    for (i in 1:length(call_items)) {
      res <- make_tree(call_items[[i]])
      if (is.environment(res))
        call_node$AddChildNode(res)
      else
        call_node$AddChild(res)
    }
    call_node
  } else
    typeof(x)
}

tree <- make_tree(quote(a+(2*b+c)))
print(tree)
plot(as.dendrogram(tree, edgetext = T), center = T, type = "triangle", yaxt = "n")

Что дает разумный текстовый вывод:

      levelName
1 +            
2  ¦--a        
3  °--+        
4      ¦--*    
5      ¦   ¦--2
6      ¦   °--b
7      °--c    

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

5
Jack Wasey 3 Ноя 2015 в 03:34