IC50 DATA
# Create dataframe from provided IC50 data
data_raw <- tribble(
~Concentration, ~Rep1, ~Rep2, ~Rep3, ~Rep4, ~Rep5,
0, 94.99, 100, 95.87, NA, NA,
0.5, 93.3, 91.28, 73.1, NA, 98.53,
1, 74.95, 75.96, 77.31, 64.9, NA,
1.5, 65.1, 56.42, 55.24, 51.54, NA,
2, 23.8, 39.8, 26.74, NA, 19.2,
2.5, 15.4, 14.7, 26.3, 17.8, NA,
3, 8.7, 8.8, 7.2, 6.54, NA
)
data_summary <- data_raw %>%
pivot_longer(-Concentration, names_to = "Replicate", values_to = "Response") %>%
group_by(Concentration) %>%
summarise(Mean = mean(Response, na.rm = TRUE), SD = sd(Response, na.rm = TRUE))
# Fit 4-parameter logistic model for IC50
model_ic50 <- drm(Mean ~ Concentration, data = data_summary, fct = LL.4(names = c("Slope", "Lower", "Upper", "IC50")))
ic50_est <- ED(model_ic50, 50, interval = "delta")
##
## Estimated effective doses
##
## Estimate Std. Error Lower Upper
## e:1:50 1.84087 0.37576 0.64504 3.03671
# Extract IC50 and CI
ic50_value <- round(ic50_est[1,1], 3)
ic50_lower <- round(ic50_est[1,2], 3)
ic50_upper <- round(ic50_est[1,3], 3)
# Prepare predictions for plotting
new_data_ic50 <- data.frame(Concentration = seq(min(data_summary$Concentration), max(data_summary$Concentration), length.out = 300))
pred_ic50 <- cbind(new_data_ic50, predict(model_ic50, newdata = new_data_ic50, interval = "confidence"))
ggplot() +
geom_ribbon(data = pred_ic50, aes(x = Concentration, ymin = Lower, ymax = Upper), fill = "gray80", alpha = 0.5) +
geom_line(data = pred_ic50, aes(x = Concentration, y = Prediction), color = "firebrick", linewidth = 1.2) +
geom_point(data = data_summary, aes(x = Concentration, y = Mean), size = 3, color = "navyblue") +
geom_errorbar(data = data_summary, aes(x = Concentration, ymin = Mean - SD, ymax = Mean + SD), width = 0.05, color = "navyblue") +
geom_vline(xintercept = ic50_value, linetype = "dashed", color = "black", linewidth = 0.7) +
annotate("text", x = ic50_value, y = max(data_summary$Mean)*0.9,
label = paste0("IC50 = ", ic50_value, " (95% CI: ", ic50_lower, "–", ic50_upper, ")"),
size = 4, hjust = -0.1, color = "black", fontface = "italic") +
scale_x_continuous(trans = "log10", breaks = scales::trans_breaks("log10", function(x) 10^x)) +
scale_y_continuous(limits = c(0, 110)) +
labs(title = expression(bold("Dose–Response Curve with 95% Confidence Band")),
subtitle = expression(italic("Four-parameter logistic model fit (IC50)")),
x = "Concentration (log scale)",
y = "Response (%)") +
theme_classic(base_size = 14) +
theme(plot.title = element_text(face = "bold", size = 16, hjust = 0.5),
plot.subtitle = element_text(size = 12, hjust = 0.5, color = "gray30"),
axis.title = element_text(face = "bold"),
panel.grid.major = element_line(color = "gray90", linetype = "dotted"))
## Warning in scale_x_continuous(trans = "log10", breaks = scales::trans_breaks("log10", : log-10 transformation introduced infinite values.
## log-10 transformation introduced infinite values.
## log-10 transformation introduced infinite values.
## log-10 transformation introduced infinite values.

