OSCR

CREsted: modeling genomic and synthetic cell-type-specific enhancers across tissues and species.

Code ↔ Paper

40 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 40 matches · 5 of them tie a paragraph to a whole file, not to given lines: weak matches, whose lines are not tinted
  1. [1] § Methods › Enhancer models › Motif analysis › Human PBMC motif analysis ↔ Figure_2/DeepBICCN2_analysis.ipynb, lines 582–594 · score 0.89 · crested.tl.modisco.create_pattern_matrix, seqlet_count_log, discard_ic_threshold, pattern_parameter, sim_threshold, crested.tl.modisco.process_patterns
  2. [2] § Methods › Enhancer models › Motif analysis › Mouse cortex motif analysis ↔ Figure_2/DeepBICCN2_analysis.ipynb, lines 582–594 · score 0.89 · crested.tl.modisco.create_pattern_matrix, seqlet_count_log, discard_ic_threshold, pattern_parameter, sim_threshold, crested.tl.modisco.process_patterns
  3. [3] § Methods › Enhancer models › Motif analysis › Mouse cortex motif analysis ↔ Figure_3/DeepPBMC_analysis.ipynb, lines 498–513 · score 0.89 · crested.tl.modisco.create_pattern_matrix, seqlet_count_log, discard_ic_threshold, pattern_parameter, sim_threshold, crested.tl.modisco.process_patterns
  4. [4] § Methods › Enhancer models › Motif analysis › Human PBMC motif analysis ↔ Figure_3/DeepPBMC_analysis.ipynb, lines 498–513 · score 0.89 · crested.tl.modisco.create_pattern_matrix, seqlet_count_log, discard_ic_threshold, pattern_parameter, sim_threshold, crested.tl.modisco.process_patterns
  5. [5] § Methods › Enhancer models › Motif analysis › Human PBMC motif enrichment analysis using pycisTarget ↔ create_fasta_with_padded_bg_from_bed.sh, lines 1–78 · score 0.86 · padded bg, create fasta, create_cistarget_motif_databases.py, bed, background, arguments
  6. [6] § A CREsted human PBMC model captures validated TFBSs ↔ Figure_3/chip_seq_analysis.ipynb, lines 1204–1343 · score 0.81 · UniBind, ChIP seq, GATA3, RUNX1, CEBPA, SPI1
  7. [7] § Methods › Enhancer models › Motif analysis › Zebrafish motif analysis ↔ src/crested/tl/modisco/_tfmodisco.py, lines 1048–1179 · score 0.79 · crested tl modisco, discard_ic_threshold, sim_threshold, trim_ic_threshold, lite, tfmodisco
  8. [8] § Methods › Enhancer models › Motif analysis › Human PBMC UniBind comparison ↔ Figure_3/chip_seq_analysis.ipynb, lines 1204–1343 · score 0.78 · recursive_seqlets, ChIP seq, Unibind, GATA3, RUNX1, CEBPA
  9. [9] § Methods › CREsted workflow › Model training › Regression models ↔ src/crested/tl/losses/_poissonmultinomial.py, lines 8–124 · score 0.77 · Poisson loss, multinomial loss, loss function, log transformed, magnitude, truth
  10. [10] § Methods › Enhancer models › Model training › CREsted cancer cell line peak regression model, DeepCCL ↔ Figure_4/evaluate_deepccl_chrombpnet.ipynb, lines 427–447 · score 0.76 · HepG2, M059J, reverse complement, GM12878, MM001, MM099
  11. [11] § CREsted identifies high similarity between MES enhancer codes in cancer ↔ Figure_4/topic_scoring.ipynb, lines 316–376 · score 0.74 · melanoma MES, GBM MES, Spearman correlation, biopsy topic, DeepCCL, NPC
  12. [12] § Methods › Enhancer models › Model evaluation › CREsted predictions against topics derived from the cisTopic analysis of human Gliomas ↔ Figure_4/topic_scoring.ipynb, lines 316–376 · score 0.74 · melanoma MES, Spearman correlation, biopsy topics, DeepCCL, NPC, OPC
  13. [13] § CREsted provides detailed insights into enhancer codes of mouse cortical cell types ↔ Figure_5/compare_models.ipynb, lines 762–806 · score 0.73 · Micro PVMs, leptomeningeal cell, GABA, VLMC, glut, vascular
  14. [14] § Methods › Enhancer models › Motif analysis › Human PBMC ChIP–seq comparison ↔ Figure_3/chip_seq_analysis.ipynb, lines 58–121 · score 0.69 · POU2F2, ChIP seq, GATA3, RUNX1, CEBPA, SPI1
  15. [15] § CREsted identifies high similarity between MES enhancer codes in cancer ↔ Figure_4/chrombpnet_porting.ipynb, lines 26–103 · score 0.67 · ChromBPnet, m059j, ensemble, HepG2, GM12878, MM001
  16. [16] § Methods › Enhancer models › Model training › CREsted Borzoi transfer learning ↔ src/crested/tl/zoo/_dilated_cnn_decoupled.py, the whole file · a weak match · score 0.66 · Conv1D, Dense layer, CNN, cropping, Softplus, head
  17. [17] § Methods › Human cancer cell lines › Data processing ↔ Figure_4/topic_scoring.ipynb, lines 258–314 · score 0.66 · HepG2, DeepCCL, MEL, GM12878, MM001, MM099
  18. [18] § Methods › Enhancer models › Motif analysis › Human PBMC motif enrichment analysis using pycisTarget ↔ create_cistarget_motif_databases.py, lines 154–217 · score 0.66 · cisTarget motif database, create cistarget motif, background padding, bg, command, fasta
  19. [19] § CREsted identifies high similarity between MES enhancer codes in cancer ↔ Figure_4/enhancer_code_deepccl.ipynb, lines 127–272 · score 0.65 · melanoma cell, RUNX, ATF, CREB, TEAD, MM029
  20. [20] § Methods › Enhancer models › Model training › CREsted Borzoi transfer learning ↔ src/crested/tl/zoo/_dilated_cnn.py, the whole file · a weak match · score 0.64 · Conv1D, Dense layer, CNN, cropping, Softplus, convolutional
  21. [21] § Methods › Enhancer models › Model training › CREsted cancer cell line peak regression model, DeepCCL ↔ Figure_4/topic_scoring.ipynb, lines 58–91 · score 0.64 · HepG2, M059J, GM12878, MM001, MM099, MM029
  22. [22] § CREsted is a software package for efficient enhancer modeling and design ↔ src/crested/tl/losses/_cosinemse_log.py, the whole file · a weak match · score 0.63 · squared error, loss function, dynamically, MSE, cosine, sum
  23. [23] § Methods › Enhancer models › Model evaluation › Gene locus predictions ↔ src/crested/tl/_tools.py, lines 256–383 · score 0.63 · transcription start site, prediction score, upstream, downstream, window, locus
  24. [24] § CREsted is a software package for efficient enhancer modeling and design ↔ src/crested/tl/losses/_cosinemse.py, the whole file · a weak match · score 0.63 · squared error, loss function, dynamically, MSE, cosine, Optionally
  25. [25] § CREsted provides detailed insights into enhancer codes of mouse cortical cell types ↔ Figure_5/compare_models.ipynb, lines 762–806 · score 0.62 · excitatory neuron, SstChodl, oligodendrocytes, microglia, model, cell
  26. [26] § A CREsted human PBMC model captures validated TFBSs ↔ Figure_3/chip_seq_analysis.ipynb, lines 449–516 · score 0.62 · Precision recall curve, ChIP seq, AR, AP, Figure 3, Thresholding
  27. [27] § Methods › CREsted workflow › Data preprocessing › Peak-height preprocessing ↔ src/crested/_datasets.py, lines 109–174 · score 0.62 · cut sites, bigWig, consensus peaks, pseudobulk, coverage, tracks
  28. [28] § Methods › Enhancer models › Model training › gReLU mouse cortex peak regression model ↔ src/crested/_datasets.py, lines 109–174 · score 0.60 · cut site, peak regression, consensus peaks, BigWigs, cortex, mouse
  29. [29] § CREsted is a software package for efficient enhancer modeling and design ↔ src/crested/pp/_normalization.py, the whole file · a weak match · score 0.60 · low variability, standard deviation, Gini, retrieved, max, peak
  30. [30] § Methods › Enhancer models › Model training › ChromBPNet cancer cell line model ↔ src/crested/pl/corr/_heatmap.py, lines 182–330 · score 0.59 · Pearson correlation coefficient, ground truth, log transformed, split, heights, trained
  31. [31] § CREsted-trained models outperform large, pretrained models on cell-type-specific chromatin accessibility predictions ↔ src/crested/_datasets.py, lines 202–336 · score 0.58 · DeepBICCN2, transfer learning, mouse cortex, brain, Borzoi, maps
  32. [32] § Methods › Enhancer models › Input size benchmark › Comparison of seqlet locations and identified patterns ↔ src/tfmindi/pp/seqlets.py, lines 203–229 · score 0.57 · TF MInDi, recursive_seqlets, tangermeme, pp, scores
  33. [33] § Methods › Enhancer models › Model evaluation › EBF1 degradation analysis in B cells ↔ src/crested/tl/_tools.py, lines 256–383 · score 0.56 · score gene locus, crested tl, upstream, downstream
  34. [34] § Methods › CREsted workflow › Model training ↔ src/crested/tl/data/utils/_datawrapper.py, lines 429–472 · score 0.55 · TensorFlow, PyTorch, backends, Keras, CREsted, accessibility
  35. [35] § Methods › Enhancer models › Model training › ChromBPNet cancer cell line model ↔ src/crested/pl/corr/_violin.py, lines 15–89 · score 0.55 · ground truth, Pearson correlation, log transformed, fine tuned, split, heights
  36. [36] § CREsted-trained models outperform large, pretrained models on cell-type-specific chromatin accessibility predictions ↔ Figure_5/compare_models.ipynb, lines 69–122 · score 0.54 · double fine tuning, fine tuned Borzoi, frozen, consensus, peaks, model
  37. [37] § Methods › CREsted workflow › Enhancer code analysis › Pattern to TF matching ↔ src/crested/tl/modisco/_tfmodisco.py, lines 1969–2019 · score 0.53 · TF candidates, correlation threshold, Pearson, kept, cell
  38. [38] § Methods › CREsted workflow › Data preprocessing › Peak-height preprocessing ↔ src/crested/_io.py, lines 567–613 · score 0.52 · bigWig, boundaries, cut, coverage, chromosome, Optionally
  39. [39] § Methods › CREsted workflow › Model training ↔ src/crested/tl/_explainer.py, lines 83–214 · score 0.52 · TensorFlow, PyTorch, backends, Keras, sequence, predicting
  40. [40] § CREsted identifies high similarity between MES enhancer codes in cancer ↔ src/crested/_datasets.py, lines 202–336 · score 0.51 · DeepGlioma, DeepCCL, gliomas, accessibility, human, CREsted

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Jupyter notebook · 1,382 lines · 48 KB · MIT · 4 matches

  1. # %%
  2. import pandas as pd
  3. import numpy as np
  4. import keras
  5. import crested
  6. import matplotlib.pyplot as plt
  7. import matplotlib
  8. %matplotlib inline
  9. matplotlib.rcParams["pdf.fonttype"] = 42
  10. matplotlib.rcParams["ps.fonttype"] = 42
  11. from pathlib import Path
  12. import anndata
  13. import h5py
  14. # %% [markdown]
  15. # Download the data for the notebooks from the dedicated Zenodo link of the CREsted paper. Then use it below.
  16. # %%
  17. DATA_DIR ="../../../crested_data/Figure_3/pbmc/"
  18. # %% [markdown]
  19. # # Load hg38 genome
  20. # %% [markdown]
  21. # This notebook requires a hg38 fasta file and a hg38 chromosome sizes file. You can download that here for example: https://hgdownload.soe.ucsc.edu/downloads.html
  22. # Once downloaded, we load them to the notebook.
  23. # %%
  24. genome_dir = "../../../human/genome/"
  25. genome_fasta = f"{genome_dir}hg38.fa"
  26. genome_chrom_sizes = f"{genome_dir}hg38.chrom.sizes"
  27. # %%
  28. genome = crested.Genome(genome_fasta, genome_chrom_sizes)
  29. crested.register_genome(genome)
  30. # %% [markdown]
  31. # # Load DeepPBMC
  32. # %%
  33. bigwigs_folder = f"{DATA_DIR}bw/"
  34. regions_file = f"{DATA_DIR}consensus_regions.bed"
  35. # %%
  36. adata = anndata.read_h5ad(f"{DATA_DIR}pbmc_filtered.h5ad")
  37. adata.obs_names = pd.Index([
  38. 'Bcell', 'CD14_monocyte', 'CD16_monocyte',
  39. 'CD4_Tcell', 'Cytotoxic_T_cell',
  40. 'Dendritic_cell', 'Natural_killer_cell'
  41. ])
  42. # %%
  43. model_path =f"{DATA_DIR}DeepPBMC.keras"
  44. model = keras.models.load_model(
  45. model_path, compile=False
  46. )
  47. # %% [markdown]
  48. # # Analysis on modisco top regions
  49. # %% [markdown]
  50. # First we define all relevant files, patterns and directories and add them to the tf_dict dictionary.
  51. # %%
  52. tf_dict = {}
  53. tf_list = ['PAX5','EBF1','POU2F2', 'RUNX1', 'GATA3', 'ETS1', 'CEBPA', 'SPI1']
  54. ct_list= ['Bcell','Bcell','Bcell','CD4_Tcell','CD4_Tcell','CD4_Tcell', 'CD14_monocyte', 'CD14_monocyte']
  55. chip_bed_files = [
  56. f'{DATA_DIR}/chip/pax5/pax5_CHIP.bed',
  57. f"{DATA_DIR}/chip/ebf1/ebf1_CHIP.bed",
  58. f"{DATA_DIR}/chip/pou2f2/pou2f2_chip.bed",
  59. f"{DATA_DIR}/chip/runx1/runx1_CHIP.bed",
  60. f"{DATA_DIR}/chip/gata3_2/gata3_CHIP.bed",
  61. f"{DATA_DIR}/chip/ets1/ets1_CHIP.bed",
  62. f"{DATA_DIR}/chip/cebpa/cebpa_CHIP.bed",
  63. f"{DATA_DIR}/chip/spi1_2/spi1_CHIP.bed",
  64. ]
  65. unibind_bed_files = [
  66. f"{DATA_DIR}/chip/pax5/pax5.bed",
  67. f"{DATA_DIR}/chip/ebf1/ebf1_unibind.bed",
  68. None,
  69. f"{DATA_DIR}/chip/runx1/runx1.bed",
  70. f"{DATA_DIR}/chip/gata3_2/gata3.bed",
  71. f"{DATA_DIR}/chip/ets1/ets1_unibind.bed",
  72. f"{DATA_DIR}/chip/cebpa/cebpa.bed",
  73. f"{DATA_DIR}/chip/spi1_2/spi1.bed",
  74. ]
  75. chip_bw_files = [
  76. f"{DATA_DIR}chip/pax5/PAX5.bigWig",
  77. f"{DATA_DIR}chip/ebf1/EBF1.bigWig",
  78. f"{DATA_DIR}chip/pou2f2/pou2f2.bigWig",
  79. f"{DATA_DIR}chip/runx1/runx1.bw",
  80. f"{DATA_DIR}chip/gata3_2/gata3.bw",
  81. f"{DATA_DIR}chip/ets1/ets1.bw",
  82. f"{DATA_DIR}chip/cebpa/cebpa.bw",
  83. f"{DATA_DIR}chip/spi1_2/spi1.bw",
  84. ]
  85. # Modisco pattern numbers
  86. pattern_nrs = [[5, 6, 9, 12, 13, 19, 23],
  87. [3,24],
  88. [2],
  89. [2, 4, 19, 22, 25],
  90. [7], #[3,7,27]
  91. [0],
  92. [0, 4],
  93. [2]]
  94. for i, tf in enumerate(tf_list):
  95. tf_dict[tf]={}
  96. tf_dict[tf]['cell_type']=ct_list[i]
  97. tf_dict[tf]['chip_bed']=chip_bed_files[i]
  98. tf_dict[tf]['chip_bw']=chip_bw_files[i]
  99. tf_dict[tf]['unibind_bed']=unibind_bed_files[i]
  100. tf_dict[tf]['pattern_indices']=pattern_nrs[i]
  101. top_n = 1000
  102. contribution_dir=f"{DATA_DIR}modisco/"
  103. modisco_regions=f"{DATA_DIR}modisco_regions.csv"
  104. # %% [markdown]
  105. # ## Load the patterns from the modisco files
  106. # %%
  107. def recursively_load_h5_data(h5_group):
  108. """
  109. Recursively load all data from an HDF5 group or dataset into Python objects.
  110. """
  111. if isinstance(h5_group, h5py.Group):
  112. # If it's a group, recurse into its items
  113. return {key: recursively_load_h5_data(h5_group[key]) for key in h5_group.keys()}
  114. elif isinstance(h5_group, h5py.Dataset):
  115. # If it's a dataset, load its value into memory
  116. return h5_group[()]
  117. else:
  118. # Unknown type, return as-is
  119. return h5_group
  120. for tf in tf_dict:
  121. ppms = []
  122. patterns = []
  123. h5_file = contribution_dir+'/'+tf_dict[tf]['cell_type']+'_modisco_results.h5'
  124. with h5py.File(h5_file) as hdf5_results:
  125. for metacluster_name in ['pos_patterns']:
  126. pattern_idx = 0
  127. for i in range(len(list(hdf5_results[metacluster_name]))):
  128. p = "pattern_" + str(i)
  129. pattern = hdf5_results[metacluster_name][p]
  130. pattern_data = recursively_load_h5_data(pattern)
  131. patterns.append(pattern_data)
  132. tf_dict[tf]['patterns']=patterns
  133. # %% [markdown]
  134. # ## Load the regions used for modisco
  135. # %%
  136. for tf in tf_dict:
  137. file_path = modisco_regions
  138. region_df = pd.read_csv(file_path)
  139. region_df['Class name'] = region_df['Class name'].str.lstrip('Merged__')
  140. region_df['Class name'] = region_df['Class name'].replace('B_cell','Bcell')
  141. region_df['Class name'] = region_df['Class name'].replace('CD4_T_cell','CD4_Tcell')
  142. region_df = region_df.loc[region_df['Class name']==tf_dict[tf]['cell_type']]
  143. region_df = region_df.head(top_n)
  144. region_df
  145. tf_dict[tf]['region_df']=region_df
  146. # %%
  147. tf_dict['EBF1']['region_df']
  148. # %% [markdown]
  149. # ## Load the ChIP Bed files
  150. # %%
  151. # Define the base column names for a standard BED file
  152. base_column_names = ["chrom", "start", "end", "name", "score", "strand", "signal_value", "p-val", "-log10(q-value)", "peak-summit"]
  153. chip_df_list=[]
  154. for tf in tf_dict:
  155. bed_file_path = tf_dict[tf]['chip_bed']
  156. bed_preview = pd.read_csv(bed_file_path, sep="\t", header=None, nrows=1)
  157. # Extend the column names list to match the number of columns in the file
  158. extra_columns = [f"extra_col_{i}" for i in range(len(bed_preview.columns) - len(base_column_names))]
  159. column_names = base_column_names + extra_columns
  160. # Read the BED file into a DataFrame with the dynamically generated column names
  161. chip_all_df = pd.read_csv(bed_file_path, sep="\t", header=None, names=column_names)
  162. chip_df_list.append(chip_all_df)
  163. # %% [markdown]
  164. # ## Load the UniBind Bed files
  165. # %%
  166. import pandas as pd
  167. # Define the base column names for a standard BED file
  168. base_column_names = ["chrom", "start", "end", "name", "score", "strand", "signal_value", "p-val", "-log10(q-value)", "peak-summit"]
  169. unibind_df_list=[]
  170. for tf in tf_dict:
  171. # Read the BED file to determine the number of columns
  172. bed_file_path = tf_dict[tf]['unibind_bed']
  173. if bed_file_path:
  174. bed_preview = pd.read_csv(bed_file_path, sep="\t", header=None, nrows=1)
  175. # Extend the column names list to match the number of columns in the file
  176. extra_columns = [f"extra_col_{i}" for i in range(len(bed_preview.columns) - len(base_column_names))]
  177. column_names = base_column_names + extra_columns
  178. # Read the BED file into a DataFrame with the dynamically generated column names
  179. unibind_df = pd.read_csv(bed_file_path, sep="\t", header=None, names=column_names)
  180. unibind_df_list.append(unibind_df)
  181. else:
  182. unibind_df_list.append(None)
  183. # %% [markdown]
  184. # ## Calculate overlapping Unibind hits in the top regions used in modisco
  185. # %%
  186. for i,tf in enumerate(tf_dict):
  187. possible_hits = []
  188. regions_to_process = tf_dict[tf]['region_df'].head(top_n) if top_n else tf_dict[tf]['region_df']
  189. unibind_df = unibind_df_list[i]
  190. if unibind_df is not None:
  191. for _, region in regions_to_process.iterrows():
  192. overlaps = unibind_df[
  193. (unibind_df["chrom"] == region["chr"]) &
  194. (unibind_df["start"] < region["end"] - 557) &
  195. (unibind_df["end"] > region["start"] + 557)
  196. ]
  197. for _, overlap in overlaps.iterrows():
  198. possible_hits.append({
  199. "region": region['region'],
  200. "region_chr": region["chr"],
  201. "region_start": region["start"],
  202. "region_end": region["end"],
  203. "chip_chr": overlap["chrom"],
  204. "chip_start": int(overlap["start"]),
  205. "chip_end": int(overlap["end"]),
  206. "chip_name": overlap["name"],
  207. "chip_score": overlap["score"],
  208. "chip_strand": overlap["strand"],
  209. "status": "Possible Hit"
  210. })
  211. possible_hits_df_unibind = pd.DataFrame(possible_hits)
  212. tf_dict[tf]['unibind_hits_df'] = possible_hits_df_unibind
  213. else:
  214. tf_dict[tf]['unibind_hits_df'] = None
  215. # %%
  216. tf_dict['RUNX1']['unibind_hits_df']
  217. # %% [markdown]
  218. # ## Retrieve seqlet positions inside top modisco regions
  219. # %%
  220. for tf in tf_dict:
  221. seqlet_starts=[]
  222. seqlet_chroms=[]
  223. seqlet_ends=[]
  224. seqlet_regions=[]
  225. seqlet_attributions=[]
  226. pattern_nrs= tf_dict[tf]['pattern_indices']
  227. patterns = tf_dict[tf]['patterns']
  228. region_df = tf_dict[tf]['region_df']
  229. for pattern_nr in pattern_nrs:
  230. pattern = patterns[pattern_nr]
  231. for i in range(pattern['seqlets']['n_seqlets'][0]):
  232. region = pattern['seqlets']['example_idx'][i]
  233. if region<top_n:
  234. region_id = region_df.iloc[region]['region']
  235. region_chr = region_df.iloc[region]['chr']
  236. region_start = region_df.iloc[region]['start']
  237. region_end = region_df.iloc[region]['end']
  238. seqlet_start = region_start+pattern['seqlets']['start'][i]+557
  239. seqlet_end = seqlet_start+30
  240. seqlet_chrom = region_chr
  241. average_contrib=pattern['seqlets']['contrib_scores'][i]
  242. average_contrib = average_contrib[average_contrib!=0]
  243. average_contrib = np.mean(average_contrib)
  244. seqlet_starts.append(seqlet_start)
  245. seqlet_ends.append(seqlet_end)
  246. seqlet_chroms.append(seqlet_chrom)
  247. seqlet_regions.append(region_id)
  248. seqlet_attributions.append(average_contrib)
  249. seqlet_df= pd.DataFrame({
  250. 'region': seqlet_regions,
  251. 'chr':seqlet_chroms,
  252. 'start':seqlet_starts,
  253. 'end':seqlet_ends,
  254. 'average_contrib':seqlet_attributions
  255. })
  256. tf_dict[tf]['seqlet_df']=seqlet_df
  257. # %%
  258. tf='POU2F2'
  259. tf_dict[tf]['seqlet_df']
  260. # %% [markdown]
  261. # ## Calculate precision and recall of Seqlets inside ChIP-seq peaks
  262. # %%
  263. def calculate_precision_recall(seqlet_df, chip_all_df, region_df, top_n=None):
  264. """
  265. Calculate precision and recall for overlap between seqlet_df and chip_all_df, with region_df for context.
  266. Parameters:
  267. - seqlet_df (pd.DataFrame): DataFrame with seqlets.
  268. - chip_all_df (pd.DataFrame): DataFrame with ChIP-seq data.
  269. - region_df (pd.DataFrame): DataFrame with regions for possible hits.
  270. - top_n (int, optional): Limit the number of regions processed from region_df.
  271. Returns:
  272. - results_df (pd.DataFrame): DataFrame with detailed overlap information.
  273. - precision (float): Precision value.
  274. - recall (float): Recall value.
  275. """
  276. # Initialize results and counts
  277. results = []
  278. true_positives = 0
  279. false_positives = 0
  280. false_negatives = 0
  281. # Step 1: True Positives and False Positives
  282. for _, seqlet in seqlet_df.iterrows():
  283. overlaps = chip_all_df[
  284. (chip_all_df["chrom"] == seqlet["chr"]) &
  285. (chip_all_df["start"] < seqlet["end"] + 5) &
  286. (chip_all_df["end"] > seqlet["start"] - 5)
  287. ]
  288. if not overlaps.empty:
  289. for _, overlap in overlaps.iterrows():
  290. results.append({
  291. "region": seqlet['region'],
  292. "seqlet_chr": seqlet["chr"],
  293. "seqlet_start": seqlet["start"],
  294. "seqlet_end": seqlet["end"],
  295. "seqlet_average_contrib": seqlet["average_contrib"],
  296. "chip_chr": overlap["chrom"],
  297. "chip_start": int(overlap["start"]),
  298. "chip_end": int(overlap["end"]),
  299. "chip_name": overlap["name"],
  300. "chip_score": overlap["score"],
  301. "chip_strand": overlap["strand"],
  302. "status": "True Positive"
  303. })
  304. true_positives += 1
  305. else:
  306. results.append({
  307. "region": seqlet['region'],
  308. "seqlet_chr": seqlet["chr"],
  309. "seqlet_start": seqlet["start"],
  310. "seqlet_end": seqlet["end"],
  311. "seqlet_average_contrib": seqlet["average_contrib"],
  312. "chip_chr": np.nan,
  313. "chip_start": np.nan,
  314. "chip_end": np.nan,
  315. "chip_name": np.nan,
  316. "chip_score": np.nan,
  317. "chip_strand": np.nan,
  318. "status": "False Positive"
  319. })
  320. false_positives += 1
  321. # Step 2: Possible Hits (region_df vs chip_all_df)
  322. possible_hits = []
  323. regions_to_process = region_df.head(top_n) if top_n else region_df
  324. for _, region in regions_to_process.iterrows():
  325. overlaps = chip_all_df[
  326. (chip_all_df["chrom"] == region["chr"]) &
  327. (chip_all_df["start"] < region["end"] - 557) &
  328. (chip_all_df["end"] > region["start"] + 557)
  329. ]
  330. for _, overlap in overlaps.iterrows():
  331. possible_hits.append({
  332. "region": region['region'],
  333. "region_chr": region["chr"],
  334. "region_start": region["start"],
  335. "region_end": region["end"],
  336. "chip_chr": overlap["chrom"],
  337. "chip_start": int(overlap["start"]),
  338. "chip_end": int(overlap["end"]),
  339. "chip_name": overlap["name"],
  340. "chip_score": overlap["score"],
  341. "chip_strand": overlap["strand"],
  342. "status": "Possible Hit"
  343. })
  344. possible_hits_df = pd.DataFrame(possible_hits)
  345. # Step 3: False Negatives (possible hits not in seqlet_df)
  346. for _, hit in possible_hits_df.iterrows():
  347. matches = seqlet_df[
  348. (seqlet_df["chr"] == hit["chip_chr"]) &
  349. (seqlet_df["start"] < hit["chip_end"] + 5) &
  350. (seqlet_df["end"] > hit["chip_start"] - 5)
  351. ]
  352. if matches.empty:
  353. results.append({
  354. "region": hit['region'],
  355. "seqlet_chr": np.nan,
  356. "seqlet_start": np.nan,
  357. "seqlet_end": np.nan,
  358. "seqlet_average_contrib": np.nan,
  359. "chip_chr": hit["chip_chr"],
  360. "chip_start": hit["chip_start"],
  361. "chip_end": hit["chip_end"],
  362. "chip_name": hit["chip_name"],
  363. "chip_score": hit["chip_score"],
  364. "chip_strand": hit["chip_strand"],
  365. "status": "False Negative"
  366. })
  367. false_negatives += 1
  368. # Convert results to DataFrame
  369. results_df = pd.DataFrame(results)
  370. # Calculate precision and recall
  371. precision = true_positives / (true_positives + false_positives) if true_positives + false_positives > 0 else 0
  372. recall = true_positives / (true_positives + false_negatives) if true_positives + false_negatives > 0 else 0
  373. return results_df, precision, recall, true_positives, false_positives, false_negatives, possible_hits_df
  374. # %%
  375. # Assuming seqlet_df, chip_all_df, and region_df are defined
  376. for i, tf in enumerate(tf_dict):
  377. print(tf)
  378. results_df, precision, recall, tp, fp, fn, possible_hits_df = calculate_precision_recall(tf_dict[tf]['seqlet_df'], chip_df_list[i], tf_dict[tf]['region_df'], top_n)
  379. tf_dict[tf]['results_df']=results_df
  380. tf_dict[tf]['chip_hits_df']=possible_hits_df
  381. # Display results
  382. print(f"Precision: {precision:.3f}")
  383. print(f"Recall: {recall:.3f}")
  384. # %% [markdown]
  385. # ### Fig. 3e
  386. # %%
  387. from tqdm import tqdm
  388. def calculate_precision_recall_by_threshold(seqlet_df, chip_all_df, region_df, thresholds, top_n=None):
  389. """
  390. Calculate precision and recall for varying thresholds on 'average_contrib' in seqlet_df.
  391. Parameters:
  392. - seqlet_df (pd.DataFrame): DataFrame with seqlets.
  393. - chip_all_df (pd.DataFrame): DataFrame with ChIP-seq data.
  394. - region_df (pd.DataFrame): DataFrame with regions for possible hits.
  395. - thresholds (list or np.array): Thresholds for filtering seqlet_df by 'average_contrib'.
  396. - top_n (int, optional): Limit the number of regions processed from region_df.
  397. Returns:
  398. - precision_list (list): List of precision values for each threshold.
  399. - recall_list (list): List of recall values for each threshold.
  400. - thresholds (list): List of thresholds used.
  401. - avg_precision (float): Average precision across thresholds.
  402. - avg_recall (float): Average recall across thresholds.
  403. """
  404. precision_list = []
  405. recall_list = []
  406. for threshold in thresholds:
  407. # Filter seqlet_df by current threshold
  408. filtered_seqlet_df = seqlet_df.loc[seqlet_df['average_contrib'] > threshold].reset_index(drop=True)
  409. # Calculate precision and recall using the previous function
  410. _, precision, recall, _, _, _, _ = calculate_precision_recall(filtered_seqlet_df, chip_all_df, region_df, top_n)
  411. # Store results
  412. precision_list.append(precision)
  413. recall_list.append(recall)
  414. # Calculate average precision and recall
  415. avg_precision = np.mean(precision_list)
  416. avg_recall = np.mean(recall_list)
  417. return precision_list, recall_list, thresholds, avg_precision, avg_recall
  418. plt.figure(figsize=(6, 6))
  419. for i, tf in tqdm(enumerate(tf_list), total=len(tf_dict)):
  420. # Define thresholds
  421. thresholds = np.linspace(np.min(tf_dict[tf]['seqlet_df']['average_contrib'].values),
  422. np.max(tf_dict[tf]['seqlet_df']['average_contrib'].values) * 0.95, 5)
  423. # Calculate precision, recall, and averages
  424. precision_list, recall_list, thresholds, avg_precision, avg_recall = calculate_precision_recall_by_threshold(
  425. tf_dict[tf]['seqlet_df'], chip_df_list[i], tf_dict[tf]['region_df'], thresholds
  426. )
  427. # Plot all PR curves on the same figure
  428. plt.plot(recall_list, precision_list, marker='o', label=f'{tf} (AP={avg_precision:.3f}, AR={avg_recall:.3f})')
  429. # Formatting
  430. plt.title("Precision-Recall Curves by Thresholding on 'average_contrib'")
  431. plt.xlabel("Recall")
  432. plt.ylabel("Precision")
  433. plt.grid(True, linestyle='--', alpha=0.7)
  434. plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))
  435. plt.xlim([0, 1])
  436. plt.ylim([0, 1])
  437. #plt.savefig('paperfigs/all_TFs_PR.pdf', bbox_inches='tight')
  438. plt.show()
  439. # %%
  440. tf_dict[tf]['chip_hits_df'] # Dataframe for ChIP-seq peaks inside top modisco regions
  441. # %% [markdown]
  442. # ## Closer look at example regions
  443. # %%
  444. tf='POU2F2'
  445. region_index = 3
  446. region_df = tf_dict[tf]['region_df']
  447. results_df = tf_dict[tf]['results_df']
  448. region_id = region_df.iloc[region_index]['region']
  449. region_data = results_df[results_df['region'] == region_id]
  450. chrom=region_id.split(':')[0]
  451. start=int(region_id.split(':')[1].split('-')[0])
  452. end=int(region_id.split(':')[1].split('-')[1])
  453. # Extract non-NaN seqlet coordinates
  454. seqlet_coords = region_data.dropna(subset=["seqlet_chr"]).apply(
  455. lambda row: (int(row["seqlet_start"]-start), int(row["seqlet_end"]-start)), axis=1
  456. ).tolist() if len(region_data.dropna(subset=["seqlet_chr"]))>0 else None
  457. # Extract non-NaN ChIP coordinates
  458. chip_coords = region_data.dropna(subset=["chip_chr"]).apply(
  459. lambda row: (int(row["chip_start"]-start), int(row["chip_end"]-start)), axis=1
  460. ).tolist() if len(region_data.dropna(subset=["chip_chr"]))>0 else None
  461. seqlet_coords
  462. chip_coords
  463. region_data
  464. # %%
  465. adata.obs_names
  466. # %%
  467. classes_of_interest = [tf_dict[tf]['cell_type']]
  468. sequence = genome.fetch(chrom, start, end).upper()
  469. class_idx = list(adata.obs_names.get_indexer(classes_of_interest))
  470. scores, one_hot_encoded_sequences = crested.tl.contribution_scores(
  471. sequence,
  472. target_idx=class_idx,
  473. model=model,
  474. )
  475. # %%
  476. # Highlight identified seqlets
  477. crested.pl.patterns.contribution_scores(
  478. scores,
  479. one_hot_encoded_sequences,
  480. sequence_labels=[''],
  481. class_labels=classes_of_interest,
  482. zoom_n_bases=500,
  483. title="Test set region",
  484. height=3.5,
  485. highlight_positions=seqlet_coords
  486. )
  487. # %%
  488. # Highlight ChIP peaks
  489. crested.pl.patterns.contribution_scores(
  490. scores,
  491. one_hot_encoded_sequences,
  492. sequence_labels=[''],
  493. class_labels=classes_of_interest,
  494. zoom_n_bases=500,
  495. title="Test set region",
  496. height=3.5,
  497. highlight_positions=chip_coords
  498. )
  499. # %%
  500. # Plot ChIP BigWigs
  501. for chip_bw in chip_bw_files:
  502. filename = chip_bw.split('/')[-1].split('.')[0].upper() # Extract and format filename
  503. # Read BigWig region
  504. vals, _ = crested.utils.read_bigwig_region(chip_bw, (chrom, start + 807, end - 807))
  505. # Plot
  506. plt.figure(figsize=(50, 2))
  507. plt.plot(np.arange(0, 500), vals)
  508. # Add filename as text in the top-left corner
  509. plt.text(0.02, 0.95, filename, transform=plt.gca().transAxes, fontsize=18, fontweight='bold',
  510. verticalalignment='top',horizontalalignment='left', bbox=dict(facecolor='white', alpha=0.7, edgecolor='black'))
  511. plt.show()
  512. # %%
  513. # Plot all possible seqlets identified by tangermeme
  514. from tangermeme.seqlet import recursive_seqlets
  515. from crested.utils import one_hot_encode_sequence
  516. oh_seq = one_hot_encode_sequence(sequence)[0].T
  517. X_attr = scores[0,0].T
  518. X_attr=X_attr*oh_seq
  519. X_attr = np.expand_dims(np.sum(X_attr, axis=0),axis=0)
  520. seqlets = recursive_seqlets(X_attr, threshold=0.05)
  521. highlight_positions=[]
  522. for i in range(len(seqlets)):
  523. start_ = seqlets.iloc[i]['start']
  524. end_ = seqlets.iloc[i]['end']
  525. highlight_positions.append((start_,end_))
  526. crested.pl.patterns.contribution_scores(
  527. scores,
  528. one_hot_encoded_sequences,
  529. sequence_labels=[''],
  530. class_labels=classes_of_interest,
  531. zoom_n_bases=500,
  532. title="Test set region",
  533. height=3.5,
  534. highlight_positions=highlight_positions
  535. )
  536. seqlets
  537. # %%
  538. # Plot region predictions
  539. sequence = genome.fetch(chrom, start, end).upper()
  540. prediction = crested.tl.predict(sequence, model)
  541. crested.pl.bar.prediction(prediction, classes=list(adata.obs_names))
  542. # %%
  543. # Plot scATAC profile
  544. bigwigs_folder = f"{DATA_DIR}/bw/"
  545. vals = np.zeros(len(list(adata.obs_names)))
  546. for i, ct in enumerate(list(adata.obs_names)):
  547. print(ct)
  548. values = crested.utils.read_bigwig_region(bigwigs_folder+ct+'.bw', (chrom,start+557,end-557))
  549. bw_values=values[0]
  550. midpoints=values[1]
  551. vals[i] = np.sum(bw_values)*adata.obsm['weights'][i]
  552. plt.figure(figsize=(20,3))
  553. plt.bar(list(adata.obs_names), vals)
  554. plt.title('ATAC profile')
  555. plt.show()
  556. # %% [markdown]
  557. # ## Recall of UniBind hits with seqlet calling
  558. # %%
  559. def get_row_with_overlap(df, input_start, input_end, overlap=0.5):
  560. """
  561. Retrieves the row with at least 30% overlap with the given input range.
  562. Parameters:
  563. df (pd.DataFrame): Input DataFrame with 'start' and 'end' columns.
  564. input_start (int): Start of the input range.
  565. input_end (int): End of the input range.
  566. overlap (float): Minimum overlap fraction required.
  567. Returns:
  568. pd.DataFrame: Row(s) with at least the specified overlap, sorted by overlap.
  569. """
  570. def overlap_fraction(row):
  571. # Calculate overlap
  572. overlap_start = max(row['start'], input_start)
  573. overlap_end = min(row['end'], input_end)
  574. overlap_length = max(0, overlap_end - overlap_start + 1) # Ensure non-negative
  575. row_length = row['end'] - row['start'] + 1
  576. return overlap_length / row_length
  577. # Calculate overlap for each row and add as a new column
  578. df['overlap'] = df.apply(overlap_fraction, axis=1)
  579. # Filter rows with at least the specified overlap
  580. overlap_rows = df[df['overlap'] >= overlap]
  581. # Sort the filtered rows by the overlap column
  582. overlap_rows = overlap_rows.sort_values(by='overlap', ascending=False)
  583. return overlap_rows
  584. # %%
  585. from tqdm import tqdm
  586. for tf in tf_dict:
  587. print(tf)
  588. filename=contribution_dir+'/'+tf_dict[tf]['cell_type']+'_contrib.npz'
  589. scores = np.load(filename)['arr_0']
  590. scores = np.transpose(scores,(0,2,1))
  591. oh_seqs=np.load(contribution_dir+'/'+tf_dict[tf]['cell_type']+'_oh.npz')['arr_0']
  592. oh_seqs = np.transpose(oh_seqs,(0,2,1))
  593. seqlet_starts=[]
  594. seqlet_ends = []
  595. p_values=[]
  596. attributions=[]
  597. attributions_exact=[]
  598. attributions_exact_padded=[]
  599. if tf_dict[tf]['unibind_hits_df'] is None:
  600. tf_dict[tf]['unibind_recall_df'] = None
  601. continue
  602. for i in tqdm(range(top_n)):
  603. oh_seq = oh_seqs[i].T
  604. X_attr = scores[i].T
  605. X_attr=X_attr*oh_seq
  606. X_attr = np.expand_dims(np.sum(X_attr, axis=0),axis=0)
  607. seqlets = recursive_seqlets(X_attr, threshold=0.05)
  608. region_id=tf_dict[tf]['region_df'].iloc[i]['region']
  609. if region_id in tf_dict[tf]['unibind_hits_df']['region'].values:
  610. rows = tf_dict[tf]['unibind_hits_df'].loc[tf_dict[tf]['unibind_hits_df']['region']==region_id]
  611. if len(rows)==1:
  612. row=rows
  613. chrom= row['region_chr']
  614. start=int(row['region_start'].iloc[0])
  615. end=int(row['region_end'].iloc[0])
  616. tf_start=int(row['chip_start'].iloc[0]-start)
  617. tf_end = int(row['chip_end'].iloc[0]-start)
  618. overlap_rows = get_row_with_overlap(seqlets, tf_start, tf_end)
  619. if len(overlap_rows)>0:
  620. r = overlap_rows.iloc[0]
  621. seq_start=start+int(r['start'])
  622. seq_end = start+int(r['end'])
  623. seqlet_starts.append(seq_start)
  624. seqlet_ends.append(seq_end)
  625. p_values.append(r['p-value'])
  626. attributions.append(r['attribution'])
  627. attribution = (np.mean(X_attr[0,int(r['start']):int(r['end'])]))
  628. padding=(30-(int(r['end'])-int(r['start'])))//2
  629. attribution_padded = (np.mean(X_attr[0,int(r['start'])-padding:int(r['end'])+padding]))
  630. attributions_exact.append(attribution)
  631. attributions_exact_padded.append(attribution_padded)
  632. else:
  633. seqlet_starts.append(np.nan)
  634. seqlet_ends.append(np.nan)
  635. p_values.append(np.nan)
  636. attributions.append(np.nan)
  637. attributions_exact.append(np.nan)
  638. attributions_exact_padded.append(np.nan)
  639. else:
  640. for i in range(len(rows)):
  641. chrom= rows['region_chr']
  642. start=int(rows['region_start'].iloc[i])
  643. end=int(rows['region_end'].iloc[i])
  644. tf_start=int(rows['chip_start'].iloc[i]-start)
  645. tf_end = int(rows['chip_end'].iloc[i]-start)
  646. overlap_rows = get_row_with_overlap(seqlets, tf_start, tf_end)
  647. if len(overlap_rows)>0:
  648. r = overlap_rows.iloc[0]
  649. seq_start=start+int(r['start'])
  650. seq_end = start+int(r['end'])
  651. seqlet_starts.append(seq_start)
  652. seqlet_ends.append(seq_end)
  653. p_values.append(r['p-value'])
  654. attributions.append(r['attribution'])
  655. attribution = (np.mean(X_attr[0,int(r['start']):int(r['end'])]))
  656. padding=(30-(int(r['end'])-int(r['start'])))//2
  657. attribution_padded = (np.mean(X_attr[0,int(r['start'])-padding:int(r['end'])+padding]))
  658. attributions_exact.append(attribution)
  659. attributions_exact_padded.append(attribution_padded)
  660. else:
  661. seqlet_starts.append(np.nan)
  662. seqlet_ends.append(np.nan)
  663. p_values.append(np.nan)
  664. attributions.append(np.nan)
  665. attributions_exact.append(np.nan)
  666. attributions_exact_padded.append(np.nan)
  667. df_final = tf_dict[tf]['unibind_hits_df'].head(top_n).copy()
  668. df_final.loc[:,'seqlet_start'] = seqlet_starts
  669. df_final.loc[:,'seqlet_end'] = seqlet_ends
  670. df_final.loc[:,'seqlet_p_val']= p_values
  671. df_final.loc[:,'seqlet_attribution'] = attributions
  672. df_final.loc[:,'chip_attribution'] = attributions_exact
  673. df_final.loc[:,'chip_attribution_padded'] = attributions_exact_padded
  674. tf_dict[tf]['unibind_recall_df']=df_final
  675. tf_dict['PAX5']['unibind_recall_df']
  676. # %%
  677. recalls ={}
  678. for tf in tf_dict:
  679. if tf_dict[tf]['unibind_recall_df'] is not None:
  680. print(tf)
  681. # Determine true positives (non-NaN in seqlet_start)
  682. true_positives = tf_dict[tf]['unibind_recall_df']['seqlet_start'].notna().sum()
  683. # Determine false negatives (NaN in seqlet_start)
  684. false_negatives = tf_dict[tf]['unibind_recall_df']['seqlet_start'].isna().sum()
  685. # Calculate recall
  686. recall = true_positives / (true_positives + false_negatives)
  687. recalls[tf] = {}
  688. recalls[tf]['recall']=recall
  689. recalls[tf]['total']=true_positives+false_negatives
  690. # Output the results
  691. print(f"True Positives (TP): {true_positives}")
  692. print(f"False Negatives (FN): {false_negatives}")
  693. print(f"Recall: {recall:.2f}")
  694. else:
  695. print('No unibind bed file provided for '+tf)
  696. # %% [markdown]
  697. # ### Fig. 3f
  698. # %%
  699. # Sorting by recall values
  700. sorted_factors = sorted(recalls, key=lambda x: recalls[x]['recall'], reverse=True)
  701. sorted_recalls = [recalls[factor]['recall'] for factor in sorted_factors]
  702. sorted_totals = [recalls[factor]['total'] for factor in sorted_factors]
  703. # Generate new x-axis labels with (n = total)
  704. x_labels = [f"{factor}\n(n = {total})" for factor, total in zip(sorted_factors, sorted_totals)]
  705. # Plotting
  706. fig, ax = plt.subplots(figsize=(8, 5))
  707. bars = ax.bar(x_labels, sorted_recalls, color='royalblue', edgecolor='black', linewidth=1.2)
  708. # Aesthetics
  709. ax.set_ylabel('Recall', fontsize=14)
  710. ax.set_xlabel('Transcription Factors', fontsize=14)
  711. ax.set_title('Recall Scores of Identified Seqlets in Unibind Sites', fontsize=16)
  712. ax.set_ylim(0, 1.05)
  713. ax.grid(axis='y', linestyle='--', alpha=0.7)
  714. # Adding value labels
  715. for bar in bars:
  716. yval = bar.get_height()
  717. ax.text(bar.get_x() + bar.get_width()/2, yval + 0.02, f'{yval:.3f}',
  718. ha='center', va='bottom', fontsize=12, color='black')
  719. # Remove top and right borders
  720. ax.spines['top'].set_visible(False)
  721. ax.spines['right'].set_visible(False)
  722. # Show the plot
  723. plt.xticks(rotation=45, fontsize=12, ha='right', va='top')
  724. plt.yticks(fontsize=12)
  725. #plt.savefig('paperfigs/unibind_recall.pdf', bbox_inches='tight')
  726. plt.show()
  727. # %% [markdown]
  728. # ## Check ChIP and ATAC peak heights in identified seqlets
  729. # %%
  730. for tf in tf_dict:
  731. tf_dict[tf]['atac_bw_file']="/home/VIB.LOCAL/niklas.kempynck/nkemp/pbmc/bw/"+tf_dict[tf]['cell_type']+".bw"
  732. # %%
  733. import pandas as pd
  734. import matplotlib.pyplot as plt
  735. from crested.utils import read_bigwig_region # Ensure you have the required module
  736. def plot_average_peak_height(bigwig_file, region_df, padding=0, seqlet_len=30, cols=None,max_carrot=None, title='', save_path=None):
  737. """
  738. Plot a histogram of average peak heights and a heatmap carrot plot for all peak values.
  739. Parameters:
  740. - bigwig_file (str): Path to the BigWig file.
  741. - region_df (pd.DataFrame): DataFrame with columns ['region', 'chr', 'start', 'end'].
  742. - padding (int): Number of bases to add as flanking around the regions.
  743. - seqlet_len (int): Expected sequence length for normalization.
  744. - cols (list): Column names for chrom, start, end. Defaults to ['chr', 'start', 'end'].
  745. """
  746. # Setup columns
  747. if cols:
  748. chrom_col = cols[0]
  749. start_col = cols[1]
  750. end_col = cols[2]
  751. else:
  752. chrom_col = 'chr'
  753. start_col = 'start'
  754. end_col = 'end'
  755. # Initialize peak values
  756. window_length = seqlet_len + 2 * padding
  757. peaks = np.zeros((len(region_df), window_length))
  758. avg_peak_heights = []
  759. for i, row in region_df.iterrows():
  760. chrom = row[chrom_col]
  761. start = int(row[start_col])
  762. end = int(row[end_col])
  763. seq_len = end - start
  764. # Normalize to seqlet_len
  765. if seq_len > seqlet_len:
  766. diff = (seq_len - seqlet_len) // 2
  767. start = start + diff
  768. end = end - diff
  769. elif seq_len < seqlet_len:
  770. diff = (seqlet_len - seq_len) // 2
  771. start = start - diff
  772. end = end + diff
  773. seq_len = end-start
  774. if seq_len<seqlet_len:
  775. end=end+1
  776. # Read the BigWig region with flanking
  777. values, _ = read_bigwig_region(bigwig_file, (chrom, start - padding, end + padding))
  778. # Handle peak values
  779. #if len(values) >= window_length:
  780. peaks[i] = values[:window_length]
  781. avg_peak_heights.append(np.mean(values))
  782. #else:
  783. # peaks[i] = np.pad(values, (0, window_length - len(values)), 'constant', constant_values=0)
  784. # avg_peak_heights.append(np.mean(values) if len(values) > 0 else 0)
  785. avg_peaks = np.mean(peaks,axis=0)
  786. num_figs= 3 if max_carrot else 2
  787. # Plot histogram of average peak heights
  788. fig, axes = plt.subplots(1, num_figs , figsize=(num_figs*4, 6))
  789. x_start = -window_length/2
  790. x_end = window_length/2
  791. # Histogram
  792. # Plot
  793. axes[0].plot(np.arange(x_start,x_end),avg_peaks)
  794. axes[0].set_title("Average Peak Height in Regions "+title)
  795. axes[0].set_xlabel("Position")
  796. axes[0].set_ylabel("Average Peak Height")
  797. axes[0].grid(axis='y', linestyle='--', alpha=0.7)
  798. peak_max_values = np.max(peaks, axis=1) # Find max value for each row
  799. sorted_indices = np.argsort(peak_max_values)[::-1] # Sort indices in descending order
  800. sorted_peaks = peaks[sorted_indices] # Reorder rows based on sorted indices
  801. # Heatmap carrot plot
  802. im = axes[1].imshow(
  803. sorted_peaks,
  804. aspect='auto',
  805. cmap='coolwarm',
  806. extent=[-window_length // 2, window_length // 2, 0, len(region_df)]
  807. )
  808. axes[1].set_title("Carrot Plot of Peak Values "+(title))
  809. axes[1].set_xlabel("Position (relative to center)")
  810. axes[1].set_ylabel("Region Index")
  811. fig.colorbar(im, ax=axes[1], orientation='vertical', label='Peak Value')
  812. if max_carrot:
  813. peaks_carrot = np.clip(peaks, 0, max_carrot)
  814. peak_max_values = np.max(peaks_carrot, axis=1) # Find max value for each row
  815. sorted_indices = np.argsort(peak_max_values)[::-1] # Sort indices in descending order
  816. sorted_peaks = peaks_carrot[sorted_indices] # Reorder rows based on sorted indices
  817. # Heatmap carrot plot
  818. im = axes[2].imshow(
  819. sorted_peaks,
  820. aspect='auto',
  821. cmap='coolwarm',
  822. extent=[-window_length // 2, window_length // 2, 0, len(region_df)]
  823. )
  824. axes[2].set_title("Carrot Plot of Peak Values - Clipped "+(title))
  825. axes[2].set_xlabel("Position (relative to center)")
  826. axes[2].set_ylabel("Region Index")
  827. fig.colorbar(im, ax=axes[2], orientation='vertical', label='Peak Value')
  828. plt.tight_layout()
  829. if save_path:
  830. plt.savefig(save_path, bbox_inches='tight')
  831. plt.show()
  832. return peaks
  833. # %%
  834. tf = 'CEBPA'
  835. bw_file =tf_dict[tf]['chip_bw']
  836. seqlet_df = tf_dict[tf]['seqlet_df']
  837. results_df = tf_dict[tf]['results_df']
  838. possible_hits_df = tf_dict[tf]['chip_hits_df']
  839. possible_hits_df_unibind = tf_dict[tf]['unibind_hits_df']
  840. peaks=plot_average_peak_height(bw_file, seqlet_df, padding=50, max_carrot=5, title='(All seqlets)')#, save_path='paperfigs/'+tf+'_all_seqlets.pdf')
  841. results_df_tp = results_df.loc[results_df['status']=='True Positive'].reset_index()
  842. _=plot_average_peak_height(bw_file, results_df_tp, padding=50, cols=['seqlet_chr','seqlet_start','seqlet_end'],max_carrot=5,title='(True positives)')
  843. results_df_fp = results_df.loc[results_df['status']=='False Positive'].reset_index()
  844. _=plot_average_peak_height(bw_file, results_df_fp, padding=50, cols=['seqlet_chr','seqlet_start','seqlet_end'],max_carrot=0.12, title='(False positives)')#, save_path='paperfigs/'+tf+'_FP.pdf')
  845. results_df_fn = results_df.loc[results_df['status']=='False Negative'].reset_index()
  846. _=plot_average_peak_height(bw_file, results_df_fn, padding=50, cols=['chip_chr','chip_start','chip_end'],max_carrot=5,title='(False negatives)')
  847. _=plot_average_peak_height(bw_file, possible_hits_df, padding=50, cols=['chip_chr','chip_start','chip_end'],max_carrot=5,title='(Chip sites)')
  848. _=plot_average_peak_height(bw_file, possible_hits_df_unibind, padding=50, cols=['chip_chr','chip_start','chip_end'],max_carrot=5,title='(Unibind sites)')
  849. # %%
  850. results_df
  851. # %%
  852. import numpy as np
  853. import matplotlib.pyplot as plt
  854. def plot_combined_average_peak_heights(bigwig_file, subset_dict, padding=0, seqlet_len=30, cols_dict=None, title='', save_path=None):
  855. """
  856. Plot a combined average peak height plot for multiple subsets.
  857. Parameters:
  858. - bigwig_file (str): Path to the BigWig file.
  859. - subset_dict (dict): Dictionary where keys are subset names and values are DataFrames with ['chr', 'start', 'end'].
  860. - padding (int): Number of bases to add as flanking around the regions.
  861. - seqlet_len (int): Expected sequence length for normalization.
  862. - cols (list): Column names for chrom, start, end. Defaults to ['chr', 'start', 'end'].
  863. - title (str): Title of the plot.
  864. """
  865. window_length = seqlet_len + 2 * padding
  866. x_positions = np.arange(-window_length / 2, window_length / 2)
  867. plt.figure(figsize=(2, 4))
  868. max_peak = 0
  869. for subset_name, region_df in subset_dict.items():
  870. if region_df is None:
  871. continue
  872. if cols_dict:
  873. cols = cols_dict[subset_name]
  874. num_instances = len(region_df) # Get the number of instances
  875. label = f"{subset_name} (n={num_instances})" # Modify the label
  876. # Extract peak values
  877. peaks = np.zeros((len(region_df), window_length))
  878. for i, row in region_df.iterrows():
  879. chrom = row[cols[0]] if cols else row['chr']
  880. start = int(row[cols[1]]) if cols else int(row['start'])
  881. end = int(row[cols[2]]) if cols else int(row['end'])
  882. seq_len = end - start
  883. diff=seq_len
  884. # Normalize to seqlet_len
  885. if seq_len > seqlet_len:
  886. diff = (seq_len - seqlet_len) // 2
  887. start += diff
  888. end -= diff
  889. elif seq_len < seqlet_len:
  890. diff = (seqlet_len - seq_len) // 2
  891. start -= diff
  892. end += diff
  893. if diff<seq_len:
  894. end=end+1
  895. # Read BigWig region
  896. values, _ = read_bigwig_region(bigwig_file, (chrom, start - padding, end + padding))
  897. peaks[i] = values[:window_length]
  898. avg_peaks = np.mean(peaks, axis=0)
  899. if np.max(avg_peaks)>max_peak:
  900. max_peak = np.max(avg_peaks)
  901. # Plot
  902. plt.plot(x_positions, avg_peaks, label=label)
  903. plt.xlabel("Position (relative to center)")
  904. plt.ylabel("Average Peak Height")
  905. plt.title(title)
  906. plt.ylim([0,max_peak+0.05*max_peak])
  907. plt.legend(loc='center left', bbox_to_anchor=(1, 0.7))
  908. if save_path:
  909. plt.savefig(save_path, bbox_inches='tight')
  910. plt.show()
  911. # %% [markdown]
  912. # ### Fig. 3g
  913. # %%
  914. for tf in tf_list:
  915. print(tf)
  916. bw_file =tf_dict[tf]['chip_bw']
  917. seqlet_df = tf_dict[tf]['seqlet_df']
  918. results_df = tf_dict[tf]['results_df']
  919. possible_hits_df = tf_dict[tf]['chip_hits_df']
  920. possible_hits_df_unibind = tf_dict[tf]['unibind_hits_df']
  921. subset_dict = {
  922. "All Seqlets": seqlet_df,
  923. "True Positives": results_df.loc[results_df['status'] == 'True Positive'].reset_index(),
  924. "False Positives": results_df.loc[results_df['status'] == 'False Positive'].reset_index(),
  925. "False Negatives": results_df.loc[results_df['status'] == 'False Negative'].reset_index(),
  926. "Chip Sites": possible_hits_df,
  927. "Unibind": possible_hits_df_unibind
  928. }
  929. cols_dict = {
  930. "All Seqlets": ['chr', 'start', 'end'],
  931. "True Positives": ['seqlet_chr','seqlet_start','seqlet_end'],
  932. "False Positives": ['seqlet_chr','seqlet_start','seqlet_end'],
  933. "False Negatives": ['chip_chr','chip_start','chip_end'],
  934. "Chip Sites": ['chip_chr','chip_start','chip_end'],
  935. "Unibind": ['chip_chr','chip_start','chip_end']
  936. }
  937. plot_combined_average_peak_heights(bw_file, subset_dict, padding=200, cols_dict=cols_dict)# title="Average Peak for "+tf, save_path='paperfigs/'+tf+'_peaks.pdf')
  938. # %% [markdown]
  939. # # Recall of seqlets on UniBind hits inside all consensus peaks
  940. # %%
  941. peak_bed=f"{DATA_DIR}consensus_regions.bed"
  942. peak_df = pd.read_csv(peak_bed, sep="\t", header=None, names=column_names)
  943. peak_df
  944. # %% [markdown]
  945. # ## Calculate overlapping ChIP peaks and consensus regions
  946. # %%
  947. from tqdm import tqdm
  948. ct_list= ['Bcell',
  949. 'Bcell',
  950. 'CD4_Tcell',
  951. 'CD4_Tcell',
  952. 'CD4_Tcell',
  953. 'CD14_monocyte',
  954. 'CD14_monocyte'
  955. ]
  956. chip_bed_files = [
  957. f"{DATA_DIR}/chip/pax5/pax5.bed",
  958. f"{DATA_DIR}/chip/ebf1/ebf1_unibind.bed",
  959. f"{DATA_DIR}/chip/runx1/runx1.bed",
  960. f"{DATA_DIR}/chip/gata3_2/gata3.bed",
  961. f"{DATA_DIR}/chip/ets1/ets1_unibind.bed",
  962. f"{DATA_DIR}/chip/cebpa/cebpa.bed",
  963. f"{DATA_DIR}/chip/spi1_2/spi1.bed",
  964. ]
  965. unibind_dfs = []
  966. for chip_bed_file in tqdm(chip_bed_files):
  967. print(chip_bed_file)
  968. column_names = ["chrom", "start", "end", "name", "score", "strand"]
  969. # Read the unibind bed files
  970. chip_df = pd.read_csv(chip_bed_file, sep="\t", header=None, names=column_names)
  971. # Merge the two DataFrames on overlapping intervals
  972. merged_df = pd.merge(
  973. chip_df,
  974. peak_df,
  975. on="chrom",
  976. suffixes=("_chip", "_peak")
  977. )
  978. # Filter for overlaps
  979. subset_chip_df = merged_df[
  980. (merged_df["start_chip"] >= merged_df["start_peak"]) &
  981. (merged_df["end_chip"] <= merged_df["end_peak"])
  982. ]
  983. sorted_chip_df = subset_chip_df.sort_values(by="score_chip", ascending=False)
  984. unibind_dfs.append(sorted_chip_df)
  985. # %% [markdown]
  986. # ## Calculate contribution scores for all overlapping regions
  987. # WARNING: TAKES A LONG TIME
  988. #
  989. # just load the results
  990. # %%
  991. #out_dirs = [
  992. # f'{DATA_DIR}chip/pax5/',
  993. # f"{DATA_DIR}chip/ebf1/",
  994. # f"{DATA_DIR}chip/runx1/",
  995. # f"{DATA_DIR}chip/gata3_2/",
  996. # f"{DATA_DIR}chip/ets1/",
  997. # f"{DATA_DIR}chip/cebpa/",
  998. # f"{DATA_DIR}chip/spi1_2/",
  999. #]
  1000. #for a, sorted_chip_df in enumerate(unibind_dfs):
  1001. # top_n=len(sorted_chip_df)
  1002. #
  1003. # sequences=[]
  1004. # for i in range(top_n):
  1005. # row= sorted_chip_df.iloc[i]
  1006. #
  1007. # chrom= row['chrom']
  1008. # start=int(row['start_peak']-807)
  1009. # end=int(row['end_peak']+807)
  1010. # sequence = genome.fetch(chrom, start, end).upper()
  1011. # sequences.append(sequence)
  1012. # scores, one_hot_encoded_sequences = evaluators[2].calculate_contribution_scores_sequence(sequences, ct_list[a], method='expected_integrated_grad', disable_tqdm=False)
  1013. # scores = np.squeeze(scores, axis=1)
  1014. # scores = np.transpose(scores,(0,2,1))
  1015. # print(scores.shape)
  1016. # one_hot_encoded_sequences = np.transpose(one_hot_encoded_sequences,(0,2,1))
  1017. # np.savez(out_dirs[a]+"/"+ct_list[a]+"_contrib.npz",scores)
  1018. # np.savez(out_dirs[a]+"/"+ct_list[a]+"_oh.npz", one_hot_encoded_sequences)
  1019. # %%
  1020. out_dirs = [
  1021. f'{DATA_DIR}/chip/pax5/',
  1022. f"{DATA_DIR}/chip/ebf1/",
  1023. f"{DATA_DIR}/chip/runx1/",
  1024. f"{DATA_DIR}/chip/gata3_2/",
  1025. f"{DATA_DIR}/chip/ets1/",
  1026. f"{DATA_DIR}/chip/cebpa/",
  1027. f"{DATA_DIR}/chip/spi1_2/",
  1028. ]
  1029. ct_list= ['Bcell',
  1030. 'Bcell',
  1031. 'CD4_Tcell',
  1032. 'CD4_Tcell',
  1033. 'CD4_Tcell',
  1034. 'CD14_monocyte',
  1035. 'CD14_monocyte'
  1036. ]
  1037. recalls={}
  1038. tfs=['PAX5','EBF1','RUNX1','GATA3','ETS1','CEBPA','SPI']
  1039. a=0
  1040. for ct, outdir, sorted_chip_df, tf in zip(ct_list, out_dirs, unibind_dfs, tfs):
  1041. scores = np.load(outdir+ct+"_contrib.npz")['arr_0']
  1042. scores = np.transpose(scores, (0,2,1))
  1043. one_hot_encoded_sequences = np.load(outdir+ct+"_oh.npz")['arr_0']
  1044. one_hot_encoded_sequences = np.transpose(one_hot_encoded_sequences, (0,2,1))
  1045. print(scores.shape)
  1046. print(sorted_chip_df.shape)
  1047. from tqdm import tqdm
  1048. seqlet_starts=[]
  1049. seqlet_ends = []
  1050. p_values=[]
  1051. attributions=[]
  1052. attributions_exact=[]
  1053. overlaps=[]
  1054. for i in tqdm(range(len(sorted_chip_df))):
  1055. oh_seq = one_hot_encoded_sequences[i].T
  1056. X_attr = scores[i].T
  1057. X_attr=X_attr*oh_seq
  1058. X_attr = np.expand_dims(np.sum(X_attr, axis=0),axis=0)
  1059. seqlets = recursive_seqlets(X_attr, threshold=0.05)
  1060. row=sorted_chip_df.iloc[i]
  1061. chrom= row['chrom']
  1062. start=int(row['start_peak']-807)
  1063. end=int(row['end_peak']+807)
  1064. tf_start=int(row['start_chip']-start)
  1065. tf_end = int(row['end_chip']-start)
  1066. attribution = (np.mean(X_attr[0,tf_start:tf_end]))
  1067. overlap_rows = get_row_with_overlap(seqlets, tf_start, tf_end)
  1068. if len(overlap_rows)>0:
  1069. r = overlap_rows.iloc[0]
  1070. seqlet_starts.append(start+int(r['start']))
  1071. seqlet_ends.append(start+int(r['end']))
  1072. p_values.append(r['p-value'])
  1073. attributions.append(r['attribution'])
  1074. overlaps.append(r['overlap'])
  1075. attributions_exact.append(attribution)
  1076. else:
  1077. seqlet_starts.append(np.nan)
  1078. seqlet_ends.append(np.nan)
  1079. p_values.append(np.nan)
  1080. attributions.append(np.nan)
  1081. overlaps.append(np.nan)
  1082. attributions_exact.append(attribution)
  1083. df_final = sorted_chip_df.copy()
  1084. df_final.loc[:,'seqlet_start'] = seqlet_starts
  1085. df_final.loc[:,'seqlet_end'] = seqlet_ends
  1086. df_final.loc[:,'seqlet_p_val']= p_values
  1087. df_final.loc[:,'seqlet_attribution'] = attributions
  1088. df_final.loc[:,'chip_attribution'] = attributions_exact
  1089. df_final.loc[:,'UniBind-Seqlet overlap fraction'] = overlaps
  1090. true_positives = df_final['seqlet_start'].notna().sum()
  1091. # Determine false negatives (NaN in seqlet_start)
  1092. false_negatives = df_final['seqlet_start'].isna().sum()
  1093. # Calculate recall
  1094. recall = true_positives / (true_positives + false_negatives)
  1095. # Output the results
  1096. print(f"True Positives (TP): {true_positives}")
  1097. print(f"False Negatives (FN): {false_negatives}")
  1098. print(f"Recall: {recall:.2f}")
  1099. recalls[tf]={}
  1100. recalls[tf]['recall']=recall
  1101. recalls[tf]['total']=true_positives+false_negatives
  1102. unibind_dfs[a]=df_final
  1103. a+=1
  1104. # %%
  1105. # Sorting by recall values
  1106. sorted_factors = sorted(recalls, key=lambda x: recalls[x]['recall'], reverse=True)
  1107. sorted_recalls = [recalls[factor]['recall'] for factor in sorted_factors]
  1108. sorted_totals = [recalls[factor]['total'] for factor in sorted_factors]
  1109. # Generate new x-axis labels with (n = total)
  1110. x_labels = [f"{factor}\n(n = {total})" for factor, total in zip(sorted_factors, sorted_totals)]
  1111. # Plotting
  1112. fig, ax = plt.subplots(figsize=(8, 5))
  1113. bars = ax.bar(x_labels, sorted_recalls, color='royalblue', edgecolor='black', linewidth=1.2)
  1114. # Aesthetics
  1115. ax.set_ylabel('Recall', fontsize=14)
  1116. ax.set_xlabel('Transcription Factors', fontsize=14)
  1117. ax.set_title('Recall Scores of Identified Seqlets in Unibind Sites', fontsize=16)
  1118. ax.set_ylim(0, 1.05)
  1119. ax.grid(axis='y', linestyle='--', alpha=0.7)
  1120. # Adding value labels
  1121. for bar in bars:
  1122. yval = bar.get_height()
  1123. ax.text(bar.get_x() + bar.get_width()/2, yval + 0.02, f'{yval:.3f}',
  1124. ha='center', va='bottom', fontsize=12, color='black')
  1125. # Remove top and right borders
  1126. ax.spines['top'].set_visible(False)
  1127. ax.spines['right'].set_visible(False)
  1128. # Show the plot
  1129. plt.xticks(rotation=45, fontsize=12, ha='right', va='top')
  1130. plt.yticks(fontsize=12)
  1131. #plt.savefig('paperfigs/unibind_recall_ALL.pdf', bbox_inches='tight')
  1132. plt.show()
  1133. # %%

chip_seq_analysis.ipynb at commit 916dee8, under MIT · at the source

Overview

Authors: Niklas Kempynck1,2,3, Seppe De Winter1,2,3, Casper H Blaauw1,2,3,4, Vasileios Konstantakos1,2,3, Eren Can Ekşi1,2,3, Sam Dieltiens1,2,3, Darina Abaffyová1,2,3, Valérie Bercier2,5, Ibrahim I Taskiran1,2,3,6, Gert Hulselmans1,2,3,7, Katina Spanier1,2,3, Valerie Christiaens1,2,3,7, Ludo Van Den Bosch2,5, Lukas Mahieu1,2,3,7, Stein Aerts1,2,3,7
  1. Laboratory of Computational Biology, VIB Center for AI and Computational Biology (VIB.AI), Leuven, Belgium
  2. VIB-KU Leuven Center for Brain and Disease Research, Leuven, Belgium
  3. Department of Human Genetics, KU Leuven, Leuven, Belgium
  4. Oncode Institute, Hubrecht Institute-KNAW (Royal Netherlands Academy of Arts and Sciences) and University Medical Center Utrecht, Utrecht, the Netherlands
  5. Department of Neurosciences, KU Leuven, Leuven, Belgium
  6. Present Address: Illumina Artificial Intelligence Laboratory, Illumina, Foster City, CA USA
  7. Aligning Science Across Parkinson’s (ASAP) Collaborative Research Network, Chevy Chase, MD USA
Journal: Nature methods, volume 23, issue 5, pages 946-959
Dates: received 2 April 2025; accepted 4 March 2026; published online 2 April 2026; in print 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41592-026-03057-2 · PMID 41927920 · PMCID PMC13167471 · OpenAlex W7147749522
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: genetics / omics (modality), human (organism), mouse (organism), zebrafish (organism), methods / tools (subfield)
Methods: Spectral & time-frequency, Statistics, Smoothing, state filtering, decompositions, Evoked potentials, Connectivity, Machine learning
Keywords: Software, Epigenomics, Machine learning, Embryogenesis
MeSH: Enhancer Elements, Genetic*, Genomics*, Software*, Animals, Chromatin, Deep Learning, Humans, Mice, Organ Specificity, Species Specificity, Zebrafish (* major topic)
Topic: Genomics and Chromatin Dynamics (Molecular Biology, Biochemistry, Genetics and Molecular Biology), according to OpenAlex
Funding: European Research Council (101054387); Fonds Wetenschappelijk Onderzoek (Research Foundation Flanders) (1267625N, 1191323N, 1SH6J24N, G0C1620N, G088523N and G026924N, S005024N, G0I2722N EOS ID 40007513, G094121N, G044124N); Stichting Tegen Kanker (Belgian Foundation Against Cancer) (2024-140, 2020-1396); KU Leuven (Katholieke Universiteit Leuven) (C14/22/132, IDN/22/012 and "Opening the Future" Fund); Fondation Thierry Latran (Thierry Latran Foundation); Association Belge contre les Maladies Neuro-Musculaires (Belgian Association against Neuromuscular Disorders); Muscular Dystrophy Association (Muscular Dystrophy Association Inc.)
Citations: cited by 8 papers (Europe PMC); 101 references in the paper
Research resources: FASTQ files for HepG2 RRID:CVCL_0027, A172 cells RRID:CVCL_0131, LN229 cells RRID:CVCL_0393, MO59J cells RRID:CVCL_0400, GM12878 RRID:CVCL_7526, WiggleTools86 RRID:SCR_001170, RRID:SCR_003030, RRID:SCR_005553, duplicates were removed using Picard RRID:SCR_006525, 95 RRID:SCR_006793, cut-site BigWigs RRID:SCR_007708, peaks were called using MACS87 RRID:SCR_013291, NIS-element RRID:SCR_014329, RRID:SCR_014583, allowing both TensorFlow RRID:SCR_016345, M059J and LN229 using deepTools85 RRID:SCR_016366, RRID:SCR_016368, RRID:SCR_018139, 80 RRID:SCR_018209, PyTorch RRID:SCR_018536, RRID:SCR_022798, by transfer learning from Enformer10 RRID:SCR_024805, 39 RRID:SCR_024811, Keras 3.0 RRID:SCR_026159, epiAneufinder90 v.1.1.3 RRID:SCR_026269, CREsted RRID:SCR_026617, 36 RRID:SCR_026618, Borzoi15 RRID:SCR_026619, RRID:SCR_026620, RRID:SCR_026621, RRID:SCR_026622, pycisTarget34 RRID:SCR_026626, RRID:SCR_026627, RRID:SCR_026702, RRID:SCR_027381, RRID:SCR_027429, RRID:SCR_027436, RRID:SCR_027456, PyTorch Lightning RRID:SCR_027468, HyenaDNA74 RRID:SCR_027471, and Nucleotide Transformer75 RRID:SCR_027472, RRID:SCR_027473

Abstract

Sequence-based deep learning models have become the state of the art for analyzing the genomic regulatory code. Particularly for enhancers, these models excel at deciphering sequence grammar that underlies their activity. To enable end-to-end enhancer modeling and design, we developed a software package called CREsted (cis-regulatory element sequence training, explanation and design). It combines preprocessing and analysis of single-cell assay for transposase-accessible chromatin using sequencing data, modeling chromatin accessibility from sequence, sequence design and downstream analysis to decipher enhancer grammar. We demonstrate CREsted’s functionality on a mouse cortex and a human peripheral blood mononuclear cell dataset. Additionally, we use CREsted to compare mesenchymal-like cancer cell states between tumor types, and we investigate different fine-tuning strategies of genomic foundation models within CREsted. Finally, we train a model on a zebrafish development atlas and use this to design and in vivo validate cell-type-specific enhancers. For varying datasets, we demonstrate that CREsted facilitates efficient training and analyses, enabling scrutinization of the enhancer logic and design of synthetic enhancers across tissues and species.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above, with 40 matches between paragraphs and lines of code.

aertslab/CREsted

License: other
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 3940ea94c8a56344c5478119c5e13755e4eb698e, 26 September 2026
Languages: Python (125), Jupyter (7)
Size: 251 files, 132 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, license file, environment (pyproject.toml), tests, continuous integration, documentation, 7 notebooks
Not found: CITATION.cff
Tools: NumPy (53 files), Keras (43 files), anndata (25 files), Matplotlib (24 files), pandas (14 files), SciPy (12 files), PyTorch (9 files), TensorFlow (8 files), seaborn (5 files), h5py (4 files), Numba (1 file), Pillow (1 file), pysam (1 file), Scanpy (1 file), scikit-learn (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
134 files

aertslab/create_cisTarget_databases

License: none: the authors keep all their rights
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 304d5dc1b15e5c923908a50a1ec291c3faaccf9c, 18 June 2025
Languages: Python (27), Shell (1)
Size: 30 files, 28 scripts
Software Heritage: not archived
Found in: the text, “Human PBMC motif enrichment analysis using pycis”
Holds: README, environment (pyproject.toml)
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (8 files), pandas (6 files), BEDTools (1 file), Numba (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
29 files

Zenodo 17791463

License: CC-BY-4.0
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Size: 1 file, 0 scripts
Software Heritage: not checked
Found in: “Code availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 28 September 2026: the link answers (HTTP 200)
  • 28 September 2026: the link answers (HTTP 200)

aertslab/CREsted-paper

License: MIT
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 916dee8dc9bcd9908049ce4aa48a0dbfefe5b15a, 3 December 2025
Languages: Jupyter (20), Python (19), Shell (12)
Size: 63 files, 51 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, license file, 21 notebooks
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (16 files), Matplotlib (15 files), pandas (15 files), anndata (12 files), Keras (12 files), seaborn (10 files), SciPy (9 files), scikit-learn (5 files), pysam (3 files), h5py (2 files), PyTorch (2 files), Biopython (1 file), Pillow (1 file), statsmodels (1 file), TensorFlow (1 file), UMAP (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
16 files

Zenodo 17791384

License: CC-BY-4.0
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: the references
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 28 September 2026: the link answers (HTTP 200)
  • 28 September 2026: the link answers (HTTP 200)
At the source:

Zenodo 17202107

License: MIT
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: the references
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (22 files), pandas (22 files), anndata (14 files), Matplotlib (9 files), SciPy (6 files), seaborn (5 files), Scanpy (4 files), Keras (2 files), CuPy (1 file), h5py (1 file), Numba (1 file), scikit-learn (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers (HTTP 200)
  • 28 September 2026: the link answers (HTTP 200)
38 files
At the source:

aertslab/tf-mindi

License: other
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: ca4467f7e020a08fcbbeedffa6f12dc870d78c40, 22 July 2026
Languages: Python (36), Jupyter (3)
Size: 95 files, 39 scripts
Software Heritage: not archived
Found in: the Zenodo archive record
Holds: README, license file, environment (pyproject.toml), tests, continuous integration, documentation, 3 notebooks
Not found: CITATION.cff
Tools: NumPy (25 files), pandas (25 files), anndata (16 files), Matplotlib (9 files), SciPy (6 files), seaborn (5 files), Scanpy (4 files), h5py (2 files), Keras (2 files), CuPy (1 file), Numba (1 file), PyWavelets (1 file), scikit-learn (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
41 files

Code availability

The CREsted package is available at https://github.com/aertslab/CREsted and https://crested.readthedocs.io and is stored100 at https://zenodo.org/records/15045960. All computational analyses for the main figures can be found in https://github.com/aertslab/CREsted-paper and are stored101 at https://zenodo.org/records/17791384. The following packages were used for data analyses; this information is also available in the key resource table99 (Supplementary Table 2) accompanying this paper (https://zenodo.org/records/17791463): CREsted v.1.4.0, pybigtools v.0.2.0, ChromBPNet v.1.0, anndata v.0.11.3, Enformer, Borzoi, Keras v.3.0, TensorFlow v.2.19.0, PyTorch v.2.6.0, tfmodisco-lite v.2.2.1, tangermeme v.0.4.0, gReLU v.1.0.3, SnapATAC2 v.2.6.4, Harmony v.0.0.10, statsmodels v.0.14.4, Scipy v.1.16, Python Programming Language v.3, NIS-Elements, SCENIC+ v.1.0a, scatac_fragment_tools v.0.1.4, TF-MInDi v.1.0.0, pycisTarget v.1.1, memesuite-lite v.0.2, scanpy v.1.11.4, pyChromVAR v.0.0.4, epiAneufinder v.1.1.3, HyenaDNA, Nucleotide Transformer, Transformers v.4.54.1, PyTorch Lightning v.2.5.2, create_cisTarget_databases, WiggleTools v.1.2.11, MACS v.2 and v.3, pycisTopic v.2 and NGSCheckMate v.1.0.1.

Reproduced under the paper's license (CC BY), from the paper cited above.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 7 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 251 scripts, each with its path and the digest of its content;
  • 40 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Data availability

Analysis data required for reproducing the findings in this paper is available at https://resources.aertslab.org/CREsted/manuscript_data. All CREsted models developed in this paper, and other legacy models, can be loaded through ‘crested.get_model’ or directly from https://resources.aertslab.org/CREsted. All raw and processed sequencing data generated in this study have been deposited in the GEO and are accessible through accession number GSE292617 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE292617). This includes the OmniATAC-seq data of three human GBM cell lines, namely A172, M059J and LN229. Raw imaging data of the zebrafish enhancer reporter assays are available on EBI Biostudies via 10.6019/S-BIAD1962. The key resources table99 (Supplementary Table 2) listing all resources needed to reproduce the results of this paper is available on Zenodo at 10.5281/zenodo.17791463 (ref. 99). The following publicly available datasets were used: Mouse cortex dataset (Zemke et al.42) downloaded from the GEO (GSE229169 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE229169)); Human PBMC dataset (De Rop et al.84) downloaded from the GEO (GSE194028 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE194028)); Zebrafish developmental dataset (Sun et al.76) downloaded from the GEO (GSE243256 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE243256)); Dataset of 100 enhancers tested in zebrafish (supplementary table of Sun et al.76; 10.1038/s41556-024-01449-0); Mouse brain pseudobulk BigWig (Zu et al.19) downloaded from the GEO (GSE246791 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE246791)); PAX5 ChIP–seq peaks downloaded from ENCODE (ENCFF827VVQ (http://www.encodeproject.org/files/ENCFF827VVQ/)); PAX5 ChIP–seq B cells—BigWig track downloaded from ENCODE (ENCFF914QGY (http://www.encodeproject.org/files/ENCFF914QGY/)); EBF1 ChIP–seq B cells—peaks downloaded from ENCODE (ENCFF895MHN (http://www.encodeproject.org/files/ENCFF895MHN/)); EBF1 ChIP–seq B cells—BigWig track downloaded from ENCODE (EBF1 ChIP–seq B cells—BigWig track); POU2F2 ChIP–seq B cells—peaks downloaded from ENCODE (ENCFF934JFA (http://www.encodeproject.org/files/ENCFF934JFA/)); POU2F2 ChIP–seq B cells—BigWig downloaded from ENCODE (ENCFF803HIP (http://www.encodeproject.org/files/ENCFF803HIP/)); GATA3 ChIP–seq CD4+ T cells—peaks and BigWig downloaded from ChIP-Atlas (SRX4705120 (http://www.ncbi.nlm.nih.gov/sra/?term=SRX4705120)); RUNX1 ChIP–seq CD4+ T cells—peaks and BigWig downloaded from ChIP-Atlas (SRX1492212 (http://www.ncbi.nlm.nih.gov/sra/?term=SRX1492212)); ETS1 ChIP–seq CD4+ T cells—peaks and BigWig downloaded from ChIP-Atlas (SRX015825 (https://www.ncbi.nlm.nih.gov/sra/?term=SRX015825)); CEBPA ChIP–seq CD14+ monocytes—peaks and BigWig downloaded from ChIP-Atlas (SRX097095 (https://www.ncbi.nlm.nih.gov/sra/?term=SRX097095)); SPI1 ChIP–seq CD14+ monocytes—peaks and BigWig downloaded from ChIP-Atlas (SRX4001818 (http://www.ncbi.nlm.nih.gov/sra/?term=SRX4001818)); PAX5 ChIP–seq direct targets B cells (https://unibind.uio.no/factor/ENCSR000BHD.GM12878_female_B-cells_lymphoblastoid_cell_line.PAX5/); EBF1 ChIP–seq direct targets B cells (https://unibind.uio.no/factor/ENCSR000DZQ.GM12878_female_B-cells_lymphoblastoid_cell_line.EBF1/); GATA3 ChIP–seq direct targets CD4+ T cells (https://unibind.uio.no/factor/GSE76181.Jurkat_T-cells.GATA3/); RUNX1 ChIP–seq direct targets CD4+ T cells (https://unibind.uio.no/factor/GSE76181.Jurkat_T-cells.RUNX1/); ETS1 ChIP–seq direct targets CD4+ T cells (https://unibind.uio.no/factor/EXP000299.Jurkat_E6_1_T-cells.ETS1); CEBPA ChIP–seq direct targets CD14+ monocytes (https://unibind.uio.no/factor/EXP000946.U937_adult_acute_monocytic_leukemia.CEBPA); SPI1 ChIP–seq direct targets CD14+ monocytes (https://unibind.uio.no/factor/EXP047756.MDMmonocyte_derived_macrophages.SPI1/); Human genome (hg38; https://hgdownload.cse.ucsc.edu/goldenPath/hg38/bigZips); Mouse genome (mm10; https://hgdownload.cse.ucsc.edu/goldenPath/mm10/bigZips/); Zebrafish genome (danRer11; https://hgdownload.cse.ucsc.edu/goldenPath/danRer11/bigZips).

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 2, 28 September 2026

  • Publisher: n/a → Nature Portfolio

Version 1, 28 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 15 authors, 4 keywords, 11 MeSH terms, 7 funders, 93 references, 42 RRIDs.

Cite

This paper

Kempynck, N., De Winter, S., Blaauw, C. H., Konstantakos, V., Ekşi, E. C., Dieltiens, S., Abaffyová, D., Bercier, V., Taskiran, I. I., Hulselmans, G., Spanier, K., Christiaens, V., Van Den Bosch, L., Mahieu, L., & Aerts, S. (2026). CREsted: modeling genomic and synthetic cell-type-specific enhancers across tissues and species. Nature methods, 23(5), 946-959. https://doi.org/10.1038/s41592-026-03057-2

BibTeX

@article{kempynck2026crested,
author = {Kempynck, Niklas and De Winter, Seppe and Blaauw, Casper H and Konstantakos, Vasileios and Ekşi, Eren Can and Dieltiens, Sam and Abaffyová, Darina and Bercier, Valérie and Taskiran, Ibrahim I and Hulselmans, Gert and Spanier, Katina and Christiaens, Valerie and Van Den Bosch, Ludo and Mahieu, Lukas and Aerts, Stein},
title = {{CREsted: modeling genomic and synthetic cell-type-specific enhancers across tissues and species}},
journal = {Nature methods},
year = {2026},
month = apr,
volume = {23},
number = {5},
pages = {946--959},
publisher = {Nature Portfolio},
issn = {1548-7091},
doi = {10.1038/s41592-026-03057-2},
url = {https://doi.org/10.1038/s41592-026-03057-2},
pmid = {41927920},
pmcid = {PMC13167471}
}

RIS

TY - JOUR
AU - Kempynck, Niklas
AU - De Winter, Seppe
AU - Blaauw, Casper H
AU - Konstantakos, Vasileios
AU - Ekşi, Eren Can
AU - Dieltiens, Sam
AU - Abaffyová, Darina
AU - Bercier, Valérie
AU - Taskiran, Ibrahim I
AU - Hulselmans, Gert
AU - Spanier, Katina
AU - Christiaens, Valerie
AU - Van Den Bosch, Ludo
AU - Mahieu, Lukas
AU - Aerts, Stein
TI - CREsted: modeling genomic and synthetic cell-type-specific enhancers across tissues and species
T2 - Nature methods
J2 - Nat Methods
PY - 2026
DA - 2026/04/02
VL - 23
IS - 5
SP - 946
EP - 959
SN - 1548-7091
PB - Nature Portfolio
DO - 10.1038/s41592-026-03057-2
UR - https://doi.org/10.1038/s41592-026-03057-2
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41592-026-03057-2",
"type": "article-journal",
"title": "CREsted: modeling genomic and synthetic cell-type-specific enhancers across tissues and species",
"container-title": "Nature methods",
"author": [
{
"family": "Kempynck",
"given": "Niklas"
},
{
"family": "De Winter",
"given": "Seppe"
},
{
"family": "Blaauw",
"given": "Casper H"
},
{
"family": "Konstantakos",
"given": "Vasileios"
},
{
"family": "Ekşi",
"given": "Eren Can"
},
{
"family": "Dieltiens",
"given": "Sam"
},
{
"family": "Abaffyová",
"given": "Darina"
},
{
"family": "Bercier",
"given": "Valérie"
},
{
"family": "Taskiran",
"given": "Ibrahim I"
},
{
"family": "Hulselmans",
"given": "Gert"
},
{
"family": "Spanier",
"given": "Katina"
},
{
"family": "Christiaens",
"given": "Valerie"
},
{
"family": "Van Den Bosch",
"given": "Ludo"
},
{
"family": "Mahieu",
"given": "Lukas"
},
{
"family": "Aerts",
"given": "Stein"
}
],
"container-title-short": "Nat Methods",
"volume": "23",
"issue": "5",
"page": "946-959",
"DOI": "10.1038/s41592-026-03057-2",
"PMID": "41927920",
"PMCID": "PMC13167471",
"ISSN": "1548-7091",
"publisher": "Nature Portfolio",
"URL": "https://doi.org/10.1038/s41592-026-03057-2",
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
2
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s42003-026-10462-y [code]
SpaDC enables sequence-based integrative analysis and regulatory inference of spatial chromatin accessibility data.
Journal: Communications biology
In common: pysam, Biopython, BEDTools, 8 other tools, genetics / omics, mouse, 12 references
[2] doi:10.1038/s41467-026-75700-7 [code]
Gene regulatory innovations from transposable elements in primate cerebellum development.
Journal: Nature communications
In common: pysam, Biopython, BEDTools, 9 other tools, genetics / omics, mouse, 8 references
[3] doi:10.1186/s13059-026-04177-w [code]
Genomic sequence evolution underlying human neocortical interareal diversification.
Journal: Genome biology
In common: pysam, BEDTools, UMAP, 10 other tools, genetics / omics, mouse, 6 references
[4] doi:10.1016/j.xcrm.2026.102766 [code]
A longitudinal single-cell and spatial multiomic atlas of pediatric high-grade glioma.
Journal: Cell reports. Medicine
In common: pysam, Keras, UMAP, 12 other tools, genetics / omics, 3 references
[5] doi:10.1093/bioinformatics/btag652 [code]
mmVelo: a deep generative model for estimating cell state-dependent dynamics across multiple modalities.
Journal: Bioinformatics (Oxford, England)
In common: pysam, BEDTools, UMAP, 11 other tools, genetics / omics, mouse, 3 references
[6] doi:10.1016/j.celrep.2026.117110 [code]
Single-nucleus multiome analysis in the human prefrontal cortex identifies gene expression and cis-regulatory elements associated with aging.
Journal: Cell reports
In common: pysam, BEDTools, anndata, 8 other tools, genetics / omics, 6 references
[7] doi:10.64898/2026.03.30.714220 [code]
An integrated single cell and spatial omics atlas of human prenatal development
Journal: bioRxiv (preprint)
In common: CuPy, Biopython, UMAP, 12 other tools, 2 references
[8] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: CuPy, Biopython, Keras, 12 other tools, zebrafish, mouse
[9] doi:10.1016/j.celrep.2026.117073 [code]
Single-cell epigenomics uncovers heterochromatin instability and transcription factor dysfunction during mouse brain aging.
Journal: Cell reports
In common: pysam, BEDTools, anndata, 6 other tools, genetics / omics, mouse, 7 references
[10] doi:10.1038/s44320-026-00208-7 [code]
Interpretable deep generative ensemble learning for single-cell omics with Hydra.
Journal: Molecular systems biology
In common: pysam, Keras, UMAP, 12 other tools, 1 reference

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.