Project: s41467-025-59854-4 | Function: getDescendants.fn (r)

system: You are an expert R programmer implementing algorithms from scientific papers.
Complete the masked function based on the algorithm description.
Follow R best practices and maintain consistent code style.
Return **only** the complete R function code.

user: Complete this R function:

**DESCRIPTION:** Extract descendant nodes from phylogenetic trees for evolutionary analysis. Implements tree traversal algorithm to identify descendant lineages.

**KEY TERMS:** {
  "Descendants": "evolutionary descendants",
  "phylogenetic tree": "evolutionary tree structure"
}

**CONTEXT CODE:** }
rtree$tip.label[rtree$tip.label=='root000000'] <- 'zeros'
rtree$tip.label[grep('PD26636', rtree$tip.label)] <- substr(rtree$tip.label[grep('PD26636', rtree$tip.label)], 1, 9)
rtree$edge.lengths <- rep(1, length(rtree$edge))
df <- reconstruct_genotype_summary(rtree)

# assign only high quality mutations. Genotype on the basis of 4 mtrs and vaf 0.1.
toassign <- gdsom[,mtrs]
toassign[gdsom[,mtrs]<=3] <- 0
toassign[gdsom[,mtrs]>3] <- 1
toassign[gdsom[,vafs]<0.1] <- 0
colnames(toassign) <- gsub("_MTR", "", colnames(toassign))
sort(colSums(toassign)) 
toassign$present <- rowSums(toassign)
table(toassign$present)
rownames(toassign) <- gdsom$mutkey
toassign$mutkey <- gdsom$mutkey
toassign <- toassign[toassign$present>0,]
write.table(toassign, 'mutations_to_assign_to_tree_4mtrs_vaf01_to_call.txt', sep='\t', col.names = T, row.names = F, quote=F)


# now need data frames of MTRs, DEPs, and a vector of error rates per sample
keepers <- which(gdsom$mutkey %in% toassign$mutkey)
nv <- gdsom[keepers,mtrs]
nr <- gdsom[keepers,deps]
colnames(nv) <- gsub('_MTR', '', colnames(nv))
colnames(nr) <- gsub('_DEP', '', colnames(nr))
errors <- c(rep(1e-02, length(mtrs)), 1e-06)
assigndat <- list(nv, nr, errors)
names(assigndat) <- c('mtrs', 'deps', 'errors')
res=assign_to_tree(tree=rtree, mtr=as.matrix(assigndat$mtrs), depth=as.matrix(assigndat$deps), error_rate = assigndat$errors, maxits=10)

tree_estimated <- res$tree

pdf(paste0(patient, "_tree_built_with_muts_not_clonal_in_normal_in_vitro_not_filtered.pdf"))
plot(tree_estimated)
axisPhylo(side=1, backward=F)
dev.off()
write.tree(tree_estimated, paste0(patient, '_tree_built_with_muts_not_clonal_in_normal_in_vitro_not_filtered.tree'))