CC50 DATA
# Data for cytotoxicity (CC50)
cc50_data <- tribble(
~Concentration, ~Rep1, ~Rep2, ~Rep3,
1, 99.1, 92.8, 94.2,
1.5, 80.1, 88.7, 93.8,
2, 95, 71, 79.3,
2.5, 63.1, 69.7, 78.4,
5, 64.3, 63.1, 57.3,
10, 52.3, 49.8, 56.9,
20, 45.2, 49, 46.9
)
cc50_summary <- cc50_data %>%
pivot_longer(-Concentration, names_to = "Replicate", values_to = "Response") %>%
group_by(Concentration) %>%
summarise(Mean = mean(Response, na.rm = TRUE), SD = sd(Response, na.rm = TRUE))
# Fit model for CC50
model_cc50 <- drm(Mean ~ Concentration, data = cc50_summary, fct = LL.4(names = c("Slope", "Lower", "Upper", "CC50")))
cc50_est <- ED(model_cc50, 50, interval = "delta")
##
## Estimated effective doses
##
## Estimate Std. Error Lower Upper
## e:1:50 1.5434 1.4290 -3.0043 6.0912
cc50_value <- round(cc50_est[1,1], 3)
cc50_lower <- round(cc50_est[1,2], 3)
cc50_upper <- round(cc50_est[1,3], 3)
# Predictions for CC50
new_data_cc50 <- data.frame(Concentration = seq(min(cc50_summary$Concentration), max(cc50_summary$Concentration), length.out = 300))
pred_cc50 <- cbind(new_data_cc50, predict(model_cc50, newdata = new_data_cc50, interval = "confidence"))
ggplot() +
geom_ribbon(data = pred_cc50, aes(x = Concentration, ymin = Lower, ymax = Upper), fill = "gray85", alpha = 0.4) +
geom_line(data = pred_cc50, aes(x = Concentration, y = Prediction), color = "darkgreen", linewidth = 1.2) +
geom_point(data = cc50_summary, aes(x = Concentration, y = Mean), size = 3, color = "steelblue") +
geom_errorbar(data = cc50_summary, aes(x = Concentration, ymin = Mean - SD, ymax = Mean + SD), width = 0.05, color = "steelblue") +
geom_vline(xintercept = cc50_value, linetype = "dashed", color = "black", linewidth = 0.7) +
annotate("text", x = cc50_value, y = max(cc50_summary$Mean)*0.9,
label = paste0("CC50 = ", cc50_value, " (95% CI: ", cc50_lower, "–", cc50_upper, ")"),
size = 4, hjust = -0.1, color = "black", fontface = "italic") +
scale_x_continuous(trans = "log10", breaks = scales::trans_breaks("log10", function(x) 10^x)) +
scale_y_continuous(limits = c(0, 110)) +
labs(title = expression(bold("Cytotoxicity Curve with 95% Confidence Band")),
subtitle = expression(italic("Four-parameter logistic model fit (CC50)")),
x = "Concentration (log scale)",
y = "Cell Viability (%)") +
theme_classic(base_size = 14) +
theme(plot.title = element_text(face = "bold", size = 16, hjust = 0.5),
plot.subtitle = element_text(size = 12, hjust = 0.5, color = "gray30"),
axis.title = element_text(face = "bold"),
panel.grid.major = element_line(color = "gray90", linetype = "dotted"))

