Project: s41467-025-61441-6 | Function: tpm (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:** Calculate Transcripts Per Million (TPM) normalization for RNA-seq data. Implements TPM calculation for cross-sample comparison of gene expression levels.

**KEY TERMS:** {
  "tpm": "transcripts per million",
  "normalization": "expression level standardization"
}

**CONTEXT CODE:** # Conditionally load packages
suppressWarnings({
  if (!requireNamespace("tidyverse", quietly = TRUE)) {
    # Package not available - not needed for tpm function
  } else {
    library(tidyverse)
  }
})


#### Generating matrix from kallisto/kb-python csvs + tpm conversion ####

# Wrap global code to prevent execution during testing
if (FALSE) {  # This code block is disabled
X<-read.csv("X.csv", header=FALSE)
var<-read.csv("var.csv")
obs<-read.csv("obs.csv")

X<-as.data.frame(X)

rownames(X)<-make.unique(obs$cell_id)
colnames(X)<-var$gene_name

matrix<-t(X)
}  # End of disabled global code

#tpm matrix function taken from ribiosNGS package
tpm <- function(x, gene.length) {
  stop("Function implementation has been masked")
  # Ensure inputs are in the correct format
  if (!is.matrix(x)) {
    stop("x should be a matrix with genes as rows and samples as columns")
  }
  if (!is.numeric(gene.length)) {
    stop("gene.length should be a numeric vector")
  }
  if (nrow(x) != length(gene.length)) {
    stop("The length of gene.length should match the number of rows in x")
  }

  # Convert gene lengths to kilobases
  gene.length_kb <- gene.length / 1000

  # Calculate RPK (Reads Per Kilobase)
  rpks <- x / gene.length_kb

  # Calculate the sum of RPKs for each sample (column)
  sum_rpks <- colSums(rpks)

  # Calculate TPM (Transcripts Per Million)
  tpm_values <- (rpks / sum_rpks) * 1e6

  return(tpm_values)
}

# Wrap more global code to prevent execution during testing
if (FALSE) {  # This code block is disabled
tpm_matrix<-tpm(matrix, var$length)

saveRDS(matrix, "matrix.rds")
saveRDS(tpm_matrix, "tpm_matrix.rds")


#### quasi-UMI normalization ####

library(quminorm)


#Now obtaining QUMI counts using custom parameter
#Using 2.945 results in mostly NA values. So instead, will use shape parameter default of 2.
#Then check if any NA in matrix. If there is, will continue to decrease shape parameter by 0.1 until not.

system.time(qumi_matrix<-quminorm(tpm_matrix, shape=1.7))
any(is.na(qumi_matrix))

#1.7 accomplished no NA for Blakely Matrix

hist(log1p(rowSums(qumi_matrix)), main="Blakely log(1+QUMI counts)")
hist(log1p(rowSums(matrix)), main="Blakely log(1+est counts)")

saveRDS(qumi_matrix, "qumi_matrix.rds")

}  # End of disabled global code block


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