///|
/// preprocessCore Demo.
///
/// This example demonstrates core microarray preprocessing algorithms
/// from Bioconductor's preprocessCore package, including:
/// 1. Quantile normalization
/// 2. Subset quantile normalization
/// 3. Invariant set normalization
/// 4. Cyclic loess normalization
/// 5. Contrast normalization
/// 6. Log2 transformation and normalization
///
/// Inspired by Bioconductor's preprocessCore package.
fn format_matrix(m : Array[Array[Double]]) -> String {
let lines : Array[String] = []
for i in 0.. 0 {
row_str = row_str + ", "
}
let v = m[i][j]
let val_str = if v >= 1000.0 {
v.to_string()[0:6].to_owned()
} else if v >= 100.0 {
v.to_string()[0:5].to_owned()
} else {
v.to_string()[0:4].to_owned()
}
row_str = row_str + val_str
}
row_str = row_str + "]"
lines.push(row_str)
}
lines.join("\n")
}
///|
fn print_matrix(label : String, m : Array[Array[Double]]) -> Unit {
println("\{label}:")
println(format_matrix(m))
}
///|
fn col_means(m : Array[Array[Double]]) -> Array[Double] {
let n_r = m.length()
let n_c = if n_r > 0 { m[0].length() } else { 0 }
let means : Array[Double] = []
for j in 0.. 0 { s / n_r.to_double() } else { 0.0 })
}
means
}
///|
fn col_sds(m : Array[Array[Double]]) -> Array[Double] {
let n_r = m.length()
let n_c = if n_r > 0 { m[0].length() } else { 0 }
let means = col_means(m)
let sds : Array[Double] = []
for j in 0.. 1 { (s / (n_r.to_double() - 1.0)).sqrt() } else { 0.0 })
}
sds
}
///|
fn pad_right(s : String, n : Int32) -> String {
if s.length() >= n {
s
} else {
s + " ".repeat(n - s.length())
}
}
///|
fn string_index_of(s : String, sub : String) -> Int32 {
let n = s.length()
let kn = sub.length()
if kn == 0 || kn > n {
return -1
}
let mut i = 0
while i <= n - kn {
let mut matched = true
let mut j = 0
while j < kn {
if s.unsafe_get(i + j) != sub.unsafe_get(j) {
matched = false
break
}
j = j + 1
}
if matched {
return i
}
i = i + 1
}
-1
}
///|
fn fmt_fixed(d : Double, decimals : Int32) -> String {
let s = d.to_string()
let dot_idx = string_index_of(s, ".")
if dot_idx >= 0 {
let end = if dot_idx + 1 + decimals < s.length() {
dot_idx + 1 + decimals
} else {
s.length()
}
s[0:end].to_owned()
} else {
let mut r = s + "."
while r.length() < s.length() + 1 + decimals {
r = r + "0"
}
r
}
}
///|
fn print_norm_summary(label : String, m : Array[Array[Double]]) -> Unit {
let means = col_means(m)
let label_width = 25
let mut min_v = means[0]
let mut max_v = means[0]
for k = 0; k < means.length(); k = k + 1 {
let v = means[k]
if v < min_v {
min_v = v
}
if v > max_v {
max_v = v
}
}
let range = max_v - min_v
println(
" " +
pad_right(label, label_width) +
" | " +
fmt_fixed(min_v, 2) +
" - " +
fmt_fixed(max_v, 2) +
" (range=" +
fmt_fixed(range, 2) +
")",
)
}
///|
fn main {
println("=== preprocessCore Demo ===")
println("Bioconductor preprocessCore: microarray preprocessing algorithms")
println("")
// Sample gene expression matrix (genes x samples)
let expr_matrix = [
[100.0, 200.0, 150.0, 180.0],
[150.0, 250.0, 180.0, 220.0],
[120.0, 180.0, 160.0, 200.0],
[180.0, 220.0, 190.0, 240.0],
[90.0, 160.0, 140.0, 170.0],
[110.0, 190.0, 170.0, 210.0],
[200.0, 100.0, 130.0, 110.0],
[140.0, 170.0, 200.0, 150.0],
[160.0, 140.0, 120.0, 190.0],
[130.0, 210.0, 175.0, 160.0],
]
let gene_names = [
"GeneA", "GeneB", "GeneC", "GeneD", "GeneE", "GeneF", "GeneG", "GeneH", "GeneI",
"GeneJ",
]
let sample_names = ["Sample1", "Sample2", "Sample3", "Sample4"]
println("=== 1. Input Expression Matrix ===")
println(" (10 genes x 4 samples)")
for i in 0.. 0 {
println(" Invariant genes: \{inv_names.join(", ")}")
}
println("\n=== 5. Median Center Columns (alternative to Loess) ===")
println(" Subtract column median from each sample")
let loess_norm = @bio.median_center_columns(expr_matrix)
print_matrix(" Median-centered normalized matrix", loess_norm)
println("\n=== 6. Contrast-style using normalize_quantiles ===")
println(" Contrast-based: weighted reference distribution")
let contrast_norm = @bio.normalize_quantiles(expr_matrix)
print_matrix(" Quantile (contrast-style) normalized matrix", contrast_norm)
println("\n=== 7. Log2 Transformations ===")
println(" Standard log2 + offset for microarray data")
let log2_std = @bio.log2_transform(expr_matrix, offset=1.0)
print_matrix(" log2(x + 1) matrix", log2_std)
println("\n Quantile + log2 transformation:")
let qn_for_log = @bio.normalize_quantiles(expr_matrix)
let log2_subset = @bio.log2_transform(qn_for_log, offset=1.0)
print_matrix(" QN + log2 matrix", log2_subset)
println("\n=== 8. Background Percentile Correction ===")
println(" Subtract 2nd-percentile background (per column)")
let pshift_norm = @bio.background_correct_percentile(
expr_matrix,
percentile=0.02,
)
print_matrix(" Background-corrected matrix", pshift_norm)
println("\n=== 9. Quantile Normalization (preserves original) ===")
// normalize_quantiles already creates a new matrix, original unmodified
let qn_copy = @bio.normalize_quantiles(expr_matrix)
println(" Input matrix preserved:")
println(" First row original: \{Repr(expr_matrix[0])}")
println(" First row copy norm: \{Repr(qn_copy[0])}")
println("\n=== Summary of Normalization Effects ===")
let label_width = 25
println(" " + pad_right("Method", label_width) + " | ColMeans range")
println(" " + "-".repeat(label_width + 28))
print_norm_summary("Original", expr_matrix)
print_norm_summary("Quantile", qn_norm)
print_norm_summary("Subset-Quantile", subset_qn)
print_norm_summary("Invariant Set", inv_result.normalized_data)
print_norm_summary("Median Center", loess_norm)
print_norm_summary("Background Correct", pshift_norm)
println("\n=== preprocessCore Demo Complete ===")
}