linf works with nonnegative measurements, such as microbial counts or proportions. Supply a numeric matrix with samples in rows and features in columns. A feature is a measured quantity, such as the abundance of a type of bacterium.
The package can rescale each sample by its largest value, group samples by which features are most abundant, and divide those groups into smaller groups. It can also assign new samples to groups you have already defined. The groups are called dominant community state types (dCSTs) in the package and methods paper.
After installation, try this example.
n0 = 4 means a group must initially contain at least four
samples to be kept. set.seed(1) makes the randomly
generated example data repeatable.
library(linf)
set.seed(1)
counts <- matrix(rpois(30, 5), nrow = 10,
dimnames = list(paste0("s", 1:10), c("A", "B", "C")))
M <- normalize.linf(counts)
dominant <- linf.dominant.features(M)
fit <- linf.csts(M, n0 = 4, low.freq.policy = "absorb")| Feature | Samples before reassignment | Samples after reassignment |
|---|---|---|
| A | 4 | 6 |
| B | 4 | 4 |
| C | 2 | 0 |
Each sample is first assigned to its largest feature. When largest
values are equal, the first column is used. This gives four samples to
A, four to B, and two to C. Groups A and B meet the minimum size of
four. Group C is too small, so each of its samples is reassigned to A or
B, whichever has the larger value in that sample. Here, both go to A:
the final groups contain six and four samples. The option
"absorb" selects this reassignment rule. A was not the
largest feature in the two reassigned samples; C was.
M contains the rescaled values. Each sample’s largest
value is now one, and zeros stay zero. Use M directly if
you only need rescaling, without groups.
Use linf.udcsts() when samples should belong to the same
group if they have the same leading features. For example, A followed by
B and B followed by A both count towards one {A, B} group.
The minimum group size is checked after combining them:
U <- rbind(c(A = 9, B = 5, C = 1), c(A = 8, B = 4, C = 1),
c(A = 4, B = 9, C = 1), c(A = 3, B = 8, C = 1),
c(A = 1, B = 3, C = 9))
unordered <- linf.udcsts(U, level = 2, n0 = 3, low.freq.policy = "filter")
unordered$frequency.table
#> ID freq cumm.pct
#> 1 1,2 4 80
#> 2 2,3 1 100
U[unordered$keep, , drop = FALSE]
#> A B C
#> [1,] 9 5 1
#> [2,] 8 4 1
#> [3,] 4 9 1
#> [4,] 3 8 1The four samples led by A and B remain. The single sample led by B and C is excluded because its group is smaller than three. It is not reassigned.
The result includes:
ID: the original column numbers of the features in each
group; 1,2 means A and B here.freq: the number of samples originally in that group,
before any reassignment or exclusion.cumm.pct: the percentage of all input samples in this
group and groups at least as large. Groups with equal counts get the
same percentage. Here the four A/B samples account for 80% of the five
input samples.pure.id: the original group of each sample, kept even
if it is later reassigned or excluded.The "filter" option excludes samples from small groups,
as above. The "pure" option keeps them but marks them as
rare. The "absorb" option reassigns them to the kept group
whose features have the largest combined abundance in that sample. This
differs from linf.csts(), which uses one feature at a time
and restricts each subdivision to its current group. See
?linf.udcsts for details.
linf.udcst.graph() connects groups defined by their two
leading features. By default, pairs such as {A, B} and
{A, C} are linked only if both occur among samples whose
three leading features are {A, B, C}. Sharing A alone is
not enough.
The result records the supporting sample counts, measures of how
often each pair and its three-feature group occur together, and which
group each sample belongs to. Counts use all input samples, even if you
keep only some pairs in the network. It also reports triangles supported
by the same three-feature group. Every link has length one; a larger
sample count does not shorten it. Unconnected pairs and separate parts
of the network are kept. See ?linf.udcst.graph for
details.
Install the released package from CRAN:
install.packages("linf")linf.udcsts() and linf.udcst.graph()
require linf 0.4.0 or later. Check your installed
version with packageVersion("linf"). Version 0.4.0 includes
four guides, called vignettes in R. Open the function and
example-data guides with:
help("linf", package = "linf")
vignette("function-guide", package = "linf")
vignette("example-datasets", package = "linf")The published website is updated separately and may describe an older version. Use the installed help for the version you run.
filter.asv() removes
samples with too few total counts and features that occur in too few
samples. normalize.linf() divides each sample’s values by
its largest value. Samples whose largest value is at or below the chosen
cutoff (tol) are left unchanged, including all-zero
samples.linf.dominant.features()
identifies each sample’s largest feature. linf.csts() forms
groups with a minimum size; refine.linf.csts() divides
selected groups into smaller groups using their next leading
features.summary() counts
assigned, rare and unassigned samples. linf.landmarks()
selects representative samples from the observed data using a chosen
feature’s value, for example the sample closest to its mean or median.
It returns actual samples, not averages of their measurements.transfer.dcsts()
uses the groups defined by an earlier call to linf.csts(),
without changing their definitions.Scaling by the largest value is called L-infinity normalization. The values are not required to sum to one, and no logarithms are taken. Zeros stay zero. Grouping results depend on which features you include, the minimum group size, how small groups are handled, and how choices between equal values are made. Adding more levels gives finer groups, but does not necessarily make them more useful. The function guide source explains these choices with runnable examples.
The figure below shows the first level of grouping for 766 gut microbiome samples provided with the package, from the American Gut Project (AGP). Filtering leaves 763 samples and 307 microbial features. Groups initially containing fewer than 30 samples are too small to keep. Their samples are reassigned to the kept groups, using the largest value among the remaining defining features in each sample. The figure shows the resulting groups, without a separate rare-sample category.
This subset includes every available sample assigned to four selected uncommon groups. The remaining places were filled by random selection from other eligible samples, using seed 42. Selection did not use participants’ recorded traits. Because groups had different chances of being selected, do not use the observed trait frequencies, differences between groups, or statistical test results to draw conclusions about the broader population. These data are provided to demonstrate how to use the package.

The 763 samples are assigned to seven groups. The largest are Bacteroides (239 samples), Escherichia-Shigella (134), and Staphylococcus (102). In total, 159 samples were reassigned from groups below the minimum size.
See vignette("linf-intro", package = "linf") for a
reproducible example using these data.
Version 0.4.0 includes four installed guides. The first two links below open their source files on GitHub; the R commands open the formatted guides locally.
vignette("function-guide", package = "linf").vignette("example-datasets", package = "linf").The interactive 3D visualization example is available on the website only.
browseVignettes("linf")If you use this package, please cite:
Gajer, P. & Ravel, J. (2025). A New Approach to Compositional Data Analysis using L∞-normalization with Applications to Vaginal Microbiome. arXiv preprint arXiv:2503.21543 [stat.CO]. doi: 10.48550/arXiv.2503.21543
BibTeX
@article{gajer2025linf,
title = {A New Approach to Compositional Data Analysis using
{$L^{\infty}$}-normalization with Applications to
Vaginal Microbiome},
author = {Gajer, Pawel and Ravel, Jacques},
year = {2025},
eprint = {2503.21543},
archivePrefix = {arXiv},
primaryClass = {stat.CO},
journal = {arXiv preprint arXiv:2503.21543},
doi = {10.48550/arXiv.2503.21543},
url = {https://arxiv.org/abs/2503.21543}
}MIT © 2025 Pawel Gajer. See LICENSE. Sources and reuse conditions for the example data are recorded in the data-source notes.