HyperImpute

repository·main·Indexed 41 days ago

https://github.com/vanderschaarlab/hyperimpute

A plugin-based library for simplifying the selection and application of data imputation algorithms in machine learning pipelines. It features novel iterative methods, classic algorithms like MICE and GAIN, and maintains compatibility with scikit-learn. The library supports various optimizers including Bayesian and Hyperband, and provides utilities for benchmarking imputation models across different missingness scenarios (MAR, MCAR, MNAR).

Tokens
15.6K
Snippets
55
Records
67
Agent score
52%

What's inside HyperImpute

  1. Install HyperImpute

    main

    You can install HyperImpute from PyPI or directly from the source code.

    From PyPI:

    $ pip install hyperimpute

    From source:

    $ pip install .

    For development/testing: To install the necessary dependencies for running tests, use:

    $ pip install .[testing]
  2. How Imputation Plugins work in HyperImpute

    main

    HyperImpute uses a plugin-based architecture for imputation. By default, it automatically loads all plugins found in the hyperimpute/plugins/imputers/ directory following the pattern plugin_*.

    Users can interact with these plugins through the Imputers class, which acts as a registry. You can list available plugins, retrieve specific plugin instances, and register new custom plugins at runtime. This allows for easy extension of the library's capabilities without modifying the core source code.

    from hyperimpute.plugins.imputers import Imputers
    
    imputers = Imputers()
    # List all loaded plugins
    imputers.list()
    # Retrieve a specific plugin by name
    plugin_instance = imputers.get("mean")
  3. Use Imputers.get() to retrieve specific imputation models

    main

    The Imputers.get() method is the primary way to instantiate an imputer. It supports different optimization modes:

    AutoML Mode (Hyperband)

    Use optimizer="hyperband" to enable automated model selection. You can constrain the search space using regression_seed and classifier_seed.

    Simple Mode

    Use optimizer="simple" to bypass the hyperband optimization process, which is useful for testing specific model seeds without the overhead of hyperparameter optimization.

    # AutoML with specific model seeds
    model = imputers.get(
        "hyperimpute",
        optimizer="hyperband",
        regression_seed=["linear_regression"],
        classifier_seed=["logistic_regression"],
        select_model_by_column=False,
        select_model_by_iteration=False,
        select_lazy=True,
    )
    
    # Simple mode with specific model seeds
    model = imputers.get(
        "hyperimpute",
        optimizer="simple",
        regression_seed=["random_forest_regressor"],
        classifier_seed=["random_forest"],
        select_model_by_column=False,
        select_model_by_iteration=False,
        select_lazy=True,
    )
  4. Initialize Imputers and Logging

    main

    To use HyperImpute, you first need to initialize the Imputers class and configure the logger. It is recommended to set the log level to INFO to monitor the imputation process. You may also want to suppress warnings during large-scale experiments.

    import sys
    import warnings
    import hyperimpute.logger as log
    from hyperimpute.plugins.imputers import Imputers
    
    # Configure logging to stderr
    log.add(sink=sys.stderr, level="INFO")
    
    # Initialize the imputers plugin
    imputers = Imputers()
    
    # Optional: suppress warnings for cleaner output
    warnings.filterwarnings("ignore")
    import sys
    import warnings
    import hyperimpute.logger as log
    from hyperimpute.plugins.imputers import Imputers
    
    log.add(sink=sys.stderr, level="INFO")
    imputers = Imputers()
    warnings.filterwarnings("ignore")
  5. Simulate multiple imputation using different random seeds

    main

    To simulate the effect of multiple imputations, you can iterate through different random seeds. For each seed, you can generate a new dataset split, simulate missingness (amputation), and apply an imputer plugin.

    Key steps in the workflow:

    1. Amputation: Use simulate_nan to create missing values based on a mechanism (e.g., 'MCAR') and a missingness probability p_miss.
    2. Imputation: Retrieve a plugin using Imputers().get(plugin_name, random_state=seed) and call .fit_transform() on the incomplete data.
    3. Evaluation: Compare the imputed data against the original ground truth using metrics like AUROC.
    from hyperimpute.plugins.imputers import Imputers
    
    # Configuration
    plugin = "ice"
    ampute_mechanism = "MCAR"
    p_miss = 0.5
    
    for seed in range(5):
        # 1. Setup data and simulate missingness
        # (Assuming dataset() and ampute() functions are defined as per tutorial)
        X_train, X_test, y_train, y_test = dataset(random_state=seed)
        x, x_miss, mask = ampute(X_train, ampute_mechanism, p_miss)
    
        # 2. Get the imputer plugin and impute
        model = Imputers().get(plugin, random_state=seed)
        X_train_imp = model.fit_transform(x_miss.copy()).astype(float)
    
        # 3. Evaluate (e.g., using an XGBoost classifier)
        # ... evaluation logic ...
  6. Benchmark HyperImpute performance by dataset size

    main

    To understand how HyperImpute scales, you can evaluate its performance on subsamples of increasing sizes. The following pattern demonstrates how to iterate through different sample sizes, run the evaluation, and cache the results in a JSON file to avoid redundant computations.

    import json
    
    results_by_df_size = {}
    for subsample in [1000, 4000, 7000, 10000, 13000, 17000, 20000]:
        try:
            with open("general_results/error_by_df_size.json") as f:
                results_by_df_size = json.load(f)
        except BaseException:
            pass
    
        if str(subsample) in results_by_df_size:
            print("subsample already cached", subsample)
            continue
    
        print("subsample eval", subsample)
        X_local = X_raw.sample(subsample)
        y_local = y[X_local.index]
    
        results_by_df_size[subsample] = evaluate_dataset_repeated(
            f"subsample_{subsample}", X_local, y_local
        )
    
        with open("general_results/error_by_df_size.json", "w") as f:
            json.dump(results_by_df_size, f)
  7. Benchmark HyperImpute performance by feature count

    main

    To evaluate how the number of features affects imputation performance, you can subsample a dataset and then progressively include more columns (features).

    import json
    
    results_by_feat_count = {}
    for feat_count in [2, 4, 6, 8, 10, 12, 14]:
        try:
            with open("general_results/error_by_df_feat_count.json") as f:
                results_by_feat_count = json.load(f)
        except BaseException:
            pass
    
        if str(feat_count) in results_by_feat_count:
            print("feat count cached", feat_count)
            continue
    
        print("eval feat count", feat_count)
        X_local = X_raw.sample(2000)
        y_local = y[X_local.index]
        X_local = X_local.iloc[:, :feat_count]
    
        results_by_feat_count[feat_count] = evaluate_dataset_repeated(
            f"feature_subsample_{feat_count}", X_local, y_local
        )
    
        with open("general_results/error_by_df_feat_count.json", "w") as f:
            json.dump(results_by_feat_count, f)
  8. Initialize Imputers and Reproducibility

    main

    To use HyperImpute, import the Imputers class and use enable_reproducible_results() to ensure consistent outcomes across runs. You can also configure logging using the hyperimpute.logger module.

    from hyperimpute.plugins.imputers import Imputers
    from hyperimpute.utils.distributions import enable_reproducible_results
    import hyperimpute.logger as log
    import sys
    
    enable_reproducible_results()
    
    imputers = Imputers()
    log.add(sink=sys.stderr, level="INFO")
  9. Perform Bayesian Optimization over imputers

    main

    You can use hyperimpute in conjunction with optuna to find the best imputer and hyperparameters for a given dataset. The workflow involves:

    1. Identify Plugins: Use Imputers().list() to see available methods.
    2. Define Objective: Create an objective function that uses plugin.sample_hyperparameters(trial) to suggest parameters.
    3. Evaluate: Use imputer.fit_transform() and a metric like RMSE to score the results.
    4. Optimize: Use create_study from hyperimpute.utils.optimizer to manage the Optuna study and handle early stopping.
    from hyperimpute.plugins.imputers import Imputers
    from hyperimpute.utils.optimizer import create_study
    import optuna
    
    imputers = Imputers()
    
    def objective(trial):
        # Get the plugin class
        plugin_t = imputers.get_type('some_imputer')
        # Sample hyperparameters
        args = plugin_t.sample_hyperparameters(trial)
        # Instantiate and run
        imputer = plugin_t(**args)
        imputed = imputer.fit_transform(X_miss)
        # Return metric (e.g., RMSE)
        return score
    
    study = create_study(study_name='imputer_opt', direction='minimize')
    study.optimize(objective, n_trials=50)