# Dual-luciferase reporter assay statistics: firefly/Renilla ratios, transfections as n and a control reporter that moves

> Normalise firefly to Renilla per well, average wells per transfection, and compare conditions as a log2 ratio per transfection. Wells as n gave 19% false positives in simulation; check that Renilla is unaffected. Python and R code.

Canonical: https://www.licklider.ai/guides/dual-luciferase-reporter-assay/
Section: Guides › Normalised data and fold change
Author: Licklider · Published 2026-11-12 · Last reviewed 2026-11-12 (UTC)

> **Quick answer.** In a dual-luciferase assay, divide firefly by Renilla in each well (after subtracting background), average the wells of each condition within each **independent transfection**, and compare conditions as the **log2 ratio** treated ÷ control per transfection, tested across transfections (n = transfections, a paired analysis). Triplicate wells are not n = 3; in our simulation 3 transfections detected a doubling about 70% of the time and 4 about 95%. Then check that the treatment did not change the **Renilla control** itself, because a moving control produces a false effect; a comparison of Renilla within the same experiment is weak, so rule it out in a separate experiment. In a simulation of 4 transfections with 3 wells each and no real effect, treating the 12 wells per condition as n gave 19.0% false positives, against 5.4% for the paired log ratio; when the treatment raised only Renilla by 50%, the paired ratio reported "repression" in 62.3% of studies.

**Terms used below.** *Firefly luciferase*: the experimental reporter, driven by the promoter or element being studied. *Renilla luciferase*: a co-transfected control reporter with a constant promoter, used to correct for transfection efficiency and cell number. *Ratio (relative luciferase activity)*: firefly ÷ Renilla in one well. *Background*: the signal from untransfected cells, subtracted before the ratio: take the mean of the untransfected wells on the same plate and subtract it from firefly and from Renilla separately; every value must stay above 0 for the ratio and its logarithm to exist. *pRL-TK*: a common Renilla plasmid with a thymidine-kinase promoter. *Plasmid ratio (for example 10:1)*: the mass ratio of firefly plasmid to Renilla plasmid in the transfection. *Empty vector*: the reporter plasmid without the element being tested. *Holm correction*: a p-value adjustment when several treatments are each compared with the same control. *Linear range*: the signal range over which the luminometer reading is proportional to the amount of enzyme. *Independent transfection*: cells transfected and assayed on a separate occasion. *Technical replicate*: a repeat well from the same transfection. *Log2 ratio*: the base-2 logarithm of treated ÷ control; 0 means no change, 1 a doubling. *One-sample t-test*: tests whether the mean of the per-transfection log2 ratios differs from 0; it is the same as a paired t-test on log2 ratios. *Welch t-test*: a two-group t-test that does not assume equal spread. *95% confidence interval (CI)*: the range of values compatible with the data. *Power*: the chance of detecting an effect that is really there. *False positive*: a "significant" result when there is no real effect.

## Which analysis for your question

| Question | Analysis | Report |
| --- | --- | --- |
| Does a treatment change reporter activity? | Background-subtracted firefly ÷ Renilla per well, mean per condition per transfection, log2 ratio treated ÷ control, one-sample t-test across transfections; plus the Renilla check below | Fold change with 95% CI, n transfections |
| Treated and control not transfected side by side | Welch t-test on log2 ratios (firefly ÷ Renilla per transfection), transfections as n; expect less power ([fold change](/guides/statistics-on-fold-change/) has the code) | Fold change with 95% CI |
| Several constructs or treatments vs one control | Log2 ratio of each vs the control per transfection; one-sample t-tests, then `p.adjust(p, "holm")` in R ([fold change](/guides/statistics-on-fold-change/) has the code) | Fold changes with adjusted p |
| Did the treatment change the control reporter? | Renilla treated ÷ control per transfection; and, better, a separate test of the treatment on the control plasmid against cell number or total protein | Renilla fold change with 95% CI |
| Constructs compared with an empty vector | The same, with the empty vector's ratio as the reference in each transfection | Fold over empty vector with 95% CI |
| Only one transfection | n = 1: describe it; repeat on separate days before testing | Ratios per well, no p-value |

