Question: Nanostring analysis with limma
gravatar for mali salmon
4.5 years ago by
mali salmon330
mali salmon330 wrote:

Hello list

In the recent  limma paper (Nucleic Acids Research 2015) it was mentioned that limma is applicable for analyzing Nanostring data.

I have nanostring miRNA counts and I'm wondering on the best way to analyze them. The data can be treated as regular digital counts (RNA-seq) and be analysed using voom-limma approach. Alternatively I can apply the normalization steps recommended by the company (using NanoStringNorm R package), and then continue with differential expression analysis with limma or voom-limma.

Which approach is recommended?



limma nanostring • 7.6k views
ADD COMMENTlink modified 4.4 years ago by ker6110 • written 4.5 years ago by mali salmon330
Answer: Nanostring analysis with limma
gravatar for James W. MacDonald
4.5 years ago by
United States
James W. MacDonald51k wrote:

I am not sure there is a 'best' way to analyze NanoString data, particularly the normalization step.

Here are some observations from a set of recent analyses that we performed. With a sufficiently large number of genes (we had a set of data with ~600 genes, with technical replicates for each sample), we found that normalizing to total counts using voom() gave better results than normalizing to the geometric mean of the supplied housekeeping genes, as well as guessing at housekeeping genes using a set of low variance genes. Using an additional quantile or cyclic loess normalization improved results moderately, but at the cost of violating Occam's razor, so we chose to just use voom() without further normalization.

Note here that we assessed the results by comparing the technical replicates, and went with the normalization that did the best job of making the technical replicates look more similar to each other.

For this first set of samples we are making the assumption that most of the genes are being expressed, and most are being expressed at relatively similar levels, so normalizing to library size may be a reasonable thing to do. We analyzed a smaller panel of genes (~200) that was more focused, and for which we couldn't assume that the genes were in general being expressed at the same levels (in other words, a particular treatment may well have reduced the expression of most of these genes, in which case normalizing to library size would have unduly affected the biological differences). In this situation we had to use the NanoString-supplied housekeeping genes.

In your situation, NanoString supplies both mRNA housekeeping genes, as well as some probes against non-mammalian small RNAs. I assume you spike in the non-mammalian small RNAs, like the ERCC probes. However, the spiked-in RNA only gives you information about the variability introduced after the spike-in occurred (plus, if you are spiking in using small volumes and a Rainin pipettor, then you are likely introducing more variability in that step than you think). So you have mRNA housekeeping genes that may correlate with the amount of miRNA that you originally started with, and some spike-ins that help assess variability introduced later in the process.

In my experience, most miRNAs seem not to be expressed, and those that are expressed appear to be at low levels. This is based on results from Affy's miRNA arrays, which are sort of questionable, but that seems to be the expectation for miRNAs, so I don't think it is far from the truth.

So all of the normalization methods have pretty serious issues for the miRNA panel, and I think you have to choose which one you think is the least worst. But do note that if you decide to use limma-voom to analyze the data, and you want to normalize using something other than total counts, you have to take steps to keep voom() from doing so.

In other words, if you just take your counts and feed into voom(), then by default you will compute the library sizes and then compute cpm, which is normalizing to library size. If you don't want that to happen, you need to give some value for the 'lib.size' argument to voom().



ADD COMMENTlink written 4.5 years ago by James W. MacDonald51k

Hi James,

I am currently trying to use voom() without normalising to library size, as you suggest in the above post. 

As you mention above, I can specify a value for lib.size prior to running voom e.g.

lib.size <- ...

and then specific lib.size in the voom function e.g.

voom(DGElist, design, lib.size=lib.size)

I am struggling with what to change lib.size to, however? 



ADD REPLYlink written 19 months ago by martha.cooper0

When you read data into a DGEList, it computes the column sums of the input data matrix and uses that as the library size (because, by definition that IS the library size). And when you use voom, the counts are converted to counts/million counts (where you are dividing by the library size, in millions). If you don't want to adjust for library size you have to specify some sensible, constant value for all of the samples.

