Skip to content

Commit 0e041b3

Browse files
committed
Retain tip labels for labelled trees
1 parent c2715a9 commit 0e041b3

File tree

2 files changed

+75
-29
lines changed

2 files changed

+75
-29
lines changed

R/tree_numbering.R

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -666,47 +666,59 @@ RenumberTips.multiPhylo <- function(tree, tipOrder) {
666666
at <- attributes(tree)
667667
labelled <- !is.null(at[["TipLabel"]])
668668

669-
startOrder <- if (labelled) at[["TipLabel"]] else tree[[1L]][["tip.label"]]
669+
if (!labelled) {
670+
# Unlabelled: each tree may have different tip orderings,
671+
# so must process individually
672+
tree <- lapply(tree, RenumberTips.phylo, tipOrder)
673+
attributes(tree) <- at
674+
return(tree)
675+
}
676+
677+
startOrder <- at[["TipLabel"]]
670678
newOrder <- if (is.numeric(tipOrder)) {
671679
startOrder[tipOrder]
672680
} else {
673681
TipLabels(tipOrder, single = TRUE)
674682
}
675683

676-
if (identical(startOrder, newOrder)) return(tree)
677-
678-
if (any(duplicated(newOrder))) {
679-
stop("Tree labels ",
680-
paste0(newOrder[duplicated(newOrder)], collapse = ", "),
681-
" repeated in `tipOrder`")
682-
}
684+
nTip <- length(startOrder)
685+
if (identical(startOrder, newOrder)) {
686+
# Tip numbering already correct; use identity permutation to ensure
687+
# each tree materializes its own tip.label element
688+
matchOrder <- seq_len(nTip)
689+
} else {
690+
if (any(duplicated(newOrder))) {
691+
stop("Tree labels ",
692+
paste0(newOrder[duplicated(newOrder)], collapse = ", "),
693+
" repeated in `tipOrder`")
694+
}
683695

684-
if (length(startOrder) != length(newOrder)) {
685-
startOnly <- setdiff(startOrder, newOrder)
686-
newOnly <- setdiff(newOrder, startOrder)
687-
if (length(startOnly)) {
688-
stop("Tree labels and tipOrder must match.",
689-
if (length(newOnly)) "\n Missing in `tree`: ",
690-
paste0(newOnly, collapse = ", "),
691-
if (length(startOnly)) "\n Missing in `tipOrder`: ",
692-
paste0(startOnly, collapse = ", ")
693-
)
696+
if (length(startOrder) != length(newOrder)) {
697+
startOnly <- setdiff(startOrder, newOrder)
698+
newOnly <- setdiff(newOrder, startOrder)
699+
if (length(startOnly)) {
700+
stop("Tree labels and tipOrder must match.",
701+
if (length(newOnly)) "\n Missing in `tree`: ",
702+
paste0(newOnly, collapse = ", "),
703+
if (length(startOnly)) "\n Missing in `tipOrder`: ",
704+
paste0(startOnly, collapse = ", ")
705+
)
706+
}
707+
newOrder <- intersect(newOrder, startOrder)
694708
}
695-
newOrder <- intersect(newOrder, startOrder)
696-
}
697709

698-
nTip <- length(startOrder)
699-
matchOrder <- match(startOrder, newOrder)
700-
if (any(is.na(matchOrder))) {
701-
stop("Tree labels ",
702-
paste0(startOrder[is.na(matchOrder)], collapse = ", "),
703-
" missing from `tipOrder`")
710+
matchOrder <- match(startOrder, newOrder)
711+
if (any(is.na(matchOrder))) {
712+
stop("Tree labels ",
713+
paste0(startOrder[is.na(matchOrder)], collapse = ", "),
714+
" missing from `tipOrder`")
715+
}
704716
}
705717

706718
tree <- .Call(`_TreeTools_renumber_tips_batch`, tree, matchOrder, nTip,
707719
newOrder)
708720

709-
if (labelled) at[["TipLabel"]] <- newOrder
721+
at[["TipLabel"]] <- newOrder
710722
attributes(tree) <- at
711723
tree
712724
}

tests/testthat/test-tree_numbering.R

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,10 @@ test_that("RenumberTips.multiPhylo() covers edge cases", {
177177
result <- RenumberTips(mp8, 8:1)
178178
expect_equal(TipLabels(result[[1]]), paste0("t", 8:1))
179179

180-
# Early return when order matches
181-
expect_identical(RenumberTips(mp8, TipLabels(mp8[[1]])), mp8)
180+
# No-op when order already matches (unlabelled: per-tree fallback)
181+
result_noop <- RenumberTips(mp8, TipLabels(mp8[[1]]))
182+
expect_equal(result_noop[[1]][["edge"]], mp8[[1]][["edge"]])
183+
expect_equal(result_noop[[2]][["edge"]], mp8[[2]][["edge"]])
182184

183185
# Duplicate error
184186
expect_error(RenumberTips(mp8, rep("t1", 8)), "repeated")
@@ -199,6 +201,38 @@ test_that("RenumberTips.multiPhylo() covers edge cases", {
199201
)
200202
})
201203

204+
test_that("RenumberTips.multiPhylo() materializes tip.label", {
205+
# TipLabel-labelled multiPhylo: individual trees should gain tip.label
206+
# even when the order already matches (needed by RoguePlot and others
207+
# that later remove TipLabel)
208+
labs <- paste0("t", 1:6)
209+
mp_labelled <- structure(
210+
list(BalancedTree(6), PectinateTree(6)),
211+
TipLabel = labs,
212+
class = "multiPhylo"
213+
)
214+
result <- RenumberTips(mp_labelled, labs)
215+
attr(result, "TipLabel") <- NULL
216+
# After removing TipLabel, individual trees should still have tip.label
217+
expect_equal(result[[1]][["tip.label"]], labs)
218+
expect_equal(result[[2]][["tip.label"]], labs)
219+
})
220+
221+
test_that("RenumberTips.multiPhylo() handles unlabelled different orderings", {
222+
# Trees with different tip orderings in an unlabelled multiPhylo
223+
tree_abc <- read.tree(text = "(a, (b, c));")
224+
tree_cab <- read.tree(text = "(c, (a, b));")
225+
mp_mixed <- structure(list(tree_abc, tree_cab), class = "multiPhylo")
226+
227+
result <- RenumberTips(mp_mixed, c("a", "b", "c"))
228+
per_tree <- lapply(mp_mixed, RenumberTips, c("a", "b", "c"))
229+
230+
expect_equal(result[[1]][["edge"]], per_tree[[1]][["edge"]])
231+
expect_equal(result[[2]][["edge"]], per_tree[[2]][["edge"]])
232+
expect_equal(result[[1]][["tip.label"]], c("a", "b", "c"))
233+
expect_equal(result[[2]][["tip.label"]], c("a", "b", "c"))
234+
})
235+
202236
test_that("RenumberTips.multiPhylo() batch matches per-tree", {
203237
set.seed(7429)
204238
trees <- c(

0 commit comments

Comments
 (0)