Aim of the Analysis

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:

  1. Which compounds can be considered hits based on their spectral response?

  2. Do they show a Type I or Type II CYP P450 binding pattern?

Background

Cytochrome P450 ligand binding can be detected from changes in the absorbance spectrum in the Soret region.

Type I binding typically shows:

  1. A peak near 390 nm

  2. A trough near 420 nm

Type II binding typically shows:

  1. A peak near 430 nm

  2. 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

Plate Labelling Procedure

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.

Packages

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)

Analysis of a representative file

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…

Extract the spectral table

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

Calculate Type I and Type II signals

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 all spectra

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"
  )

Plot only hits

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")

Interpretation of results

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.

Conclusion

This analysis used spectral screening data to identify possible CYP P450-binding compounds. The final hit-calling approach combined the following things:

  1. Wavelength-specific difference signals

  2. Comparison of Type I and Type II patterns

  3. Total spectral amplitude

This help us to classification the compounds into:

  1. Type I hits

  2. Type II hits

  3. No hit