-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathphylomorpho.R
136 lines (135 loc) · 5.01 KB
/
phylomorpho.R
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
#' Plot a 2-D phylomorphospace in ggplot2
#'
#' This behaves similar to [phytools::phylomorphospace()], but is for plotting a
#' 2-D phylomorphospace with [ggplot2::ggplot()]. This function works like any
#' other `ggplot2` geom; it can be combined with other geoms (see the example
#' below), and the output can be modified using scales, themes, etc.
#'
#' The ancestral states are estimated using [phytools::fastAnc()]. Note that
#' `phytools` is not necessarily installed with `deeptime`, but it is required
#' to use this function. Following the estimation of the ancestral states, the
#' nodes are connected using [ggplot2::geom_segment()], while the tips are
#' indicated using [ggplot2::geom_point()].
#'
#' The default expectation is that the order of the data is the same order as
#' the tip labels of the tree (`tree$tip.label`). However, if this is not the
#' case, you can map the optional `label` aesthetic to a column in the data that
#' contains the tip names (see example below).
#'
#' @param tree An object of class "phylo".
#' @param position A position adjustment to use on the data for this layer. This
#' can be used in various ways, including to prevent overplotting and
#' improving the display. The `position` argument accepts the following:
#' * The result of calling a position function, such as `position_jitter()`.
#' This method allows for passing extra arguments to the position.
#' * A string naming the position adjustment. To give the position as a
#' string, strip the function name of the `position_` prefix. For example, to
#' use `position_jitter()`, give the position as `"jitter"`.
#' @param seg_args A list of arguments passed only to [ggplot2::geom_segment()].
#' @param point_args A list of arguments passed only to [ggplot2::geom_point()].
#' @param ... Other arguments passed on to both [ggplot2::geom_segment()] and
#' [ggplot2::geom_point()].
#' @importFrom ggplot2 layer
#' @inheritParams ggplot2::layer
#' @inheritParams ggplot2::geom_segment
#' @export
#' @examples
#' library(ggplot2)
#' @examplesIf require(ape)
#' library(ape)
#' tr <- rtree(10)
#' dat <- data.frame(
#' x = runif(10), y = runif(10), label = tr$tip.label,
#' row.names = tr$tip.label
#' )
#' ggplot(dat, aes(x = x, y = y, label = label)) +
#' geom_phylomorpho(tr) +
#' geom_label(size = 5)
geom_phylomorpho <- function(tree, mapping = NULL, data = NULL,
position = "identity", ...,
seg_args = list(), point_args = list(),
arrow = NULL, arrow.fill = NULL,
lineend = "butt", linejoin = "round",
na.rm = FALSE, show.legend = NA,
inherit.aes = TRUE) {
rlang::check_installed("phytools", reason = "to use `geom_phylomorpho()`")
if (!is(tree, "phylo")) {
cli::cli_abort("`tree` must be a phylo object.")
}
mapping2 <- mapping
mapping2$label <- NULL
list(
layer(
data = data,
mapping = mapping,
stat = StatPhylomorpho,
geom = "segment",
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = c(list(
tree = tree,
arrow = arrow,
arrow.fill = arrow.fill,
lineend = lineend,
linejoin = linejoin,
na.rm = na.rm,
...
), seg_args)
),
layer(
data = data,
mapping = mapping2,
stat = "identity",
geom = "point",
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = c(list(
na.rm = na.rm,
...
), point_args)
)
)
}
#' @importFrom ggplot2 ggproto Stat
StatPhylomorpho <- ggproto("StatPhylomorpho", Stat,
required_aes = c("x", "y"),
optional_aes = c("label"),
compute_group = function(data, params) {
data
},
compute_panel = function(self, data, scales, params, tree) {
if (nrow(data) != length(tree$tip.label)) {
cli::cli_abort("`data` must contain the same number of rows as species in
`tree`.")
}
if ("label" %in% colnames(data)) {
rownames(data) <- data$label
data$label <- NULL
} else {
rownames(data) <- tree$tip.label
}
# copied from phytools
A <- apply(data, 2, phytools::fastAnc, tree = tree)
aa <- setNames(
c(data[tree$tip.label, "x"], A[, 1]),
c(seq_along(tree$tip.label), rownames(A))
)
bb <- setNames(
c(data[tree$tip.label, "y"], A[, 2]),
c(seq_along(tree$tip.label), rownames(A))
)
XX <- matrix(aa[as.character(tree$edge)], nrow(tree$edge), 2)
YY <- matrix(bb[as.character(tree$edge)], nrow(tree$edge), 2)
xx <- setNames(c(XX[1, 1], XX[, 2]), c(tree$edge[1, 1], tree$edge[, 2]))
xx <- xx[order(as.numeric(names(xx)))]
yy <- setNames(c(YY[1, 1], YY[, 2]), c(tree$edge[1, 1], tree$edge[, 2]))
yy <- yy[order(as.numeric(names(yy)))]
df <- data.frame(
x = xx[tree$edge[, 1]], xend = xx[tree$edge[, 2]],
y = yy[tree$edge[, 1]], yend = yy[tree$edge[, 2]]
)
return(df)
}
)