================================================================================ README — TaRGET II / Liver Adult / BPA ATAC-seq Differential Accessibility ================================================================================ Directory : ~/TaRGET_II/Li_adt/BPA/ Date : 2026-03-16 (updated) Author : Yan (processed with Claude Code) ================================================================================ CONTEXT ================================================================================ ATAC-seq data from the TaRGET II consortium. Differentially Accessible Regions (DARs) were identified comparing BPA-exposed vs. control male adult liver (Li, adt, M) samples from lab BA. Normalization was performed with RUVr (k=3) to remove unwanted variation across a shared multi-lab control cohort. Two BPA dose groups: BPA10mg : 10 mg/kg BPA BPA10ug : 10 ug/kg BPA Sample naming convention: {Lab}_{Assay}_{Treatment}_{MouseID}_{Tissue}_{Sex}_{Age}_{LibraryID} e.g. BA_ATAC_BPA10mg_M19_Li_M_adt_L21 AL_ATAC_Ctrl_M6_Li_M_adt_L5 Note on R file format: All .txt files follow R's write.table row-name convention. The header line has one fewer field than data lines (no row-name field in header). header[i] corresponds to data[i+1]. ================================================================================ SAMPLE COUNTS (verified from file headers) ================================================================================ BPA10mg file (48 total samples): Treated (n=4) : BA_ATAC_BPA10mg_M19_Li_M_adt_L21 BA_ATAC_BPA10mg_M10_Li_M_adt_L8 BA_ATAC_BPA10mg_M11_Li_M_adt_L9 BA_ATAC_BPA10mg_M15_Li_M_adt_L14 Control (n=40): AL(6), BA(5), BI(6), DO(6), MU(6), WK(6), ZB(5) BPA10ug file (45 total samples): Treated (n=5) : BA_ATAC_BPA10ug_M33_Li_M_adt_L30 BA_ATAC_BPA10ug_M25_Li_M_adt_L38 BA_ATAC_BPA10ug_M4_Li_M_adt_L2 BA_ATAC_BPA10ug_M6_Li_M_adt_L4 BA_ATAC_BPA10ug_M13_Li_M_adt_L11 Control (n=40): same 40 animals and libraries as BPA10mg file ================================================================================ FILES — PROCESSING ORDER ================================================================================ -------------------------------------------------------------------------------- STEP 0 — Original edgeR output (input files, not generated here) -------------------------------------------------------------------------------- BA_DAR_BPA10mg_Li_M_adt_Ctrl_Li_M_adt_RUVr_k3.txt (87,562 OCRs, 49 cols) BA_DAR_BPA10ug_Li_M_adt_Ctrl_Li_M_adt_RUVr_k3.txt (86,946 OCRs, 50 cols) Column order: [row name] chr,start,end logFC log2 fold-change (BPA vs. Control) logCPM average log2 counts per million (edgeR) PValue raw p-value FDR Benjamini-Hochberg adjusted p-value [samples] RUVr-normalised read counts (CPM units) per sample Data-point counts (peaks x samples): BPA10mg treated (n=4) : 87,562 x 4 = 350,248 BPA10ug treated (n=5) : 86,946 x 5 = 434,730 Control / 10mg file : 87,562 x 40 = 3,502,480 Control / 10ug file : 86,946 x 40 = 3,477,840 Other file (pre-existing): MORE_BA_DAR_BPA10mg_Li_M_adt_Ctrl_Li_M_adt_RUVr_k3_filter.txt BED-format (no header), 359 regions, 6 columns: chrom | start | end | chr_start_end | score(1) | strand(+) -------------------------------------------------------------------------------- STEP 1 — Add DAR status column -------------------------------------------------------------------------------- Output: BA_DAR_BPA10mg_Li_M_adt_Ctrl_Li_M_adt_RUVr_k3_DARstatus.txt (49+1=50 cols? no: 52 cols... actually 49 cols header) BA_DAR_BPA10ug_Li_M_adt_Ctrl_Li_M_adt_RUVr_k3_DARstatus.txt Source: ~/TaRGET_II/Li_adt/DAR/OCR_allTissue.txt Filter: Tissue=Li, Age=adt, Sex=M, Lab=BA, Exposure=BPA10mg or BPA10ug Join key: OCR (chr,start,end) New column inserted after FDR, before first sample column: DAR : TRUE (significant DAR) / FALSE (not significant) Column order: [row name], logFC, logCPM, PValue, FDR, DAR, [sample columns ...] Statistics (0 unmatched OCRs): BPA10mg : 10,444 TRUE | 77,118 FALSE | 87,562 total BPA10ug : 16,201 TRUE | 70,745 FALSE | 86,946 total Code: import csv ocr_file = "~/TaRGET_II/Li_adt/DAR/OCR_allTissue.txt" # Build lookup: {exposure: {OCR: DAR}} lookup = {} with open(ocr_file) as f: reader = csv.DictReader(f, delimiter='\t') for row in reader: if (row['Tissue']=='Li' and row['Age']=='adt' and row['Sex']=='M' and row['Lab']=='BA'): exp = row['Exposure'] lookup.setdefault(exp, {})[row['OCR']] = row['DAR'] bpa_files = { 'BPA10mg': ('...RUVr_k3.txt', '...RUVr_k3_DARstatus.txt'), 'BPA10ug': ('...RUVr_k3.txt', '...RUVr_k3_DARstatus.txt'), } for exp, (in_path, out_path) in bpa_files.items(): dar_dict = lookup.get(exp, {}) with open(in_path) as fin, open(out_path, 'w', newline='') as fout: reader = csv.reader(fin, delimiter='\t') writer = csv.writer(fout, delimiter='\t') for i, row in enumerate(reader): if i == 0: # Header: FDR at index 3, insert DAR at index 4 new_row = row[:4] + ['DAR'] + row[4:] else: # Data: OCR at index 0, FDR at index 4, insert at index 5 dar_status = dar_dict.get(row[0], 'NA') new_row = row[:5] + [dar_status] + row[5:] writer.writerow(new_row) -------------------------------------------------------------------------------- STEP 2 — Add gene annotation columns -------------------------------------------------------------------------------- Output: BA_DAR_BPA10mg_Li_M_adt_Ctrl_Li_M_adt_RUVr_k3_DARstatus_annotated.txt BA_DAR_BPA10ug_Li_M_adt_Ctrl_Li_M_adt_RUVr_k3_DARstatus_annotated.txt Source: ~/TaRGET_II/Li_adt/DAR/OCR_allTissue_annotated.txt Filter: Tissue=Li, Age=adt, Sex=M, Lab=BA, Exposure=BPA10mg or BPA10ug Join key: OCR (chr,start,end) Two columns appended at the end of each file: Gene_bodyWindow_1Mb : gene whose body falls within 1 Mb of the OCR Nearest_TSS_1Mb : gene with nearest TSS within 1 Mb (dot "." = no annotation) Window definitions (from ~/TaRGET_II/Li_adt/DAR/README.txt): Gene_bodyWindow_1Mb (Method A — bedtools window): bedtools window -w 1000000 applied between OCR peaks and gene body coordinates. Window extends 1 Mb on BOTH sides of the entire gene body, i.e. [gene_start-1Mb, gene_end+1Mb]. A peak is assigned to a gene if it falls anywhere within this interval. A peak can be assigned to multiple genes if it falls within 1 Mb of more than one gene body. Nearest_TSS_1Mb (Method B — bedtools closest): TSS reference: GENCODE vM10 mm10 protein-coding + lncRNA genes /home/yan/TaRGET_II/Li_adt/Target_expose/ gencode_mm10_gene_TSS_pc_lncRNA.sorted.bed Command: bedtools closest -a OCR_allTissue.sorted.bed \ -b gencode_mm10_gene_TSS_pc_lncRNA.sorted.bed \ -D a -k 1 -sorted -D a : signed distance from peak perspective (negative = upstream of TSS, positive = downstream) -k 1 : only the single nearest TSS per peak Filter: keep peaks where |distance| <= 1,000,000 bp AND nearest TSS gene == target gene (Esr1 / Dapk2 / Nod1). Key logic: a peak is assigned ONLY if the target gene has the closest TSS genome-wide among ALL genes AND that TSS is within ±1 Mb. If any other gene's TSS is closer, the peak receives no annotation ("."). This is why Nearest_TSS_1Mb is more stringent (fewer peaks) but gives 100% concordance with HOMER's nearest-gene annotation (same logic). Column order: [row name], logFC, logCPM, PValue, FDR, DAR, [samples], Gene_bodyWindow_1Mb, Nearest_TSS_1Mb Total columns: BPA10mg : 51 header cols (49 original + 2 annotation) BPA10ug : 52 header cols (50 original + 2 annotation) Annotation coverage (sparse — curated gene set only): BPA10mg : 433 annotated | 87,129 dot | 87,562 total BPA10ug : 429 annotated | 86,517 dot | 86,946 total Genes present in annotation (examples): Nod1, Esr1, Dapk2 Code: import csv ann_file = "~/TaRGET_II/Li_adt/DAR/OCR_allTissue_annotated.txt" # Build lookup: {exposure: {OCR: (Gene_bodyWindow_1Mb, Nearest_TSS_1Mb)}} lookup = {} with open(ann_file) as f: reader = csv.DictReader(f, delimiter='\t') for row in reader: if (row['Tissue']=='Li' and row['Age']=='adt' and row['Sex']=='M' and row['Lab']=='BA'): exp = row['Exposure'] ocr = row['OCR'] lookup.setdefault(exp, {})[ocr] = ( row['Gene_bodyWindow_1Mb'], row['Nearest_TSS_1Mb'] ) jobs = { 'BPA10mg': ('...DARstatus.txt', '...DARstatus_annotated.txt'), 'BPA10ug': ('...DARstatus.txt', '...DARstatus_annotated.txt'), } for exp, (in_path, out_path) in jobs.items(): dar_dict = lookup.get(exp, {}) with open(in_path) as fin, open(out_path, 'w', newline='') as fout: reader = csv.reader(fin, delimiter='\t') writer = csv.writer(fout, delimiter='\t') for i, row in enumerate(reader): if i == 0: new_row = row + ['Gene_bodyWindow_1Mb', 'Nearest_TSS_1Mb'] else: gene_body, nearest_tss = dar_dict.get(row[0], ('.', '.')) new_row = row + [gene_body, nearest_tss] writer.writerow(new_row) -------------------------------------------------------------------------------- STEP 3 — Add condition column -------------------------------------------------------------------------------- Output: BA_DAR_BPA10mg_Li_M_adt_Ctrl_Li_M_adt_RUVr_k3_DARstatus_annotated_cond.txt BA_DAR_BPA10ug_Li_M_adt_Ctrl_Li_M_adt_RUVr_k3_DARstatus_annotated_cond.txt Source: ~/TaRGET_II/Li_adt/BPA/BPA_adt_OCR_table.csv Filter: Age=adt, Sex=M, Lab=BA, Exposure=BPA10mg or BPA10ug Join key: OCR (chr,start,end) New column inserted after DAR, before first sample column: condition : LESS (less accessible in BPA vs control) MORE (more accessible in BPA vs control) Column order: [row name], logFC, logCPM, PValue, FDR, DAR, condition, [samples], Gene_bodyWindow_1Mb, Nearest_TSS_1Mb Total columns: BPA10mg : 52 header cols (51 + 1 condition) BPA10ug : 53 header cols (52 + 1 condition) Statistics (0 unmatched OCRs): BPA10mg : 51,735 LESS | 35,827 MORE | 87,562 total BPA10ug : 38,231 LESS | 48,715 MORE | 86,946 total Code: import csv csv_path = "~/TaRGET_II/Li_adt/BPA/BPA_adt_OCR_table.csv" # Build lookup: {exposure: {OCR: condition}} lookup = {} with open(csv_path) as f: for row in csv.DictReader(f): if (row['Age']=='adt' and row['Sex']=='M' and row['Lab']=='BA'): lookup.setdefault(row['Exposure'], {})[row['OCR']] = row['condition'] jobs = { 'BPA10mg': ('...DARstatus_annotated.txt', '...DARstatus_annotated_cond.txt'), 'BPA10ug': ('...DARstatus_annotated.txt', '...DARstatus_annotated_cond.txt'), } for exp, (in_path, out_path) in jobs.items(): ocr_to_cond = lookup.get(exp, {}) with open(in_path) as fin, open(out_path, 'w', newline='') as fout: reader = csv.reader(fin, delimiter='\t') writer = csv.writer(fout, delimiter='\t') for i, row in enumerate(reader): if i == 0: # Header: DAR at index 4, insert condition at index 5 new_row = row[:5] + ['condition'] + row[5:] else: # Data: DAR value at index 5, insert condition at index 6 cond = ocr_to_cond.get(row[0], 'NA') new_row = row[:6] + [cond] + row[6:] writer.writerow(new_row) ================================================================================ STEP 4 — Split merged file by dose ================================================================================ Input: BA_DAR_BPA10mg_10ug_merged_Nfi.txt (87,638 OCRs, 107 cols) Output: BA_DAR_BPA10mg_10ug_merged_Nfi_10mg.txt (87,562 OCRs, 54 cols) BA_DAR_BPA10mg_10ug_merged_Nfi_10ug.txt (86,946 OCRs, 56 cols) Logic: The merged file is the union of 10mg and 10ug OCR sets. OCRs present in only one dose have empty strings in the other dose's stat columns. Each output file keeps only OCRs where that dose's logFC is non-empty. Column selection per output file: 10mg : logFC/logCPM/PValue/FDR/DAR/condition (renamed from _10mg suffix) BPA10mg treated samples (n=4) Ctrl samples from 10mg model (n=40, _10mg suffix stripped) Gene_bodyWindow_1Mb, Nearest_TSS_1Mb Nfi_motif_LESS_BPA10mg_BA_M_adt, Nfi_motif_any 10ug : same structure for 10ug (n=5 treated, n=40 ctrl) Nfi_motif_LESS_BPA10ug_BA_M_adt, Nfi_motif_MORE_BPA10ug_BA_M_adt, Nfi_motif_any OCR overlap: 10mg only : 692 10ug only : 76 Both doses : 86,870 Union : 87,638 Code (Python): BPA = "/home/yan/TaRGET_II/Li_adt/BPA/" merged_f = BPA + "BA_DAR_BPA10mg_10ug_merged_Nfi.txt" out_mg = BPA + "BA_DAR_BPA10mg_10ug_merged_Nfi_10mg.txt" out_ug = BPA + "BA_DAR_BPA10mg_10ug_merged_Nfi_10ug.txt" with open(merged_f) as f: hdr = f.readline().rstrip('\n').split('\t')[1:] # strip leading empty mg_hdr_idx = list(range(0,6)) + list(range(12,16)) + \ list(range(21,61)) + [101,102,103,106] ug_hdr_idx = list(range(6,12)) + list(range(16,21)) + \ list(range(61,101)) + [101,102,104,105,106] def make_hdr(idx_list, suffix): out = [] for i in idx_list: name = hdr[i].replace(f'_10{suffix}', '') out.append(name) return out fmg = open(out_mg, 'w'); fug = open(out_ug, 'w') fmg.write('\t'.join(make_hdr(mg_hdr_idx, 'mg')) + '\n') fug.write('\t'.join(make_hdr(ug_hdr_idx, 'ug')) + '\n') for line in f: parts = line.rstrip('\n').split('\t') ocr = parts[0]; vals = parts[1:] if vals[0] != '': # has 10mg stats fmg.write('\t'.join([ocr] + [vals[i] for i in mg_hdr_idx]) + '\n') if vals[6] != '': # has 10ug stats fug.write('\t'.join([ocr] + [vals[i] for i in ug_hdr_idx]) + '\n') fmg.close(); fug.close() ================================================================================ STEP 5 — Split Nfi GO Term Summary xlsx by dose ================================================================================ Input: Nfi_GO_Term_Summary_Esr1_Dapk2_Nod1.xlsx (3 sheets, 53 GO terms total) Output: Nfi_GO_Term_Summary_Esr1_Dapk2_Nod1_10mg.xlsx Nfi_GO_Term_Summary_Esr1_Dapk2_Nod1_10ug.xlsx Sheet structure (same in both input and outputs): Sheet 1 "GO Term Summary" : one row per GO term; filtered by dose Sheet 2 "Condition Overview": summary counts; condition table filtered Sheet 3 "Shared GO Terms" : GO terms shared across conditions (kept as-is in both files — all 6 shared terms involve both 10mg LESS and 10µg LESS) Filtering logic (Sheet 1): Rows are assigned to a dose if the Condition(s) column (col 4) contains "10mg" or "10µg". Rows with both strings appear in both output files. The '#' column is renumbered sequentially after filtering. GO term counts: 10mg file : 44 terms (38 10mg-only + 6 shared with 10µg LESS) 10ug file : 15 terms ( 9 10ug-only + 6 shared with 10mg LESS) Shared : 6 terms (all between BPA 10mg LESS and BPA 10µg LESS) Condition Overview updates (Sheet 2): 10mg file: Total unique : 44 Two-condition terms : 0 (no cross-condition sharing within 10mg alone) Condition-specific : 44 Condition table : BPA 10mg LESS only (10µg rows removed) 10ug file: Total unique : 15 Two-condition terms : 6 (shared with 10mg LESS) Condition-specific : 9 Condition table : BPA 10µg LESS + BPA 10µg MORE (10mg row removed) Code (R): library(openxlsx) s1 <- read.xlsx(infile, sheet=1, colNames=FALSE, skipEmptyRows=FALSE) s2 <- read.xlsx(infile, sheet=2, colNames=FALSE, skipEmptyRows=FALSE) s3 <- read.xlsx(infile, sheet=3, colNames=FALSE, skipEmptyRows=FALSE) data_rows <- s1[4:nrow(s1), ] has_mg <- grepl("10mg", data_rows$X4, fixed=TRUE) has_ug <- grepl("\u00b5g", data_rows$X4) # Locate condition rows and summary rows by content row_10ug_less <- grep("10\u00b5g LESS", s2$X1) row_10ug_more <- grep("10\u00b5g MORE", s2$X1) row_10mg_less <- grep("10mg LESS", s2$X1, fixed=TRUE) row_total <- grep("Total unique", s2$X1) row_two <- grep("two condition", s2$X1, ignore.case=TRUE) row_spec <- grep("condition-specific", s2$X1, ignore.case=TRUE) # 10mg: remove 10ug condition rows, update counts s2_mg <- s2[-c(row_10ug_less, row_10ug_more), ] s2_mg[row_total, 2] <- sum(has_mg) s2_mg[row_two, 2] <- 0 s2_mg[row_spec, 2] <- sum(has_mg) # 10ug: remove 10mg condition row, update counts s2_ug <- s2[-row_10mg_less, ] s2_ug[row_total, 2] <- sum(has_ug) s2_ug[row_two, 2] <- sum(has_mg & has_ug) s2_ug[row_spec, 2] <- sum(has_ug & !has_mg) ================================================================================ SUMMARY TABLE — ALL FILES ================================================================================ File Cols Rows Description ------------------------------------------------------------ ---- ------ --------------------------- BA_DAR_BPA10mg_..._RUVr_k3.txt 49 87,562 Original edgeR output BA_DAR_BPA10ug_..._RUVr_k3.txt 50 86,946 Original edgeR output BA_DAR_BPA10mg_..._RUVr_k3_DARstatus.txt 49 87,562 + DAR column BA_DAR_BPA10ug_..._RUVr_k3_DARstatus.txt 50 86,946 + DAR column BA_DAR_BPA10mg_..._RUVr_k3_DARstatus_annotated.txt 51 87,562 + DAR + gene annotation BA_DAR_BPA10ug_..._RUVr_k3_DARstatus_annotated.txt 52 86,946 + DAR + gene annotation BA_DAR_BPA10mg_..._RUVr_k3_DARstatus_annotated_cond.txt 52 87,562 + DAR + annotation + cond BA_DAR_BPA10ug_..._RUVr_k3_DARstatus_annotated_cond.txt 53 86,946 + DAR + annotation + cond BA_DAR_BPA10mg_10ug_merged_Nfi.txt 107 87,638 Both doses merged + Nfi flags BA_DAR_BPA10mg_10ug_merged_Nfi_10mg.txt 54 87,562 Merged split → 10mg only BA_DAR_BPA10mg_10ug_merged_Nfi_10ug.txt 56 86,946 Merged split → 10ug only MORE_BA_DAR_BPA10mg_..._RUVr_k3_filter.txt 6 359 BED subset (pre-existing) BPA_adt_OCR_table.csv 12 354,106 Condition reference table Nfi_GO_Term_Summary_Esr1_Dapk2_Nod1.xlsx — 53 GO enrichment (3 sheets) Nfi_GO_Term_Summary_Esr1_Dapk2_Nod1_10mg.xlsx — 44 GO enrichment, 10mg only Nfi_GO_Term_Summary_Esr1_Dapk2_Nod1_10ug.xlsx — 15 GO enrichment, 10ug only BA_nonDAR_BPA10mg_Li_M_adt.bed 3 77,118 Non-DAR peaks, BPA10mg (BED) BA_nonDAR_BPA10ug_Li_M_adt.bed 3 70,745 Non-DAR peaks, BPA10ug (BED) homer_motif/nonDAR_BPA10mg/ — — HOMER motif results, BPA10mg homer_motif/nonDAR_BPA10ug/ — — HOMER motif results, BPA10ug homer_motif/extract_nonDAR_motifs.sh — — Script: extract motif table from HOMER results homer_motif/motif_gene_nonDAR.txt 10 33 de novo motif summary table (all passing motifs) homer_motif/nonDAR_BPA10mg_NFI_annotated.txt 23 77,118 annotatePeaks output, BPA10mg + 2 NFI motifs homer_motif/nonDAR_BPA10ug_NFI_annotated.txt 23 70,745 annotatePeaks output, BPA10ug + 2 NFI motifs homer_motif/nonDAR_NFI_peaks.txt 9 78,673 NFI motif instances in non-DAR peaks (long format) homer_motif/nonDAR_NFI_peaks_merged.txt 22 42,456 Non-DAR NFI peaks joined to merged Nfi file BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt 112 87,638 Merged Nfi + NFI flags + Gene_bodyWindow_5kb + motif15 (see STEPs 14, 25, 26) BA_DAR_BPA10mg_10ug_merged_Nfi_final.txt 103 87,638 Final: sample counts at end; NFI_present_all_condition removed (see STEP 12b) Nfi_DAR_group_summary_Esr1_Dapk2_Nod1.csv 7 18 Nfi×DAR summary, Gene_bodyWindow_1Mb (3 genes × 2 doses × 3 groups) Nfi_DAR_group_summary_Esr1_Dapk2_Nod1_NearestTSS.csv 7 18 Nfi×DAR summary, Nearest_TSS_1Mb (3 genes × 2 doses × 3 groups) Nfi_DAR_group_summary_Esr1_Dapk2_Nod1_5kb.csv 7 18 Nfi×DAR summary, Gene_bodyWindow_5kb + motif15 nonDAR (see STEP 27) OCR_gene_annotation.txt 19 217,623 All-tissue OCR peaks; HOMER annotatePeaks mm10 (1-based coords) OCR_Esr1_Dapk2_Nod1_peaks.txt 19 114 OCR peaks with Gene Name = Esr1 / Dapk2 / Nod1 BA_DAR_Esr1_Dapk2_Nod1_peaks.txt 109 433 BA_DAR peaks where gene_body OR nearest_TSS = Esr1/Dapk2/Nod1 OCR_in_BA_DAR_peaks.txt 19 87,638 OCR peaks present in BA_DAR full set (coordinate-matched) OCR_in_BA_DAR_Esr1_Dapk2_Nod1_peaks.txt 19 55 OCR_in_BA_DAR peaks with Gene Name = Esr1 / Dapk2 / Nod1 OCR_from_BA_DAR_gene_peaks.txt 19 433 OCR peaks at BA_DAR Esr1/Dapk2/Nod1 gene-window coordinates gene_peak_comparison_summary.csv 14 6 OCR vs BA_DAR window summary: 3 genes × 2 windows (STEP 22) gene_peak_comparison_detail.csv 10 487 Per-peak comparison detail, all windows (STEP 22) OCR_vs_BADAR_by_window.csv 14 6 OCR_in_BA_DAR vs BA_DAR by window summary (STEP 23) OCR_vs_BADAR_by_window_detail.csv 14 487 Per-peak detail separated by window (STEP 23) OCR_vs_BADAR_TSS_55peaks_detail.csv 22 55 TSS-only detail: 55 OCR target-gene peaks joined to BA_DAR (STEP 24) -------------------------------------------------------------------------------- STEP 6 — Extract non-DAR peaks as BED files -------------------------------------------------------------------------------- Input: BA_DAR_BPA10mg_Li_M_adt_Ctrl_Li_M_adt_RUVr_k3_DARstatus_annotated_cond.txt BA_DAR_BPA10ug_Li_M_adt_Ctrl_Li_M_adt_RUVr_k3_DARstatus_annotated_cond.txt Output: BA_nonDAR_BPA10mg_Li_M_adt.bed (77,118 peaks) BA_nonDAR_BPA10ug_Li_M_adt.bed (70,745 peaks) BED columns: chrom, chromStart, chromEnd (3-column BED, no header) Logic: Select rows where DAR (col 6 in data rows) == FALSE. Row names are in "chr,start,end" format; split on comma for BED columns. Peak counts: BPA10mg : 77,118 non-DAR | 10,444 DAR | 87,562 total BPA10ug : 70,745 non-DAR | 16,201 DAR | 86,946 total Command: awk 'NR>1 && $6=="FALSE" {split($1,a,","); print a[1]"\t"a[2]"\t"a[3]}' \ BA_DAR_BPA10mg_Li_M_adt_Ctrl_Li_M_adt_RUVr_k3_DARstatus_annotated_cond.txt \ > BA_nonDAR_BPA10mg_Li_M_adt.bed awk 'NR>1 && $6=="FALSE" {split($1,a,","); print a[1]"\t"a[2]"\t"a[3]}' \ BA_DAR_BPA10ug_Li_M_adt_Ctrl_Li_M_adt_RUVr_k3_DARstatus_annotated_cond.txt \ > BA_nonDAR_BPA10ug_Li_M_adt.bed -------------------------------------------------------------------------------- STEP 7 — HOMER de novo motif finding on non-DAR peaks -------------------------------------------------------------------------------- Input: BA_nonDAR_BPA10mg_Li_M_adt.bed BA_nonDAR_BPA10ug_Li_M_adt.bed Output: homer_motif/nonDAR_BPA10mg/ (HOMER results for BPA10mg non-DAR) homer_motif/nonDAR_BPA10ug/ (HOMER results for BPA10ug non-DAR) Parameters: Genome : mm10 -size given : use peak coordinates as-is (no resizing) -preparsedDir : preparsed/ (local writable directory) -p 8 : 8 CPU threads HOMER path: /home/zhangbo/Tools/homer/bin/findMotifsGenome.pl Commands (run in parallel): export PATH=/home/zhangbo/Tools/homer/bin:$PATH findMotifsGenome.pl \ BA_nonDAR_BPA10mg_Li_M_adt.bed mm10 \ homer_motif/nonDAR_BPA10mg \ -size given \ -preparsedDir preparsed \ -p 8 findMotifsGenome.pl \ BA_nonDAR_BPA10ug_Li_M_adt.bed mm10 \ homer_motif/nonDAR_BPA10ug \ -size given \ -preparsedDir preparsed \ -p 8 Note: The preparsed/ directory must be local and writable. Using the shared HOMER preparsed directory (/home/zhangbo/Tools/homer/data/genomes/mm10/preparsed) will fail due to write permissions. -------------------------------------------------------------------------------- STEP 8 — Extract de novo motif summary table from HOMER results -------------------------------------------------------------------------------- Script: homer_motif/extract_nonDAR_motifs.sh (adapted from /home/bemiao/TaRGET_ATAC/All/analysis/DAR_consortium/Motif/ Li_adt/MORE/process.sh) Input: homer_motif/nonDAR_BPA10mg/homerResults.html homer_motif/nonDAR_BPA10mg/homerResults/motif*.info.html homer_motif/nonDAR_BPA10ug/homerResults.html homer_motif/nonDAR_BPA10ug/homerResults/motif*.info.html Output: homer_motif/motif_gene_nonDAR.txt (33 motifs, 10 columns) Filters applied: de novo motif p-value < 1e-11 best known match score >= 0.8 individual TF match score >= 0.8 (for matched_TFs column) Columns: sample : nonDAR_BPA10mg / nonDAR_BPA10ug pvalue : de novo motif p-value log_pvalue : log p-value pct_target : % target sequences with motif pct_bg : % background sequences with motif best_match : best matching known motif name (TF family) match_score : similarity score to known motif (>= 0.8) motif_file : info.html filename in homerResults/ matched_TFs : TF gene names with match score >= 0.8 (comma-separated) NFI_type : TRUE if matched_TFs contains Nfia, Nfib, Nfic, or Nfix Results summary: BPA10mg : 15 motifs (2 NFI_type TRUE: NF1(CTF), NFIX) BPA10ug : 18 motifs (2 NFI_type TRUE: Tlx?(NR), NF1-halfsite(CTF)) Total : 33 motifs Run command: export PATH=/home/zhangbo/Tools/homer/bin:$PATH bash homer_motif/extract_nonDAR_motifs.sh -------------------------------------------------------------------------------- STEP 9 — Annotate non-DAR peaks with NFI motifs; extract NFI peak table -------------------------------------------------------------------------------- Input: BA_nonDAR_BPA10mg_Li_M_adt.bed BA_nonDAR_BPA10ug_Li_M_adt.bed homer_motif/nonDAR_BPA10mg/homerResults/motif9.motif (NF1/CTF) homer_motif/nonDAR_BPA10mg/homerResults/motif11.motif (NFIX) homer_motif/nonDAR_BPA10ug/homerResults/motif8.motif (Tlx?/NFI) homer_motif/nonDAR_BPA10ug/homerResults/motif9.motif (NF1-halfsite) Intermediate output (HOMER annotatePeaks, all peaks): homer_motif/nonDAR_BPA10mg_NFI_annotated.txt (77,118 peaks, 23 cols) homer_motif/nonDAR_BPA10ug_NFI_annotated.txt (70,745 peaks, 23 cols) Standard HOMER annotation columns (1–21) + 2 motif columns: BPA10mg col 22: NF1(CTF)/motif9 — offset(sequence,strand,conservation) BPA10mg col 23: NFIX/motif11 — offset(sequence,strand,conservation) BPA10ug col 22: Tlx?(NR)/motif8 — offset(sequence,strand,conservation) BPA10ug col 23: NF1-halfsite/motif9 — offset(sequence,strand,conservation) Empty string = motif absent in that peak. Final output (NFI motif instances only, long format): homer_motif/nonDAR_NFI_peaks.txt (78,673 rows, 9 cols) Columns: chr, start, end : peak coordinates sample : nonDAR_BPA10mg / nonDAR_BPA10ug best_match : NFI motif name NFI_members : NFI TF genes matching this motif (score >= 0.8) offset : motif position within peak (bp from peak start) sequence : matched DNA sequence strand : + / - Instance counts: nonDAR_BPA10mg NF1(CTF) : 11,033 nonDAR_BPA10mg NFIX : 29,102 nonDAR_BPA10ug Tlx?(NR)/NFI : 13,084 nonDAR_BPA10ug NF1-halfsite(CTF) : 25,454 Total : 78,673 Note: long format — a single peak matching both motifs appears on 2 rows. Commands: export PATH=/home/zhangbo/Tools/homer/bin:$PATH annotatePeaks.pl BA_nonDAR_BPA10mg_Li_M_adt.bed mm10 -size given \ -m homer_motif/nonDAR_BPA10mg/homerResults/motif9.motif \ homer_motif/nonDAR_BPA10mg/homerResults/motif11.motif \ > homer_motif/nonDAR_BPA10mg_NFI_annotated.txt annotatePeaks.pl BA_nonDAR_BPA10ug_Li_M_adt.bed mm10 -size given \ -m homer_motif/nonDAR_BPA10ug/homerResults/motif8.motif \ homer_motif/nonDAR_BPA10ug/homerResults/motif9.motif \ > homer_motif/nonDAR_BPA10ug_NFI_annotated.txt Parsing code (Python): See logic in STEP 9 — filters rows where motif column is non-empty, parses "offset(sequence,strand,conservation)" format, adds metadata (best_match, NFI_members) from motif_gene_nonDAR.txt, outputs long table. -------------------------------------------------------------------------------- STEP 10 — Map non-DAR NFI peaks to merged Nfi file by dose -------------------------------------------------------------------------------- Input: homer_motif/nonDAR_NFI_peaks.txt BA_DAR_BPA10mg_10ug_merged_Nfi.txt Output: homer_motif/nonDAR_NFI_peaks_merged.txt (42,456 unique OCRs, 22 cols) Join key: OCR (chr,start,end). Note: HOMER annotatePeaks.pl shifts BED start +1 (0-based → 1-based). Correction applied: orig_start = annotatePeaks_start - 1 before joining. Logic: Unique OCRs from nonDAR_NFI_peaks.txt are matched to the merged file. Multiple motif rows per OCR are collapsed (best_match and NFI_members joined with "|" and "," respectively). Sample values are "|"-joined. Columns: OCR, chr, start, end : peak coordinates (start = 0-based, matching merged) sample : nonDAR_BPA10mg | nonDAR_BPA10ug (or both "|"-joined) best_match : NFI motif name(s), "|"-joined if multiple NFI_members : all matched NFI TF genes, ","-joined, sorted logFC_10mg : log2 fold-change BPA10mg vs Control FDR_10mg : BH-adjusted p-value, BPA10mg model DAR_10mg : True / False / "" (absent from 10mg analysis) condition_10mg : LESS / MORE / "" for BPA10mg logFC_10ug : log2 fold-change BPA10ug vs Control FDR_10ug : BH-adjusted p-value, BPA10ug model DAR_10ug : True / False / "" (absent from 10ug analysis) condition_10ug : LESS / MORE / "" for BPA10ug Gene_bodyWindow_1Mb : gene body within 1 Mb (curated set, "." = none) Nearest_TSS_1Mb : nearest TSS within 1 Mb (curated set, "." = none) Nfi_motif_LESS_BPA10mg : NFI motif flag from DAR analysis, BPA10mg LESS Nfi_motif_LESS_BPA10ug : NFI motif flag from DAR analysis, BPA10ug LESS Nfi_motif_MORE_BPA10ug : NFI motif flag from DAR analysis, BPA10ug MORE Nfi_motif_any : any NFI motif flag from DAR analysis dose_category : dose-difference classification (see below) Dose-difference categories (dose_category column): nonDAR_both : non-DAR in both BPA10mg and BPA10ug (37,854) nonDAR_10mg__DAR_10ug : non-DAR in 10mg, DAR in 10ug (3,534) DAR_10mg__nonDAR_10ug : DAR in 10mg, non-DAR in 10ug (925) nonDAR_10mg__absent_10ug : non-DAR in 10mg, absent in 10ug (127) absent_10mg__nonDAR_10ug : absent in 10mg, non-DAR in 10ug (16) Sample breakdown (which dose(s) the NFI motif was identified in): nonDAR_BPA10mg only : 10,185 nonDAR_BPA10mg|nonDAR_BPA10ug: 24,246 nonDAR_BPA10ug only : 8,025 Code (Python): # Build dose-specific OCR sets (correcting HOMER +1 start shift) nfi_10mg, nfi_10ug = set(), set() for row in csv.DictReader(open(NFI_FILE), delimiter='\t'): key = f"{row['chr']},{int(row['start'])-1},{row['end']}" if 'nonDAR_BPA10mg' in row['sample']: nfi_10mg.add(key) if 'nonDAR_BPA10ug' in row['sample']: nfi_10ug.add(key) # Join with merged file, collapse motif info per OCR, assign dose_category -------------------------------------------------------------------------------- STEP 11 — Add NFI_non_DAR_present flag to merged Nfi file -------------------------------------------------------------------------------- Input: homer_motif/nonDAR_NFI_peaks.txt BA_DAR_BPA10mg_10ug_merged_Nfi.txt Output: BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt (109 cols, 87,638 OCRs) = BA_DAR_BPA10mg_10ug_merged_Nfi.txt + 1 new column at end New column (col 109): NFI_non_DAR_present : TRUE / FALSE Logic (same-dose): TRUE if: (DAR_10mg == "False" AND OCR ∈ nonDAR_BPA10mg NFI set) OR (DAR_10ug == "False" AND OCR ∈ nonDAR_BPA10ug NFI set) Enforces same-dose matching: an OCR is only flagged TRUE if it is non-DAR in the dose where the NFI motif was identified. Note: DAR values in merged file use Python-style capitalisation ("True"/"False"), not R-style ("TRUE"/"FALSE"). Counts: NFI_non_DAR_present = TRUE : 42,456 NFI_non_DAR_present = FALSE : 45,182 Total : 87,638 Code (Python): nfi_10mg, nfi_10ug = set(), set() # built same as STEP 10 for line in merged_reader: flag = ((line[5]=="False" and ocr in nfi_10mg) or (line[11]=="False" and ocr in nfi_10ug)) writer.writerow(line + ["TRUE" if flag else "FALSE"]) ================================================================================ STEP 12 — Restructure merged file: merge NFI flags, move sample counts to end ================================================================================ Input : BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt (87,638 OCRs, 109 cols) Output: BA_DAR_BPA10mg_10ug_merged_Nfi_final.txt (87,638 OCRs, 105 cols) Script: restructure_merged.py Two changes applied: 1. Merge cols 105–109 into a single column NFI_present_all_condition (TRUE/FALSE) Merged columns: col 105: Nfi_motif_LESS_BPA10mg_BA_M_adt col 106: Nfi_motif_LESS_BPA10ug_BA_M_adt col 107: Nfi_motif_MORE_BPA10ug_BA_M_adt col 108: Nfi_motif_any col 109: NFI_non_DAR_present NFI_present_all_condition = TRUE if ANY of the five source columns is TRUE/True 2. Move sample read counts (old cols 14–102, 89 columns) to the end of the file New column order (1-based): col 1 : OCR key (unnamed) col 2–13 : DAR stats for both doses (logFC, logCPM, PValue, FDR, DAR, condition × 2) col 14 : Gene_bodyWindow_1Mb (was col 103) col 15 : Nearest_TSS_1Mb (was col 104) col 16 : NFI_present_all_condition (new merged column) col 17–105 : sample read counts (was cols 14–102) BPA10mg treated (n=4), BPA10ug treated (n=5), Control _10mg (n=40), Control _10ug (n=40) Counts: NFI_present_all_condition = TRUE : 46,715 NFI_present_all_condition = FALSE : 40,923 Total : 87,638 Note: TRUE count (46,715) is larger than col 109 alone (42,456) because cols 105–108 (DAR-based NFI motif flags) add additional TRUE OCRs. -------------------------------------------------------------------------------- STEP 12b — Remove NFI_present_all_condition column from final file -------------------------------------------------------------------------------- Input / Output: BA_DAR_BPA10mg_10ug_merged_Nfi_final.txt (in-place edit) Reason: NFI_present_all_condition was a broad OR-merge of five source columns spanning both DAR and non-DAR HOMER analyses. This conflates condition- specific motif signals and should not be used for per-condition comparisons. Downstream analyses use condition-specific columns in the nonDARflag file instead (see STEP 13). Change: Col 16 (NFI_present_all_condition) removed. 105 cols → 103 cols. Final column order (1-based): col 1 : OCR key (unnamed) col 2–13 : DAR stats for both doses (logFC, logCPM, PValue, FDR, DAR, condition × 2) col 14 : Gene_bodyWindow_1Mb col 15 : Nearest_TSS_1Mb col 16–103 : sample read counts (89 columns) BPA10mg treated (n=4), BPA10ug treated (n=5), Control _10mg (n=40), Control _10ug (n=40) Command: awk 'BEGIN{OFS="\t"} NR==1 { for(i=1;i<=NF;i++) if(i!=15) printf "%s%s", $i, (i==NF?"\n":OFS) } NR>1 { for(i=1;i<=NF;i++) if(i!=16) printf "%s%s", $i, (i==NF?"\n":OFS) }' \ BA_DAR_BPA10mg_10ug_merged_Nfi_final.txt > tmp.txt && mv tmp.txt \ BA_DAR_BPA10mg_10ug_merged_Nfi_final.txt ================================================================================ STEP 13 — Generate Nfi motif × DAR group summary tables for Esr1, Dapk2, Nod1 ================================================================================ Input: BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt Output: Nfi_DAR_group_summary_Esr1_Dapk2_Nod1.csv (Gene_bodyWindow_1Mb) Nfi_DAR_group_summary_Esr1_Dapk2_Nod1_NearestTSS.csv (Nearest_TSS_1Mb) Genes: Esr1, Dapk2, Nod1 Annotation filters: Gene_bodyWindow version : p[102] == gene (Gene_bodyWindow_1Mb column) Nearest_TSS version : p[103] == gene (Nearest_TSS_1Mb column) Peak counts per gene (Gene_bodyWindow_1Mb): Esr1 : 91 peaks | Dapk2 : 189 peaks | Nod1 : 153 peaks Peak counts per gene (Nearest_TSS_1Mb): Esr1 : 23 peaks | Dapk2 : 21 peaks | Nod1 : 10 peaks DAR groups and NFI columns used: DAR_MORE BPA10mg : no HOMER MORE analysis for 10mg → Nfi+ = 0 by definition DAR_LESS BPA10mg : Nfi_motif_LESS_BPA10mg_BA_M_adt (col 105 in data rows) DAR_MORE BPA10ug : Nfi_motif_MORE_BPA10ug_BA_M_adt (col 107 in data rows) DAR_LESS BPA10ug : Nfi_motif_LESS_BPA10ug_BA_M_adt (col 106 in data rows) non-DAR : NFI_non_DAR_present (col 109 in data rows) Note: The column indices above reflect the pre-STEP 14 layout. After STEP 14 reorganization, gene annotation is at cols 14–15 and NFI flag columns are at cols 16–20 in data rows of nonDARflag file. Rationale: condition-specific NFI columns are used rather than the merged NFI_present_all_condition flag, to avoid cross-condition signal contamination. Output columns: Gene, Dose, DAR Group, Total regions, Nfi+ (motif present), Nfi- (motif absent), Nfi+ % DAR group values: DAR_MORE | DAR_LESS | non-DAR Code (Python): # see inline script run via Claude Code, 2026-03-16 ================================================================================ STEP 14 — Reorganize columns in nonDARflag file: move sample counts to end ================================================================================ Input / Output: BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt (in-place reorder) Reason: Place gene annotation and NFI flag columns immediately after DAR statistics, so that biologically relevant columns appear before the 89 sample read-count columns. Mirrors the layout of BA_DAR_BPA10mg_10ug_merged_Nfi_final.txt. Old column order (1-based data rows): col 1 : OCR key (unnamed) col 2–13 : DAR stats (logFC, logCPM, PValue, FDR, DAR, condition × 2 doses) col 14–102 : sample read counts (89 columns) col 103 : Gene_bodyWindow_1Mb col 104 : Nearest_TSS_1Mb col 105 : Nfi_motif_LESS_BPA10mg_BA_M_adt col 106 : Nfi_motif_LESS_BPA10ug_BA_M_adt col 107 : Nfi_motif_MORE_BPA10ug_BA_M_adt col 108 : Nfi_motif_any col 109 : NFI_non_DAR_present Column order after STEP 14 (before STEP 25): col 1 : OCR key (unnamed) col 2–13 : DAR stats (logFC, logCPM, PValue, FDR, DAR, condition × 2 doses) col 14 : Gene_bodyWindow_1Mb (was col 103) col 15 : Nearest_TSS_1Mb (was col 104) col 16 : Nfi_motif_LESS_BPA10mg_BA_M_adt (was col 105) col 17 : Nfi_motif_LESS_BPA10ug_BA_M_adt (was col 106) col 18 : Nfi_motif_MORE_BPA10ug_BA_M_adt (was col 107) col 19 : Nfi_motif_any (was col 108) col 20 : NFI_non_DAR_present (was col 109) col 21–109 : sample read counts (89 columns, was cols 14–102) BPA10mg treated (n=4), BPA10ug treated (n=5), Control _10mg (n=40), Control _10ug (n=40) Note: After STEP 25, Gene_bodyWindow_5kb is inserted at col 16, shifting NFI flags to cols 17–21 and sample counts to cols 22–110. Note: The header row has a leading empty field at index 0 (R write.table format), so Python 0-indexed positions = 1-based data-row positions − 1. awk was attempted first but failed due to off-by-one caused by the leading empty header field; Python (0-indexed) was used instead. Code (Python): new_order = list(range(0, 13)) + list(range(102, 109)) + list(range(13, 102)) with open(infile) as fin, open(outfile, 'w') as fout: for line in fin: fields = line.rstrip('\n').split('\t') fout.write('\t'.join(fields[i] for i in new_order) + '\n') ================================================================================ STEP 15 — Sanity check: nonDARflag file vs. source condition files ================================================================================ Purpose: Verify that BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt is consistent with the two original per-condition DAR files after column reorganization. Source files: BA_DAR_BPA10mg_Li_M_adt_Ctrl_Li_M_adt_RUVr_k3_DARstatus_annotated_cond.txt BA_DAR_BPA10ug_Li_M_adt_Ctrl_Li_M_adt_RUVr_k3_DARstatus_annotated_cond.txt CHECK 1 — OCR overlap: OCRs in 10mg file not in merged : 0 (all 10mg OCRs present in merged) OCRs in 10ug file not in merged : 0 (all 10ug OCRs present in merged) OCRs in merged not in 10mg file : 76 (10ug-only OCRs, expected) OCRs in merged not in 10ug file : 692 (10mg-only OCRs, expected) OCRs present in all three files : 86,870 (matches README union counts) CHECK 2 — Field consistency (0 mismatches): Fields verified for each OCR present in both merged and source file: logFC, logCPM, PValue, FDR, DAR, condition (× 2 doses) Gene_bodyWindow_1Mb, Nearest_TSS_1Mb Result: 10mg OCRs checked : 87,562 | mismatches : 0 10ug OCRs checked : 86,946 | mismatches : 0 Conclusion: nonDARflag file is fully consistent with both source condition files. ================================================================================ COORDINATE SYSTEM NOTE ================================================================================ Two coordinate systems are used across files in this directory. Mixing them without correction will produce zero matches. BED / BA_DAR files (0-based half-open): Row name format: "chr,start,end" start = 0-based (first base of peak is 0) Used in: BA_DAR_*.txt, BA_nonDAR_*.bed HOMER annotatePeaks (1-based): start = 1-based (first base of peak is 1) Used in: OCR_gene_annotation.txt, OCR_*_peaks.txt, OCR_in_BA_DAR*.txt Conversion rule (verified 100% on 87,638 BA_DAR peaks): OCR_start (1-based) = BA_DAR_start (0-based) + 1 end coordinates are IDENTICAL between both systems ================================================================================ STEP 16 — HOMER annotatePeaks on all-tissue OCR peak set ================================================================================ Input: OCR_position.bed (all-tissue merged OCR peak positions, 0-based BED) Output: OCR_gene_annotation.txt (217,623 peaks, 19 columns, 1-based start) Tool: HOMER annotatePeaks.pl (genome: mm10) Command: export PATH=/home/zhangbo/Tools/homer/bin:$PATH annotatePeaks.pl OCR_position.bed mm10 \ -annStats stat_OCR_gene_annotation.txt \ > OCR_gene_annotation.txt Column order (tab-delimited, 1-based coordinates): col 1 : PeakID col 2 : Chr col 3 : Start (1-based) col 4 : End col 5 : Strand col 6 : Peak Score col 7 : Focus Ratio/Region Size col 8 : Annotation (e.g. Intergenic, promoter-TSS, intron) col 9 : Detailed Annotation col 10 : Distance to TSS col 11 : Nearest PromoterID col 12 : Entrez ID col 13 : Nearest Unigene col 14 : Nearest Refseq col 15 : Nearest Ensembl col 16 : Gene Name ← primary gene identifier used in Steps 17–23 col 17 : Gene Alias col 18 : Gene Description col 19 : Gene Type Notes: - Contains 217,623 OCR peaks from the all-tissue merged peak set. - "Gene Name" (col 16) is the nearest gene assigned by HOMER; equivalent to Nearest_TSS_1Mb in BA_DAR files (same underlying logic). - The PeakID is a numeric index, not the chr,start,end row name of BA_DAR. -------------------------------------------------------------------------------- STEP 17 — Extract Esr1/Dapk2/Nod1 peaks from OCR annotation -------------------------------------------------------------------------------- Input: OCR_gene_annotation.txt Output: OCR_Esr1_Dapk2_Nod1_peaks.txt (114 peaks, 19 columns) Filter: Gene Name (col 16) == Esr1 OR Dapk2 OR Nod1 Peak counts: Esr1 : 49 peaks Dapk2 : 39 peaks Nod1 : 26 peaks Total :114 peaks Command: head -1 OCR_gene_annotation.txt > OCR_Esr1_Dapk2_Nod1_peaks.txt grep -P '\t(Esr1|Dapk2|Nod1)\t' OCR_gene_annotation.txt | sort \ >> OCR_Esr1_Dapk2_Nod1_peaks.txt Note: grep targets a tab-flanked gene name to avoid partial matches. The gene name at col 16 is followed by a tab (col 17 = Gene Alias), so the pattern '\t(gene)\t' is exact. -------------------------------------------------------------------------------- STEP 18 — Extract Esr1/Dapk2/Nod1 peaks from BA_DAR (gene_body OR nearest_TSS) -------------------------------------------------------------------------------- Input: BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt Output: BA_DAR_Esr1_Dapk2_Nod1_peaks.txt (433 peaks, 109 columns) Filter: Gene_bodyWindow_1Mb ($14) OR Nearest_TSS_1Mb ($15) == Esr1/Dapk2/Nod1 (awk field numbers; header row has one fewer field — leading tab creates empty $1 in header, shifting data columns by +1 vs header column numbers) Peak counts by Gene_bodyWindow_1Mb ($14): Esr1 : 91 | Dapk2 : 189 | Nod1 : 153 | Total: 433 Peak counts by Nearest_TSS_1Mb ($15): Esr1 : 23 | Dapk2 : 21 | Nod1 : 10 | Total: 54 (54 peaks have nearest_TSS annotated; all 54 are a subset of the 433) Command: head -1 BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt \ > BA_DAR_Esr1_Dapk2_Nod1_peaks.txt awk -F'\t' 'NR>1 && ($14=="Esr1" || $14=="Dapk2" || $14=="Nod1" || \ $15=="Esr1" || $15=="Dapk2" || $15=="Nod1")' \ BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt \ >> BA_DAR_Esr1_Dapk2_Nod1_peaks.txt Key columns (0-based Python indices in data rows): p[0] : OCR key (chr,start0,end) p[1] : logFC_10mg p[4] : FDR_10mg p[5] : DAR_10mg p[6] : condition_10mg p[7] : logFC_10ug p[10] : FDR_10ug p[11] : DAR_10ug p[12] : condition_10ug p[13] : Gene_bodyWindow_1Mb p[14] : Nearest_TSS_1Mb p[19] : NFI_non_DAR_present -------------------------------------------------------------------------------- STEP 19 — Extract OCR peaks present in the full BA_DAR set (coordinate join) -------------------------------------------------------------------------------- Input: OCR_gene_annotation.txt (217,623 peaks, 1-based) BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt (87,638 peaks, 0-based) Output: OCR_in_BA_DAR_peaks.txt (87,638 peaks, 19 columns) Method: 1. Build lookup set from BA_DAR: key = (chr, str(start0+1), end) — convert 0-based start to 1-based to match OCR coordinates. 2. For each OCR peak, check if (chr, start, end) is in BA_DAR lookup. 3. Write matching OCR rows to output. Result: 100% of 87,638 BA_DAR peaks matched an OCR peak. This confirms the systematic 1-bp start offset between files is the only coordinate difference (end coordinates are identical in both systems). Code (Python): badar_keys = set() with open(badar_file) as f: for i, line in enumerate(f): if i == 0: continue rid = line.split('\t')[0] # "chr,start0,end" chrom, s0, end = rid.split(',') badar_keys.add((chrom, str(int(s0)+1), end)) with open(ocr_file) as f, open(out_file, 'w') as out: for i, line in enumerate(f): if i == 0: out.write(line); continue p = line.split('\t') if (p[1], p[2], p[3]) in badar_keys: out.write(line) -------------------------------------------------------------------------------- STEP 20 — Filter OCR_in_BA_DAR peaks for Esr1/Dapk2/Nod1 gene annotation -------------------------------------------------------------------------------- Input: OCR_in_BA_DAR_peaks.txt Output: OCR_in_BA_DAR_Esr1_Dapk2_Nod1_peaks.txt (55 peaks, 19 columns) Filter: Gene Name (col 16) == Esr1 OR Dapk2 OR Nod1 Peak counts: Esr1 : 24 peaks Dapk2 : 21 peaks Nod1 : 10 peaks Total : 55 peaks Interpretation: These 55 peaks are: (a) present in BA_DAR (tissue-specific, BA lab); (b) annotated by HOMER to Esr1/Dapk2/Nod1 as nearest gene. The HOMER nearest-gene annotation is equivalent to the Nearest_TSS_1Mb logic in BA_DAR — both assign a peak to the gene with the nearest TSS. Command: head -1 OCR_in_BA_DAR_peaks.txt > OCR_in_BA_DAR_Esr1_Dapk2_Nod1_peaks.txt grep -P '\t(Esr1|Dapk2|Nod1)\t' OCR_in_BA_DAR_peaks.txt \ >> OCR_in_BA_DAR_Esr1_Dapk2_Nod1_peaks.txt -------------------------------------------------------------------------------- STEP 21 — Extract OCR peaks at BA_DAR gene-window peak coordinates -------------------------------------------------------------------------------- Input: OCR_gene_annotation.txt BA_DAR_Esr1_Dapk2_Nod1_peaks.txt Output: OCR_from_BA_DAR_gene_peaks.txt (433 peaks, 19 columns) Method: Build lookup from BA_DAR_Esr1_Dapk2_Nod1_peaks.txt using 1-based keys (start0+1), then extract matching rows from OCR_gene_annotation.txt. This retrieves the HOMER annotation for each BA_DAR gene-window peak. Peak counts (OCR Gene Name for the 433 BA_DAR gene-window peaks): Annotated to target gene (Esr1/Dapk2/Nod1) : 55 (12.7%) Annotated to OTHER genes : 378 (87.3%) Interpretation: The gene_body_Window_1Mb in BA_DAR extends ±~1 Mb around the gene body. Most peaks in this wide window are closer (in TSS distance) to OTHER genes, so HOMER assigns them to non-target genes. Only peaks in the narrow nearest_TSS_1Mb window are consistently annotated to the target gene in both BA_DAR and HOMER. Code (Python): # Build 1-based key set from BA_DAR gene peaks gene_keys = set() with open(badar_gene_file) as f: for i, line in enumerate(f): if i == 0: continue rid = line.split('\t')[0] chrom, s0, end = rid.split(',') gene_keys.add((chrom, str(int(s0)+1), end)) with open(ocr_file) as f, open(out_file, 'w') as out: for i, line in enumerate(f): if i == 0: out.write(line); continue p = line.split('\t') if (p[1], p[2], p[3]) in gene_keys: out.write(line) ================================================================================ STEP 22 — Gene-peak comparison: OCR annotation vs BA_DAR gene windows ================================================================================ Input: OCR_gene_annotation.txt (217,623 peaks, 1-based) BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt (87,638 peaks, 0-based) Output: gene_peak_comparison_summary.csv (6 rows: 3 genes × 2 windows) gene_peak_comparison_detail.csv (487 rows: per-peak, all windows) Purpose: For each of the three target genes (Esr1, Dapk2, Nod1), compare which peaks in the BA_DAR gene window (gene_body OR nearest_TSS) are also annotated to the same gene in the OCR/HOMER annotation (col 16 Gene Name). Separated by Gene_bodyWindow_1Mb and Nearest_TSS_1Mb windows. Summary CSV columns: Gene, Window, Total_peaks, MATCH_n, MATCH_pct, DIFF_n, DIFF_pct, MATCH_DAR_10mg, MATCH_DAR_10ug, MATCH_DAR_both, DIFF_DAR_10mg, DIFF_DAR_10ug, DIFF_DAR_both, DIFF_top3_OCR_genes Definition of MATCH vs DIFF: MATCH : BA_DAR gene-window peak AND OCR nearest gene == same target gene DIFF : BA_DAR gene-window peak BUT OCR nearest gene == different gene Key results: Gene_bodyWindow_1Mb — MATCH rates: Esr1 : 24/91 (26.4%) | Dapk2 : 21/189 (11.1%) | Nod1 : 10/153 (6.5%) Nearest_TSS_1Mb — MATCH rates: Esr1 : 23/23 (100%) | Dapk2 : 21/21 (100%) | Nod1 : 10/10 (100%) Interpretation: Nearest_TSS_1Mb perfectly matches HOMER annotation (100%) because both methods use nearest-TSS logic. Gene_bodyWindow_1Mb is a much broader window; most peaks in it are annotated to other (closer) genes in HOMER. Code (Python): # see inline script (generate_comparison.py), run 2026-03-18 ================================================================================ STEP 23 — Compare OCR_in_BA_DAR gene peaks vs BA_DAR gene-window peaks by window ================================================================================ Input: OCR_in_BA_DAR_Esr1_Dapk2_Nod1_peaks.txt (55 peaks, HOMER gene = target) BA_DAR_Esr1_Dapk2_Nod1_peaks.txt (433 peaks, BA_DAR window = target) Output: OCR_vs_BADAR_by_window.csv (6 rows: 3 genes × 2 windows) OCR_vs_BADAR_by_window_detail.csv (487 rows: per-peak) Purpose: Determine what fraction of BA_DAR gene-window peaks (both windows separately) correspond to the 55 OCR peaks that are both (a) in BA_DAR and (b) annotated by HOMER to the same target gene. Separated by window type. Match logic: Build a (chr, start1based, end) set from OCR_in_BA_DAR_Esr1_Dapk2_Nod1_peaks.txt. For each BA_DAR gene-window peak, convert start+1 and check membership. Summary CSV columns: Window, Gene, Total_BA_DAR, MATCH_n, MATCH_pct, BA_DAR_only_n, BA_DAR_only_pct, MATCH_DAR_10mg, MATCH_DAR_10ug, MATCH_DAR_both, DIFF_DAR_10mg, DIFF_DAR_10ug, DIFF_DAR_both Results — gene_body_Window_1Mb (433 BA_DAR peaks): Gene Total MATCH MATCH% BA_DAR_only Esr1 91 24 26.4% 67 Dapk2 189 21 11.1% 168 Nod1 153 10 6.5% 143 TOTAL 433 55 12.7% 378 MATCH group DAR: DAR_10mg=7, DAR_10ug=14, Both=7 BA_DAR_only DAR: DAR_10mg=60, DAR_10ug=91, Both=49 Results — nearest_TSS_1Mb (54 BA_DAR peaks): Gene Total MATCH MATCH% BA_DAR_only Esr1 23 23 100.0% 0 Dapk2 21 21 100.0% 0 Nod1 10 10 100.0% 0 TOTAL 54 54 100.0% 0 MATCH group DAR: DAR_10mg=7, DAR_10ug=14, Both=7 BA_DAR_only DAR: 0 Key findings: 1. nearest_TSS_1Mb gives 100% concordance. All 54 BA_DAR peaks with nearest_TSS = target gene are also present in the OCR_in_BA_DAR set with HOMER nearest gene = same target gene. The two annotation methods are fully consistent for this window. 2. gene_body_Window_1Mb gives only 12.7% concordance. The remaining 378 BA_DAR peaks fall within 1 Mb of the gene body but are annotated to OTHER (closer) genes in the all-tissue OCR/HOMER analysis. These 378 BA_DAR-only peaks include 91 DARs at 10ug and 60 DARs at 10mg. 3. The one-peak discrepancy (55 OCR peaks vs 54 nearest_TSS peaks) arises because one OCR peak is annotated to a target gene in HOMER but its BA_DAR nearest_TSS column is "." (no annotation within 1 Mb), while it is captured in the gene_body window instead. Code (Python): # see inline script (compare_windows.py), run 2026-03-18 -------------------------------------------------------------------------------- STEP 24 — Per-peak detail: OCR_in_BA_DAR vs BA_DAR nearest_TSS (55 peaks) -------------------------------------------------------------------------------- Input: OCR_in_BA_DAR_peaks.txt (55 target-gene peaks, 1-based) BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt (87,638 OCRs, 0-based) Output: OCR_vs_BADAR_TSS_55peaks_detail.csv (55 rows, 22 columns) Purpose: For each of the 55 OCR peaks annotated by HOMER to Esr1/Dapk2/Nod1, join the corresponding BA_DAR stats (DAR status, logFC, condition, NFI flags) and record whether the BA_DAR Nearest_TSS_1Mb column agrees with the HOMER gene assignment (TSS_match). Coordinate note: OCR_in_BA_DAR_peaks.txt uses 1-based start (HOMER output). BA_DAR files use 0-based start (BED/edgeR format). Conversion applied: key = (chr, str(BA_DAR_start0 + 1), end). Comparing raw coordinate strings across the two systems gives 0 matches (false negative); the +1 correction is required for correct joining. Columns: chr, start_1based, end : peak coordinates (1-based, matching OCR file) OCR_HOMER_gene : Gene Name from OCR_in_BA_DAR (HOMER nearest TSS) HOMER_annotation : genomic feature annotation (HOMER col 8) dist_to_TSS : distance to nearest TSS (HOMER col 10) BA_DAR_nearest_TSS : Nearest_TSS_1Mb from BA_DAR nonDARflag BA_DAR_gene_body : Gene_bodyWindow_1Mb from BA_DAR nonDARflag TSS_match : MATCH if OCR_HOMER_gene == BA_DAR_nearest_TSS logFC_10mg, FDR_10mg : edgeR stats, BPA10mg model DAR_10mg, condition_10mg : DAR status and direction, BPA10mg logFC_10ug, FDR_10ug : edgeR stats, BPA10ug model DAR_10ug, condition_10ug : DAR status and direction, BPA10ug Nfi_LESS_10mg : Nfi_motif_LESS_BPA10mg_BA_M_adt flag Nfi_LESS_10ug : Nfi_motif_LESS_BPA10ug_BA_M_adt flag Nfi_MORE_10ug : Nfi_motif_MORE_BPA10ug_BA_M_adt flag Nfi_any : Nfi_motif_any flag NFI_nonDAR : NFI_non_DAR_present flag Results: Total peaks : 55 (Esr1=24, Dapk2=21, Nod1=10) TSS_match = MATCH : 54 TSS_match = DIFF : 1 The 1 DIFF peak (chr10:4875858-4876339, Esr1): HOMER assigns it to Esr1 (intron, dist_to_TSS = 165,940 bp). BA_DAR Nearest_TSS_1Mb = "." (no TSS annotation within 1 Mb in curated set). BA_DAR Gene_bodyWindow_1Mb = Esr1 (captured by the broader body window). This is the same discrepancy noted in STEP 23 finding 3. DAR status: non-DAR in both doses; no NFI motif. Code (Python): # Build BA_DAR lookup: (chr, str(start0+1), end) -> stats dict badar_lookup = {} with open(nonDARflag_file) as f: next(f) for line in f: p = line.rstrip('\n').split('\t') chrom, s0, end = p[0].split(',') key = (chrom, str(int(s0)+1), end) badar_lookup[key] = { 'logFC_10mg': p[1], 'FDR_10mg': p[4], 'DAR_10mg': p[5], 'condition_10mg': p[6], 'logFC_10ug': p[7], 'FDR_10ug': p[10], 'DAR_10ug': p[11], 'condition_10ug': p[12], 'Gene_bodyWindow': p[13], 'Nearest_TSS': p[14], 'Nfi_LESS_10mg': p[15], 'Nfi_LESS_10ug': p[16], 'Nfi_MORE_10ug': p[17], 'Nfi_any': p[18], 'NFI_nonDAR': p[19], } # Iterate OCR_in_BA_DAR_peaks.txt, keep target genes, join BA_DAR info with open(ocr_in_badar_file) as f: next(f) for line in f: p = line.rstrip('\n').split('\t') if p[15] not in ('Esr1','Dapk2','Nod1'): continue key = (p[1], p[2], p[3]) # chr, start1, end (already 1-based) bd = badar_lookup.get(key, {}) tss_match = 'MATCH' if bd.get('Nearest_TSS') == p[15] else 'DIFF' # ... write row ================================================================================ STEP 25 — Add Gene_bodyWindow_5kb annotation column to nonDARflag file ================================================================================ Input: BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt (87,638 OCRs, 109 cols) ~/TaRGET_II/Li_adt/DAR/genes_Esr1_Dapk2_Nod1.bed Output: BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt (in-place, 110 cols) — new column Gene_bodyWindow_5kb inserted at col 16 (data rows) Method (Gene_bodyWindow_5kb — bedtools window): bedtools window -w 5000 applied between OCR peaks and gene body coordinates (genes_Esr1_Dapk2_Nod1.bed). Window extends 5 kb on BOTH sides of the entire gene body, i.e. [gene_start-5kb, gene_end+5kb]. A peak is assigned to a gene if it falls anywhere within this interval. The three target genes are on different chromosomes (Esr1 chr10, Dapk2 chr9, Nod1 chr6), so no peak can overlap more than one gene. Commands: # Extract BPA male OCR coordinates as sorted BED awk 'NR>1 {split($1,a,","); print a[1]"\t"a[2]"\t"a[3]}' \ BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt | \ sort -k1,1 -k2,2n > /tmp/BPA_male_OCR.sorted.bed # Sort gene body BED sort -k1,1 -k2,2n \ ~/TaRGET_II/Li_adt/DAR/genes_Esr1_Dapk2_Nod1.bed \ > /tmp/genes_Esr1_Dapk2_Nod1.sorted.bed # Run bedtools window -w 5000 bedtools window \ -a /tmp/BPA_male_OCR.sorted.bed \ -b /tmp/genes_Esr1_Dapk2_Nod1.sorted.bed \ -w 5000 | \ awk '{print $1"\t"$2"\t"$3"\t"$7}' > /tmp/OCR_5kb_gene_window.txt Gene body coordinates (from genes_Esr1_Dapk2_Nod1.bed): Esr1 : chr10 4,611,592 – 5,005,614 (gene body length ~394 kb) Dapk2 : chr9 66,158,222 – 66,272,242 (gene body length ~114 kb) Nod1 : chr6 54,923,948 – 54,972,612 (gene body length ~49 kb) Peak counts (Gene_bodyWindow_5kb): Esr1 : 18 peaks Dapk2 : 22 peaks Nod1 : 9 peaks Total : 49 peaks (vs 433 at 1 Mb, vs 54 Nearest_TSS_1Mb) Column insertion (Python): Inserted at 0-based index 15 in both header and data rows (after Nearest_TSS_1Mb, before Nfi_motif_LESS_BPA10mg_BA_M_adt). All downstream columns (NFI flags, sample counts) shift by +1. New column order (1-based data rows): col 1 : OCR key (unnamed) col 2–13 : DAR stats (logFC, logCPM, PValue, FDR, DAR, condition × 2) col 14 : Gene_bodyWindow_1Mb col 15 : Nearest_TSS_1Mb col 16 : Gene_bodyWindow_5kb ← NEW col 17 : Nfi_motif_LESS_BPA10mg_BA_M_adt (was col 16) col 18 : Nfi_motif_LESS_BPA10ug_BA_M_adt (was col 17) col 19 : Nfi_motif_MORE_BPA10ug_BA_M_adt (was col 18) col 20 : Nfi_motif_any (was col 19) col 21 : NFI_non_DAR_present (was col 20) col 22–110 : sample read counts (89 columns, was cols 21–109) Code (Python): BPA = "/home/yan/TaRGET_II/Li_adt/BPA/" infile = BPA + "BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt" # Build lookup from bedtools output: "chr,start,end" -> gene lookup = {} with open("/tmp/OCR_5kb_gene_window.txt") as f: for line in f: p = line.rstrip('\n').split('\t') lookup[f"{p[0]},{p[1]},{p[2]}"] = p[3] # Insert new column at index 15 (0-based) in every row lines = open(infile).readlines() with open(infile, 'w') as fout: for i, line in enumerate(lines): fields = line.rstrip('\n').split('\t') if i == 0: new_fields = fields[:15] + ['Gene_bodyWindow_5kb'] + fields[15:] else: gene_5kb = lookup.get(fields[0], '.') new_fields = fields[:15] + [gene_5kb] + fields[15:] fout.write('\t'.join(new_fields) + '\n') Comparison of window methods for Esr1, Dapk2, Nod1: Method Esr1 Dapk2 Nod1 Total Gene_bodyWindow_5kb 18 22 9 49 Nearest_TSS_1Mb 23 21 10 54 Gene_bodyWindow_1Mb 91 189 153 433 Note: Gene_bodyWindow_5kb captures peaks physically within or immediately flanking the gene body (±5 kb). It is stricter than Gene_bodyWindow_1Mb but not equivalent to Nearest_TSS_1Mb, which requires the target gene to be the genome-wide nearest TSS. A peak can be within 5 kb of a gene body but have a different gene's TSS as its nearest TSS. DAR status breakdown for the 49 Gene_bodyWindow_5kb peaks: Dose Status Esr1 Dapk2 Nod1 Total BPA10mg LESS 0 6 4 10 BPA10mg MORE 0 0 0 0 BPA10mg nonDAR 18 16 5 39 BPA10ug LESS 0 9 4 13 BPA10ug MORE 3 0 0 3 BPA10ug nonDAR 15 13 5 33 Comparison with HOMER OCR annotation (OCR_in_BA_DAR_peaks.txt col 16): HOMER annotatePeaks.pl assigns each peak to its nearest gene by TSS (equivalent to Nearest_TSS_1Mb logic). The 49 Gene_bodyWindow_5kb peaks were compared against the HOMER gene assignment. Gene Total MATCH DIFF MATCH% Esr1 18 18 0 100.0% Dapk2 22 19 3 86.4% Nod1 9 9 0 100.0% Total 49 46 3 93.9% 3 DIFF peaks (Gene_bodyWindow_5kb = Dapk2, HOMER = Herc1): chr9,66270077,66270423 chr9,66275601,66275965 chr9,66276779,66277445 These 3 peaks lie 0–5 kb downstream of Dapk2's gene body end (chr9:66,272,242). They fall within the ±5 kb window around Dapk2, but HOMER assigns them to Herc1 because Herc1's TSS is the genome-wide nearest TSS from those positions. Code (Python — MATCH/DIFF comparison): nondar = BPA + "BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt" ocr_file = BPA + "OCR_in_BA_DAR_peaks.txt" genes = {'Esr1', 'Dapk2', 'Nod1'} # Collect 49 peaks and their 5kb gene assignment (col 16, 0-based idx 15) peaks_5kb = {} with open(nondar) as f: next(f) for line in f: p = line.split('\t') if p[15] in genes: peaks_5kb[p[0]] = p[15] # Build HOMER lookup: convert 1-based start to 0-based key ocr_lookup = {} with open(ocr_file) as f: next(f) for line in f: p = line.split('\t') key = f"{p[1]},{int(p[2])-1},{p[3]}" ocr_lookup[key] = p[15].strip() # Compare for ocr, gene_5kb in peaks_5kb.items(): homer_gene = ocr_lookup.get(ocr, "NOT_FOUND") status = 'MATCH' if homer_gene == gene_5kb else 'DIFF' ================================================================================ STEP 26 — Motif15 (NFIC) scanning in nonDAR 5kb-window peaks (Esr1/Dapk2/Nod1) ================================================================================ Goal: For the non-DAR peaks assigned to Esr1, Dapk2, or Nod1 by Gene_bodyWindow_5kb (from STEP 25), scan for the presence of motif15 (NFIC) from each dose's own HOMER de-novo motif discovery result. Motif files used: BPA10mg motif15 : LESS_BA_DAR_BPA10mg_.../homerResults/motif15.motif Name : 21-CACTTGGC (BestGuess: NFIC/MA0161.2/Jaspar, score 0.860) Length : 8 bp Stats : T:3779.0(37.47%), B:12630.1(32.06%), P:1e-30 BPA10ug motif15 : LESS_BA_DAR_BPA10ug_.../homerResults/motif15.motif Name : 20-BTYTTGGCAG (BestGuess: NFIC/MA0161.2/Jaspar, score 0.894) Length : 10 bp Stats : T:796.0(6.90%), B:1625.3(4.29%), P:1e-36 Input peak sets: BPA10mg nonDAR (DAR_10mg == False AND Gene_bodyWindow_5kb in {Esr1,Dapk2,Nod1}): Esr1=18 Dapk2=16 Nod1=5 Total=39 BPA10ug nonDAR (DAR_10ug == False AND Gene_bodyWindow_5kb in {Esr1,Dapk2,Nod1}): Esr1=15 Dapk2=13 Nod1=5 Total=33 Commands: # Extract nonDAR peak files (HOMER format: PeakID Chr Start1 End Strand) python3 [see Code section below] # Run HOMER annotatePeaks with -m and -mbed flags (genome mm10) annotatePeaks.pl nonDAR_5kb_BPA10mg.bed mm10 \ -m ~/TaRGET_II/Li_adt/LESS/LESS_BA_DAR_BPA10mg_.../homerResults/motif15.motif \ -mbed motif15_10mg_in_nonDAR10mg.bed \ > annotated_nonDAR10mg_motif15_10mg.txt annotatePeaks.pl nonDAR_5kb_BPA10ug.bed mm10 \ -m ~/TaRGET_II/Li_adt/LESS/LESS_BA_DAR_BPA10ug_.../homerResults/motif15.motif \ -mbed motif15_10ug_in_nonDAR10ug.bed \ > annotated_nonDAR10ug_motif15_10ug.txt Results: BPA10mg nonDAR × motif15 (NFIC 8-mer CACTTGGC): Gene Peaks With_motif15 % Esr1 18 10 55.6% Dapk2 16 8 50.0% Nod1 5 2 40.0% Total 39 20 51.3% BPA10ug nonDAR × motif15 (NFIC 10-mer BTYTTGGCAG): Gene Peaks With_motif15 % Esr1 15 1 6.7% Dapk2 13 1 7.7% Nod1 5 0 0.0% Total 33 2 6.1% Note on dose difference: The BPA10mg 8-mer motif (CACTTGGC) is more degenerate and matches more broadly (51.3% hit rate). The BPA10ug 10-mer motif (BTYTTGGCAG) is longer and more stringent, resulting in far fewer hits (6.1%). Both are best-match NFIC motifs. Peaks with motif15 hit — BPA10mg (20 peaks): PeakID Gene Motif_instance (dist, seq, strand, cons) chr10_4611384_4612593 Esr1 814(CCCTTGGC,+,0.00) chr10_4620841_4621436 Esr1 360(AACTGGGC,+,0.00) chr10_4624584_4625129 Esr1 228(GCCAAAGG,-,0.00) chr10_4667082_4667424 Esr1 105(GCCAAAGG,-,0.00) chr10_4709935_4710372 Esr1 159(GCCAAGTG,-,0.00) chr10_4712274_4712902 Esr1 221(CCCTGGGC,+,0.00) chr10_4734095_4734345 Esr1 73(CCTTTGGC,+,0.00) chr10_4738016_4738678 Esr1 153(GCCCAGTT,-,0.00), 569(GCCAAGTC,-,0.00) chr10_4776096_4776322 Esr1 157(GCCAAATT,-,0.00) chr10_4875857_4876339 Esr1 74(ACCTTGGC,+,0.00) chr6_54954715_54955580 Nod1 282(CCCTCGGC,+,0.00) chr6_54971788_54972818 Nod1 32(CCTTTGGC,+,0.00), 808(GCCCAGGG,-,0.00), 864(GCCAAGGC,-,0.00) chr9_66157807_66158752 Dapk2 348(GCCGAGTG,-,0.00) chr9_66163010_66163333 Dapk2 137(GCCAAGGG,-,0.00) chr9_66177753_66178406 Dapk2 90(GCCAAGTG,-,0.00), 324(GCCAAGGG,-,0.00) chr9_66179633_66180213 Dapk2 386(GACTTGGC,+,0.00) chr9_66183160_66183371 Dapk2 117(CCCGTGGC,+,0.00) chr9_66183958_66184286 Dapk2 241(CCCTGGGC,+,0.00) chr9_66212883_66213951 Dapk2 555(GCCAAGAG,-,0.00), 703(AATTTGGC,+,0.00) chr9_66245673_66246296 Dapk2 410(GACGTGGC,+,0.00), 527(GCCCAGTT,-,0.00) Peaks with motif15 hit — BPA10ug (2 peaks): PeakID Gene Motif_instance chr10_4709935_4710372 Esr1 157(CTGCCAAGTG,-,0.00) chr9_66245673_66246296 Dapk2 239(CTGCCAAAAG,-,0.00) Output files (~/TaRGET_II/Li_adt/BPA/motif15_nonDAR_5kb/): nonDAR_5kb_BPA10mg.bed — 39 nonDAR peak input (HOMER fmt, 10mg) nonDAR_5kb_BPA10ug.bed — 33 nonDAR peak input (HOMER fmt, 10ug) annotated_nonDAR10mg_motif15_10mg.txt — HOMER output: 39 peaks + motif15 col annotated_nonDAR10ug_motif15_10ug.txt — HOMER output: 33 peaks + motif15 col motif15_10mg_in_nonDAR10mg.bed — BED of motif15 instances (10mg peaks) motif15_10ug_in_nonDAR10ug.bed — BED of motif15 instances (10ug peaks) Code (Python — extract nonDAR 5kb peak files): infile = BPA + "BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt" genes = {'Esr1', 'Dapk2', 'Nod1'} peaks_10mg, peaks_10ug = [], [] with open(infile) as f: next(f) for line in f: p = line.rstrip('\n').split('\t') gene = p[15] # Gene_bodyWindow_5kb (0-based idx 15) if gene not in genes: continue chrom, s0, end = p[0].split(',') pid = f"{chrom}_{s0}_{end}" row = f"{pid}\t{chrom}\t{int(s0)+1}\t{end}\t+" if p[5] == "False": peaks_10mg.append(row) # DAR_10mg (idx 5) if p[11] == "False": peaks_10ug.append(row) # DAR_10ug (idx 11) Column annotation added to BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt: Two new columns inserted at 0-based index 21–22 (after NFI_non_DAR_present, before sample read-count columns). File updated from 110 → 112 columns. col 22 (idx 21) : motif15_nonDAR_BPA10mg TRUE : peak is nonDAR in BPA10mg AND in Gene_bodyWindow_5kb AND motif15 found FALSE : peak is nonDAR in BPA10mg AND in Gene_bodyWindow_5kb AND motif15 absent . : peak is a DAR in BPA10mg, OR not in Gene_bodyWindow_5kb for {Esr1,Dapk2,Nod1} col 23 (idx 22) : motif15_nonDAR_BPA10ug TRUE : peak is nonDAR in BPA10ug AND in Gene_bodyWindow_5kb AND motif15 found FALSE : peak is nonDAR in BPA10ug AND in Gene_bodyWindow_5kb AND motif15 absent . : peak is a DAR in BPA10ug, OR not in Gene_bodyWindow_5kb for {Esr1,Dapk2,Nod1} New full column order (1-based data rows): col 1 : OCR key (unnamed) col 2–13 : DAR stats (logFC, logCPM, PValue, FDR, DAR, condition × 2 doses) col 14 : Gene_bodyWindow_1Mb col 15 : Nearest_TSS_1Mb col 16 : Gene_bodyWindow_5kb col 17 : Nfi_motif_LESS_BPA10mg_BA_M_adt col 18 : Nfi_motif_LESS_BPA10ug_BA_M_adt col 19 : Nfi_motif_MORE_BPA10ug_BA_M_adt col 20 : Nfi_motif_any col 21 : NFI_non_DAR_present col 22 : motif15_nonDAR_BPA10mg ← NEW col 23 : motif15_nonDAR_BPA10ug ← NEW col 24–112 : sample read counts (89 columns) Code (Python — insert motif15 annotation columns): def build_motif_lookup(homer_file): lookup = {} with open(homer_file) as f: f.readline() for line in f: p = line.rstrip('\n').split('\t') ocr_key = p[0].replace('_', ',', 2) # chr_s0_end → chr,s0,end motif_col = p[-1].strip() lookup[ocr_key] = motif_col != '' and motif_col != 'NA' return lookup lookup_10mg = build_motif_lookup(".../annotated_nonDAR10mg_motif15_10mg.txt") lookup_10ug = build_motif_lookup(".../annotated_nonDAR10ug_motif15_10ug.txt") lines = open(infile).readlines() with open(infile, 'w') as fout: for i, line in enumerate(lines): fields = line.rstrip('\n').split('\t') if i == 0: new_fields = fields[:21] + ['motif15_nonDAR_BPA10mg', 'motif15_nonDAR_BPA10ug'] + fields[21:] else: key = fields[0] gene = fields[15] # Gene_bodyWindow_5kb v_10mg = (str(lookup_10mg.get(key,'.')).upper() if gene in genes and fields[5] == 'False' else '.') v_10ug = (str(lookup_10ug.get(key,'.')).upper() if gene in genes and fields[11] == 'False' else '.') new_fields = fields[:21] + [v_10mg, v_10ug] + fields[21:] fout.write('\t'.join(new_fields) + '\n') ================================================================================ STEP 27 — Nfi×DAR group summary for 5kb-window peaks (Gene_bodyWindow_5kb) ================================================================================ Input: BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt (112 cols, 87,638 OCRs) Output: Nfi_DAR_group_summary_Esr1_Dapk2_Nod1_5kb.csv Gene filter : Gene_bodyWindow_5kb (idx 15) ∈ {Esr1, Dapk2, Nod1} Peak counts : Esr1=18 Dapk2=22 Nod1=9 Total=49 Motif source by DAR group and dose: DAR_LESS BPA10mg : Nfi_motif_LESS_BPA10mg_BA_M_adt (idx 16, True/False) DAR_MORE BPA10mg : no HOMER MORE run → Nfi+ = 0 by definition DAR_LESS BPA10ug : Nfi_motif_LESS_BPA10ug_BA_M_adt (idx 17, True/False) DAR_MORE BPA10ug : Nfi_motif_MORE_BPA10ug_BA_M_adt (idx 18, True/False) non-DAR BPA10mg : motif15_nonDAR_BPA10mg (idx 21, TRUE/FALSE/.) non-DAR BPA10ug : motif15_nonDAR_BPA10ug (idx 22, TRUE/FALSE/.) DAR grouping per peak per dose: DAR_10mg=True & condition_10mg=LESS → DAR_LESS (BPA10mg) DAR_10mg=True & condition_10mg=MORE → DAR_MORE (BPA10mg) DAR_10mg=False → non-DAR (BPA10mg) (same logic applied independently for BPA10ug using idx 11 and 12) Results: Gene,Dose,DAR Group,Total regions,Nfi+ (motif present),Nfi- (motif absent),Nfi+ % Esr1,BPA10mg,DAR_MORE,0,0,0,N/A Esr1,BPA10mg,DAR_LESS,0,0,0,N/A Esr1,BPA10mg,non-DAR,18,10,8,55.6% Esr1,BPA10ug,DAR_MORE,3,3,0,100.0% Esr1,BPA10ug,DAR_LESS,0,0,0,N/A Esr1,BPA10ug,non-DAR,15,1,14,6.7% Dapk2,BPA10mg,DAR_MORE,0,0,0,N/A Dapk2,BPA10mg,DAR_LESS,6,4,2,66.7% Dapk2,BPA10mg,non-DAR,16,8,8,50.0% Dapk2,BPA10ug,DAR_MORE,0,0,0,N/A Dapk2,BPA10ug,DAR_LESS,9,1,8,11.1% Dapk2,BPA10ug,non-DAR,13,1,12,7.7% Nod1,BPA10mg,DAR_MORE,0,0,0,N/A Nod1,BPA10mg,DAR_LESS,4,2,2,50.0% Nod1,BPA10mg,non-DAR,5,2,3,40.0% Nod1,BPA10ug,DAR_MORE,0,0,0,N/A Nod1,BPA10ug,DAR_LESS,4,1,3,25.0% Nod1,BPA10ug,non-DAR,5,0,5,0.0% Code (Python): from collections import defaultdict infile = BPA + "BA_DAR_BPA10mg_10ug_merged_Nfi_nonDARflag.txt" outfile = BPA + "Nfi_DAR_group_summary_Esr1_Dapk2_Nod1_5kb.csv" genes = ['Esr1', 'Dapk2', 'Nod1'] tally = defaultdict(lambda: defaultdict(lambda: defaultdict( lambda: {'total':0,'nfi':0}))) with open(infile) as f: next(f) for line in f: p = line.rstrip('\n').split('\t') gene = p[15] # Gene_bodyWindow_5kb if gene not in genes: continue for dose, dar_idx, cond_idx, nfi_less_idx, nfi_more_idx, m15_idx in [ ('BPA10mg', 5, 6, 16, None, 21), ('BPA10ug', 11, 12, 17, 18, 22), ]: if p[dar_idx] == "True": grp = "DAR_" + p[cond_idx] tally[gene][dose][grp]['total'] += 1 if grp == "DAR_LESS" and p[nfi_less_idx].lower() == "true": tally[gene][dose][grp]['nfi'] += 1 elif grp == "DAR_MORE" and nfi_more_idx and \ p[nfi_more_idx].lower() == "true": tally[gene][dose][grp]['nfi'] += 1 else: tally[gene][dose]['non-DAR']['total'] += 1 if p[m15_idx] == "TRUE": tally[gene][dose]['non-DAR']['nfi'] += 1 ================================================================================ REFERENCE FILES USED ================================================================================ ~/TaRGET_II/Li_adt/DAR/OCR_allTissue.txt Master differential accessibility table across all TaRGET II conditions. Columns: OCR, logFC, logCPM, PValue, FDR, DAR, Tissue, Age, Sex, Exposure, Lab Total rows: ~4.2M | Tissues: Bl, Br, Li | Ages: adt, wl | Sexes: F, M Exposures: As, BPA10mg, BPA10ug, DEHP, Pb, PM2.5, TBT, TCDD Labs: AL, BA, BI, DO, MU, WK, ZB ~/TaRGET_II/Li_adt/DAR/OCR_allTissue_annotated.txt Same as above with three additional columns appended: Gene_bodyWindow_1Mb, Nearest_TSS_1Mb, Dist_to_TSS Total annotated rows (non-dot): 20,478 out of ~4.2M (sparse — annotation limited to a curated set of genes of interest) ~/TaRGET_II/Li_adt/BPA/BPA_adt_OCR_table.csv BPA-specific OCR table with condition labels. Columns: OCR, logFC, logCPM, PValue, FDR, DAR, Tissue, Age, Sex, Exposure, Lab, condition Total rows: 354,106 | Ages: adt | Sex: F & M | Exposures: BPA10mg, BPA10ug condition values: LESS (closed in BPA), MORE (open in BPA) ================================================================================ EXTRACTED SUBSETS ================================================================================ OCR_Li_adt_F_BPA_BA.txt (2026-03-31) Subset of ~/TaRGET_II/Li_adt/DAR/OCR_allTissue.txt filtered to: Tissue=Li, Age=adt, Sex=F, Exposure=BPA10mg or BPA10ug, Lab=BA Columns: OCR, logFC, logCPM, PValue, FDR, DAR, Tissue, Age, Sex, Exposure, Lab, status Total rows: 179,598 (+ 1 header) status column (added 2026-03-31): MORE : logFC > 0 (more accessible in BPA vs control) LESS : logFC < 0 (less accessible in BPA vs control) Command: awk 'NR==1 || ($7=="Li" && $8=="adt" && $9=="F" && ($10=="BPA10mg" || $10=="BPA10ug") && $11=="BA")' \ OCR_allTissue.txt > OCR_Li_adt_F_BPA_BA.txt # status column appended: awk 'BEGIN{OFS="\t"} NR==1{print $0,"status"} NR>1{print $0, ($2>0?"MORE":"LESS")}' \ OCR_Li_adt_F_BPA_BA.txt > tmp.txt && mv tmp.txt OCR_Li_adt_F_BPA_BA.txt -------------------------------------------------------------------------------- BPA_female/MORE_DAR_F_Li_adt_BPA_BA.bed (2026-03-31) BPA_female/LESS_DAR_F_Li_adt_BPA_BA.bed (2026-03-31) Union BED files of DAR==TRUE peaks for MORE and LESS groups respectively, across BPA10mg and BPA10ug doses. Each peak annotated by dose overlap. Columns: chr, start, end, annotation annotation values: shared : DAR in both BPA10mg and BPA10ug 10mg_unique: DAR in BPA10mg only 10ug_unique: DAR in BPA10ug only MORE DAR summary (3,674 peaks total): shared : 251 10mg_unique: 2,705 10ug_unique: 718 LESS DAR summary (3,240 peaks total): shared : 151 10mg_unique: 2,520 10ug_unique: 569 Input counts (DAR==TRUE, Sex=F, Lab=BA): MORE BPA10mg: 2,956 | MORE BPA10ug: 969 LESS BPA10mg: 2,671 | LESS BPA10ug: 720 Code (Python): def load_set(path): with open(path) as f: return set(line.strip() for line in f) def write_bed(ocr_set_a, ocr_set_b, label_a, label_b, outpath): union = ocr_set_a | ocr_set_b with open(outpath, 'w') as out: out.write("chr\tstart\tend\tannotation\n") for ocr in sorted(union): chrom, start, end = ocr.split(',') in_a = ocr in ocr_set_a in_b = ocr in ocr_set_b if in_a and in_b: ann = "shared" elif in_a: ann = f"{label_a}_unique" else: ann = f"{label_b}_unique" out.write(f"{chrom}\t{start}\t{end}\t{ann}\n") -------------------------------------------------------------------------------- BPA_female/plot_venn_DAR_F.R / plot_venn_DAR_F.pdf (2026-03-31) Two-panel Venn diagram of DAR==TRUE peaks (Sex=F, Lab=BA, Tissue=Li, Age=adt). Left panel : MORE DAR — BPA10mg vs BPA10ug overlap (red tones) Right panel : LESS DAR — BPA10mg vs BPA10ug overlap (blue tones) Package: VennDiagram -------------------------------------------------------------------------------- BPA_female/MORE_DAR_F_Li_adt_BPA_BA_nearestGene.txt (2026-03-31) BPA_female/LESS_DAR_F_Li_adt_BPA_BA_nearestGene.txt (2026-03-31) Genome-wide nearest gene annotation for MORE and LESS DAR BED files. Each peak assigned to the single nearest TSS across all protein-coding and lncRNA genes (no gene filter applied). Columns: chr, start, end, annotation, gene_id, gene_name, gene_type, strand, dist_to_TSS annotation : shared / 10mg_unique / 10ug_unique (from BED file) gene_id : Ensembl gene ID (GENCODE vM10) gene_name : gene symbol gene_type : protein_coding or lncRNA strand : gene strand (+ / -) dist_to_TSS : signed distance in bp from peak to nearest TSS negative = peak is upstream of TSS positive = peak is downstream of TSS 0 = peak overlaps TSS Peak counts (incl. header): MORE : 3,678 peaks LESS : 3,240 peaks TSS reference: /home/yan/TaRGET_II/Li_adt/Target_expose/ gencode_mm10_gene_TSS_pc_lncRNA.sorted.bed GENCODE vM10 mm10, protein-coding + lncRNA genes Command: TSS=/home/yan/TaRGET_II/Li_adt/Target_expose/gencode_mm10_gene_TSS_pc_lncRNA.sorted.bed for status in MORE LESS; do tail -n +2 ${status}_DAR_F_Li_adt_BPA_BA.bed \ | bedtools sort -i - \ | bedtools closest -a - -b $TSS -D a -k 1 -sorted \ | awk 'BEGIN{OFS="\t"; print "chr","start","end","annotation",\ "gene_id","gene_name","gene_type","strand","dist_to_TSS"} \ {print $1,$2,$3,$4,$8,$9,$10,$11,$NF}' \ > ${status}_DAR_F_Li_adt_BPA_BA_nearestGene.txt done -------------------------------------------------------------------------------- BPA_female/DEG_Bartolomei_liver_20weeks_female.txt (2026-03-31) BPA_female/DEG_Bartolomei_liver_20weeks_male.txt (2026-03-31) DEG subsets extracted from: ~/TaRGET_II/Li_adt/BPA/share/ 20240130_liver_blood_treatment_deg_after_PseudoCtrl_using_all_control.txt Filter: lab=Bartolomei, tissue=liver, stage=20weeks, sex=Female/Male Columns (17): baseMean, log2FoldChange, lfcSE, stat, pvalue, padj, cond1, cond2, gene, lab, type, sig, direction, group, tissue, stage, sex Exposures (cond1): BPA10mg_Female_adult, BPA10ug_Female_adult (female file) BPA10mg_Male_adult, BPA10ug_Male_adult (male file) Row counts (excl. header): Female : 24,440 Male : 24,561 Command: awk -F'\t' 'NR==1 || ($10=="Bartolomei" && $15=="liver" && $16=="20weeks" && $17=="Female")' \ 20240130_liver_blood_treatment_deg_after_PseudoCtrl_using_all_control.txt \ > DEG_Bartolomei_liver_20weeks_female.txt # same with $17=="Male" for male file -------------------------------------------------------------------------------- BPA_female/MORE_DAR_F_Li_adt_BPA_BA_nearestGene_DEG.txt (2026-03-31) BPA_female/LESS_DAR_F_Li_adt_BPA_BA_nearestGene_DEG.txt (2026-03-31) Filtered join of nearestGene files with DEG_Bartolomei_liver_20weeks_female.txt. Keeps only DAR peaks whose nearest gene appears in the female DEG list. Each peak row is expanded once per matching DEG entry (peak x DEG row). Columns (26): chr, start, end, annotation, gene_id, gene_name, gene_type, strand, dist_to_TSS, baseMean, log2FoldChange, lfcSE, stat, pvalue, padj, cond1, cond2, gene, lab, type, sig, direction, group, tissue, stage, sex Row counts: MORE : 5,382 (1,760 unique genes matched) LESS : 4,769 (1,575 unique genes matched) sig=TRUE breakdown: MORE : 35 rows (18 down, 17 up) LESS : 38 rows (22 down, 16 up) Join key: nearestGene.gene_name == DEG.gene Source files: nearestGene : MORE/LESS_DAR_F_Li_adt_BPA_BA_nearestGene.txt DEG : DEG_Bartolomei_liver_20weeks_female.txt Code (Python): deg_rows = {} # {gene_name: [row, ...]} with open(deg_file) as f: for row in csv.DictReader(f, delimiter='\t'): deg_rows.setdefault(row['gene'], []).append(row) with open(ng_file) as f: for ng in csv.DictReader(f, delimiter='\t'): if ng['gene_name'] in deg_rows: for deg in deg_rows[ng['gene_name']]: writer.writerow({**ng, **deg}) ================================================================================