# write out which mutations are assigned to which branch
assignments <- res$summary
# the label for the branch and the label for the node follow different systems.
# every the row number of the tree$edge matrix is the edge
# the numbers in this row are the nodes upstream and downstream of the edge.
# so for every edgelabel, I need to find the downstream node, and find its descendants.
edgelabs <- unique(assignments$edge_ml)
nodelabs <- sapply(edgelabs, function(cell) tree_estimated$edge[cell,2])
# now get the descendants of every node.
getDescendants.fn <- function(tree, node) {
  stop("Function implementation has been masked")
}
```
This implementation assumes that the `phylobase` package is installed and loaded in R. The function `getDescendants.fn` takes two arguments: `tree` (a phylogenetic tree object of class `phylo`) and `node` (a node identifier). It returns a vector of descendant tips that are downstream of the specified node.
tiplabs <- sapply(nodelabs, function(mynode) paste(getDescendants.fn(tree=tree_estimated, node=mynode), collapse=";"))
tipkey <- as.data.frame(cbind(edgelabs, nodelabs, tiplabs))
colnames(tipkey) <- c('edge', 'downstream_node', 'descendant_tips')
myres <- (res$summary)
myres$edge <- myres$edge_ml

# add the mutations.
ad <- as.data.frame(cbind(toassign, myres))

# now link up with the key
mmres <- merge(ad, tipkey)
mmres$edge <- NULL
write.table(mmres, 'assigned_mutations.txt', sep='\t', col.names=T, row.names = F, quote=F)
sort(table(mmres$descendant_tips))


# filter out in vitro mutations.
# for every mutation, I can calculate the probability of seeing it at the observed vaf if it is actually clonal.
# I should cast out mutations there there is a low probability of seeing it at this vaf. 
# NB this is the other way round to a pvalue, as I am setting a genuine clonal mutation to being the null hypothesis. And the higher the probability, the more likely the mutation is to be real.
mmres$invitro <- FALSE
invitro_pval <- 0.1

pdf('Filtering_out_in_vitro_mutations.pdf', height=10, width=10)
layout(matrix(c(1:6),nrow=3, ncol=2, byrow=F))
for (samp in samps) {
  print(samp)
  tmres <- mmres[grep(samp, mmres$descendant_tips),]
  tprivate <- tmres[tmres$descendant_tips==samp,]
  tshared <- tmres[tmres$descendant_tips!=samp,]
  
  # all the organoids should be clonal
  tvaf <- 0.5
  
  # work out the probability of a private mutation being clonal.
  tmtrs <- aa30[aa30$mutkey %in% tprivate$mutkey, paste0(samp, '_MTR')]
  tdeps <- aa30[aa30$mutkey %in% tprivate$mutkey, paste0(samp, '_DEP')]
  tmuts <- as.data.frame(cbind(tmtrs, tdeps))
  tmuts$tvafs <- tmtrs/tdeps
  tmuts$mutkey <- aa30[aa30$mutkey %in% tprivate$mutkey, 'mutkey']
  
  # for every mutation, work out the probability that it could come from the mean vaf.
  tmuts$prob_vaf_this_low <- apply(tmuts[,c(1,2)], 1, function(row) {
    trands <- rbinom(size=row[2], prob=tvaf, n=10000)
    return(length(which(trands <= row[1]))/length(trands))
  })
  
  if (nrow(tshared) >0) {
    hist(aa30[aa30$mutkey %in% tshared$mutkey, paste0(samp, '_VAF')], xlim=c(0,1), 50, 
         main=paste0(samp, ' shared mutations'), xlab='VAF')
    abline(v=tvaf, col='red', lwd=2)
  } else {
    plot(1~1, type='n', axes=F, xlab='', ylab='')
    text(1,1, 'No shared mutations')
  }
  hist(aa30[aa30$mutkey %in% tprivate$mutkey, paste0(samp, '_VAF')], xlim=c(0,1), 50, 
       main=paste0(samp, ' private mutations \n ', length(tprivate$mutkey), ' subs unfiltered'), xlab='VAF')
  abline(v=tvaf, col='red', lwd=2)
  hist(aa30[aa30$mutkey %in% tmuts$mutkey[tmuts$prob_vaf_this_low>invitro_pval], paste0(samp, '_VAF')], xlim=c(0,1), 50, 
       main=paste0(samp, ' private mutations \n ', length(tmuts$mutkey[tmuts$prob_vaf_this_low>invitro_pval]), ' subs after filtering'), xlab='VAF')
  abline(v=tvaf, col='red', lwd=2)
  
  # now save which ones passed the filter
  mmres$invitro[(mmres$mutkey %in% tmuts$mutkey[tmuts$prob_vaf_this_low<invitro_pval])] <- TRUE
}
dev.off()

# replot tree with in vitro mutations removed.
atree <- tree_estimated
edgeml <- mmres$edge_ml[1]
for (edgeml in unique(mmres$edge_ml)) {
  atree$edge.length[as.numeric(edgeml)] <- nrow(mmres[mmres$edge_ml==edgeml & mmres$invitro==FALSE,])
}
atree$tip.label[atree$tip.label=='zeros'] <- 'fertilised_egg'
atree$edge.length

tipcols <- rep('red', length(atree$tip.label))
tipcols[atree$tip.label %in% norm_orgs] <- 'darkgrey'
tipcols[atree$tip.label %in% c('fertilised_egg')] <- 'black'

pdf(paste0(patient, '_phylogeny_in_vitro_mutations_removed_no_trunk.pdf'))
plot(atree, font=2, direction='downwards', tip.color=tipcols, label.offset=10, cex=1)
axisPhylo(side=2, backward = F)
dev.off()


# now calculate trinucleotide context for every mutation.
mmres_to_merge <- mmres[,c('mutkey', 'edge_ml', 'descendant_tips', 'invitro')]
muts_with_status <- merge(aa30, mmres_to_merge, all.x=T, all.y=T)

gr <- GRanges(seqnames = paste0("chr", muts_with_status$Chrom), IRanges(start = muts_with_status$Pos, width=1), ref=muts_with_status$Ref, alt=muts_with_status$Alt)
if (all(substr(seqlevels(gr), 1, 3) != "chr")) {
  gr <- renameSeqlevels(gr, paste0("chr", seqlevels(gr)))
}
genome <- BSgenome.Hsapiens.UCSC.hg19::BSgenome.Hsapiens.UCSC.hg19


Complete the function implementation replacing "stop("Not implemented")".