Skip to content

Contributing Guide

We welcome contributions to online-fdr! This guide will help you get started with contributing code, documentation, examples, or bug reports.

Ways to Contribute

🐛 Bug Reports

  • Report issues with existing functionality
  • Include minimal reproducible examples
  • Describe expected vs actual behavior

🚀 Feature Requests

  • Suggest new online FDR methods
  • Propose API improvements
  • Request documentation enhancements

💻 Code Contributions

  • Implement new methods from recent literature
  • Fix bugs and improve performance
  • Add tests and improve test coverage

📚 Documentation

  • Improve existing documentation
  • Add examples and tutorials
  • Fix typos and clarify explanations

🧪 Examples and Tutorials

  • Real-world application examples
  • Method comparison studies
  • Educational content

Getting Started

Development Environment Setup

  1. Fork and Clone

    git clone https://github.com/yourusername/online-fdr.git
    cd online-fdr
    

  2. Install Development Dependencies

    # Using uv (recommended)
    pip install uv
    uv sync --all-extras
    
    # Or using pip
    pip install -e ".[dev]"
    

  3. Install Pre-commit Hooks

    uv run pre-commit install
    # or: pre-commit install
    

  4. Verify Installation

    uv run pytest
    # or: pytest
    

Development Workflow

  1. Create Feature Branch

    git checkout -b feature/new-method-name
    

  2. Make Changes

  3. Write code following our style guide
  4. Add tests for new functionality
  5. Update documentation as needed

  6. Run Tests and Checks

    # Run tests
    uv run pytest
    
    # Check formatting
    uv run black --check online_fdr tests
    
    # Check linting  
    uv run ruff check online_fdr tests
    
    # Type checking
    uv run mypy online_fdr
    

  7. Commit Changes

    git add .
    git commit -m "feat: add new LORD variant with decay"
    

  8. Push and Create PR

    git push origin feature/new-method-name
    
    Then create a pull request on GitHub.

Code Style and Standards

Formatting

We use Black for code formatting with these settings: - Line length: 88 characters - Target Python versions: 3.8+

# Format code
uv run black online_fdr tests

# Check formatting
uv run black --check online_fdr tests

Linting

We use Ruff for fast Python linting:

# Run linting
uv run ruff check online_fdr tests

# Fix auto-fixable issues
uv run ruff check --fix online_fdr tests

Type Hints

All new code should include type hints:

from typing import List, Optional, Union

def test_one(self, p_value: float) -> bool:
    """
    Test a single p-value.

    Parameters
    ----------
    p_value : float
        P-value to test (0 ≤ p_value ≤ 1)

    Returns
    -------
    bool
        True if null hypothesis is rejected
    """
    pass

Docstring Format

Use NumPy-style docstrings:

def method_name(param1: float, param2: str = "default") -> bool:
    """
    Brief description of the method.

    Longer description explaining the method's purpose,
    algorithm, and usage context.

    Parameters
    ----------
    param1 : float
        Description of first parameter
    param2 : str, optional
        Description of second parameter, by default "default"

    Returns
    -------
    bool
        Description of return value

    Raises
    ------
    ValueError
        When parameters are invalid

    Examples
    --------
    >>> method = ClassName(alpha=0.05)
    >>> result = method.method_name(0.01, "custom")
    >>> print(result)
    True

    References
    ----------
    .. [1] Author, A. "Paper Title." Journal Name (Year).
    """
    pass

Adding New Methods

Method Implementation Structure

All sequential testing methods should inherit from AbstractSequentialTest:

from online_fdr.abstract.abstract_sequential_test import AbstractSequentialTest
from online_fdr.utils import validity

