Skip to content

Commit 7e5f175

Browse files
authored
ClusterTable-based split_frequencies for SplitFrequency(NULL) (#252)
1 parent 2d350fe commit 7e5f175

File tree

10 files changed

+349
-33
lines changed

10 files changed

+349
-33
lines changed

NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,14 @@ S3method(print,ClusterTable)
254254
S3method(print,Splits)
255255
S3method(print,TreeNumber)
256256
S3method(rev,Splits)
257+
S3method(sort,Splits)
257258
S3method(sort,multiPhylo)
258259
S3method(summary,ClusterTable)
259260
S3method(summary,Splits)
260261
S3method(t,Splits)
261262
S3method(tail,Splits)
262263
S3method(unique,Splits)
264+
S3method(xtfrm,Splits)
263265
export(.CompatibleRaws)
264266
export(.CompatibleSplit)
265267
export(.RandomParent)

NEWS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# TreeTools 2.1.0.9000 (2026-02-16) #
22

3-
- Add Splits and phylo methods for `SplitInformation()`.
3+
- `SplitInformation()` supports `Splits` and `phylo` objects.
4+
- `sort` and `order` support `Splits` objects.
45
- `SplitFrequency(reference = NULL)` returns frequency of all splits.
56

67

R/RcppExports.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ as_newick <- function(edge) {
2525
.Call(`_TreeTools_as_newick`, edge)
2626
}
2727

28+
split_frequencies <- function(trees) {
29+
.Call(`_TreeTools_split_frequencies`, trees)
30+
}
31+
2832
consensus_tree <- function(trees, p) {
2933
.Call(`_TreeTools_consensus_tree`, trees, p)
3034
}

R/Splits.R

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ as.logical.Splits <- function(x, tipLabels = attr(x, "tip.label"), ...) {
315315
print.Splits <- function(x, details = FALSE, ...) {
316316
nTip <- attr(x, "nTip")
317317
tipLabels <- attr(x, "tip.label")
318+
count <- attr(x, "count")
318319
trivial <- TrivialSplits(x)
319320
cat(dim(x)[1], "bipartition", ifelse(dim(x)[1] == 1, "split", "splits"),
320321
if(any(trivial)) paste0("(", sum(trivial), " trivial)"),
@@ -324,9 +325,9 @@ print.Splits <- function(x, details = FALSE, ...) {
324325
} else {
325326
if (nTip) {
326327
if (nTip == 1) {
327-
paste("tip,", tipLabels[1])
328+
paste("tip,", tipLabels[[1]])
328329
} else {
329-
paste("tips,", tipLabels[1], "..", tipLabels[nTip])
330+
paste("tips,", tipLabels[[1]], "..", tipLabels[[nTip]])
330331
}
331332
} else {
332333
"tips"
@@ -345,18 +346,57 @@ print.Splits <- function(x, details = FALSE, ...) {
345346
splitNames <- character(length(x))
346347
nameLengths = 0L
347348
}
348-
cat("\n ", paste0(rep.int(" ", max(nameLengths)), collapse = ""),
349+
if (length(splitNames) > 0) {
350+
cat("\n ", paste0(rep.int(" ", max(nameLengths)), collapse = ""),
349351
paste0(rep_len(c(1:9, " "), nTip), collapse = ""))
350-
351-
for (i in seq_len(dim(x)[1])) {
352-
split <- x[i, , drop = FALSE]
353-
cat("\n", splitNames[i], "",
354-
paste(ifelse(as.logical(rawToBits(split)[seq_len(nTip)]), "*", "."),
355-
collapse = ""))
352+
353+
354+
nSplits <- dim(x)[[1]]
355+
splitCounts <- if (!is.null(count)) {
356+
if (length(count) != nSplits) {
357+
warning("\"count\" attribute does not match number of splits")
358+
}
359+
paste0("\UD7 ", count)
360+
} else {
361+
rep("", nSplits)
362+
}
363+
364+
for (i in seq_len(nSplits)) {
365+
split <- x[i, , drop = FALSE]
366+
cat("\n", splitNames[i], "",
367+
paste(ifelse(as.logical(rawToBits(split)[seq_len(nTip)]), "*", "."),
368+
collapse = ""),
369+
splitCounts[i])
370+
}
356371
}
357372
}
358373
}
359374

375+
#' @family Splits operations
376+
#' @export
377+
sort.Splits <- function(x, decreasing = TRUE, ...) {
378+
newOrder <- order(x, decreasing = decreasing, ...)
379+
count <- attr(x, "count")
380+
if (is.null(count)) {
381+
x[[newOrder]]
382+
} else {
383+
structure(x[[newOrder]], count = count[newOrder])
384+
}
385+
}
386+
387+
# Underpins `order`
388+
#' @family Splits operations
389+
#' @export
390+
xtfrm.Splits <- function(x) {
391+
count <- attr(x, "count")
392+
splitRanking <- as.integer(x)
393+
if (is.null(count)) {
394+
splitRanking
395+
} else {
396+
count + (splitRanking / max(splitRanking))
397+
}
398+
}
399+
360400
#' @family Splits operations
361401
#' @importFrom utils head
362402
#' @export
@@ -390,11 +430,14 @@ tail.Splits <- function(x, n = 6L, ...) {
390430
summary.Splits <- function(object, ...) {
391431
print(object, details = TRUE, ...)
392432
nTip <- attr(object, "nTip")
393-
if (is.null(attr(object, "tip.label"))) {
433+
tipLabels <- attr(object, "tip.label")
434+
if (is.null(tipLabels)) {
394435
cat("\n\nTips not labelled.")
395436
} else {
396-
cat("\n\n", paste0("Tip ", seq_len(nTip), ": ", attr(object, "tip.label"),
397-
"\t", c(character(4L), "\n")[seq_len(min(nTip, 5L))]))
437+
if (length(tipLabels) > 0) {
438+
cat("\n\n", paste0("Tip ", seq_len(nTip), ": ", tipLabels,
439+
"\t", c(character(4L), "\n")[seq_len(min(nTip, 5L))]))
440+
}
398441
}
399442
}
400443

@@ -584,7 +627,7 @@ rev.Splits <- function(x) {
584627

585628
#' Polarize splits on a single taxon
586629
#'
587-
#' @param x Object of class [`Splits`].
630+
#' @param x Object that can be coerced into class [`Splits`].
588631
#' @param pole Numeric, character or logical vector identifying tip that will
589632
#' polarize each split.
590633
#'
@@ -593,6 +636,7 @@ rev.Splits <- function(x) {
593636
#' @family Splits operations
594637
#' @export
595638
PolarizeSplits <- function(x, pole = 1L) {
639+
x <- as.Splits(x)
596640
nTip <- attr(x, "nTip")
597641
if (is.logical(pole)) {
598642
pole <- which(pole)[[1]]

R/Support.R

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,60 @@
3434
SplitFrequency <- function(reference, forest = NULL) {
3535
if (is.null(reference) || is.null(forest)) {
3636
if (is.null(forest)) forest <- reference
37+
if (inherits(forest, "phylo")) forest <- list(forest)
3738
if (length(unique(lapply(lapply(forest, TipLabels), sort))) > 1) {
3839
stop("All trees must bear identical labels")
3940
}
40-
forestSplits <- do.call(c, as.Splits(forest, tipLabels = TipLabels(forest[[1]])))
41-
dup <- duplicated(forestSplits)
42-
ret <- forestSplits[[!dup]]
43-
logicals <- vapply(seq_along(forestSplits),
44-
function(cf) ret %in% forestSplits[[cf]],
45-
logical(sum(!dup)))
46-
count <- if (is.null(dim(logicals))) {
47-
sum(logicals)
48-
} else {
49-
rowSums(logicals)
41+
if (length(forest) == 0) {
42+
return(structure(forest, count = integer()))
43+
}
44+
tipLabels <- TipLabels(forest[[1]])
45+
if (length(tipLabels) < 4) {
46+
return(structure(matrix(raw()), nTip = length(tipLabels),
47+
tip.label = tipLabels, count = integer(),
48+
class = "Splits"))
49+
}
50+
forest <- RenumberTips(forest, tipLabels)
51+
forest <- Preorder(forest)
52+
result <- split_frequencies(forest)
53+
splits <- result[["splits"]]
54+
counts <- result[["counts"]]
55+
nTip <- length(tipLabels)
56+
nbin <- ncol(splits)
57+
if (nrow(splits) == 0) { # Not been able to hit these lines - included just in case
58+
return(structure(splits, nTip = nTip, tip.label = tipLabels, # nocov
59+
count = integer(), class = "Splits")) # nocov
60+
}
61+
# The ClusterTable outputs clusters (clades); normalize so bit 0 (tip 1)
62+
# is not in the set (matching as.Splits convention)
63+
nTipMod <- nTip %% 8L
64+
lastByteMask <- if (nTipMod == 0L) as.raw(0xff) else as.raw(bitwShiftL(1L, nTipMod) - 1L)
65+
keep <- logical(nrow(splits))
66+
for (i in seq_len(nrow(splits))) {
67+
val <- splits[i, ]
68+
# Count bits set (to filter trivial splits)
69+
nBits <- sum(vapply(as.integer(val), function(b) sum(as.integer(intToBits(b))), integer(1)))
70+
if (nBits < 2L || nBits > nTip - 2L) next # trivial split
71+
# Normalize: if bit 0 is NOT set, complement to match as.Splits format
72+
if (!as.logical(as.integer(val[1]) %% 2L)) {
73+
for (j in seq_along(val)) {
74+
splits[i, j] <- as.raw(bitwXor(as.integer(val[j]), 0xffL))
75+
}
76+
# Mask last byte
77+
if (nTipMod > 0L) {
78+
splits[i, nbin] <- as.raw(bitwAnd(as.integer(splits[i, nbin]),
79+
as.integer(lastByteMask)))
80+
}
81+
}
82+
keep[i] <- TRUE
5083
}
51-
attr(ret, "count") <- unname(count)
84+
splits <- splits[keep, , drop = FALSE]
85+
counts <- counts[keep]
86+
ret <- structure(splits,
87+
nTip = nTip,
88+
tip.label = tipLabels,
89+
class = "Splits")
90+
attr(ret, "count") <- counts
5291
ret
5392
} else {
5493
referenceSplits <- as.Splits(reference)

man/PolarizeSplits.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/RcppExports.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,17 @@ BEGIN_RCPP
8585
return rcpp_result_gen;
8686
END_RCPP
8787
}
88+
// split_frequencies
89+
List split_frequencies(const List trees);
90+
RcppExport SEXP _TreeTools_split_frequencies(SEXP treesSEXP) {
91+
BEGIN_RCPP
92+
Rcpp::RObject rcpp_result_gen;
93+
Rcpp::RNGScope rcpp_rngScope_gen;
94+
Rcpp::traits::input_parameter< const List >::type trees(treesSEXP);
95+
rcpp_result_gen = Rcpp::wrap(split_frequencies(trees));
96+
return rcpp_result_gen;
97+
END_RCPP
98+
}
8899
// consensus_tree
89100
RawMatrix consensus_tree(const List trees, const NumericVector p);
90101
RcppExport SEXP _TreeTools_consensus_tree(SEXP treesSEXP, SEXP pSEXP) {
@@ -478,6 +489,7 @@ static const R_CallMethodDef CallEntries[] = {
478489
{"_TreeTools_ape_neworder_phylo", (DL_FUNC) &_TreeTools_ape_neworder_phylo, 5},
479490
{"_TreeTools_ape_neworder_pruningwise", (DL_FUNC) &_TreeTools_ape_neworder_pruningwise, 5},
480491
{"_TreeTools_as_newick", (DL_FUNC) &_TreeTools_as_newick, 1},
492+
{"_TreeTools_split_frequencies", (DL_FUNC) &_TreeTools_split_frequencies, 1},
481493
{"_TreeTools_consensus_tree", (DL_FUNC) &_TreeTools_consensus_tree, 2},
482494
{"_TreeTools_descendant_edges", (DL_FUNC) &_TreeTools_descendant_edges, 3},
483495
{"_TreeTools_descendant_edges_single", (DL_FUNC) &_TreeTools_descendant_edges_single, 5},

0 commit comments

Comments
 (0)