The aim of this analysis was to identify compounds that interact with Mycobacterium tuberculosis CYP P450 using absorbance spectra obtained from high-throughput screening in 96-well plates.
Each sample corresponds to a different compound. The main question was:
Which compounds can be considered hits based on their spectral response?
Do they show a Type I or Type II CYP P450 binding pattern?
Cytochrome P450 ligand binding can be detected from changes in the absorbance spectrum in the Soret region.
Type I binding typically shows:
A peak near 390 nm
A trough near 420 nm
Type II binding typically shows:
A peak near 430 nm
A trough near 410 nm
Therefore, the following signals were used:
Type I signal = A390 − A420
Type II signal = A430 − A410
In addition, the total spectral amplitude was calculated as:
Amplitude = Amax − Amin
The data were obtained from 96-well plates.
Rows: A-H
Columns: 1-12
Wells A01 and A02 were used as blank wells
The remaining wells contained test compounds
Each sample was labeled as a separate compound, for example Sample X3, Sample X4, etc.
Thus, each well corresponds to one unique tested compound.
library(readxl)
library(dplyr)
##
## 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
library(tidyr)
library(ggplot2)
library(writexl)
Below, I have shown the full workflow for one file.
file_path <- "D:/Drug Design & Development/Presentation/1/CYP_â„–1 2(a).xlsx"
raw <- read_excel(file_path, sheet = "Table End point", col_names = FALSE)
## New names:
## • `` -> `...1`
## • `` -> `...2`
## • `` -> `...3`
## • `` -> `...4`
## • `` -> `...5`
## • `` -> `...6`
## • `` -> `...7`
## • `` -> `...8`
## • `` -> `...9`
## • `` -> `...10`
## • `` -> `...11`
## • `` -> `...12`
## • `` -> `...13`
## • `` -> `...14`
## • `` -> `...15`
## • `` -> `...16`
## • `` -> `...17`
## • `` -> `...18`
## • `` -> `...19`
## • `` -> `...20`
## • `` -> `...21`
## • `` -> `...22`
## • `` -> `...23`
## • `` -> `...24`
## • `` -> `...25`
## • `` -> `...26`
## • `` -> `...27`
## • `` -> `...28`
## • `` -> `...29`
## • `` -> `...30`
## • `` -> `...31`
## • `` -> `...32`
## • `` -> `...33`
## • `` -> `...34`
## • `` -> `...35`
## • `` -> `...36`
## • `` -> `...37`
## • `` -> `...38`
## • `` -> `...39`
## • `` -> `...40`
## • `` -> `...41`
## • `` -> `...42`
## • `` -> `...43`
## • `` -> `...44`
## • `` -> `...45`
## • `` -> `...46`
## • `` -> `...47`
## • `` -> `...48`
## • `` -> `...49`
## • `` -> `...50`
## • `` -> `...51`
## • `` -> `...52`
## • `` -> `...53`
## • `` -> `...54`
## • `` -> `...55`
## • `` -> `...56`
## • `` -> `...57`
## • `` -> `...58`
## • `` -> `...59`
## • `` -> `...60`
## • `` -> `...61`
## • `` -> `...62`
## • `` -> `...63`
## • `` -> `...64`
## • `` -> `...65`
## • `` -> `...66`
## • `` -> `...67`
## • `` -> `...68`
## • `` -> `...69`
## • `` -> `...70`
## • `` -> `...71`
## • `` -> `...72`
## • `` -> `...73`
## • `` -> `...74`
## • `` -> `...75`
## • `` -> `...76`
## • `` -> `...77`
## • `` -> `...78`
## • `` -> `...79`
## • `` -> `...80`
## • `` -> `...81`
## • `` -> `...82`
## • `` -> `...83`
## • `` -> `...84`
## • `` -> `...85`
## • `` -> `...86`
## • `` -> `...87`
## • `` -> `...88`
## • `` -> `...89`
## • `` -> `...90`
## • `` -> `...91`
## • `` -> `...92`
## • `` -> `...93`
## • `` -> `...94`
## • `` -> `...95`
## • `` -> `...96`
## • `` -> `...97`
## • `` -> `...98`
## • `` -> `...99`
## • `` -> `...100`
## • `` -> `...101`
## • `` -> `...102`
## • `` -> `...103`
## • `` -> `...104`
## • `` -> `...105`
## • `` -> `...106`
## • `` -> `...107`
## • `` -> `...108`
## • `` -> `...109`
## • `` -> `...110`
## • `` -> `...111`
## • `` -> `...112`
## • `` -> `...113`
## • `` -> `...114`
## • `` -> `...115`
## • `` -> `...116`
## • `` -> `...117`
## • `` -> `...118`
## • `` -> `...119`
## • `` -> `...120`
## • `` -> `...121`
## • `` -> `...122`
## • `` -> `...123`
## • `` -> `...124`
## • `` -> `...125`
## • `` -> `...126`
## • `` -> `...127`
## • `` -> `...128`
## • `` -> `...129`
## • `` -> `...130`
## • `` -> `...131`
## • `` -> `...132`
## • `` -> `...133`
## • `` -> `...134`
## • `` -> `...135`
## • `` -> `...136`
## • `` -> `...137`
## • `` -> `...138`
## • `` -> `...139`
## • `` -> `...140`
## • `` -> `...141`
## • `` -> `...142`
## • `` -> `...143`
## • `` -> `...144`
## • `` -> `...145`
## • `` -> `...146`
## • `` -> `...147`
## • `` -> `...148`
## • `` -> `...149`
## • `` -> `...150`
## • `` -> `...151`
## • `` -> `...152`
## • `` -> `...153`
raw[1:15, 1:10]
## # A tibble: 15 × 10
## ...1 ...2 ...3 ...4 ...5 ...6 ...7 ...8 ...9 ...10
## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 "User: Grabovetc" <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA>
## 2 "Path: C:\\Program Fil… <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA>
## 3 "Test ID: 531" <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA>
## 4 "Test Name: CYP P450 m… <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA>
## 5 "Date: 15.11.2024" <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA>
## 6 "Time: 10:38:16" <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA>
## 7 "Absorbance spectrum" <NA> <NA> Abso… <NA> <NA> <NA> <NA> <NA> <NA>
## 8 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA>
## 9 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA>
## 10 "Well" Cont… Blan… Blan… Blan… Blan… Blan… Blan… Blan… Blan…
## 11 <NA> Wave… 350 351 352 353 354 355 356 357
## 12 "A01" Blan… <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA>
## 13 "A02" Blan… <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA>
## 14 "A03" Samp… 0.14… 0.14… 0.14… 0.14… 0.14… 0.14… 0.14… 0.14…
## 15 "A04" Samp… 0.95… 0.94… 0.94… 0.93… 0.92… 0.91… 0.90… 0.88…
For the standard files, the layout is:
row 10 = headers row 11 = wavelengths rows 12-107 = wells columns 3-153 = absorbance values from 350 to 500 nm
wavelengths <- as.numeric(raw[11, 3:153] %>% unlist())
spectra <- raw[12:107, 1:153]
colnames(spectra) <- c("Well", "Content", paste0("nm_", wavelengths))
spectra <- spectra %>%
mutate(
Well = as.character(Well),
Content = as.character(Content)
)
samples <- spectra %>%
filter(grepl("^Sample", Content))
nm_cols <- grep("^nm_", names(samples), value = TRUE)
samples[nm_cols] <- lapply(samples[nm_cols], as.numeric)
head(samples[, 1:10])
## # A tibble: 6 × 10
## Well Content nm_350 nm_351 nm_352 nm_353 nm_354 nm_355 nm_356 nm_357
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 A03 Sample X3 0.148 0.147 0.147 0.146 0.145 0.145 0.144 0.143
## 2 A04 Sample X4 0.955 0.949 0.941 0.932 0.921 0.913 0.902 0.888
## 3 A05 Sample X5 0.1 0.099 0.097 0.095 0.092 0.091 0.09 0.087
## 4 A06 Sample X6 0.402 0.396 0.392 0.387 0.381 0.376 0.371 0.366
## 5 A07 Sample X7 0.036 0.036 0.035 0.034 0.033 0.033 0.033 0.031
## 6 A08 Sample X8 0.407 0.407 0.406 0.405 0.403 0.404 0.403 0.402
threshold <- 0.02
results <- samples %>%
mutate(
A390 = nm_390,
A410 = nm_410,
A420 = nm_420,
A430 = nm_430,
TypeI_signal = A390 - A420,
TypeII_signal = A430 - A410
) %>%
mutate(
Hit_Class = case_when(
TypeI_signal > threshold & TypeI_signal > TypeII_signal ~ "Type I hit",
TypeII_signal > threshold & TypeII_signal > TypeI_signal ~ "Type II hit",
TRUE ~ "No hit"
)
) %>%
rowwise() %>%
mutate(
MaxAbs = max(c_across(all_of(nm_cols)), na.rm = TRUE),
MinAbs = min(c_across(all_of(nm_cols)), na.rm = TRUE),
Amplitude = MaxAbs - MinAbs
) %>%
ungroup()
summary_table <- results %>%
select(
Well, Content,
A390, A410, A420, A430,
TypeI_signal, TypeII_signal,
Amplitude, Hit_Class
) %>%
arrange(Well)
summary_table
## # A tibble: 94 × 10
## Well Content A390 A410 A420 A430 TypeI_signal TypeII_signal Amplitude
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 A03 Sample X3 0.133 0.124 0.121 0.117 0.0120 -0.00700 0.056
## 2 A04 Sample X4 0.285 0.074 0.036 0.019 0.249 -0.055 0.952
## 3 A05 Sample X5 0.051 0.018 -0.006 0.008 0.057 -0.01 0.107
## 4 A06 Sample X6 0.243 0.207 0.199 0.221 0.044 0.0140 0.204
## 5 A07 Sample X7 0.022 0.007 -0.003 0.004 0.025 -0.003 0.04
## 6 A08 Sample X8 0.366 0.343 0.331 0.319 0.0350 -0.0240 0.151
## 7 A09 Sample X9 0.311 0.267 0.244 0.235 0.067 -0.0320 0.118
## 8 A10 Sample X… 0.363 0.233 0.21 0.212 0.153 -0.0210 0.387
## 9 A11 Sample X… 0.016 0.01 0.007 0.012 0.009 0.002 0.025
## 10 A12 Sample X… 0.157 0.123 0.109 0.105 0.048 -0.018 0.194
## # ℹ 84 more rows
## # ℹ 1 more variable: Hit_Class <chr>
plot_data <- samples %>%
select(Well, Content, all_of(nm_cols)) %>%
pivot_longer(
cols = all_of(nm_cols),
names_to = "Wavelength",
values_to = "Absorbance"
) %>%
mutate(
Wavelength = as.numeric(sub("nm_", "", Wavelength))
)
ggplot(plot_data, aes(x = Wavelength, y = Absorbance, group = Well)) +
geom_line(alpha = 0.25) +
theme_minimal() +
labs(
title = "All sample spectra",
x = "Wavelength (nm)",
y = "Blank-corrected absorbance"
)
hit_wells <- summary_table %>%
filter(Hit_Class != "No hit") %>%
pull(Well)
plot_hits <- plot_data %>%
filter(Well %in% hit_wells)
ggplot(plot_hits, aes(x = Wavelength, y = Absorbance, color = Well, group = Well)) +
geom_line() +
theme_minimal() +
labs(
title = "Spectra of hits",
x = "Wavelength (nm)",
y = "Blank-corrected absorbance"
)
# Save the result table
write_xlsx(summary_table, "CYP1_hit_summary.xlsx")
Compounds were classified as hits when they showed a clear positive difference signal above the threshold. Type I hits were assigned when the signal at 390 nm relative to 420 nm was stronger than the Type II signal. Type II hits were assigned when the signal at 430 nm relative to 410 nm was stronger than the Type I signal. The amplitude was also considered, because larger amplitudes indicate a stronger spectral effect. Compounds with weak or unclear spectral changes were classified as No hit.
This analysis used spectral screening data to identify possible CYP P450-binding compounds. The final hit-calling approach combined the following things:
Wavelength-specific difference signals
Comparison of Type I and Type II patterns
Total spectral amplitude
This help us to classification the compounds into:
Type I hits
Type II hits
No hit