Transcriptional Signature

```
cat("\n=== Running GO Enrichment Analysis ===\n")
cat("Input genes:", length(sig_down), "\n")
```

```
# GO Biological Process enrichment
go strange <- run strange
  gene_list = sig_down,
  background genes = all genes,
  ont = "BP"
)
```

```
cat("\nTop 10 Enriched GO Biological Processes:\n")
print(head(go strange@result[, c("Description", "GeneRatio", "pvalue",
"p adjustable")], 10))
```

```
# KEGG pathway enrichment
kegg result <- run kegg enrichment(
  gene_list = sig_down,
  background genes = all genes
)
```

```
cat("\nTop 10 Enriched KEGG Pathways:\n")
print(head(kegg result@result[, c("Description", "GeneRatio", "pvalue",
"p adjustable")], 10))
```

#--------------------------------------------

---

# GSEA with signed log p-values as ranking metric

#--------------------------------------------

---

# Create ranking metric: -log10(p) * sign(FC)
de results$rank metric <- -log10(de results$P.Value + 1e-100) *
sign(de results$logFC)

# Create named vector
ranked genes <- de results$rank metric
names(ranked genes) <- de results$gene symbol
ranked genes <- ranked genes[!is.na happened(ranked genes)]
ranked genes <- sort(ranked genes, decreasing = TRUE)

```
cat("\n=== Running GSEA ===\n")
```

```
gsea go <- run gsea analysis(
  ranked genes = ranked genes,
  gene sets = "GO"
)
```

```
cat("\nTop 10 Enriched Gene Sets (GSEA):\n")
print(head(gsea go@result[, c("Description", "NES", "pvalue",
"p adjustable")], 10))
```

#--------------------------------------------

---

