-
Notifications
You must be signed in to change notification settings - Fork 1
/
XploRer.Rmd
278 lines (178 loc) · 10 KB
/
XploRer.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
---
title: "Introduction to XploRer"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Introduction to XploRer}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>"
)
```
```{r setup}
library(XploRer)
```
## Installation
you can install the release version of XploRer from [GitHub](https://github.com/) with:
``` r
# install.packages("devtools")
devtools::install_github("VEZY/XploRer")
```
## Read a file
Read a simple MTG file:
```{r example}
library(XploRer)
MTG = read_mtg("https://raw.githubusercontent.com/VEZY/XploRer/master/inst/extdata/simple_plant.mtg")
```
The `read_mtg()` function returns a list of 4:
- classes: a `data.frame` that holds information about the type of nodes used in the MTG (e.g. Internode, Leaf, etc...), the MTG scale for each one, and some more details. Here is the classes `data.frame` from our example:
```{r}
attr(MTG,which = "classes")
```
- description: a `data.frame` defining the potential topological constraints existing between nodes. Here is the description `data.frame` from our example:
```{r}
attr(MTG,which = "description")
```
- features: a `data.frame` listing all attributes present in the file and their associated type. Here is the features `data.frame` from our example:
```{r}
attr(MTG,which = "features")
```
- MTG: a `data.tree` structure defining all nodes, their attributes and their relationships:
```{r}
MTG
```
## Print/extract variables
The variables / attributes of an MTG can be printed using the `print()` function on the MTG field, *e.g.*:
```{r}
print(MTG, ".symbol", "Length")
```
The variables can also be extracted in a `data.frame()` using the functions from the `data.tree` package *e.g.*:
```{r}
data.tree::ToDataFrameTree(MTG,"Length","Width")
```
## Mutate the MTG
The attributes (also known as features or variables) of the MTG can be mutated using `mutate_mtg()`. It allows to compute new variables, or modify the existing ones.
### Compute new variables
`mutate_mtg()` borrows its syntax from `dplyr`. We can compute a new variable based on the values of others:
```{r}
# Import the MTG:
filepath= system.file("extdata", "simple_plant.mtg", package = "XploRer")
MTG = read_mtg(filepath)
# And mutate it by adding two new variables, Length2 and Length3:
mutate_mtg(MTG, Length2 = node$Length + 2, Length3 = node$Length2 * 2)
print(MTG, ".symbol", "Length", "Length2", "Length3")
```
We can note two things here:
1. We use `node$` to access the values of a variable inside the MTG. This is done to avoid any conflicts between variables from the MTG, and variables from your environment;
1. `Length3` uses the results of `Length2` before it even exist. This is very powerful to construct several new variables at once. It is allowed thanks to a sequential construction of the variables.
As with `dplyr` main functions, `mutate_mtg()` can be used with pipes:
```{r}
read_mtg(filepath)%>%
mutate_mtg(Length2 = node$Length + 2)
```
This is allowed because the function returns the results invisibly. Note that it is mutating the MTG in place though, so no need to assign the results of `mutate_mtg()` to a variable.
### Use parent values
You can also use functions inside the call. Some helpers are provided by the package to compute variables based on the ancestors or children of the node (see `parent()`, `children()` and `ancestors()`). Here is an example were we define a new variable called `Length_parent` that is the length of the node's parent:
```{r}
mutate_mtg(MTG, Length_parent = parent(node$Length))
```
`parent()` is used to get the value of the "Length" variable from the parent of each node.
If we need the values of all ancestors of a node along the tree, we can use `ancestors()` instead:
```{r}
mutate_mtg(MTG, total_length = sum(ancestors(Length,self = TRUE),na.rm = TRUE))
```
Here are the results for both:
```{r}
print(MTG,".symbol","Length","Length_parent","total_length")
```
### Use children values
To get the children values of a node, use `children()`. This function returns the values of a field for all children of a node:
```{r}
children(attribute = "Length", node = extract_node(MTG, "node_3"))
```
It can be used to get *e.g.* the average length of the children:
```{r}
mutate_mtg(MTG, children_length = mean(children(node$Length), na.rm = TRUE))
print(MTG, ".symbol", "Length","children_length")
```
### Combine values
We can also make more complex associations. Here is an example were we need the sum of the surface of the section of all children for the nodes:
```{r}
mutate_mtg(MTG, section_surface = pi * ((node$Width / 2)^2),
s_surf_child_sum = sum(children(node$section_surface),na.rm=TRUE))
```
We first compute the surface of the section of each node, and then we sum the values for all children of the nodes. The `s_surf_child_sum` variable uses the `section_surface` variable that was just created before.
### Filter by scale or symbol
#### Introduction on scale and symbol
We can also filter the nodes by scale (*i.e.* the value of the SCALE column in the MTG classes), or by symbol (*i.e.* the name of the SYMBOL column), which corresponds to the `.scale` and `.symbol` values in the node respectively.
To get all the possible values in an MTG, we can print the classes section:
```{r}
MTG$classes
```
To get the actual symbol of a node use the `.symbol` field on a node, and the `.scale` field for the scale. Here is an example for the root node of the mtg:
```{r}
MTG$.symbol
MTG$.scale
```
#### The scale and symbol arguments from the helper functions
`parent()` and `children()` implement the `scale` and `symbol` arguments to filter by scale or symbol. It allows to tell the function to only consider the scales/symbols passed to this argument, ignoring all others.
For example we can filter the parent node by its symbol using the `symbol` argument with `parent()`:
```{r}
parent(attribute = "Length", node = extract_node(MTG, "node_6"), symbol = "Axis")
```
Here it returns `NA` because the first parent is an `Internode`, and the first node with symbol "Axis" (`node_2`) has no values for "Length", see by yourself:
```{r}
print(MTG, ".symbol", "Length")
```
By default, the function will search for the first node which satisfies the symbol required by the user and return its value:
- `parent()` looks if the parent node is of the required symbol, if it is, it returns its value, if not, it looks at the parent of the parent. And it does this recursively until either finding an ancestor with the required symbol, or finding the root node (returns `NA` in this case).
- `children()` looks if the children are of the required symbol. If a child is, it return its value, if not, it will look at the children of the node if any, and repeat the procedure recursively until it finds either a child with the required symbol or a leaf (returns `NA` in this case).
An easier way to think about this is that when using the symbol or scale argument, functions work on a different tree with only the symbol(s) or scale(s) required.
The `continue` argument can be used in `parent()` and `children()` functions to disable this default behavior of "climbing" or "descending" the tree. If `continue` is explicitly set to `FALSE`, the function returns `NA` if the first parent is not of the right symbol.
Here is an example with recursive behavior (the default):
```{r}
children(attribute = Length, node = MTG$node_2, symbol = "Leaf")
```
And without:
```{r}
children(attribute = Length, node = MTG$node_2, symbol = "Leaf", continue = FALSE)
```
The function returns `NA` without the recursive behavior because "node_2" has one child ("node_3") that is not of the right symbol.
> Note that `ancestors()` does not have the `continue` argument because it is a recursive function by design. So if an ancestor is not of the right symbol, it just jumps to the next ancestor.
##### The .symbol and the .scale arguments from mutate_mtg()
`mutate_mtg()` has a `.symbol` and a `.scale` arguments used similarly to the `symbol` and `scale` arguments of the other functions. The only difference is that functions inside a `mutate_mtg()` call will only be applied to the nodes of the chosen scales, while still having access to the parents and children nodes if needed. The `.symbol` and `.scale` arguments are applied to all functions inside the `mutate_mtg()` call.
There can be a benefit in combining those arguments `mutate_mtg()` and the ones from the other functions in a single call.
Be careful though, if you need a computation at one scale for a variable but at another scale for another, or a variable before it is computed, it is preferable to use multiple calls to `mutate_mtg()`, possibly chained using a pipe. For example if we need the section surface for the internodes only, and the surface for the leaves only, we would use two separate calls to `mutate_mtg()` with each its own scale filter:
```{r}
mutate_mtg(MTG, section_surface = pi * ((node$Width / 2)^2),
.symbol = "Internode")%>%
mutate_mtg(area = node$Width * node$Length, .symbol = "Leaf")
print(MTG, ".symbol", "Width","section_surface", "area")
```
## Plotting a plant
### Static plot
The plant topology can be plotted using the `autoplot()` function. This function is implemented by `ggplot2` so you'll need to load this package before-hand:
```{r}
library(ggplot2)
autoplot(MTG)
```
The function can be used in a pipe, such as:
```{r eval=FALSE}
read_mtg(filepath)%>%
mutate_mtg(Length2 = node$Length + 2)%>%
autoplot(.)
```
### Interactive plot
The same plot can be rendered as an interactive plot using:
```{r eval=FALSE}
plotly_mtg(MTG)
```
![Interactive plot](https://raw.githubusercontent.com/VEZY/XploRer/master/www/plotly_MTG.gif)
`plotly_mtg()` uses the [plotly API](https://plotly.com/) under the hood.
It is also possible to add any variable in the tooltip appearing on hover of a node by adding it to the call. For example if we need the `Length` and the `Width` of the nodes, we would write:
```{r eval=FALSE}
plotly_mtg(MTG, Length, Width)
```