class NewMethod(AbstractSequentialTest):
    """
    Implementation of NewMethod for online FDR control.

    This method implements the algorithm described in [1]_.

    Parameters
    ----------
    alpha : float
        Target FDR level (0 < alpha < 1)
    param1 : float
        Method-specific parameter
    param2 : float, optional
        Optional method-specific parameter, by default 0.5

    References
    ----------
    .. [1] Author, A. "NewMethod: Description." Journal (Year).
    """

    def __init__(self, alpha: float, param1: float, param2: float = 0.5):
        super().__init__(alpha)

        # Validate parameters
        validity.check_alpha(alpha)
        validity.check_param1(param1)  # Add custom validation

        # Store parameters
        self.param1 = param1
        self.param2 = param2

        # Initialize state
        self.num_tests = 0
        self.wealth = alpha * 0.5  # Example initialization

    def test_one(self, p_value: float) -> bool:
        """
        Test a single p-value.

        Parameters
        ----------
        p_value : float
            P-value to test (0 ≤ p_value ≤ 1)

        Returns
        -------
        bool
            True if null hypothesis is rejected
        """
        validity.check_p_val(p_value)

        self.num_tests += 1

        # Calculate current threshold
        self.alpha = self._calculate_threshold()

        if self.alpha is None or p_value > self.alpha:
            return False

        # Update state after rejection
        self._update_wealth()
        return True

    def _calculate_threshold(self) -> Optional[float]:
        """Calculate current significance threshold."""
        if self.wealth <= 0:
            return None
        # Implement threshold calculation
        return self.wealth * self.param1

    def _update_wealth(self) -> None:
        """Update wealth after a discovery."""
        self.wealth += self.param2  # Example wealth update

Testing Requirements

Every new method requires comprehensive tests:

# tests/test_new_method.py
import pytest
import numpy as np
from online_fdr.investing.new_method import NewMethod
from online_fdr.utils.generation import DataGenerator, GaussianLocationModel

class TestNewMethod:
    """Test suite for NewMethod."""

    def test_initialization(self):
        """Test proper initialization."""
        method = NewMethod(alpha=0.05, param1=0.25)
        assert method.alpha0 == 0.05
        assert method.param1 == 0.25
        assert method.param2 == 0.5  # default

    def test_invalid_parameters(self):
        """Test parameter validation."""
        with pytest.raises(ValueError):
            NewMethod(alpha=1.5, param1=0.25)  # Invalid alpha

        with pytest.raises(ValueError):
            NewMethod(alpha=0.05, param1=-0.1)  # Invalid param1

    def test_single_p_value(self):
        """Test single p-value testing."""
        method = NewMethod(alpha=0.05, param1=0.25)

        # Should reject small p-value
        assert method.test_one(0.001) is True

        # Should not reject large p-value
        assert method.test_one(0.9) is False

    def test_fdr_control_simulation(self):
        """Test FDR control through simulation."""
        np.random.seed(42)

        method = NewMethod(alpha=0.1, param1=0.25)

        # Generate data with known null proportion
        dgp = GaussianLocationModel(alt_mean=2.0, alt_std=1.0, one_sided=True)
        generator = DataGenerator(n=1000, pi0=0.9, dgp=dgp)

        true_positives = false_positives = 0

        for _ in range(100):
            p_value, is_alternative = generator.sample_one()
            decision = method.test_one(p_value)

            if decision:
                if is_alternative:
                    true_positives += 1
                else:
                    false_positives += 1

        # Check FDR control
        total_discoveries = true_positives + false_positives
        if total_discoveries > 0:
            empirical_fdr = false_positives / total_discoveries
            assert empirical_fdr <= 0.15  # Allow some variance

    def test_wealth_dynamics(self):
        """Test wealth updates correctly."""
        method = NewMethod(alpha=0.05, param1=0.25)
        initial_wealth = method.wealth

        # Make a discovery
        method.test_one(0.01)

        # Wealth should have changed appropriately
        assert method.wealth != initial_wealth

    def test_edge_cases(self):
        """Test edge cases and boundary conditions."""
        method = NewMethod(alpha=0.05, param1=0.25)

        # Test p-value = 0
        result = method.test_one(0.0)
        assert isinstance(result, bool)

        # Test p-value = 1
        result = method.test_one(1.0)
        assert isinstance(result, bool)

        # Test when wealth is depleted
        method.wealth = 0.0
        assert method.test_one(0.001) is False

