In limma-voom (from the limma package), your input is typically raw or normalized counts (e.g., CPM or similar).
No, voom can only accept raw counts. It cannot accept input in the form of normalized counts, and certainly not CPM or similar. You may be getting confused with the lmFit pipelines in limma, which can accept logCPM values, but even lmFit cannot accept CPM on an unlogged scale.
voom then applies a log2 transformation (roughly log2(count + offset))
It is correct that voom applies an offset and a log2 transformation, but you are forgetting the need for library size normalization, which is crucial. I already made this point in a comment to the OP above.
voom does use log2(count + offset) internally, without library size correction, to predict the variance trend, but the linear models and downstream analysis is on log2CPM values.
Counts 0–15 sound like log2(CPM) or log2(TPM) (common in voom pipelines)
Again, I suspect you are thinking of limma-trend with lmFit rather than limma-voom.
For transcript-level analyses, we recommend the use of divided transcript counts instead of log2(TPM), see Baldoni et al (2025):
Baldoni PL#, Chen L#, Li M, Chen Y, Smyth GK (2025). Dividing out quantification uncertainty enables assessment of differential transcript usage with limma and edgeR. Nucleic Acids Research (to appear). bioRxiv https://doi.org/10.1101/2025.04.07.647659
a) Limma-voom input is raw counts
b) add code
c) follow the limma user guide, not these random yt videos
Cannot comment further because mainly b) is missing.
Thank you for your answer,
a) I know, however when I skip the voom function, while most of the logFC are between -2 and 2 (which is already a lot for my data), the maximum logFC is 5 (i.e. a fold change of 32), which is absolutely not realistic when looking at this gene. However, when using voom, most of the logFC are between -0.5 and 0.5, which is more realistic. For a given gene, 2^logFC really matches with the difference between groups seen on the histogram. I feel like voom makes sense here, I might be wrong but I don't really understand why.
b) count_mat is a count matrix with samples in column and gene in rows.
Without voom, the most deregulated genes in A.vs.B1 have absolute logFC between 2 and 5, which is never the case in reality. With voom, the most deregulated genes have an absolute logFC between 0.3 and 1, which is in fact what I see when looking at an histogram.
So I have the answer for my original post, and I understand correctly the logFC, however now the issue is that my logFC only seem correct when using voom and I don't know why.
c) I've been reading and comparing a lot of documentation (Bionconductor, github, etc) on limma, voom, how to write it, to have a consistent and well defined method, I am not just following a random yt video.
Edit: Most of the volcano plot I see online have larger absolute logFC than 0.5. But when I look my histogram, my guess is that there is no larger logFC than 1. Am I still missing something? Or maybe is it just because of the data normalization?
You really need to follow the user guide if you're new. The voom part is wrong because you do not calculate any normalizatiuon prior to voom (calcNormFactors) so youre vooming the raw counts without correction for depth and composition, and the "not using voom" part is even more wrong because you're plainly fitting models on the raw counts which makes no sense.
OP's data is not raw counts as noted in original post
I think the biggest issue is that I don't know what normalization procedure was applied to my data, as I don't know if calcNormFactors was already used, if they only applied log2, etc.
Missed that. Ok, then voom makes no sense. You can try limma-trend if it is log2, see user guide.
How can that be, since your original post says that you have some sort of normalized log2 quantity, rather than actual counts? What precise quantities did you put into
count_mat?This absolutely is the problem!
You have to know, at very least, whether the quantities have been normalized for library size or not. Without that, I can't see how you can approach the data in any sensible way.
Thank you for your answer. Sorry it was unclear, count_mat is not really a count matrix, but the normalized count matrix I was talking about. For each cell, I tried to sum the (normalized) count for every genes, and the sums are close for every cell. The coefficient of variation is 0.8%, which makes me think the quantities have been normalized for library size.
THere is no point in guessing. A reproducible analysis starts from a matrix of raw counts and the documentation how it was created. Otherwise, what's the point?