Promega's manual states that "normalizing the activity of the experimental reporter to the activity of the internal control minimizes experimental variability caused by differences in cell viability or transfection efficiency" (provided the treatment leaves the control reporter alone, as discussed below), and that when measuring small amounts of luciferase "it is important to subtract the background signal from the measurement of total luminescence" ([Promega TM040](https://www.promega.com/-/media/files/resources/protocols/technical-manuals/0/dual-luciferase-reporter-assay-system-protocol.pdf)). Schagat and colleagues add: "Although data are assayed in triplicate, this should not be considered n=3 for drawing scientific conclusions. True replicates are done on separate days with independent samples" ([Schagat et al. 2007](https://worldwide.promega.com/-/media/files/resources/cell-notes/cn017/normalizing-genetic-reporter-assays.pdf)). Ho and Strauss found that plasmids expressing GATA-4 or GATA-6 "increased Renilla luciferase gene expression by 2 to 8 fold", which "was shown to cause erroneous normalization of transfection efficiency and thus misinterpretation of results in a transactivation assay" ([Ho and Strauss 2004](https://doi.org/10.1186/1472-6750-4-10)). Repele and Manu add that the ratio "performs poorly when the transfection efficiency is low and leads to biased estimates of relative activity", and propose regression of firefly on Renilla as an alternative when signals are near background ([Repele and Manu 2019](https://doi.org/10.3390/mps2030062)).

## Worked example: a treatment with no real effect

Constructed data: control and treated cells transfected side by side on **4 separate days**, each read in 3 wells per condition (24 wells). Transfection efficiency and cell number vary between days, between the two conditions of a day and between wells, and move firefly and Renilla together; promoter activity (firefly only) also varies a little between transfections (a term added after a first run without it, in which the ratio cancelled every shared source of variation), and each reading has its own noise. By design, **the treatment changes neither reporter**. The data's random seed was the first one tried and was not chosen for its result. Stored code and data: [luciferase.csv](/guides/dual-luciferase-reporter-assay/luciferase.csv), [model.py](/guides/dual-luciferase-reporter-assay/model.py), [make_data.py](/guides/dual-luciferase-reporter-assay/make_data.py), [analysis.py](/guides/dual-luciferase-reporter-assay/analysis.py), [analysis.R](/guides/dual-luciferase-reporter-assay/analysis.R) and [the analysis output](/guides/dual-luciferase-reporter-assay/analysis_output.txt); analysis.py and analysis.R print the same output.

| Transfection | Firefly ÷ Renilla, control | Firefly ÷ Renilla, treated | Fold | Renilla, treated ÷ control |
| --- | --- | --- | --- | --- |
| 1 | 4.23 | 3.72 | 0.88 | 0.77 |
| 2 | 3.48 | 3.61 | 1.04 | 1.41 |
| 3 | 4.50 | 4.42 | 0.98 | 0.66 |
| 4 | 3.90 | 3.17 | 0.81 | 0.76 |

| Analysis, treated vs control | Result |
| --- | --- |
| A. Firefly alone, log2 ratio per transfection (n = 4) | 0.80 (95% CI 0.42 to 1.52), p = 0.35 |
| B. (Not valid: wells repeat one transfection) Firefly ÷ Renilla, wells as n (12 vs 12), Welch t-test | 0.93, p = 0.21 |
| **C. Firefly ÷ Renilla, log2 ratio per transfection (n = 4)** | **0.92 (95% CI 0.78 to 1.10), p = 0.24** |
| D. Check: Renilla alone, log2 ratio per transfection (n = 4) | 0.86 (95% CI 0.50 to 1.47), p = 0.44 |

