qPCR results from (i)- NOTE: This data is from the same sample but I
completed 2 technical replicates of the qPCR.
need <- c(
"readr","readxl","dplyr","tidyr","stringr","purrr","janitor","ggplot2","forcats"
)
to_install <- setdiff(need, rownames(installed.packages()))
if (length(to_install)) install.packages(to_install, quiet = TRUE)
invisible(lapply(need, library, character.only = TRUE))
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
##
## Attaching package: 'janitor'
## The following objects are masked from 'package:stats':
##
## chisq.test, fisher.test
data <- read_csv("13.Aug.pooled.csv")
## New names:
## Rows: 8 Columns: 14
## ── Column specification
## ──────────────────────────────────────────────────────── Delimiter: "," chr
## (13): ...1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 lgl (1): ...14
## ℹ Use `spec()` to retrieve the full column specification for this data. ℹ
## Specify the column types or set `show_col_types = FALSE` to quiet this message.
## • `` -> `...1`
## • `` -> `...14`
head(data)
## # A tibble: 6 × 14
## ...1 `1` `2` `3` `4` `5` `6` `7` `8` `9` `10` `11` `12`
## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 A Undet… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde…
## 2 B Undet… 17.6… 17.2… 17.1… 17.7… 17.6… 17.7… 19.1… 19.5… 19.5… Unde… Unde…
## 3 C Undet… 35.4… Unde… 35.1… 33.2… 34.2… 33.0… 29.8… 30.1… 30.2… Unde… Unde…
## 4 D Undet… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde…
## 5 E Undet… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde…
## 6 F Undet… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde…
## # ℹ 1 more variable: ...14 <lgl>
data <- read_csv("15.Aug.96w.csv")
## New names:
## Rows: 8 Columns: 13
## ── Column specification
## ──────────────────────────────────────────────────────── Delimiter: "," chr
## (13): ...1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
## ℹ Use `spec()` to retrieve the full column specification for this data. ℹ
## Specify the column types or set `show_col_types = FALSE` to quiet this message.
## • `` -> `...1`
head(data)
## # A tibble: 6 × 13
## ...1 `1` `2` `3` `4` `5` `6` `7` `8` `9` `10` `11` `12`
## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 A Undet… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde…
## 2 B Undet… 17.7… 18.0… 17.9… 18.2… 18.1… 18.1… 20.1… 19.8… 20.0… Unde… Unde…
## 3 C Undet… Unde… Unde… 35.7… 34.1… 34.9… 34.1… 31.3… 31.3… 31.0… Unde… Unde…
## 4 D Undet… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde…
## 5 E Undet… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde…
## 6 F Undet… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde… Unde…
## 1) Paths + basic labels
CSV_REP1 <- "13.Aug.pooled.csv" # first technical replicate
CSV_REP2 <- "15.Aug.96w.csv" # second technical replicate
CONTROL_LABEL <- "NtsgRNA" # your calibrator / control
TARGET_GENE <- "ATM" # target gene
REF_GENE <- "ACTB" # housekeeping gene
# How to handle "Undetermined": NA = drop from means; use 40 if your SOP wants max cycles
UND_CT <- NA_real_ # or 40
## 2) Plate mapping encoded in code
# Columns 2–4 -> "ATM 2 sgRNA"
# Columns 5–7 -> "ATM 1 sgRNA"
# Columns 8–10 -> "NtsgRNA"
sample_from_col <- function(colN) case_when(
colN %in% 2:4 ~ "ATM 2 sgRNA",
colN %in% 5:7 ~ "ATM 1 sgRNA",
colN %in% 8:10 ~ "NtsgRNA",
TRUE ~ NA_character_
)
# Rows: B = ACTB (ref), C = ATM (target).
gene_from_row <- function(rowL) case_when(
toupper(rowL) == "B" ~ REF_GENE,
toupper(rowL) == "C" ~ TARGET_GENE,
TRUE ~ NA_character_
)
## 3) Read + tidy ONE csv
parse_raw_csv <- function(path, run_label) {
# Read everything as character so "Undetermined" is preserved
df <- read_csv(path, col_types = cols(.default = col_character()), show_col_types = FALSE)
# Rename the first column to "rowL" (A..H). Keep only columns named 1..12.
names(df)[1] <- "rowL"
df <- df %>% select(rowL, matches("^\\d+$"))
# Make long format: one row per well
df_long <- df %>%
pivot_longer(-rowL, names_to = "col", values_to = "ct_raw") %>%
mutate(
rowL = toupper(str_trim(rowL)), # "A".."H"
col_num = as.integer(col), # "1".."12" -> 1..12
well = sprintf("%s%02d", rowL, col_num),
# Convert Ct: numeric if possible; if "Undetermined", set to UND_CT (NA or 40)
ct = suppressWarnings(as.numeric(ct_raw)),
ct = if_else(is.na(ct) & !is.na(ct_raw) &
str_detect(ct_raw, regex("^undetermined$", TRUE)),
UND_CT, ct),
# Map to your sample/groups and genes
sample = sample_from_col(col_num),
gene = gene_from_row(rowL),
run = run_label
) %>%
# Keep only wells that belong to our mapping and that have numeric Ct
filter(!is.na(sample), !is.na(gene), !is.na(ct)) %>%
select(run, well, rowL, col_num, sample, gene, ct)
df_long
}
## 4) ΔΔCt for ONE replicate
compute_ddct <- function(per_well_df) {
# Mean Ct per run × sample × gene
gene_means <- per_well_df %>%
group_by(run, sample, gene) %>%
summarise(mean_ct = mean(ct, na.rm = TRUE), .groups = "drop")
# ΔCt = Ct_target - Ct_ref for each sample (within each run)
wide <- gene_means %>%
filter(gene %in% c(TARGET_GENE, REF_GENE)) %>%
pivot_wider(names_from = gene, values_from = mean_ct)
# Safety check: both columns present
stopifnot(all(c(TARGET_GENE, REF_GENE) %in% names(wide)))
dCt <- wide %>%
mutate(delta_ct = .data[[TARGET_GENE]] - .data[[REF_GENE]]) %>%
select(run, sample, delta_ct)
# Calibrator ΔCt (control mean within each run)
cal <- dCt %>%
filter(sample == CONTROL_LABEL) %>%
group_by(run) %>%
summarise(cal_delta_ct = mean(delta_ct, na.rm = TRUE), .groups = "drop")
# ΔΔCt and fold change
final <- dCt %>%
left_join(cal, by = "run") %>%
mutate(
delta_delta_ct = delta_ct - cal_delta_ct,
fold_change = 2^(-delta_delta_ct)
) %>%
arrange(run, sample)
list(gene_means = gene_means, final = final)
}
## 5) Run on the two files
rep1_per_well <- parse_raw_csv(CSV_REP1, "Rep1")
## New names:
## • `` -> `...1`
## • `` -> `...14`
rep2_per_well <- parse_raw_csv(CSV_REP2, "Rep2")
## New names:
## • `` -> `...1`
# Show the organised per-well data
cat("\n=== Rep1: per-well (mapped; Undetermined removed) ===\n")
##
## === Rep1: per-well (mapped; Undetermined removed) ===
print(rep1_per_well)
## # A tibble: 17 × 7
## run well rowL col_num sample gene ct
## <chr> <chr> <chr> <int> <chr> <chr> <dbl>
## 1 Rep1 B02 B 2 ATM 2 sgRNA ACTB 17.6
## 2 Rep1 B03 B 3 ATM 2 sgRNA ACTB 17.3
## 3 Rep1 B04 B 4 ATM 2 sgRNA ACTB 17.2
## 4 Rep1 B05 B 5 ATM 1 sgRNA ACTB 17.8
## 5 Rep1 B06 B 6 ATM 1 sgRNA ACTB 17.6
## 6 Rep1 B07 B 7 ATM 1 sgRNA ACTB 17.7
## 7 Rep1 B08 B 8 NtsgRNA ACTB 19.2
## 8 Rep1 B09 B 9 NtsgRNA ACTB 19.5
## 9 Rep1 B10 B 10 NtsgRNA ACTB 19.5
## 10 Rep1 C02 C 2 ATM 2 sgRNA ATM 35.4
## 11 Rep1 C04 C 4 ATM 2 sgRNA ATM 35.1
## 12 Rep1 C05 C 5 ATM 1 sgRNA ATM 33.2
## 13 Rep1 C06 C 6 ATM 1 sgRNA ATM 34.3
## 14 Rep1 C07 C 7 ATM 1 sgRNA ATM 33.0
## 15 Rep1 C08 C 8 NtsgRNA ATM 29.8
## 16 Rep1 C09 C 9 NtsgRNA ATM 30.1
## 17 Rep1 C10 C 10 NtsgRNA ATM 30.2
cat("\n=== Rep2: per-well (mapped; Undetermined removed) ===\n")
##
## === Rep2: per-well (mapped; Undetermined removed) ===
print(rep2_per_well)
## # A tibble: 16 × 7
## run well rowL col_num sample gene ct
## <chr> <chr> <chr> <int> <chr> <chr> <dbl>
## 1 Rep2 B02 B 2 ATM 2 sgRNA ACTB 17.8
## 2 Rep2 B03 B 3 ATM 2 sgRNA ACTB 18.1
## 3 Rep2 B04 B 4 ATM 2 sgRNA ACTB 17.9
## 4 Rep2 B05 B 5 ATM 1 sgRNA ACTB 18.2
## 5 Rep2 B06 B 6 ATM 1 sgRNA ACTB 18.2
## 6 Rep2 B07 B 7 ATM 1 sgRNA ACTB 18.1
## 7 Rep2 B08 B 8 NtsgRNA ACTB 20.1
## 8 Rep2 B09 B 9 NtsgRNA ACTB 19.9
## 9 Rep2 B10 B 10 NtsgRNA ACTB 20.0
## 10 Rep2 C04 C 4 ATM 2 sgRNA ATM 35.8
## 11 Rep2 C05 C 5 ATM 1 sgRNA ATM 34.1
## 12 Rep2 C06 C 6 ATM 1 sgRNA ATM 35.0
## 13 Rep2 C07 C 7 ATM 1 sgRNA ATM 34.1
## 14 Rep2 C08 C 8 NtsgRNA ATM 31.4
## 15 Rep2 C09 C 9 NtsgRNA ATM 31.3
## 16 Rep2 C10 C 10 NtsgRNA ATM 31.0
## 6) Compute ΔΔCt + fold change for each replicate
rep1 <- compute_ddct(rep1_per_well)
rep2 <- compute_ddct(rep2_per_well)
cat("\n=== Rep1: mean Ct per sample × gene ===\n"); print(rep1$gene_means)
##
## === Rep1: mean Ct per sample × gene ===
## # A tibble: 6 × 4
## run sample gene mean_ct
## <chr> <chr> <chr> <dbl>
## 1 Rep1 ATM 1 sgRNA ACTB 17.7
## 2 Rep1 ATM 1 sgRNA ATM 33.5
## 3 Rep1 ATM 2 sgRNA ACTB 17.3
## 4 Rep1 ATM 2 sgRNA ATM 35.3
## 5 Rep1 NtsgRNA ACTB 19.4
## 6 Rep1 NtsgRNA ATM 30.0
cat("\n=== Rep1: ΔΔCt and fold change ===\n"); print(rep1$final)
##
## === Rep1: ΔΔCt and fold change ===
## # A tibble: 3 × 6
## run sample delta_ct cal_delta_ct delta_delta_ct fold_change
## <chr> <chr> <dbl> <dbl> <dbl> <dbl>
## 1 Rep1 ATM 1 sgRNA 15.8 10.6 5.17 0.0277
## 2 Rep1 ATM 2 sgRNA 17.9 10.6 7.28 0.00643
## 3 Rep1 NtsgRNA 10.6 10.6 0 1
cat("\n=== Rep2: mean Ct per sample × gene ===\n"); print(rep2$gene_means)
##
## === Rep2: mean Ct per sample × gene ===
## # A tibble: 6 × 4
## run sample gene mean_ct
## <chr> <chr> <chr> <dbl>
## 1 Rep2 ATM 1 sgRNA ACTB 18.2
## 2 Rep2 ATM 1 sgRNA ATM 34.4
## 3 Rep2 ATM 2 sgRNA ACTB 17.9
## 4 Rep2 ATM 2 sgRNA ATM 35.8
## 5 Rep2 NtsgRNA ACTB 20.0
## 6 Rep2 NtsgRNA ATM 31.2
cat("\n=== Rep2: ΔΔCt and fold change ===\n"); print(rep2$final)
##
## === Rep2: ΔΔCt and fold change ===
## # A tibble: 3 × 6
## run sample delta_ct cal_delta_ct delta_delta_ct fold_change
## <chr> <chr> <dbl> <dbl> <dbl> <dbl>
## 1 Rep2 ATM 1 sgRNA 16.2 11.2 5.02 0.0308
## 2 Rep2 ATM 2 sgRNA 17.9 11.2 6.64 0.00999
## 3 Rep2 NtsgRNA 11.2 11.2 0 1
## 7) Combine replicates + quick summary + plot
fc_all <- bind_rows(rep1$final, rep2$final) %>%
mutate(sample = fct_relevel(sample, CONTROL_LABEL)) %>%
arrange(sample, run)
summary_df <- fc_all %>%
group_by(sample) %>%
summarise(
mean_expr = mean(fold_change, na.rm = TRUE),
sd_expr = sd(fold_change, na.rm = TRUE),
.groups = "drop"
)
cat("\n=== Combined: per-run fold change ===\n"); print(fc_all)
##
## === Combined: per-run fold change ===
## # A tibble: 6 × 6
## run sample delta_ct cal_delta_ct delta_delta_ct fold_change
## <chr> <fct> <dbl> <dbl> <dbl> <dbl>
## 1 Rep1 NtsgRNA 10.6 10.6 0 1
## 2 Rep2 NtsgRNA 11.2 11.2 0 1
## 3 Rep1 ATM 1 sgRNA 15.8 10.6 5.17 0.0277
## 4 Rep2 ATM 1 sgRNA 16.2 11.2 5.02 0.0308
## 5 Rep1 ATM 2 sgRNA 17.9 10.6 7.28 0.00643
## 6 Rep2 ATM 2 sgRNA 17.9 11.2 6.64 0.00999
cat("\n=== Summary: mean ± SD across replicates ===\n"); print(summary_df)
##
## === Summary: mean ± SD across replicates ===
## # A tibble: 3 × 3
## sample mean_expr sd_expr
## <fct> <dbl> <dbl>
## 1 NtsgRNA 1 0
## 2 ATM 1 sgRNA 0.0293 0.00217
## 3 ATM 2 sgRNA 0.00821 0.00252
# Bar (mean ± SD) with the 2 replicate points
ggplot(summary_df, aes(x = sample, y = mean_expr, fill = sample)) +
geom_col(width = 0.6, colour = "black") +
geom_errorbar(aes(ymin = mean_expr - sd_expr, ymax = mean_expr + sd_expr),
width = 0.2) +
geom_point(data = fc_all, aes(sample, fold_change),
position = position_jitter(width = 0.08, height = 0), size = 2) +
labs(
y = paste(TARGET_GENE, "Fold Expression (vs", CONTROL_LABEL, ")"),
x = NULL,
title = paste(TARGET_GENE, "expression (mean ± SD), normalised to", REF_GENE)
) +
theme_minimal(base_size = 13) +
theme(legend.position = "none")