Documentation Requirements

New methods need:

  1. API documentation (docstrings in code)
  2. Usage examples in docstrings
  3. Theoretical background in theory section
  4. Practical examples in examples section

Performance Benchmarks

Include performance tests for new methods:

def test_performance_benchmark(benchmark):
    """Benchmark method performance."""
    method = NewMethod(alpha=0.05, param1=0.25)
    p_values = np.random.uniform(0, 1, 1000)

    def run_tests():
        for p_val in p_values:
            method.test_one(p_val)

    benchmark(run_tests)

Documentation Contributions

Building Documentation Locally

cd docs
uv run mkdocs serve
# Open http://localhost:8000

Documentation Structure

  • docs/index.md: Home page
  • docs/installation.md: Installation guide
  • docs/quickstart.md: Quick start tutorial
  • docs/user_guide/: Detailed user guides
  • docs/api/: API reference documentation
  • docs/examples/: Practical examples
  • docs/theory/: Mathematical theory
  • docs/contributing.md: This file

Writing Guidelines

  • Clear and concise: Explain concepts simply
  • Code examples: Include runnable examples
  • Mathematical notation: Use LaTeX for equations
  • Cross-references: Link between sections
  • Accessibility: Consider all skill levels

Review Process

Pull Request Guidelines

  1. Clear description: Explain what and why
  2. Reference issues: Link to related issues
  3. Tests included: All new code needs tests
  4. Documentation updated: Update relevant docs
  5. Changelog entry: Add entry for user-facing changes

Review Criteria

  • Functionality: Does it work correctly?
  • Tests: Adequate test coverage?
  • Documentation: Clear and complete?
  • Code quality: Follows style guidelines?
  • Performance: No significant regressions?

Release Process

Version Numbering

We follow Semantic Versioning:

  • MAJOR: Incompatible API changes
  • MINOR: New functionality (backward compatible)
  • PATCH: Bug fixes (backward compatible)

Changelog

Update CHANGELOG.md for all user-facing changes:

## [0.2.0] - 2024-01-15

### Added
- New LORD variant with memory decay
- Support for asynchronous testing

### Changed  
- Improved ADDIS parameter validation
- Updated documentation structure

### Fixed
- Bug in wealth calculation for edge case
- Typo in SAFFRON documentation

Getting Help

Community Resources

  • GitHub Discussions: Ask questions, share ideas
  • GitHub Issues: Report bugs, request features
  • Documentation: Comprehensive guides and references

Maintainer Contact

Communication Guidelines

  • Be respectful: Follow our code of conduct
  • Be specific: Include details, code examples, error messages
  • Be patient: Maintainers are volunteers
  • Search first: Check existing issues and discussions

Recognition

Contributors will be:

  • Listed in CONTRIBUTORS.md
  • Mentioned in release notes for significant contributions
  • Added as co-authors on academic papers when appropriate

Code of Conduct

We are committed to providing a welcoming and inclusive environment. All contributors are expected to:

  • Be respectful of different viewpoints and experiences
  • Accept constructive criticism gracefully
  • Focus on what's best for the community
  • Show empathy towards other community members

Quick Reference

Common Commands

# Set up development environment
uv sync --all-extras
uv run pre-commit install

# Run tests
uv run pytest
uv run pytest tests/test_specific.py -v

# Check code quality
uv run black online_fdr tests
uv run ruff check online_fdr tests  
uv run mypy online_fdr

# Build documentation
cd docs && uv run mkdocs serve

# Run specific test category
uv run pytest -m "not slow"
uv run pytest tests/ -k "test_fdr_control"

File Naming Conventions

  • Source files: snake_case.py
  • Test files: test_module_name.py
  • Documentation: kebab-case.md
  • Examples: descriptive_example_name.py

Thank you for contributing to online-fdr! Your efforts help advance the field of online multiple testing and benefit researchers worldwide. 🚀