Aesthetic academic-style IC50 plot
# Assuming 'pred_ic50', 'data_summary', and IC50 estimates already exist
ggplot() +
geom_ribbon(data = pred_ic50, aes(x = Concentration, ymin = Lower, ymax = Upper),
fill = "gray85", alpha = 0.4) +
geom_line(data = pred_ic50, aes(x = Concentration, y = Prediction),
color = "darkred", linewidth = 1.2) +
geom_point(data = data_summary, aes(x = Concentration, y = Mean),
size = 3, color = "royalblue") +
geom_errorbar(data = data_summary, aes(x = Concentration, ymin = Mean - SD, ymax = Mean + SD),
width = 0.05, color = "royalblue") +
geom_vline(xintercept = ic50_value, linetype = "dashed", color = "black", linewidth = 0.7) +
annotate("text", x = ic50_value, y = max(data_summary$Mean)*0.9,
label = paste0("IC50 = ", ic50_value, " (95% CI: ", ic50_lower, "–", ic50_upper, ")"),
size = 4, hjust = -0.1, color = "black", fontface = "italic") +
scale_x_continuous(trans = "log10", breaks = scales::trans_breaks("log10", function(x) 10^x)) +
scale_y_continuous(limits = c(0, 110)) +
labs(title = expression(bold("Inhibition Curve with 95% Confidence Band")),
subtitle = expression(italic("Four-parameter logistic model fit (IC50)")),
x = "Concentration (log scale)",
y = "Response (%)") +
theme_classic(base_size = 14) +
theme(
plot.title = element_text(face = "bold", size = 16, hjust = 0.5),
plot.subtitle = element_text(size = 12, hjust = 0.5, color = "gray30"),
axis.title = element_text(face = "bold"),
panel.grid.major = element_line(color = "gray90", linetype = "dotted")
)
# Publication-quality IC50/CC50 plot for Fisetin
# Assuming variables: pred_ic50, pred_cc50, data_summary, ic50_value, ic50_lower, ic50_upper, cc50_value, cc50_lower, cc50_upper
# Round to 2 decimals
ic50_value <- round(ic50_value, 2)
ic50_lower <- round(ic50_lower, 2)
ic50_upper <- round(ic50_upper, 2)
cc50_value <- round(cc50_value, 2)
cc50_lower <- round(cc50_lower, 2)
cc50_upper <- round(cc50_upper, 2)
# Generate the plot
ggplot() +
geom_ribbon(data = pred_ic50, aes(x = Concentration, ymin = Lower, ymax = Upper),
fill = "gray85", alpha = 0.4) +
geom_line(data = pred_ic50, aes(x = Concentration, y = Prediction),
color = "darkred", linewidth = 1.3) +
geom_point(data = data_summary, aes(x = Concentration, y = Mean),
size = 3, color = "royalblue") +
geom_errorbar(data = data_summary, aes(x = Concentration, ymin = Mean - SD, ymax = Mean + SD),
width = 0.05, color = "royalblue") +
geom_vline(xintercept = ic50_value, linetype = "dashed", color = "black", linewidth = 0.8) +
annotate("text", x = ic50_value, y = max(data_summary$Mean)*0.85,
label = paste0("IC50 = ", ic50_value, " (95% CI: ", ic50_lower, "–", ic50_upper, ")"),
size = 4, hjust = -0.1, color = "black", fontface = "italic") +
annotate("text", x = ic50_value, y = max(data_summary$Mean)*0.75,
label = paste0("CC50 = ", cc50_value, " (95% CI: ", cc50_lower, "–", cc50_upper, ")"),
size = 4, hjust = -0.1, color = "gray20", fontface = "italic") +
scale_x_continuous(trans = "log10", breaks = c(0.1, 0.5, 1, 2, 3), limits = c(0.1, 3.2)) +
scale_y_continuous(limits = c(0, 110)) +
labs(title = expression(bold("Fisetin Inhibition Curve")),
subtitle = expression(italic("Four-parameter logistic model fit with 95% CI")),
x = "Concentration (log scale)",
y = "Response (%)") +
theme_classic(base_size = 14) +
theme(
plot.title = element_text(face = "bold", size = 16, hjust = 0.5),
plot.subtitle = element_text(size = 12, hjust = 0.5, color = "gray30"),
axis.title = element_text(face = "bold"),
axis.text.x = element_text(size = 12),
axis.text.y = element_text(size = 12),
panel.grid.major = element_line(color = "gray90", linetype = "dotted")
)

# Refined Fisetin IC50/CC50 plot with Selective Index (SI)
# Assumes: ic50_value, ic50_lower, ic50_upper, cc50_value, cc50_lower, cc50_upper, pred_ic50, data_summary exist
# Compute Selective Index (SI = CC50 / IC50)
SI_value <- round(ic50_value/cc50_value)
# Generate the plot with repositioned annotation and added SI
ggplot() +
geom_ribbon(data = pred_ic50, aes(x = Concentration, ymin = Lower, ymax = Upper),
fill = "gray85", alpha = 0.5) +
geom_line(data = pred_ic50, aes(x = Concentration, y = Prediction),
color = "darkred", linewidth = 0.6) +
geom_point(data = data_summary, aes(x = Concentration, y = Mean),
size = 3, color = "royalblue") +
geom_errorbar(data = data_summary, aes(x = Concentration, ymin = Mean - SD, ymax = Mean + SD),
width = 0.05, color = "royalblue") +
geom_vline(xintercept = ic50_value, linetype = "dashed", color = "black", linewidth = 0.8) +
annotate("text", x = 0.15, y = 30,
label = paste0("IC50 = ", ic50_value, " (95% CI: ", ic50_lower, "–", ic50_upper, ")"),
size = 4, hjust = 0, color = "black", fontface = "italic") +
annotate("text", x = 0.15, y = 20,
label = paste0("CC50 = ", cc50_value, " (95% CI: ", cc50_lower, "–", cc50_upper, ")"),
size = 4, hjust = 0, color = "gray20", fontface = "italic") +
annotate("text", x = 0.15, y = 10,
label = paste0("SI = ", SI_value),
size = 4, hjust = 0.1, color = "darkgreen", fontface = "bold") +
scale_x_continuous(trans = "log10", breaks = c(0.1, 0.5, 1, 2, 3), limits = c(0.1, 3.2)) +
scale_y_continuous(limits = c(0, 110)) +
labs(title = expression(bold("Fisetin Inhibition Curve")),
subtitle = expression(italic("Four-parameter logistic model fit with 95% CI")),
x = "Concentration (log scale)",
y = "Response (%)") +
theme_classic(base_size = 14) +
theme(
plot.title = element_text(face = "bold", size = 16, hjust = 0.5),
plot.subtitle = element_text(size = 12, hjust = 0.5, color = "gray30"),
axis.title = element_text(face = "bold"),
axis.text.x = element_text(size = 12),
axis.text.y = element_text(size = 12),
panel.grid.major = element_line(color = "gray90", linetype = "dotted")
)