This draw happened to be tighter than usual: in the model the per-transfection log2 ratio varies with a standard deviation of about 0.35, so expect wider intervals than 0.78–1.10 in real data. Normalising to Renilla narrowed the interval from 0.42–1.52 (firefly alone) to 0.78–1.10, because transfection efficiency and cell number cancel from the ratio. Renilla alone swung from 0.66 to 1.41 between transfections, which shows how hard it is to spot a real change in the control reporter from the same experiment. Across 5,000 simulated studies of this design ([simulation.py](/guides/dual-luciferase-reporter-assay/simulation.py), [the simulation output](/guides/dual-luciferase-reporter-assay/simulation_output.txt)):

| Scenario | A. Firefly alone | B. Ratio, wells as n (invalid) | C. Ratio, paired log2 | D. Renilla check |
| --- | --- | --- | --- | --- |
| No effect (false positives) | 5.5% | **19.0%** | 5.4% | 4.8% |
| Treatment doubles firefly (power) | 57.8% | 100.0% | **95.5%** | 5.1% |
| Treatment raises only Renilla by 50% (false "repression") | 5.4% | 96.7% | **62.3%** | 31.7% (detects the change) |
| Treatment doubles firefly, 3 transfections (power) | 33.0% | 100.0% | 71.0% | 4.9% |

These percentages describe this simulated design (4 transfections, 3 wells, the variation in model.py), not every reporter assay. Wells as n treated the 12 wells per condition as independent and found effects that were not there, because promoter activity varies from transfection to transfection. The paired log ratio kept false positives near 5% and, by cancelling transfection differences, detected a doubling of firefly far more often than firefly alone; with 3 transfections instead of 4 its power fell from 95.5% to 71.0% ([t-test with n = 3](/guides/t-test-with-n-3/)). When the treatment raised only the Renilla control, the ratio fell and the paired test reported "repression" in 62.3% of studies, while the Renilla check from the same experiment caught the change in only 31.7%. A control reporter that responds to the treatment has to be ruled out in its own experiment, for example with a second control plasmid or by comparing Renilla with cell number or total protein.

The code expects one row per well with columns `transfection`, `condition` (labels exactly `control` and `treated`), `well`, `firefly` and `renilla`, already background-subtracted (subtract the mean of the untransfected wells of the same plate from each reporter; check that no value is 0 or below; a well at or below background is reported as such, not turned into a ratio). It compares one treatment with one control, side by side in each transfection. Python needs NumPy, pandas and SciPy 1.10 or later (for the confidence interval); the R code uses base R only. Rename your columns and labels to match, and run the code in the folder that holds the CSV file.

```python
import numpy as np, pandas as pd
from scipy import stats

d = pd.read_csv("luciferase.csv"); d["ratio"] = d.firefly / d.renilla
m = d.groupby(["transfection", "condition"])[["ratio", "renilla"]].mean().unstack()   # mean of the wells
for v in ("ratio", "renilla"):                                     # the result, then the control-reporter check
    lr = np.log2(m[v]["treated"] / m[v]["control"]); t = stats.ttest_1samp(lr, 0); ci = t.confidence_interval()
    print(f"{v}: treated / control {2 ** lr.mean():.2f} (95% CI {2 ** ci.low:.2f} to {2 ** ci.high:.2f}), p = {t.pvalue:.2g}")
# ratio: 0.92 (0.78 to 1.10), p = 0.24; renilla: 0.86 (0.50 to 1.47), p = 0.44
```

```r
d <- read.csv("luciferase.csv"); d$ratio <- d$firefly / d$renilla
m <- tapply(d$ratio, list(d$transfection, d$condition), mean)          # mean ratio of the wells
t <- t.test(log2(m[, "treated"] / m[, "control"]))                     # one-sample t-test of log2 ratios
round(c(fold = 2^unname(t$estimate), lower = 2^t$conf.int[1], upper = 2^t$conf.int[2], p = t$p.value), 2)
# fold 0.92, lower 0.78, upper 1.10, p 0.24
rl <- tapply(d$renilla, list(d$transfection, d$condition), mean)       # the Renilla check
t.test(log2(rl[, "treated"] / rl[, "control"]))$p.value                # p = 0.44
```

## Before you analyse

