---
title: "Introduction to songR"
author: "Raban Heller"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Introduction to songR}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
resource_files:
- figures/logo.svg
- figures/logo-dark.svg
---
[](https://github.com/CTTIR/songR/actions/workflows/R-CMD-check.yaml)
[](https://cttir.github.io/songR/)
[](https://CRAN.R-project.org/package=songR)
[](https://app.codecov.io/gh/CTTIR/songR?branch=main)
[](https://cran.r-project.org/package=songR)
[](https://cran.r-project.org/package=songR)
[](https://opensource.org/licenses/MIT)
[](https://lifecycle.r-lib.org/articles/stages.html#experimental)
```{r setup, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.width = 7,
fig.height = 5
)
```
## What is SONG?
The Self-Organizing Nebulous Growths (SONG) algorithm is a parametric method
for nonlinear dimensionality reduction. Unlike t-SNE and UMAP, SONG supports
**incremental visualization**: new data can be added to an existing embedding
without reinitializing or retraining the model. SONG is also robust to noise
and highly mixed clusters.
| Property | SONG | t-SNE | UMAP |
|----------|------|-------|------|
| Incremental updates | Yes | No | Limited |
| Parametric model | Yes (codebook) | No | Optional |
| Noise robustness | High | Low | Medium |
| Speed | Moderate | Slow | Fast |
| Deterministic | With seed | With seed | With seed |
The algorithm is described in:
> Senanayake, D. A., Wang, W., Naik, S. H., & Halgamuge, S. (2021).
> Self-Organizing Nebulous Growths for Robust and Incremental Data
> Visualization. *IEEE TNNLS*, 32(10), 4588--4602.
## Installation
```{r install, eval = FALSE}
# Install from GitHub
devtools::install_github("cttir/songR")
```
## Quick Start with Iris
```{r quickstart}
library(songR)
model <- song(as.matrix(iris[, 1:4]), seed = 42, epochs = 20L)
plot(model, color_by = iris$Species)
```
## Working with the Bundled Dataset
```{r blobs}
data(songR_blobs)
model_blobs <- song(songR_blobs$data, seed = 42, epochs = 15L, verbose = FALSE)
plot(model_blobs, color_by = songR_blobs$labels)
```
## Incremental Visualization
This is SONG's key feature. We train on the first half of the data, then
incrementally add the second half.
```{r incremental}
# Split data
data(songR_blobs)
n <- nrow(songR_blobs$data)
idx1 <- 1:(n / 2)
idx2 <- (n / 2 + 1):n
# Train on first half
model_v1 <- song(songR_blobs$data[idx1, ], seed = 42, epochs = 15L, verbose = FALSE)
# Update with second half
model_v2 <- update(model_v1, songR_blobs$data[idx2, ], epochs = 10L, verbose = FALSE)
par(mfrow = c(1, 2))
plot(model_v1$embedding, pch = 16, cex = 0.5,
col = rainbow(8)[as.integer(songR_blobs$labels[idx1])],
main = "Before update", xlab = "SONG 1", ylab = "SONG 2", bty = "n")
plot(model_v2$embedding, pch = 16, cex = 0.5,
col = rainbow(8)[as.integer(songR_blobs$labels[idx2])],
main = "After update", xlab = "SONG 1", ylab = "SONG 2", bty = "n")
```
The codebook grows to accommodate new data while preserving the existing
embedding structure.
## Predicting New Points
```{r predict}
# Train on 90%, predict on 10%
train_idx <- 1:135
test_idx <- 136:150
model <- song(as.matrix(iris[train_idx, 1:4]), epochs = 15L, seed = 42,
verbose = FALSE)
new_coords <- predict(model, newdata = as.matrix(iris[test_idx, 1:4]))
# Plot training and test points together
plot(model$embedding[, 1], model$embedding[, 2],
col = "gray70", pch = 16, cex = 0.6,
xlab = "SONG 1", ylab = "SONG 2", main = "Training (gray) + Predicted (red)")
points(new_coords[, 1], new_coords[, 2], col = "red", pch = 17, cex = 1.2)
```
## Comparison: SONG vs t-SNE vs UMAP
```{r comparison, eval = requireNamespace("Rtsne", quietly = TRUE) && requireNamespace("uwot", quietly = TRUE)}
mat <- as.matrix(iris[, 1:4])
# SONG
song_model <- song(mat, seed = 42, epochs = 20L, verbose = FALSE)
# t-SNE
tsne_result <- Rtsne::Rtsne(mat, dims = 2, perplexity = 30,
verbose = FALSE, check_duplicates = FALSE)
# UMAP
umap_result <- uwot::umap(mat, n_neighbors = 15, verbose = FALSE)
par(mfrow = c(1, 3))
col <- as.integer(iris$Species)
plot(song_model$embedding, col = col, pch = 16, main = "SONG",
xlab = "SONG 1", ylab = "SONG 2")
plot(tsne_result$Y, col = col, pch = 16, main = "t-SNE",
xlab = "tSNE 1", ylab = "tSNE 2")
plot(umap_result, col = col, pch = 16, main = "UMAP",
xlab = "UMAP 1", ylab = "UMAP 2")
```
All three methods separate the Iris species, but only SONG supports
incremental updates and retains a parametric codebook model.
## Tuning Guide
| Parameter | Default | Description |
|-----------|---------|-------------|
| `spread_factor` | 0.5 | Growth threshold; higher = more coding vectors. Range: (0, 1). |
| `k` | 3 | Neighborhood size. Must be >= `d + 1`. Lower = finer topology. |
| `epsilon` | 0.9 | Edge decay rate (0--1). Lower = sparser, faster-pruning graph. |
| `epochs` | 50 | Number of self-organisation iterations. More = better convergence. |
| `alpha` | 1.0 | Initial learning rate. |
| `a`, `b` | 1.577, 0.895 | Kernel shape parameters from the UMAP literature. |
| `dispersion` | TRUE | UMAP refinement step for visual cluster separation. |
## The Codebook Model
SONG retains a codebook of coding vectors connected by a
topology-preserving graph. This is what enables incremental updates
and fast projection.
```{r codebook}
model <- song(as.matrix(iris[, 1:4]), seed = 42, epochs = 15L, verbose = FALSE)
summary(model)
plot(model, type = "graph", color_by = iris$Species)
```
## Interactive Comparison App
For interactive exploration, launch the Shiny comparison app:
```{r shiny, eval = FALSE}
run_songR_app()
```
This opens a browser-based interface to compare SONG, t-SNE, and UMAP
side-by-side on your own data.
## Citation
```{r citation}
citation("songR")
```