Figure 1. ATM expression after sgRNA transfection, quantified by
qPCR. Bars show the mean fold expression of ATM (2^-ΔΔCt), where ΔCt
values were normalised to the reference gene ACTB, and ΔΔCt values were
calibrated to the non-targeting control (NtsgRNA), which is set to 1.0.
Black dots indicate the two technical replicates (n = 2) both from the
same sample, and error bars represent ± SD. Both ATM-targeting guides
(ATM 1 sgRNA and ATM 2 sgRNA) show strong knockdown of ATM compared with
the NtsgRNA control, with expression reduced to only a small fraction of
control levels (low bars indicate greater knockdown).
qPCR results from (ii), comparing 0.45 µM vs 0.045 µM sgRNA input
across 2 biological replicates of reverse transfections manually.
## 1) Read your dataset (Excel)
library(readxl)
data <- read_excel("pcr_reverse_transfection_data.xlsx", sheet = 1)
head(data)
## # A tibble: 6 × 5
## gene concentration_uM primer Ct_Bio1 Ct_Bio2
## <chr> <dbl> <chr> <dbl> <dbl>
## 1 ATM 1 0.045 ACTB 16.4 18.6
## 2 ATM 1 0.045 ATM 31.6 32.6
## 3 ATM 2 0.045 ACTB 16.7 19.0
## 4 ATM 2 0.045 ATM 32.0 32.6
## 5 NtsgRNA 0.045 ACTB 16.0 17.4
## 6 NtsgRNA 0.045 ATM 25.4 26.7
## 1) Labels
CONTROL_LABEL <- "NtsgRNA" # control
TARGET_GENE <- "ATM" # target gene
REF_GENE <- "ACTB" # housekeeping gene
## 2) Robust column mapping
get_first_col <- function(df, options) {
found <- intersect(names(df), options)
if (length(found) == 0) stop("Missing expected column. Looked for: ",
paste(options, collapse=", "))
found[1]
}
gene_col <- get_first_col(data, c("gene","Gene"))
primer_col <- get_first_col(data, c("primer","Primer"))
ct1_col <- get_first_col(data, c("Ct_Bio1","ct_bio1"))
ct2_col <- get_first_col(data, c("Ct_Bio2","ct_bio2"))
conc_col <- get_first_col(data, c("concentration_uM","concentration_um",
"Concentration_uM","Concentration_um"))
df <- data %>%
transmute(
gene = as.character(.data[[gene_col]]),
concentration_uM = suppressWarnings(as.numeric(.data[[conc_col]])),
primer = toupper(str_trim(as.character(.data[[primer_col]]))),
Ct_Bio1 = suppressWarnings(as.numeric(.data[[ct1_col]])),
Ct_Bio2 = suppressWarnings(as.numeric(.data[[ct2_col]]))
) %>%
mutate(
gene = case_when(
str_to_upper(gene) == "NTSGRNA" ~ "NtsgRNA",
str_to_upper(gene) == "ATM 1" ~ "ATM 1",
str_to_upper(gene) == "ATM 2" ~ "ATM 2",
TRUE ~ gene
)
)
## 3) Display label for concentration
fmt_conc <- function(x) {
s <- format(x, trim = TRUE, scientific = FALSE, digits = 12)
s <- sub("0+$", "", s) # drop trailing zeros
s <- sub("\\.$", "", s) # drop trailing dot if any
s
}
df <- df %>% mutate(conc_label = fmt_conc(concentration_uM))
## 4) Show the INPUT Ct table per replicate
ct_input_table <- df %>%
arrange(concentration_uM, gene, primer) %>%
transmute(
gene,
concentration_uM = conc_label, # formatted display only
primer,
Bio1 = round(Ct_Bio1, 3),
Bio2 = round(Ct_Bio2, 3)
)
if (requireNamespace("knitr", quietly = TRUE)) {
knitr::kable(ct_input_table, caption = "Input Ct per replicate (Bio1, Bio2)")
} else {
print(ct_input_table)
}
Input Ct per replicate (Bio1, Bio2)
ATM 1 |
0.045 |
ACTB |
16.428 |
18.642 |
ATM 1 |
0.045 |
ATM |
31.591 |
32.600 |
ATM 2 |
0.045 |
ACTB |
16.670 |
18.990 |
ATM 2 |
0.045 |
ATM |
31.988 |
32.618 |
NtsgRNA |
0.045 |
ACTB |
16.028 |
17.423 |
NtsgRNA |
0.045 |
ATM |
25.445 |
26.719 |
ATM 1 |
0.45 |
ACTB |
16.948 |
18.327 |
ATM 1 |
0.45 |
ATM |
30.522 |
33.205 |
ATM 2 |
0.45 |
ACTB |
16.523 |
19.205 |
ATM 2 |
0.45 |
ATM |
31.160 |
34.315 |
NtsgRNA |
0.45 |
ACTB |
17.024 |
16.934 |
NtsgRNA |
0.45 |
ATM |
26.323 |
26.255 |
## 5) ΔCt PER REPLICATE: Ct(TARGET) - Ct(REF) for each Bio
ct_long <- df %>%
pivot_longer(c(Ct_Bio1, Ct_Bio2),
names_to = "bio", values_to = "ct") %>%
mutate(bio = recode(bio, Ct_Bio1 = "Bio1", Ct_Bio2 = "Bio2")) %>%
filter(primer %in% c(TARGET_GENE, REF_GENE), is.finite(ct))
dCt <- ct_long %>%
select(bio, gene, concentration_uM, primer, ct) %>%
# (group_by/summarise kept for safety—does nothing if already 1 row per combo)
group_by(bio, gene, concentration_uM, primer) %>%
summarise(ct = mean(ct, na.rm = TRUE), .groups = "drop") %>%
tidyr::pivot_wider(names_from = primer, values_from = ct) %>%
mutate(delta_ct = .data[[TARGET_GENE]] - .data[[REF_GENE]]) %>%
select(bio, gene, concentration_uM, delta_ct)
## 6) ΔΔCt PER REPLICATE: calibrate to NtsgRNA at SAME conc.
cal <- dCt %>%
filter(gene == CONTROL_LABEL) %>%
select(bio, concentration_uM, cal_delta_ct = delta_ct)
ddCt <- dCt %>%
left_join(cal, by = c("bio","concentration_uM")) %>%
mutate(
delta_delta_ct = delta_ct - cal_delta_ct,
fold_change = 2^(-delta_delta_ct)
) %>%
arrange(concentration_uM, gene, bio)
## 7) TABLE A — PER-REPLICATE FOLD CHANGE (Bio1 & Bio2)
fc_per_bio <- ddCt %>%
mutate(concentration = fmt_conc(concentration_uM)) %>% # display-only label
select(concentration, gene, bio, fold_change) %>%
mutate(fold_change = round(fold_change, 4)) %>%
tidyr::pivot_wider(names_from = bio, values_from = fold_change) %>%
arrange(concentration, gene)
if (requireNamespace("knitr", quietly = TRUE)) {
knitr::kable(fc_per_bio, caption = "Fold change (2^-ΔΔCt) per biological replicate")
} else {
print(fc_per_bio)
}
Fold change (2^-ΔΔCt) per biological replicate
0.045 |
ATM 1 |
0.0186 |
0.0395 |
0.045 |
ATM 2 |
0.0167 |
0.0497 |
0.045 |
NtsgRNA |
1.0000 |
1.0000 |
0.45 |
ATM 1 |
0.0517 |
0.0212 |
0.45 |
ATM 2 |
0.0247 |
0.0181 |
0.45 |
NtsgRNA |
1.0000 |
1.0000 |
## 8) TABLE B — COMBINED Mean ± SD (across Bio1 & Bio2)
fc_summary <- ddCt %>%
mutate(concentration = fmt_conc(concentration_uM)) %>%
group_by(concentration, gene) %>%
summarise(
mean_fold_change = round(mean(fold_change, na.rm = TRUE), 4),
sd_fold_change = round(sd(fold_change, na.rm = TRUE), 4),
.groups = "drop"
) %>%
arrange(concentration, gene)
if (requireNamespace("knitr", quietly = TRUE)) {
knitr::kable(fc_summary, caption = "Mean ± SD fold change across biological replicates")
} else {
print(fc_summary)
}
Mean ± SD fold change across biological replicates
0.045 |
ATM 1 |
0.0291 |
0.0148 |
0.045 |
ATM 2 |
0.0332 |
0.0233 |
0.045 |
NtsgRNA |
1.0000 |
0.0000 |
0.45 |
ATM 1 |
0.0364 |
0.0215 |
0.45 |
ATM 2 |
0.0214 |
0.0047 |
0.45 |
NtsgRNA |
1.0000 |
0.0000 |
## 9) Combined plot (two concentrations + SD + replicate points)
library(ggplot2)
library(forcats)
# Display labels for plot only (keep your calculations unchanged)
fc_plot <- fc_summary %>%
mutate(
gene_lbl = dplyr::recode(gene,
"ATM 1" = "ATM-sgRNA 1",
"ATM 2" = "ATM-sgRNA 2",
"NtsgRNA" = "NtsgRNA"
),
conc_lbl = factor(paste0(concentration, " \u00B5M"),
levels = c("0.45 \u00B5M", "0.045 \u00B5M"))
)
dd_plot <- ddCt %>%
mutate(
gene_lbl = dplyr::recode(gene,
"ATM 1" = "ATM-sgRNA 1",
"ATM 2" = "ATM-sgRNA 2",
"NtsgRNA" = "NtsgRNA"
),
conc_lbl = factor(paste0(fmt_conc(concentration_uM), " \u00B5M"),
levels = c("0.45 \u00B5M", "0.045 \u00B5M"))
)
# Colours to match your example (feel free to tweak)
cols <- c("0.45 \u00B5M" = "#F29988", "0.045 \u00B5M" = "#2CC5D5")
p <- ggplot(fc_plot, aes(x = gene_lbl, y = mean_fold_change, fill = conc_lbl)) +
# bars (nearly overlapping via a tight dodge)
geom_col(position = position_dodge(width = 0.6), width = 0.55, colour = "black") +
# error bars (SD)
geom_errorbar(aes(ymin = mean_fold_change - sd_fold_change,
ymax = mean_fold_change + sd_fold_change,
group = conc_lbl),
position = position_dodge(width = 0.6), width = 0.18) +
# overlay the two bio replicate points
geom_point(data = dd_plot,
aes(x = gene_lbl, y = fold_change, fill = conc_lbl),
position = position_jitterdodge(jitter.width = 0.06, dodge.width = 0.6),
size = 2, shape = 21, stroke = 0.2, colour = "black") +
scale_fill_manual(values = cols, name = "Concentration") +
labs(title = "ATM Expression",
y = "ATM Fold Expression",
x = NULL) +
theme_minimal(base_size = 14) +
theme(
panel.grid.major.x = element_blank(),
legend.position = "right"
)
p

Figure 2. ATM expression across two sgRNA concentrations (mean ±
SD). qPCR of ATM after reverse transfection with ATM-sgRNA 1 or
ATM-sgRNA 2 at 0.45 µM or 0.045 µM, with NtsgRNA as the control. Ct
values were first normalised to ACTB to give ΔCt = Ct(ATM) − Ct(ACTB).
Then, for each concentration, we subtracted the control’s ΔCt (NtsgRNA)
from each sample’s ΔCt—i.e., ΔΔCt = ΔCt(sample) − ΔCt(control at same
conc). Fold expression was calculated as 2^-ΔΔCt, so the control is
~1.0. Bars show the mean of n = 2 biological replicates, error bars =
SD, and open circles show individual replicate values. Both
ATM-targeting guides strongly reduce ATM expression at both
concentrations, with only modest guide and dose-dependent
differences.