Given that this is your analysis, you will have to decide what a 'sensible' constant value might be.

ADD REPLYlink written 19 months ago by James W. MacDonald51k

Thanks for your help James, and for replying so quickly! I am analysing a nanostring panel of 150 and as per your suggestion above, don't want to minimise biological differences by normalising to library size (or total count for nanostring). I thought that I could use limma-voom to calculate DEGs by making limma-voom use nanostring normalised count data rather than cpm.

To do this I have calculated a normalisation factor based on the nanostring positive controls and housekeeping genes = Nanostring NF. 

I have then read my raw count data into a DEGList. 

I have then changed all values for DEGlist$samples$lib.size to 10^6. 

I have then changed all values for DEGlist$samples$norm.factor to 1/Nanostring NF (specific for each sample)

Am I right in thinking that voom(DEGlist, design) will calculate cpm as:

(count/(10^6 * (1/nanostring NF)) * 10^6 = count*(nanostring NF). 

I was also wondering if the voom transformation still makes statistical sense if the nanostring normalised counts rather than cpm normalised counts are calculated as a base for the mean-variance relationship to be estimated? 

Thanks again! 


ADD REPLYlink modified 19 months ago • written 19 months ago by martha.cooper0
Answer: Nanostring analysis with limma
gravatar for Gordon Smyth
4.5 years ago by
Gordon Smyth38k
Walter and Eliza Hall Institute of Medical Research, Melbourne, Australia
Gordon Smyth38k wrote:

I agree with James' comments.

Our experience with NanoString is still very limited, but you should not use NanoStrongNorm before limma and voom because voom wants to see the real counts. Putting the counts into a DGEList is a good first step:

d <- DGEList(counts=yourcounts)

Here are some normalization options, in increasing order of strength of normalization. In general, the noisier the data, the stronger the normalization that might be appropriate.

1. Normalizing only to library size:

v <- voom(d, design)

2. TMM normalization:

d <- calcNormFactors(d)
v <- voom(d, design)

3. Cyclic loess normalization:

v <- voom(d, design, normalize="cyclicloess")

4. Quantile normalization:

v <- voom(d, design, normalize="quantile")



ADD COMMENTlink modified 4.5 years ago • written 4.5 years ago by Gordon Smyth38k

Sorry, I have digital raw read counts of HTG EdgeSeq Oncology Biomarker Panel (OBP). Whatever I am googling I am not finding anybody used DESeq2, limma, voom or EdgeR for differenial expresseion in this assay. In this assay the expression levels of 2559 genes are assessed. Do you thinK I can use the suggested code as above for normalization following differential expression?

Thanks for any help

ADD REPLYlink written 9 months ago by jivarajivaraj10
Answer: Nanostring analysis with limma
gravatar for ker61
4.4 years ago by
United States
ker6110 wrote:


I have run voom/limma on miRNA Nanostring data.

The data does not include technical replicates. When creating a design matrix from a matrix where the first column includes individual sample identifiers, I received an error that the package could not be run without replicates. I am wondering if this refers to biological and/or technical replicates. If the former, I am wondering if it would be permissible to use general class identifiers (e.g. disease, control) to define replicate conditions. 

Thank you,


ADD COMMENTlink written 4.4 years ago by ker6110

Yes, you've got the general idea. You don't need technical replicates (well -- you might, if you're trying to analyze variability due to sample prep, etc), but biological replicates are what you want.

If you're not using "general class identifiers" (as you say), what did you use?

You're trying to analyze differential expression between 2+ groups, and the point is you need biological replicate samples per group to be able to do that.

ADD REPLYlink written 4.4 years ago by Steve Lianoglou12k
Please log in to add an answer.


Use of this site constitutes acceptance of our User Agreement and Privacy Policy.
Powered by Biostar version 16.09
Traffic: 395 users visited in the last hour