1. **Repeat the transfection on at least 3 separate days** with fresh cells; wells within a day are technical replicates and are averaged.
2. **Subtract background** (untransfected cells) from both reporters, and check that readings sit within the luminometer's linear range.
3. **Check the control reporter**: compare raw Renilla between conditions, and test the treatment on the control plasmid alone (or against cell number or total protein) in a separate experiment, especially for transcription factors and drugs that change transcription or cell number. If Renilla moves with the treatment, the ratio cannot be interpreted: report firefly with a different normaliser.
4. **Analyse ratios on the log scale and pair by transfection** ([fold change](/guides/statistics-on-fold-change/)), rather than setting each control to 1 and testing the treated values against it.
5. **Keep plasmid amounts and the firefly:Renilla ratio constant** across conditions and days.

## Check what an AI assistant ran

1. **The assistant ran a t-test on all wells.** Ask for one value per condition per transfection.
2. **The assistant normalised to Renilla without looking at Renilla.** Ask for raw Renilla per condition and the control-plasmid, cell-number or total-protein test that rules out a Renilla effect.
3. **The assistant set each control to 1 and t-tested the treated values against 1.** Ask for a one-sample test of log2 ratios.
4. **The assistant reported firefly alone.** Ask for the normalised ratio, or why normalisation was skipped.

## Report it

> Cells were co-transfected with the firefly reporter and a Renilla control plasmid ([pRL-TK], firefly:Renilla plasmid mass ratio [10:1]) on [4] separate days, and luminescence was read in [3] wells per condition after subtracting untransfected-cell background. Firefly ÷ Renilla ratios were averaged per condition and day, and the log2 ratio treated ÷ control was compared with 0 by a one-sample t-test across days (R [4.3.3]). Renilla activity did not clearly differ between conditions (treated ÷ control [0.86], 95% CI [0.50] to [1.47]), but this check is weak, so [the treatment was also tested on the control plasmid alone against cell number or total protein, with result …].

> In the constructed example, which has no effect built in, the treatment did not clearly change reporter activity (fold [0.92], 95% CI [0.78] to [1.10]; p = [0.24]; n = [4] transfections).

Replace the bracketed values with your own.

## References

- Promega Corporation. Dual-Luciferase Reporter Assay System, Technical Manual TM040. [PDF](https://www.promega.com/-/media/files/resources/protocols/technical-manuals/0/dual-luciferase-reporter-assay-system-protocol.pdf)
- Schagat T, Paguio A, Kopish K (2007). Normalizing genetic reporter assays: approaches and considerations for increasing consistency and statistical significance. *Cell Notes* 17:9–12. [PDF](https://worldwide.promega.com/-/media/files/resources/cell-notes/cn017/normalizing-genetic-reporter-assays.pdf)
- Repele A, Manu (2019). Robust normalization of luciferase reporter data. *Methods and Protocols* 2:62. [doi:10.3390/mps2030062](https://doi.org/10.3390/mps2030062)
- Ho CKM, Strauss JF 3rd (2004). Activation of the control reporter plasmids pRL-TK and pRL-SV40 by multiple GATA transcription factors can lead to aberrant normalization of transfection efficiency. *BMC Biotechnology* 4:10. [doi:10.1186/1472-6750-4-10](https://doi.org/10.1186/1472-6750-4-10)

## Related

- [Statistics on fold change](https://www.licklider.ai/guides/statistics-on-fold-change/)
- [qPCR ΔΔCt statistics](https://www.licklider.ai/guides/qpcr-delta-delta-ct-statistics/)
- [What counts as n](https://www.licklider.ai/guides/what-counts-as-n/)
- [ChIP-qPCR percent input](https://www.licklider.ai/guides/chip-qpcr-percent-input/)

## How this page was made

- Written and published by Licklider. Recommendations cite peer-reviewed methods papers and manufacturer documentation listed under References; cited passages were checked against the source text.
- The worked example uses constructed data. Its numbers were produced by stored code (Python 3.13 with SciPy 1.18.1; R 4.3.3, base R), and the two languages print identical output; the simulation was run in Python.
- Separate review passes checked the statistics, the citations and readability before publication.