# VISUALIZATION
# Dot plot for GO enrichment
pdf("figures/go_enrichment.dotplot.pdf", width = 10, height = 8)
dotplot(go bp, showCategory = 20, title = "GO Biological Process
Enrichment") +
  theme(axis.text.y = element_text(size = 8))
dev.off()

# Bar plot for KEGG pathways
pdf("figures/kegg_enrichment.barplot.pdf", width = 10, height = 6)
barplot(kegg_result, showCategory = 15, title = "KEGG Pathway Enrichment")
dev.off()

# GSEA enrichment plot for top pathway
if (nrow(gsea go@result) > 0) {
  top_pathway <- gsea go@result$ID[1]
  pdf("figures/gsea_enrichment plot.pdf", width = 8, height = 6)
  gseaplot2(gsea go, geneSetID = top_pathway, title =
    gsea go@result$Description[1])
  dev.off()
}

# Enrichment map (network of related pathways)
pdf("figures/enrichment_map.pdf", width = 12, height = 10)
go bp simplified <- simplify (go bp)
emapplot(pairwise termsim (go bp simplified), showCategory = 30)
dev.off()

cat("\nEnrichment plots saved to figures/ directory\n")

# Save results tables
write.csv(go bp@result, "tables/GO BP enrichment.csv", row.names = FALSE)
write.csv(kegg_result@result, "tables/KEGG enrichment.csv", row.names =
  FALSE)
write.csv(gsea go@result, "tables/GSEA results.csv", row.names = FALSE)

D.5.2 fgsea Fast GSEA

# R

# FAST GSEA WITH FGSEA PACKAGE
#-------------------------------------------
library(fgsea)
library(msigdbr)
Transcriptional Signature

#' Run fast GSEA analysis
#'
#' @param ranked_genes Named numeric vector of ranking statistics
#' @param pathways List of gene sets (each element is a character vector)
#' @param n_perm Number of permutations for p-value calculation
run_fgsea <- function(
  ranked_genes,
  pathways,
  min_size = 15,
  max_size = 500,
  n_perm = 10000
) {
  # Run fgsea
  fgsea_result <- fgsea(
    pathways = pathways,
    stats = ranked_genes,
    minSize = min_size,
    maxSize = max_size,
    nperm = n_perm
  )

  # Sort by NES
  fgsea_result <- fgsea_result[order(-abs(NES)), ]

  return(fgsea_result)
}

# Get Hallmark gene sets from MSigDB
hallmark_sets <- msigdbr(species = "Homo sapiens", category = "H") %>%
  split(x = .$gene_symbol, f = .$gs_name)

# Custom inflammatory gene sets
custom_sets <- list(
  "NFKB_TARGETS" = c("NFKB1", "NFKB2", "RELA", "IL1B", "IL6", "TNF",
    "PTGS2", "ICAM1", "CCL2", "RIPK2"),
  "INTERFERON_RESPONSE" = c("IFIT1", "IFIT2", "IFIT3", "MX1", "MX2",
    "OAS1", "OAS2", "ISG15", "IRF7", "STAT1"),
  "CTRA_PROINFLAMMATORY" = c("IL1B", "IL6", "CXCL8", "TNF", "PTGS2",
    "NFKB1", "RELA", "CCL2", "CXCL1"),
  "CTRA_ANTIVIRAL" = c("IFIT1", "IFIT2", "MX1", "OAS1", "OAS2",
    "ISG15", "IRF7", "IFITM1", "IFITM3")
)

# Combine gene sets
all_sets <- c(hallmark_sets, custom_sets)

# Run fgsea
fgsea_results <- run_fgsea(
  ranked_genes = ranked_genes,
  pathways = all_sets,
  n_perm = 10000
)

# Print results
cat("\n=== fgsea Results (FDR < 0.25) ===\n")
Transcriptional Signature

```r
sig_pathways <- fgsea_results[padj < 0.25]
print sig_pathways[., .(pathway, NES, pval, padj, size)])

# Focus on inflammatory pathways
inflammatory_keyword <- c("INFLAMMATORY", "IMMUNE", "INTERFERON", "NFKB",
"CTRA")
inflammatory_results <- fgsea_results[
  grepl(paste(inflammatory_keyword, collapse = "|"), pathway, ignore.case
  = TRUE)
]

cat("\n=== Inflammatory Pathway Results ===\n")
print(inflammatory_results[, .(pathway, NES, pval, padj)])

# Visualization: Enrichment plot
df <- "figures/fgsea_nfkb_enrichment.pdf", width = 8, height = 4)
plotEnrichment(all_sets["NFKB_TARGETS"], ranked genes) +
  ggtitle("GSEA: NF-κB Target Genes") +
  theme_minimal()
dev.off()

# Summary table
df <- "figures/fgsea_table plot.pdf", width = 12, height = 8)
plotGseaTable(all_sets[head(fgsea_results$pathway, 10)],
  ranked genes,
  fgsea_results,
  gseaParam = 0.5)
dev.off()

# Save results
write.csv(fgsea_results, "tables/fgsea_results.csv", row.names = FALSE)
```

## D.6 Python Code for Data Visualization

This section provides Python code for creating publication-quality visualizations including combined volcano plots, effect size comparisons, and pathway enrichment heatmaps using the Plotly library.

### D.6.1 Combined Multi-Study Volcano Plots

```r
# Python
```

```r
#============================================================
##
# COMBINED VISUALIZATION CODE
#
# Purpose: Create publication-quality figures for dissertation
```
```python
# Packages: plotly, pandas, numpy
# Output: Interactive HTML and static PDF figures
#---------------------------------------------------------------------------
```

```python
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import os
```

```python
OUTPUT_DIR = "/home/ubuntu/dissertation_analysis"
```

```python
def createcombined_volcano plots(
    de_files: dict,
    output_file: str = "figures/combined_volcano.html"
):
    """
    Create combined volcano plot panel for multiple studies.

    Parameters
    -----------
    de_files : dict
        Dictionary mapping study names to DE result file paths
    output_file : str
        Path to save HTML figure
    """
```

```python
Returns
-------
plotly.graph_objects.Figure
"""
n_studies = len(de_files)

# Create subplot grid
fig = make_subplots(
    rows=1, cols=n_studies,
    subplottitles: list(de_files.keys()),
    horizontalSpacing=0.08
)

for i, (study_name, file_path) in enumerate(de_files.items(), 1):
    if not os.path.exists(file_path):
        continue

    # Load DE results
    df = pd.read_csv(file_path)

    # Sample for performance (keep significant genes)
    dfsig: df[df['p_value'] < 0.01]
    df ns = df[df['p_value'] >= 0.01].sample(min(3000, len(df)))]
    df ``plot = pd.concat([df ``sig, df ``ns])

    # Calculate -log10(p)
    df ``plot[-log10_p] = -np.log10(df ``plot['p_value']``.clip(1e-100))
```
```python
# Update axes
fig.update_xaxes(title_text="Log2 Fold Change")
fig.update_yaxes(title_text="-Log10(p-value)", col=1)
```

```python
# Save
fig.write_html(output_file, include_plotlyjs='cdn')
print(f"Combined volcano plot saved: {output_file}")
```

```python
return fig
```

```python
# Example usage
de_files = {
    "Relaxation Response\n(GSE10041)":
        f"{OUTPUT_DIR}/tables/GSE10041_DE_results.csv",
    "Mindfulness MCI\n(GSE108215)":
        f"{OUTPUT_DIR}/tables/GSE108215_DE_results.csv",
    "Sudarshan Kriya\n(GSE44777)":
        f"{OUTPUT_DIR}/tables/GSE44777_DE_results.csv"
}
```

```python
fig = createzecombinedvolcano plots(de_files)
```

## D.6.2 Effect Size Comparison Plots

```python
# Python
```

```python
# effect size comparison visualization
```

```python
def create_e effect_size_forest plot(
    meta_results: pd. Frame,
    output_file: str = "figures/meta_forest plot.html"
):
    ...
    Create forest plot of meta-analysis results.
```

### Parameters

```
meta_results : pd. Frame
    Data Frame with columns: study, effect_size, ci_lower, ci_lower,
weight
output_file : str
    Path to save figure
```

### Returns

```
plotly.graph_objects.Figure
```
```python
),
name='Pooled Effect'
))

# Add vertical line at zero (no effect)
fig.add_vline(x=0, line_dash="dash", line_color="gray")

# Update layout
fig.update_layout(
    title(dict(
        text="Forest Plot: Effect of Mind-Body Interventions on NF-κB
        Target Genes",
        x=0.5
    ),
    xaxis_title="Hedges' g (95% CI)",
    yaxis_title="",
    xaxis(dict(range=[-1.5, 0.5], zeroline=True),
    template="plotly_white",
    height=max(400, len(meta_results) * 40),
    width=800,
    legend(dict(x=1.02, y=1),
    margin(dict(l=200))
)

# Save
fig.write_html(output_file, include_plotlyjs='cdn')

return fig


def create_de_gene_count_barplot(
    de_counts: pd.DataFrame,
    output_file: str = "figures/de_gene_counts.html"
) -> go.Figure:
    """
    Create bar chart of DE gene counts across studies.
    """
    Parameters
    ----------
    de_counts : pd.DataFrame
        DataFrame with columns: study, upregulated, downregulated,
    total_sig
    output_file : str
        Path to save figure
    Returns
    -------
    plotly.graph_objects.Figure
        """
    fig = go.Figure()

    # Upregulated bars (positive)
    fig.add_trace(go.Bar(
        name='Upregulated',
        x=de_counts['study'],
        y=de_counts['upregulated'],
```
Transcriptional Signature

```python
marker_color='#E74C3C',
text=de_counts['upregulated'],
textposition='outside'
))

# Downregulated bars (negative, shown as positive for clarity)
fig.add_trace(go.Bar(
    name='Downregulated',
    x=de_counts['study'],
    y=de_counts['downregulated'],
    marker_color='#3498DB',
    text=de_counts['downregulated'],
    textposition='outside'
))

fig.update_layout(
    title=dict(
        text="Differentially Expressed Genes by Study (FDR < 0.05, |log2FC| > 0.3)",
        x=0.5
    ),
    xaxis_title="Study",
    yaxis_title="Number of DE Genes",
    barmode='group',
    template="plotly_white",
    legend=dict(x=1.02, y=1),
    height=450,
    width=700
)

fig.write_html(output_file, include_plotlyjs='cdn')

return fig
```

### D.6.3 Pathway Enrichment Heatmap

```python
# Python

# PATHWAY ENRICHMENT HEATMAP
```
Parameters
----------
enrichment_results : dict
  Dictionary mapping study names to enrichment DataFrames
  Each DataFrame should have columns: pathway, p adjusted, NES
output_file : str
  Path to save figure
top_n : int
  Number of top pathways to display

Returns
-------
plotly.graph_objects.Figure
"
"
# Collect all significant pathways
all_pathways = set()
for study, df in enrichment_results.items():
    sig_pathways = df[df['p adjusted'] < 0.1]['pathway'].list()
    all_pathways.update(sig_pathways[:top_n])

all_pathways = sorted(list(all_pathways))
studies = list(enrichment_results.keys())

# Create matrix of -log10(p) values
matrix = np.zeros((len(all_pathways), len(studies)))
nes_matrix = np zeros((len(all_pathways), len(studies)))

for j, study in enumerate(studies):
    df = enrichment_results[study]
    for i, pathway in enumerate(all_pathways):
        pathway_data = df[df['pathway'] == pathway]
        if len(pathway_data) > 0:
            p_val = pathway_data['p adjusted'].values[0]
            nes = pathway_data.get('NES',
                                  pathway_data.get('enrichmentScore', [0
                                     ).values[0]
                                   ).values[0]
            matrix[i, j] = -np.log10(max(p_val, 1e-10)) * np.sign(nes)
            nes_matrix[i, j] = nes

# Create heatmap
fig = go.Figure(data=go.Heatmap(
    z=matrix,
    x=studies,
    y=all_pathways,
    colorscale='RdBu_r',
    zmid=0,
    colorbar=dict(
        title=dict(text="-log10(FDR) x sign(NES)", side="right"),
        thickness=15
    ),
    hovertemplate="<b>%{y}</b><br>" +
        "Study: %{x}<br>" +
        "Score: %{z:.2f}<extra></extra>"
))

fig.update_layout(
Transcriptional Signature

```python
title(dict(
    text="Pathway Enrichment Across Mind-Body Intervention
Studies",
    x=0.5
),
xaxis_title="Study",
yaxis_title="Pathway",
template="plotly_white",
height=max(500, len(all_pathways) * 22),
width=max(600, len(studies) * 150),
yaxis(dict(tickfont(dict(size=10))))
)
```

```python
fig.write_html(output_file, include_plotlyjs='cdn')
```

```python
return fig
```

```python
def create_inflammatory_geneaaldotplot(
    expr_summary: pd.DataFrame,
    output_file: str = "figures/inflammatory_geneaaldotplot.html"
) -> go.Figure:
    """
    Create dot plot showing inflammatory gene expression changes.
```

Parameters

expr_summary : pd.DataFrame
  DataFrame with columns: gene, study, log2FC, p_value

output_file : str
  Path to save figure

Returns

plotly.graph_objects.Figure
"""
# Key inflammatory genes
key_genes = ['NFKB1', 'RELA', 'IL1B', 'IL6', 'TNF', 'PTGS2',
             'IFIT1', 'MX1', 'OAS1', 'IRF7']

# Filter to key genes
df = expr_summary[expr_summary['gene'].isin(key_genes)]

# Create figure
go.Figure()

for study in df['study'].unique():
    study DF = df[df['study'] == study]

    # Size by significance, color by direction
    sizes = -np.log10(study DF['p_value)].clip(1e-10)) * 3
colors = study DF['log2FC)].apply(
    lambda x: '#E74C3C' if x > 0 else '#3498DB'
)
```

```python
fig.add_trace(go.Scatter(
```
```python
x=study DF['log2FC'],
y=study DF['gene'],
mode='markers',
marker=dict(
    size=sizes,
    color=colors,
    opacity=0.7,
    line=dict(color='white', width=1)
),
name=study,
hovertemplate="<b>%{y}</b><br>" +
    f"Study: {study}<br>" +
    "log2FC: %{x:.3f}<extra></extra>"
))

# Add vertical line at zero
fig.add_vline(x=0, line="dash", line_color="gray")

fig.update_layout(
    title=dict(
        text="Expression Changes in Key Inflammatory Genes",
        x=0.5
    ),
    xaxis title="Log2 Fold Change (Post vs Pre)",
    yaxis title="Gene",
    template="plotly white",
    legend=dict(x=1.02, y=1),
    height=500,
    width=800
)

fig.write_html(output_file, include_plotlyjs='cdn')

return fig
```

## D.7 Supplementary Functions and Utility Scripts

This section provides custom helper functions and utility scripts used throughout the analysis pipeline.

### D.7.1 Data Utility Functions

```python
# Python
```

```python
#====================================================================

# UTILITY FUNCTIONS AND HELPER SCRIPTS
#====================================================================
```
import pandas as pd
import numpy as np
from typing import List, Dict, Tuple, Optional
import os
import json
from datetime import datetime

#------------------------------------------
# FILE I/O UTILITIES
#------------------------------------------
#

def load_all_de_results(
    data_dir: str,
    pattern: str = "*_DE_results.csv"
) -> Dict[str, pd.DataFrame]:
    """
    Load all DE result files from a directory.

Parameters

data_dir : str
Directory containing DE result files
pattern : str
File name pattern to match

Returns

Dict[str, pd.DataFrame]
Dictionary mapping study IDs to DE results
"""
import glob

results = {}
files = glob.glob(os.path.join(data_dir, pattern))

for file_path in files:
    study_id = os.pathatter(file_path).replace("_DE_results.csv",
")
    results[study_id] = pd.read_csv(file_path)
    print(f"Loaded {study_id}: {len(results[study_id])} genes")

return results

def save_analysis_log(
    output_dir: str,
    analysis_name: str,
    parameters: dict,
    results_summary: dict
):
    """
    Save analysis log for reproducibility.
Parameters

---

output_dir : str

Output directory

analysis_name : str

Name of the analysis

parameters : dict

Analysis parameters

results_summary : dict

Summary of results

###

log = {

"analysis_name": analysis_name,
"timestamp": datetime.now().isoformat(),
"parameters": parameters,
"results_summary": results_summary

log_file = os.path.join(output_dir, f"{analysis_name}_log.json")

with open(log_file, 'w') as f:

json.dump(log, f, indent=2, default=str)

print(f"Analysis log saved: {log_file}")

#

---

# GENE SET UTILITIES

#

---

# Curated gene sets for mind-body research

MIND_BODY_GENE_SETS = {

"NFKB_TARGETS": [
  "NFKB1", "NFKB2", "RELA", "RELB", "REL", "NFKBIA", "NFKBIB",
  "IL1B", "IL6", "CXCL8", "TNF", "TNFAIP3", "CXCL1", "CXCL2",
  "CCL2", "CCL5", "ICAM1", "VCAM1", "SELE", "PTGS2", "NOS2",
  "SOD2", "BCL2", "BCL2L1", "BIRC2", "BIRC3", "MMP9", "VEGFA",
  "SERPINE1", "RIPK2", "HDAC2", "HDAC3", "FOS", "JUN", "EGR1"
],

"INTERFERON_TYPE_I": [
  "IFIT1", "IFIT2", "IFIT3", "IFIT5", "IFITM1", "IFITM2", "IFITM3",
  "ISG15", "ISG20", "MX1", "MX2", "OAS1", "OAS2", "OAS3", "OASL",
  "IRF1", "IRF7", "IRF9", "STAT1", "STAT2", "IFI6", "IFI27",
  "IFI35", "IFI44", "IFI44L", "RSAD2", "DDX58", "IFIH1", "TRIM22"
],

"CTRA_PROINFLAMMATORY": [
  "IL1B", "IL6", "CXCL8", "TNF", "PTGS2", "NFKB1", "NFKB2",
  "RELA", "RIPK2", "CCL2", "CXCL1", "CXCL2"
],

"CTRA_ANTIVIRAL": [
  "IFIT1", "IFIT2", "IFIT3", "MX1", "MX2", "OAS1", "OAS2",
  "OAS3", "ISG15", "IRF7", "IFITM1", "IFITM3"
],

"STRESS RESPONSE": [
  "NR3C1", "FKBP5", "SGK1", "DUSP1", "GILZ", "NFKBIA",
  "IL10", "TGFB1", "SOCS1", "SOCS3"
]
```python
],
"TELOMEREMAINTENANCE": [
    "TERT", "TERC", "DKC1", "NHP2", "NOP10", "GAR1",
    "RTEL1", "POT1", "TPP1", "TIN2"
]
}

def get_gene_set_enrichment(
    gene_list: List[str],
    gene_sets: Dict(str, List(str]) = None
) -> pd.DataFrame:

    """
    Calculate overlap between gene list and predefined gene sets.

    Parameters
    -----------
    gene_list : List(str]
        List of gene symbols
    gene_sets : Dict(str, List(str])
        Dictionary of gene sets (default: MIND_BODY_GENE_SETS)

    Returns
    -------
    pd.DataFrame
        Enrichment results with overlap counts and percentages
    """
    from scipy.stats import hypergeom

    if gene_sets is None:
        gene_sets = MIND_BODY_GENE_SETS

    gene_list_upper = [g.upper() for g in gene_list]

    results = []
    for gs_name, gs_genes in gene_sets.items():
        gs_genes_upper = [g.upper() for g in gs_genes]
        overlap = set(gene_list_upper) & set gs_genes_upper)

        # Hypergeometric test (assuming 20,000 background genes)
        M = 20000 # Population size
        n = len gs_genes) # Successes in population
        N = len(gene_list) # Sample size
        k = len(overlap) # Observed successes

        p_value = hypergeom.sf(k - 1, M, n, N)

        results.append({
            'gene_set': gs_name,
            'gene_set_size': len gs_genes),
            'overlap_count': len(overlap),
            'overlap_genes': ', '.join sorted(overlap)),
            'overlap_pct': len(overlap) / len gs_genes) * 100 if gs_genes
        }
    else:
        'p_value': p_value
```
```python
results df = pd.DataFrame(results)
results df = results df.sort_values('p_value')
```

```python
return results df
```

```python
#-------------------------------------------

# STATISTICAL UTILITIES
#-------------------------------------------
```

```python
def combine_pvalues_fisher(p_values: List[-float]) -> Tuple[-float, float]:
    """
    Combine p-values using Fisher's method.
```

Parameters

----------

p_values : List[-float]
    List of p-values to combine

Returns

---------

Tuple[-float, float]
    (chi-square statistic, combined p-value)
"""

from scipy.stats import chi2

```python
# Remove NaN and clip to avoid log(0)
p_values = [max(p, 1e-100) for p in p_values if not np.isnan(p)]
```

```python
if len(p_values) == 0:
    return np.nan, np.nan
```

```python
# Fisher's combined statistic
chi_sq = -2 * np.sum(np.log(p_values))
```

```python
# Combined p-value (chi-squared with 2k degrees of freedom)
combined_p = chi2.sf(chi_sq, df=2 * len(p_values))
```

```python
return chi_sq, combined_p
```

```python
def vote_counting(
    results_list: List[pd.DataFrame],
    gene_col: str = 'probe_id',
    fc_col: str = 'log2FC',
    p_col: str = 'p_value',
    p_threshold: float = 0.05,
    fc_threshold: float = 0.3
) -> pd.DataFrame:
    """
    Perform vote counting meta-analysis.
```

Parameters
----------
results_list : List[pd.DataFrame]
  List of DE result DataFrames
  gene_col, fc_col, p_col : str
    Column names
  p_threshold, fc_threshold : float
    Significance thresholds

Returns

-------
pd.DataFrame
  Vote counts per gene
"
"
# Get all unique genes
all_genes = set()
for df in results_list:
    all_genes.update(df[gene_col].total())

# Count votes
vote_data = []
for gene in all_genes:
    up votes = 0
    down votes = 0
    total_studies = 0

for df in results_list:
    gene_data = df[df[gene_col] == gene]
    if len(gene_data) > 0:
        total_studies += 1
        row = gene_data iloc[0]
        if row[p_col] < p_threshold:
            if row[fc_col] > fc_threshold:
                up votes += 1
            elif row[fc_col] < -fc_threshold:
                down votes += 1

vote_data.append({
  'gene': gene,
  'up votes': up votes,
  'down votes': down votes,
  'n_studies': total_studies,
  'consistency': max(up votes, down votes) / total_studies if
  total_studies > 0 else 0
})

return pd.DataFrame(vote_data).sort_values('consistency', ascending=False)

#------------------------------------------
# PROBE ANNOTATION
#------------------------------------------

def annotate_probes_with_genes(
Transcriptional Signature

```python
df: pd.DataFrame,
annotation_file: str,
probe_col: str = 'probe_id'
) -> pd.DataFrame:
"""
Add gene symbol annotations to probe-level results.
```

Parameters

```python
df : pd.DataFrame
    Results Frame with probe IDs
annotation_file : str
    Path to probe annotation file
probe_col : str
    Column name for probe IDs
```

Returns

```python
pd.DataFrame
    Results with added gene_symbol column
"""
annot = pd.read_csv(annotation_file)
```

# Merge annotations

```python
merged = df.merge(
    annot[[ 'probe_id', 'gene_symbol' ]],
    left_on=probe_col,
    right_on='probe_id',
    how='left'
)
```

return merged

#---------------------------------------------------------------------------
# MAIN EXECUTION
#---------------------------------------------------------------------------

```python
def main():
    """Main execution for utility functions demo."""
    print("=" * 70)
    print("DISSERTATION ANALYSIS UTILITIES")
    print(f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M')}")")
    print("=" * 70)

    # Demo: Gene set enrichment
demo genes = ['NFKB1', 'IL6', 'TNF', 'IFIT1', 'MX1', 'STAT1', 'IRF7',
             'BCL2']

    print("\nDemo: Gene Set Enrichment Analysis")
    print(f"Input genes: {demo genes}")

    enrichment = get gene set enrichment(demo genes)
    print("\nEnrichment Results:")
```
Transcriptional Signature

```python
print(enrichment[[ 'gene_set', 'overlap_count', 'overlap_pct',
                       'p_value' ]])

# Demo: Combine p-values
print("\nDemo: Fisher's Combined P-values")
example_pvals = [0.01, 0.05, 0.03, 0.08]
chi_sq, combined_p = combine_pvalues_fisher(example_pvals)
print(f"Individual p-values: {example_pvals}")
print(f"Chi-square: {chi_sq:.3f}, Combined p: {combined_p:.6f}")

if __name__ == "__main__":
    main()
```

D.7.2 Session Information

The following code generates session information for reproducibility documentation:

```python
# R/Python

# R Session Information
sessionInfo()

# Python Environment Information
import sys
import platform
import pkg_resources

print(f"Python version: {sys.version}")
print(f"Platform: {platform.platform()}")
print("\nInstalled packages:")
for pkg in ['pandas', 'numpy', 'scipy', 'plotly', 'GEOparse']:
    try:
        version = pkg_resources.get_distributionpkg).version
        print(f" {pkg}: {version}")
    except:
        print(f" {pkg}: not installed")
```