# Adjusted combined facet figure: CC50 (cytotoxicity) and IC50 (inhibition) for Fisetin
# CC50 x-axis now starts from 1.0 to match IC50 visual style
library(ggpubr)
# ---- CC50 plot ----
plot_cc50 <- ggplot() +
geom_ribbon(data = pred_cc50, aes(x = Concentration, ymin = Lower, ymax = Upper),
fill = "gray85", alpha = 0.4) +
geom_line(data = pred_cc50, aes(x = Concentration, y = Prediction), color = "darkgreen", linewidth = 1.3) +
geom_point(data = cc50_summary, aes(x = Concentration, y = Mean), size = 3, color = "steelblue") +
geom_errorbar(data = cc50_summary, aes(x = Concentration, ymin = Mean - SD, ymax = Mean + SD),
width = 0.05, color = "steelblue") +
geom_vline(xintercept = cc50_value, linetype = "dashed", color = "black", linewidth = 0.8) +
annotate("text", x = 3, y = 30,
label = paste0("CC50 = ", cc50_value, " (95% CI: ", cc50_lower, "–", cc50_upper, ")"),
size = 4, hjust = 0, color = "black", fontface = "italic") +
labs(title = expression(bold("A. Fisetin Cytotoxicity (CC50)")),
x = "Concentration (log scale)", y = "Cell Viability (%)") +
scale_x_continuous(trans = "log10", breaks = c(1, 2, 3, 5, 10, 20), limits = c(1, 20)) +
scale_y_continuous(limits = c(0, 110)) +
theme_classic(base_size = 14) +
theme(plot.title = element_text(face = "bold", size = 14, hjust = 0),
axis.title = element_text(face = "bold"),
panel.grid.major = element_line(color = "gray90", linetype = "dotted"))
# ---- IC50 plot ----
SI_value <- round(cc50_value / ic50_value, 2)
plot_ic50 <- ggplot() +
geom_ribbon(data = pred_ic50, aes(x = Concentration, ymin = Lower, ymax = Upper),
fill = "gray85", alpha = 0.4) +
geom_line(data = pred_ic50, aes(x = Concentration, y = Prediction), color = "darkred", linewidth = 1.3) +
geom_point(data = data_summary, aes(x = Concentration, y = Mean), size = 3, color = "royalblue") +
geom_errorbar(data = data_summary, aes(x = Concentration, ymin = Mean - SD, ymax = Mean + SD),
width = 0.05, color = "royalblue") +
geom_vline(xintercept = ic50_value, linetype = "dashed", color = "black", linewidth = 0.8) +
annotate("text", x = 0.1, y = 30,
label = paste0("IC50 = ", ic50_value, " (95% CI: ", ic50_lower, "–", ic50_upper, ")"),
size = 4, hjust = 0, color = "black", fontface = "italic") +
annotate("text", x = 0.1, y = 20,
label = paste0("CC50 = ", cc50_value, " (95% CI: ", cc50_lower, "–", cc50_upper, ")"),
size = 4, hjust = 0, color = "gray20", fontface = "italic") +
annotate("text", x = 0.1, y = 10,
label = paste0("SI = ", SI_value),
size = 4, hjust = 0, color = "darkgreen", fontface = "bold") +
labs(title = expression(bold("B. Fisetin Antiviral Activity (IC50)")),
x = "Concentration (log scale)", y = "Response (%)") +
scale_x_continuous(trans = "log10", breaks = c(0.1, 0.5, 1, 2, 3), limits = c(-0.1, 3.2)) +
scale_y_continuous(limits = c(0, 110)) +
theme_classic(base_size = 14) +
theme(plot.title = element_text(face = "bold", size = 14, hjust = 0),
axis.title = element_text(face = "bold"),
panel.grid.major = element_line(color = "gray90", linetype = "dotted"))
# ---- Combine in facet style ----
combined_plot <- ggarrange(plot_cc50, plot_ic50, ncol = 1, nrow = 2, labels = c("A", "B"),
common.legend = FALSE, align = "v")
combined_plot
